diff --git a/RELEASES.md b/RELEASES.md index 1e94fb8e42..02778204a4 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,24 @@ +Version 1.52.1 (2021-05-10) +============================ + +This release disables incremental compilation, unless the user has explicitly +opted in via the newly added RUSTC_FORCE_INCREMENTAL=1 environment variable. + +This is due to the widespread, and frequently occuring, breakage encountered by +Rust users due to newly enabled incremental verification in 1.52.0. Notably, +Rust users **should** upgrade to 1.52.0 or 1.52.1: the bugs that are detected by +newly added incremental verification are still present in past stable versions, +and are not yet fixed on any channel. These bugs can lead to miscompilation of +Rust binaries. + +These problems only affect incremental builds, so release builds with Cargo +should not be affected unless the user has explicitly opted into incremental. +Debug and check builds are affected. + +See [84970] for more details. + +[84970]: https://github.com/rust-lang/rust/issues/84970 + Version 1.52.0 (2021-05-06) ============================ diff --git a/compiler/rustc_query_system/src/query/plumbing.rs b/compiler/rustc_query_system/src/query/plumbing.rs index 77267489a7..da37209160 100644 --- a/compiler/rustc_query_system/src/query/plumbing.rs +++ b/compiler/rustc_query_system/src/query/plumbing.rs @@ -590,7 +590,19 @@ fn incremental_verify_ich( let old_hash = tcx.dep_graph().fingerprint_of(dep_node_index); - assert!(new_hash == old_hash, "found unstable fingerprints for {:?}: {:?}", dep_node, result); + if new_hash != old_hash { + let run_cmd = if let Some(crate_name) = &tcx.sess().opts.crate_name { + format!("`cargo clean -p {}` or `cargo clean`", crate_name) + } else { + "`cargo clean`".to_string() + }; + tcx.sess().struct_err(&format!("internal compiler error: encountered incremental compilation error with {:?}", dep_node)) + .help(&format!("This is a known issue with the compiler. Run {} to allow your project to compile", run_cmd)) + .note(&format!("Please follow the instructions below to create a bug report with the provided information")) + .note(&format!("See for more information.")) + .emit(); + panic!("Found unstable fingerprints for {:?}: {:?}", dep_node, result); + } } fn force_query_with_job( diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 75078a1231..85448b7fe7 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1885,7 +1885,12 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { check_thread_count(&debugging_opts, error_format); - let incremental = cg.incremental.as_ref().map(PathBuf::from); + let incremental = + if std::env::var_os("RUSTC_FORCE_INCREMENTAL").map(|v| v == "1").unwrap_or(false) { + cg.incremental.as_ref().map(PathBuf::from) + } else { + None + }; if debugging_opts.profile && incremental.is_some() { early_error( diff --git a/git-commit-hash b/git-commit-hash index 186c1d8f9c..7b5c989351 100644 --- a/git-commit-hash +++ b/git-commit-hash @@ -1 +1 @@ -88f19c6dab716c6281af7602e30f413e809c5974 \ No newline at end of file +9bc8c42bb2f19e745a63f3445f1ac248fb015e53 \ No newline at end of file diff --git a/src/doc/rustc/src/codegen-options/index.md b/src/doc/rustc/src/codegen-options/index.md index 1883346430..4eb2aa9917 100644 --- a/src/doc/rustc/src/codegen-options/index.md +++ b/src/doc/rustc/src/codegen-options/index.md @@ -161,6 +161,9 @@ to save information after compiling a crate to be reused when recompiling the crate, improving re-compile times. This takes a path to a directory where incremental files will be stored. +Note that this option currently does not take effect unless +`RUSTC_FORCE_INCREMENTAL=1` in the environment. + ## inline-threshold This option lets you set the default threshold for inlining a function. It diff --git a/src/tools/clippy/.cargo/config b/src/tools/clippy/.cargo/config new file mode 100644 index 0000000000..9b5add4df1 --- /dev/null +++ b/src/tools/clippy/.cargo/config @@ -0,0 +1,7 @@ +[alias] +uitest = "test --test compile-test" +dev = "run --target-dir clippy_dev/target --package clippy_dev --bin clippy_dev --manifest-path clippy_dev/Cargo.toml --" +lintcheck = "run --target-dir lintcheck/target --package lintcheck --bin lintcheck --manifest-path lintcheck/Cargo.toml -- " + +[build] +rustflags = ["-Zunstable-options"] diff --git a/src/tools/clippy/.editorconfig b/src/tools/clippy/.editorconfig new file mode 100644 index 0000000000..ec6e107d54 --- /dev/null +++ b/src/tools/clippy/.editorconfig @@ -0,0 +1,21 @@ +# 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] +# double whitespace at end of line +# denotes a line break in Markdown +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/blank_issue.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/blank_issue.md new file mode 100644 index 0000000000..9aef3ebe63 --- /dev/null +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/blank_issue.md @@ -0,0 +1,4 @@ +--- +name: Blank Issue +about: Create a blank issue. +--- diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..2bc87db123 --- /dev/null +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,47 @@ +--- +name: Bug Report +about: Create a bug report for Clippy +labels: C-bug +--- + + +I tried this code: + +```rust + +``` + +I expected to see this happen: *explanation* + +Instead, this happened: *explanation* + +### Meta + +- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20) +- `rustc -Vv`: + ``` + rustc 1.46.0-nightly (f455e46ea 2020-06-20) + binary: rustc + commit-hash: f455e46eae1a227d735091091144601b467e1565 + commit-date: 2020-06-20 + host: x86_64-unknown-linux-gnu + release: 1.46.0-nightly + LLVM version: 10.0 + ``` + + +
Backtrace +

+ + ``` + + ``` + +

+
diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/config.yml b/src/tools/clippy/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..bd7dc0ac95 --- /dev/null +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Rust Programming Language Forum + url: https://users.rust-lang.org + about: Please ask and answer questions about Rust here. diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/false_negative.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/false_negative.md new file mode 100644 index 0000000000..53341c7a92 --- /dev/null +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/false_negative.md @@ -0,0 +1,35 @@ +--- +name: Bug Report (False Negative) +about: Create a bug report about missing warnings from a lint +labels: C-bug, I-false-negative +--- + +Lint name: + + +I tried this code: + +```rust + +``` + +I expected to see this happen: *explanation* + +Instead, this happened: *explanation* + +### Meta + +- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20) +- `rustc -Vv`: + ``` + rustc 1.46.0-nightly (f455e46ea 2020-06-20) + binary: rustc + commit-hash: f455e46eae1a227d735091091144601b467e1565 + commit-date: 2020-06-20 + host: x86_64-unknown-linux-gnu + release: 1.46.0-nightly + LLVM version: 10.0 + ``` diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/false_positive.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/false_positive.md new file mode 100644 index 0000000000..34fd6f4bdb --- /dev/null +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/false_positive.md @@ -0,0 +1,35 @@ +--- +name: Bug Report (False Positive) +about: Create a bug report about a wrongly emitted lint warning +labels: C-bug, I-false-positive +--- + +Lint name: + + +I tried this code: + +```rust + +``` + +I expected to see this happen: *explanation* + +Instead, this happened: *explanation* + +### Meta + +- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20) +- `rustc -Vv`: + ``` + rustc 1.46.0-nightly (f455e46ea 2020-06-20) + binary: rustc + commit-hash: f455e46eae1a227d735091091144601b467e1565 + commit-date: 2020-06-20 + host: x86_64-unknown-linux-gnu + release: 1.46.0-nightly + LLVM version: 10.0 + ``` diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/ice.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/ice.md new file mode 100644 index 0000000000..0b7cd1ed0f --- /dev/null +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/ice.md @@ -0,0 +1,53 @@ +--- +name: Internal Compiler Error +about: Create a report for an internal compiler error in Clippy. +labels: C-bug, I-ICE +--- + + +### Code + +```rust + +``` + +### Meta + +- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20) +- `rustc -Vv`: + ``` + rustc 1.46.0-nightly (f455e46ea 2020-06-20) + binary: rustc + commit-hash: f455e46eae1a227d735091091144601b467e1565 + commit-date: 2020-06-20 + host: x86_64-unknown-linux-gnu + release: 1.46.0-nightly + LLVM version: 10.0 + ``` + +### Error output + +``` + +``` + + +
Backtrace +

+ + ``` + + ``` + +

+
diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/new_lint.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/new_lint.md new file mode 100644 index 0000000000..e182c99ce0 --- /dev/null +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/new_lint.md @@ -0,0 +1,35 @@ +--- +name: New lint suggestion +about: Suggest a new Clippy lint. +labels: A-lint +--- + +### What it does + +*What does this lint do?* + +### Categories (optional) + +- Kind: *See for list of lint kinds* + +*What is the advantage of the recommended code over the original code* + +For example: +- Remove bounce checking inserted by ... +- Remove the need to duplicating/storing/typo ... + +### Drawbacks + +None. + +### Example + +```rust + +``` + +Could be written as: + +```rust + +``` diff --git a/src/tools/clippy/.github/PULL_REQUEST_TEMPLATE.md b/src/tools/clippy/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..a3f114e0bb --- /dev/null +++ b/src/tools/clippy/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,32 @@ +Thank you for making Clippy better! + +We're collecting our changelog from pull request descriptions. +If your PR only includes internal changes, you can just write +`changelog: 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. + +--- + +*Please write a short comment explaining your change (or "none" for internal only changes)* +changelog: diff --git a/src/tools/clippy/.github/deploy.sh b/src/tools/clippy/.github/deploy.sh new file mode 100644 index 0000000000..e85e8874ba --- /dev/null +++ b/src/tools/clippy/.github/deploy.sh @@ -0,0 +1,66 @@ +#!/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 [[ -n $TAG_NAME ]]; then + # track files, so that the following check works + git add --intent-to-add "$TAG_NAME" + if git diff --exit-code --quiet -- $TAG_NAME/; then + echo "No changes to the output on this push; exiting." + exit 0 + fi + # 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 + if git diff --exit-code --quiet -- beta/; then + echo "No changes to the output on this push; exiting." + exit 0 + fi + git add beta + git commit -m "Automatic deploy to GitHub Pages (beta): ${SHA}" +else + if git diff --exit-code --quiet; then + echo "No changes to the output on this push; exiting." + exit 0 + fi + git add . + git commit -m "Automatic deploy to GitHub Pages: ${SHA}" +fi + +git push "$SSH_REPO" "$TARGET_BRANCH" diff --git a/src/tools/clippy/.github/driver.sh b/src/tools/clippy/.github/driver.sh new file mode 100644 index 0000000000..6ff189fc85 --- /dev/null +++ b/src/tools/clippy/.github/driver.sh @@ -0,0 +1,39 @@ +#!/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/double_neg.rs 2>double_neg.stderr && exit 1 +sed -e "s,tests/ui,\$DIR," -e "/= help/d" double_neg.stderr >normalized.stderr +diff -u normalized.stderr tests/ui/double_neg.stderr + +# make sure "clippy-driver --rustc --arg" and "rustc --arg" behave the same +SYSROOT=$(rustc --print sysroot) +diff -u <(LD_LIBRARY_PATH=${SYSROOT}/lib ./target/debug/clippy-driver --rustc --version --verbose) <(rustc --version --verbose) + +echo "fn main() {}" >target/driver_test.rs +# we can't run 2 rustcs on the same file at the same time +CLIPPY=$(LD_LIBRARY_PATH=${SYSROOT}/lib ./target/debug/clippy-driver ./target/driver_test.rs --rustc) +RUSTC=$(rustc ./target/driver_test.rs) +diff -u <($CLIPPY) <($RUSTC) + +# TODO: CLIPPY_CONF_DIR / CARGO_MANIFEST_DIR diff --git a/src/tools/clippy/.github/workflows/clippy.yml b/src/tools/clippy/.github/workflows/clippy.yml new file mode 100644 index 0000000000..32103f59d8 --- /dev/null +++ b/src/tools/clippy/.github/workflows/clippy.yml @@ -0,0 +1,87 @@ +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: + # NOTE: If you modify this job, make sure you copy the changes to clippy_bors.yml + runs-on: ubuntu-latest + + steps: + # Setup + - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master + with: + github_token: "${{ secrets.github_token }}" + + - name: Checkout + uses: actions/checkout@v2.3.3 + + - name: Install toolchain + run: rustup show active-toolchain + + # Run + - name: Set LD_LIBRARY_PATH (Linux) + run: | + SYSROOT=$(rustc --print sysroot) + echo "LD_LIBRARY_PATH=${SYSROOT}/lib${LD_LIBRARY_PATH+:${LD_LIBRARY_PATH}}" >> $GITHUB_ENV + + - name: Build + run: cargo build --features deny-warnings,internal-lints + + - name: Test + run: cargo test --features deny-warnings,internal-lints + + - name: Test clippy_lints + run: cargo test --features deny-warnings,internal-lints + 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 cargo-clippy --fix + run: ../target/debug/cargo-clippy clippy --fix -Zunstable-options + working-directory: clippy_workspace_tests + + - name: Test clippy-driver + run: bash .github/driver.sh + env: + OS: ${{ runner.os }} + + - name: Test cargo dev new lint + run: | + cargo dev new_lint --name new_early_pass --pass early + cargo dev new_lint --name new_late_pass --pass late + cargo check + git reset --hard HEAD diff --git a/src/tools/clippy/.github/workflows/clippy_bors.yml b/src/tools/clippy/.github/workflows/clippy_bors.yml new file mode 100644 index 0000000000..47253eecc4 --- /dev/null +++ b/src/tools/clippy/.github/workflows/clippy_bors.yml @@ -0,0 +1,267 @@ +name: Clippy Test (bors) + +on: + push: + branches: + - auto + - try + +env: + RUST_BACKTRACE: 1 + CARGO_TARGET_DIR: '${{ github.workspace }}/target' + NO_FMT_TEST: 1 + +defaults: + run: + shell: bash + +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.3.3 + 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 }} + + # NOTE: If you modify this job, make sure you copy the changes to clippy.yml + 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: Checkout + uses: actions/checkout@v2.3.3 + + - name: Install toolchain + run: rustup show active-toolchain + + # Run + - name: Set LD_LIBRARY_PATH (Linux) + if: runner.os == 'Linux' + run: | + SYSROOT=$(rustc --print sysroot) + echo "LD_LIBRARY_PATH=${SYSROOT}/lib${LD_LIBRARY_PATH+:${LD_LIBRARY_PATH}}" >> $GITHUB_ENV + - 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) + echo "$SYSROOT/bin" >> $GITHUB_PATH + + - name: Build + run: cargo build --features deny-warnings,internal-lints + + - name: Test + run: cargo test --features deny-warnings,internal-lints + + - name: Test clippy_lints + run: cargo test --features deny-warnings,internal-lints + 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 cargo-clippy --fix + run: ../target/debug/cargo-clippy clippy --fix -Zunstable-options + working-directory: clippy_workspace_tests + + - name: Test clippy-driver + run: bash .github/driver.sh + env: + OS: ${{ runner.os }} + + - name: Test cargo dev new lint + run: | + cargo dev new_lint --name new_early_pass --pass early + cargo dev new_lint --name new_late_pass --pass late + cargo check + git reset --hard HEAD + + 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: Checkout + uses: actions/checkout@v2.3.3 + + - name: Install toolchain + run: rustup show active-toolchain + + # 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 + + integration: + needs: integration_build + strategy: + fail-fast: false + max-parallel: 6 + matrix: + integration: + - 'rust-lang/cargo' + # FIXME: re-enable once fmt_macros is renamed in RLS + # - 'rust-lang/rls' + - 'rust-lang/chalk' + - 'rust-lang/rustfmt' + - 'Marwes/combine' + - 'Geal/nom' + - 'rust-lang/stdarch' + - 'serde-rs/serde' + # FIXME: chrono currently cannot be compiled with `--all-targets` + # - '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: Checkout + uses: actions/checkout@v2.3.3 + + - name: Install toolchain + run: rustup show active-toolchain + + # 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: | + RUSTUP_TOOLCHAIN="$(rustup show active-toolchain | grep -o -E "nightly-[0-9]{4}-[0-9]{2}-[0-9]{2}")" \ + $CARGO_TARGET_DIR/debug/integration + env: + INTEGRATION: ${{ matrix.integration }} + + # 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: [changelog, base, integration_build, 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: [changelog, base, integration_build, integration] + + steps: + - name: Mark the job as a failure + run: exit 1 diff --git a/src/tools/clippy/.github/workflows/clippy_dev.yml b/src/tools/clippy/.github/workflows/clippy_dev.yml new file mode 100644 index 0000000000..95da775b7b --- /dev/null +++ b/src/tools/clippy/.github/workflows/clippy_dev.yml @@ -0,0 +1,78 @@ +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: Checkout + uses: actions/checkout@v2.3.3 + + - name: remove toolchain file + run: rm rust-toolchain + + - name: rust-toolchain + uses: actions-rs/toolchain@v1.0.6 + with: + toolchain: nightly + target: x86_64-unknown-linux-gnu + profile: minimal + components: rustfmt + default: true + + # Run + - name: Build + run: cargo build --features deny-warnings + working-directory: clippy_dev + + - name: Test limit_stderr_length + run: cargo dev limit_stderr_length + + - name: Test update_lints + run: cargo dev update_lints --check + + - name: Test fmt + run: cargo dev fmt --check + + # These jobs doesn't actually test anything, but they're only used to tell + # bors the build completed, as there is no practical way to detect when a + # workflow is successful listening to webhooks only. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + + end-success: + name: bors dev test finished + if: github.event.pusher.name == 'bors' && success() + runs-on: ubuntu-latest + needs: [clippy_dev] + + steps: + - name: Mark the job as successful + run: exit 0 + + end-failure: + name: bors dev test finished + if: github.event.pusher.name == 'bors' && (failure() || cancelled()) + runs-on: ubuntu-latest + needs: [clippy_dev] + + steps: + - name: Mark the job as a failure + run: exit 1 diff --git a/src/tools/clippy/.github/workflows/deploy.yml b/src/tools/clippy/.github/workflows/deploy.yml new file mode 100644 index 0000000000..15aeaf907d --- /dev/null +++ b/src/tools/clippy/.github/workflows/deploy.yml @@ -0,0 +1,51 @@ +name: Deploy + +on: + push: + branches: + - master + - beta + tags: + - rust-1.** + +env: + TARGET_BRANCH: 'gh-pages' + SHA: '${{ github.sha }}' + SSH_REPO: 'git@github.com:${{ github.repository }}.git' + +jobs: + deploy: + runs-on: ubuntu-latest + if: github.repository == 'rust-lang/rust-clippy' + + steps: + # Setup + - name: Checkout + uses: actions/checkout@v2.3.3 + + - name: Checkout + uses: actions/checkout@v2.3.3 + with: + ref: ${{ env.TARGET_BRANCH }} + path: 'out' + + # Run + - name: Set tag name + if: startswith(github.ref, 'refs/tags/') + run: | + TAG=$(basename ${{ github.ref }}) + echo "TAG_NAME=$TAG" >> $GITHUB_ENV + - name: Set beta to true + if: github.ref == 'refs/heads/beta' + run: echo "BETA=true" >> $GITHUB_ENV + + - name: Use scripts and templates from master branch + run: | + git fetch --no-tags --prune --depth=1 origin master + git checkout origin/master -- .github/deploy.sh util/gh-pages/ util/*.py + + - name: Deploy + run: | + eval "$(ssh-agent -s)" + ssh-add - <<< "${{ secrets.DEPLOY_KEY }}" + bash .github/deploy.sh diff --git a/src/tools/clippy/.github/workflows/remark.yml b/src/tools/clippy/.github/workflows/remark.yml new file mode 100644 index 0000000000..4f25a86b2e --- /dev/null +++ b/src/tools/clippy/.github/workflows/remark.yml @@ -0,0 +1,55 @@ +name: Remark + +on: + push: + branches: + - auto + - try + pull_request: + paths: + - '**.md' + +jobs: + remark: + runs-on: ubuntu-latest + + steps: + # Setup + - name: Checkout + uses: actions/checkout@v2.3.3 + + - name: Setup Node.js + uses: actions/setup-node@v1.4.4 + + - name: Install remark + run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended + + # Run + - name: Check *.md files + run: git ls-files -z '*.md' | xargs -0 -n 1 -I {} ./node_modules/.bin/remark {} -u lint -f > /dev/null + + # These jobs doesn't actually test anything, but they're only used to tell + # bors the build completed, as there is no practical way to detect when a + # workflow is successful listening to webhooks only. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + + end-success: + name: bors remark test finished + if: github.event.pusher.name == 'bors' && success() + runs-on: ubuntu-latest + needs: [remark] + + steps: + - name: Mark the job as successful + run: exit 0 + + end-failure: + name: bors remark test finished + if: github.event.pusher.name == 'bors' && (failure() || cancelled()) + runs-on: ubuntu-latest + needs: [remark] + + steps: + - name: Mark the job as a failure + run: exit 1 diff --git a/src/tools/clippy/.remarkrc b/src/tools/clippy/.remarkrc new file mode 100644 index 0000000000..0ede7ac75c --- /dev/null +++ b/src/tools/clippy/.remarkrc @@ -0,0 +1,12 @@ +{ + "plugins": [ + "remark-preset-lint-recommended", + ["remark-lint-list-item-indent", false], + ["remark-lint-no-literal-urls", false], + ["remark-lint-no-shortcut-reference-link", false], + ["remark-lint-maximum-line-length", 120] + ], + "settings": { + "commonmark": true + } +} diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md new file mode 100644 index 0000000000..41c334c681 --- /dev/null +++ b/src/tools/clippy/CHANGELOG.md @@ -0,0 +1,2444 @@ +# Changelog + +All notable changes to this project will be documented in this file. +See [Changelog Update](doc/changelog_update.md) if you want to update this +document. + +## Unreleased / In Rust Nightly + +[3e41797...master](https://github.com/rust-lang/rust-clippy/compare/3e41797...master) + +## Rust 1.51 + +Current beta, release 2021-03-25 + +[4911ab1...3e41797](https://github.com/rust-lang/rust-clippy/compare/4911ab1...3e41797) + +### New Lints + +* [`upper_case_acronyms`] + [#6475](https://github.com/rust-lang/rust-clippy/pull/6475) +* [`from_over_into`] [#6476](https://github.com/rust-lang/rust-clippy/pull/6476) +* [`case_sensitive_file_extension_comparisons`] + [#6500](https://github.com/rust-lang/rust-clippy/pull/6500) +* [`needless_question_mark`] + [#6507](https://github.com/rust-lang/rust-clippy/pull/6507) +* [`missing_panics_doc`] + [#6523](https://github.com/rust-lang/rust-clippy/pull/6523) +* [`redundant_slicing`] + [#6528](https://github.com/rust-lang/rust-clippy/pull/6528) +* [`vec_init_then_push`] + [#6538](https://github.com/rust-lang/rust-clippy/pull/6538) +* [`ptr_as_ptr`] [#6542](https://github.com/rust-lang/rust-clippy/pull/6542) +* [`collapsible_else_if`] (split out from `collapsible_if`) + [#6544](https://github.com/rust-lang/rust-clippy/pull/6544) +* [`inspect_for_each`] [#6577](https://github.com/rust-lang/rust-clippy/pull/6577) +* [`manual_filter_map`] + [#6591](https://github.com/rust-lang/rust-clippy/pull/6591) +* [`exhaustive_enums`] + [#6617](https://github.com/rust-lang/rust-clippy/pull/6617) +* [`exhaustive_structs`] + [#6617](https://github.com/rust-lang/rust-clippy/pull/6617) + +### Moves and Deprecations + +* Replace [`find_map`] with [`manual_find_map`] + [#6591](https://github.com/rust-lang/rust-clippy/pull/6591) +* [`unknown_clippy_lints`] Now integrated in the `unknown_lints` rustc lint + [#6653](https://github.com/rust-lang/rust-clippy/pull/6653) + +### Enhancements + +* [`ptr_arg`] Now also suggests to use `&Path` instead of `&PathBuf` + [#6506](https://github.com/rust-lang/rust-clippy/pull/6506) +* [`cast_ptr_alignment`] Also lint when the `pointer::cast` method is used + [#6557](https://github.com/rust-lang/rust-clippy/pull/6557) +* [`collapsible_match`] Now also deals with `&` and `*` operators in the `match` + scrutinee [#6619](https://github.com/rust-lang/rust-clippy/pull/6619) + +### False Positive Fixes + +* [`similar_names`] Ignore underscore prefixed names + [#6403](https://github.com/rust-lang/rust-clippy/pull/6403) +* [`print_literal`] and [`write_literal`] No longer lint numeric literals + [#6408](https://github.com/rust-lang/rust-clippy/pull/6408) +* [`large_enum_variant`] No longer lints in external macros + [#6485](https://github.com/rust-lang/rust-clippy/pull/6485) +* [`empty_enum`] Only lint if `never_type` feature is enabled + [#6513](https://github.com/rust-lang/rust-clippy/pull/6513) +* [`field_reassign_with_default`] No longer lints in macros + [#6553](https://github.com/rust-lang/rust-clippy/pull/6553) +* [`size_of_in_element_count`] No longer lints when dividing by element size + [#6578](https://github.com/rust-lang/rust-clippy/pull/6578) +* [`needless_return`] No longer lints in macros + [#6586](https://github.com/rust-lang/rust-clippy/pull/6586) +* [`match_overlapping_arm`] No longer lint when first arm is completely included + in second arm [#6603](https://github.com/rust-lang/rust-clippy/pull/6603) +* [`doc_markdown`] Add `WebGL` to the default configuration as an allowed + identifier [#6605](https://github.com/rust-lang/rust-clippy/pull/6605) + +### Suggestion Fixes/Improvements + +* [`field_reassign_with_default`] Don't expand macro in lint suggestion + [#6531](https://github.com/rust-lang/rust-clippy/pull/6531) +* [`match_like_matches_macro`] Strip references in suggestion + [#6532](https://github.com/rust-lang/rust-clippy/pull/6532) +* [`single_match`] Suggest `if` over `if let` when possible + [#6574](https://github.com/rust-lang/rust-clippy/pull/6574) +* [`ref_in_deref`] Use parentheses correctly in suggestion + [#6609](https://github.com/rust-lang/rust-clippy/pull/6609) +* [`stable_sort_primitive`] Clarify error message + [#6611](https://github.com/rust-lang/rust-clippy/pull/6611) + +### ICE Fixes + +* [`zero_sized_map_values`] + [#6582](https://github.com/rust-lang/rust-clippy/pull/6582) + +### Documentation Improvements + +* Improve search performance on the Clippy website and make it possible to + directly search for lints on the GitHub issue tracker + [#6483](https://github.com/rust-lang/rust-clippy/pull/6483) +* Clean up `README.md` by removing outdated paragraph + [#6488](https://github.com/rust-lang/rust-clippy/pull/6488) +* [`await_holding_refcell_ref`] and [`await_holding_lock`] + [#6585](https://github.com/rust-lang/rust-clippy/pull/6585) +* [`as_conversions`] [#6608](https://github.com/rust-lang/rust-clippy/pull/6608) + +### Others + +* Clippy now has a [Roadmap] for 2021. If you like to get involved in a bigger + project, take a look at the [Roadmap project page]. All issues listed there + are actively mentored + [#6462](https://github.com/rust-lang/rust-clippy/pull/6462) +* The Clippy version number now corresponds to the Rust version number + [#6526](https://github.com/rust-lang/rust-clippy/pull/6526) +* Fix oversight which caused Clippy to lint deps in some environments, where + `CLIPPY_TESTS=true` was set somewhere + [#6575](https://github.com/rust-lang/rust-clippy/pull/6575) +* Add `cargo dev-lintcheck` tool to the Clippy Dev Tool + [#6469](https://github.com/rust-lang/rust-clippy/pull/6469) + +[Roadmap]: https://github.com/rust-lang/rust-clippy/blob/master/doc/roadmap-2021.md +[Roadmap project page]: https://github.com/rust-lang/rust-clippy/projects/3 + +## Rust 1.50 + +Current stable, released 2021-02-11 + +[b20d4c1...4bd77a1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4bd77a1) + +### New Lints + +* [`suspicious_operation_groupings`] [#6086](https://github.com/rust-lang/rust-clippy/pull/6086) +* [`size_of_in_element_count`] [#6394](https://github.com/rust-lang/rust-clippy/pull/6394) +* [`unnecessary_wraps`] [#6070](https://github.com/rust-lang/rust-clippy/pull/6070) +* [`let_underscore_drop`] [#6305](https://github.com/rust-lang/rust-clippy/pull/6305) +* [`collapsible_match`] [#6402](https://github.com/rust-lang/rust-clippy/pull/6402) +* [`redundant_else`] [#6330](https://github.com/rust-lang/rust-clippy/pull/6330) +* [`zero_sized_map_values`] [#6218](https://github.com/rust-lang/rust-clippy/pull/6218) +* [`print_stderr`] [#6367](https://github.com/rust-lang/rust-clippy/pull/6367) +* [`string_from_utf8_as_bytes`] [#6134](https://github.com/rust-lang/rust-clippy/pull/6134) + +### Moves and Deprecations + +* Previously deprecated [`str_to_string`] and [`string_to_string`] have been un-deprecated + as `restriction` lints [#6333](https://github.com/rust-lang/rust-clippy/pull/6333) +* Deprecate [`panic_params`] lint. This is now available in rustc as `panic_fmt` + [#6351](https://github.com/rust-lang/rust-clippy/pull/6351) +* Move [`map_err_ignore`] to `restriction` + [#6416](https://github.com/rust-lang/rust-clippy/pull/6416) +* Move [`await_holding_refcell_ref`] to `pedantic` + [#6354](https://github.com/rust-lang/rust-clippy/pull/6354) +* Move [`await_holding_lock`] to `pedantic` + [#6354](https://github.com/rust-lang/rust-clippy/pull/6354) + +### Enhancements + +* Add the `unreadable-literal-lint-fractions` configuration to disable + the `unreadable_literal` lint for fractions + [#6421](https://github.com/rust-lang/rust-clippy/pull/6421) +* [`clone_on_copy`]: Now shows the type in the lint message + [#6443](https://github.com/rust-lang/rust-clippy/pull/6443) +* [`redundant_pattern_matching`]: Now also lints on `std::task::Poll` + [#6339](https://github.com/rust-lang/rust-clippy/pull/6339) +* [`redundant_pattern_matching`]: Additionally also lints on `std::net::IpAddr` + [#6377](https://github.com/rust-lang/rust-clippy/pull/6377) +* [`search_is_some`]: Now suggests `contains` instead of `find(foo).is_some()` + [#6119](https://github.com/rust-lang/rust-clippy/pull/6119) +* [`clone_double_ref`]: Now prints the reference type in the lint message + [#6442](https://github.com/rust-lang/rust-clippy/pull/6442) +* [`modulo_one`]: Now also lints on -1. + [#6360](https://github.com/rust-lang/rust-clippy/pull/6360) +* [`empty_loop`]: Now lints no_std crates, too + [#6205](https://github.com/rust-lang/rust-clippy/pull/6205) +* [`or_fun_call`]: Now also lints when indexing `HashMap` or `BTreeMap` + [#6267](https://github.com/rust-lang/rust-clippy/pull/6267) +* [`wrong_self_convention`]: Now also lints in trait definitions + [#6316](https://github.com/rust-lang/rust-clippy/pull/6316) +* [`needless_borrow`]: Print the type in the lint message + [#6449](https://github.com/rust-lang/rust-clippy/pull/6449) + +[msrv_readme]: https://github.com/rust-lang/rust-clippy#specifying-the-minimum-supported-rust-version + +### False Positive Fixes + +* [`manual_range_contains`]: No longer lints in `const fn` + [#6382](https://github.com/rust-lang/rust-clippy/pull/6382) +* [`unnecessary_lazy_evaluations`]: No longer lints if closure argument is used + [#6370](https://github.com/rust-lang/rust-clippy/pull/6370) +* [`match_single_binding`]: Now ignores cases with `#[cfg()]` macros + [#6435](https://github.com/rust-lang/rust-clippy/pull/6435) +* [`match_like_matches_macro`]: No longer lints on arms with attributes + [#6290](https://github.com/rust-lang/rust-clippy/pull/6290) +* [`map_clone`]: No longer lints with deref and clone + [#6269](https://github.com/rust-lang/rust-clippy/pull/6269) +* [`map_clone`]: No longer lints in the case of &mut + [#6301](https://github.com/rust-lang/rust-clippy/pull/6301) +* [`needless_update`]: Now ignores `non_exhaustive` structs + [#6464](https://github.com/rust-lang/rust-clippy/pull/6464) +* [`needless_collect`]: No longer lints when a collect is needed multiple times + [#6313](https://github.com/rust-lang/rust-clippy/pull/6313) +* [`unnecessary_cast`] No longer lints cfg-dependent types + [#6369](https://github.com/rust-lang/rust-clippy/pull/6369) +* [`declare_interior_mutable_const`] and [`borrow_interior_mutable_const`]: + Both now ignore enums with frozen variants + [#6110](https://github.com/rust-lang/rust-clippy/pull/6110) +* [`field_reassign_with_default`] No longer lint for private fields + [#6537](https://github.com/rust-lang/rust-clippy/pull/6537) + + +### Suggestion Fixes/Improvements + +* [`vec_box`]: Provide correct type scope suggestion + [#6271](https://github.com/rust-lang/rust-clippy/pull/6271) +* [`manual_range_contains`]: Give correct suggestion when using floats + [#6320](https://github.com/rust-lang/rust-clippy/pull/6320) +* [`unnecessary_lazy_evaluations`]: Don't always mark suggestion as MachineApplicable + [#6272](https://github.com/rust-lang/rust-clippy/pull/6272) +* [`manual_async_fn`]: Improve suggestion formatting + [#6294](https://github.com/rust-lang/rust-clippy/pull/6294) +* [`unnecessary_cast`]: Fix incorrectly formatted float literal suggestion + [#6362](https://github.com/rust-lang/rust-clippy/pull/6362) + +### ICE Fixes + +* Fix a crash in [`from_iter_instead_of_collect`] + [#6304](https://github.com/rust-lang/rust-clippy/pull/6304) +* Fix a silent crash when parsing doc comments in [`needless_doctest_main`] + [#6458](https://github.com/rust-lang/rust-clippy/pull/6458) + +### Documentation Improvements + +* The lint website search has been improved ([#6477](https://github.com/rust-lang/rust-clippy/pull/6477)): + * Searching for lints with dashes and spaces is possible now. For example + `missing-errors-doc` and `missing errors doc` are now valid aliases for lint names + * Improved fuzzy search in lint descriptions +* Various README improvements + [#6287](https://github.com/rust-lang/rust-clippy/pull/6287) +* Add known problems to [`comparison_chain`] documentation + [#6390](https://github.com/rust-lang/rust-clippy/pull/6390) +* Fix example used in [`cargo_common_metadata`] + [#6293](https://github.com/rust-lang/rust-clippy/pull/6293) +* Improve [`map_clone`] documentation + [#6340](https://github.com/rust-lang/rust-clippy/pull/6340) + +### Others + +* You can now tell Clippy about the MSRV your project supports. Please refer to + the specific README section to learn more about MSRV support [here][msrv_readme] + [#6201](https://github.com/rust-lang/rust-clippy/pull/6201) +* Add `--no-deps` option to avoid running on path dependencies in workspaces + [#6188](https://github.com/rust-lang/rust-clippy/pull/6188) + +## Rust 1.49 + +Released 2020-12-31 + +[e636b88...b20d4c1](https://github.com/rust-lang/rust-clippy/compare/e636b88...b20d4c1) + +### New Lints + +* [`field_reassign_with_default`] [#5911](https://github.com/rust-lang/rust-clippy/pull/5911) +* [`await_holding_refcell_ref`] [#6029](https://github.com/rust-lang/rust-clippy/pull/6029) +* [`disallowed_method`] [#6081](https://github.com/rust-lang/rust-clippy/pull/6081) +* [`inline_asm_x86_att_syntax`] [#6092](https://github.com/rust-lang/rust-clippy/pull/6092) +* [`inline_asm_x86_intel_syntax`] [#6092](https://github.com/rust-lang/rust-clippy/pull/6092) +* [`from_iter_instead_of_collect`] [#6101](https://github.com/rust-lang/rust-clippy/pull/6101) +* [`mut_mutex_lock`] [#6103](https://github.com/rust-lang/rust-clippy/pull/6103) +* [`single_element_loop`] [#6109](https://github.com/rust-lang/rust-clippy/pull/6109) +* [`manual_unwrap_or`] [#6123](https://github.com/rust-lang/rust-clippy/pull/6123) +* [`large_types_passed_by_value`] [#6135](https://github.com/rust-lang/rust-clippy/pull/6135) +* [`result_unit_err`] [#6157](https://github.com/rust-lang/rust-clippy/pull/6157) +* [`ref_option_ref`] [#6165](https://github.com/rust-lang/rust-clippy/pull/6165) +* [`manual_range_contains`] [#6177](https://github.com/rust-lang/rust-clippy/pull/6177) +* [`unusual_byte_groupings`] [#6183](https://github.com/rust-lang/rust-clippy/pull/6183) +* [`comparison_to_empty`] [#6226](https://github.com/rust-lang/rust-clippy/pull/6226) +* [`map_collect_result_unit`] [#6227](https://github.com/rust-lang/rust-clippy/pull/6227) +* [`manual_ok_or`] [#6233](https://github.com/rust-lang/rust-clippy/pull/6233) + +### Moves and Deprecations + +* Rename `single_char_push_str` to [`single_char_add_str`] + [#6037](https://github.com/rust-lang/rust-clippy/pull/6037) +* Rename `zero_width_space` to [`invisible_characters`] + [#6105](https://github.com/rust-lang/rust-clippy/pull/6105) +* Deprecate [`drop_bounds`] (uplifted) + [#6111](https://github.com/rust-lang/rust-clippy/pull/6111) +* Move [`string_lit_as_bytes`] to `nursery` + [#6117](https://github.com/rust-lang/rust-clippy/pull/6117) +* Move [`rc_buffer`] to `restriction` + [#6128](https://github.com/rust-lang/rust-clippy/pull/6128) + +### Enhancements + +* [`manual_memcpy`]: Also lint when there are loop counters (and produce a + reliable suggestion) + [#5727](https://github.com/rust-lang/rust-clippy/pull/5727) +* [`single_char_add_str`]: Also lint on `String::insert_str` + [#6037](https://github.com/rust-lang/rust-clippy/pull/6037) +* [`invisible_characters`]: Also lint the characters `\u{AD}` and `\u{2060}` + [#6105](https://github.com/rust-lang/rust-clippy/pull/6105) +* [`eq_op`]: Also lint on the `assert_*!` macro family + [#6167](https://github.com/rust-lang/rust-clippy/pull/6167) +* [`items_after_statements`]: Also lint in local macro expansions + [#6176](https://github.com/rust-lang/rust-clippy/pull/6176) +* [`unnecessary_cast`]: Also lint casts on integer and float literals + [#6187](https://github.com/rust-lang/rust-clippy/pull/6187) +* [`manual_unwrap_or`]: Also lint `Result::unwrap_or` + [#6190](https://github.com/rust-lang/rust-clippy/pull/6190) +* [`match_like_matches_macro`]: Also lint when `match` has more than two arms + [#6216](https://github.com/rust-lang/rust-clippy/pull/6216) +* [`integer_arithmetic`]: Better handle `/` an `%` operators + [#6229](https://github.com/rust-lang/rust-clippy/pull/6229) + +### False Positive Fixes + +* [`needless_lifetimes`]: Bail out if the function has a `where` clause with the + lifetime [#5978](https://github.com/rust-lang/rust-clippy/pull/5978) +* [`explicit_counter_loop`]: No longer lints, when loop counter is used after it + is incremented [#6076](https://github.com/rust-lang/rust-clippy/pull/6076) +* [`or_fun_call`]: Revert changes addressing the handling of `const fn` + [#6077](https://github.com/rust-lang/rust-clippy/pull/6077) +* [`needless_range_loop`]: No longer lints, when the iterable is used in the + range [#6102](https://github.com/rust-lang/rust-clippy/pull/6102) +* [`inconsistent_digit_grouping`]: Fix bug when using floating point exponent + [#6104](https://github.com/rust-lang/rust-clippy/pull/6104) +* [`mistyped_literal_suffixes`]: No longer lints on the fractional part of a + float (e.g. `713.32_64`) + [#6114](https://github.com/rust-lang/rust-clippy/pull/6114) +* [`invalid_regex`]: No longer lint on unicode characters within `bytes::Regex` + [#6132](https://github.com/rust-lang/rust-clippy/pull/6132) +* [`boxed_local`]: No longer lints on `extern fn` arguments + [#6133](https://github.com/rust-lang/rust-clippy/pull/6133) +* [`needless_lifetimes`]: Fix regression, where lifetime is used in `where` + clause [#6198](https://github.com/rust-lang/rust-clippy/pull/6198) + +### Suggestion Fixes/Improvements + +* [`unnecessary_sort_by`]: Avoid dereferencing the suggested closure parameter + [#6078](https://github.com/rust-lang/rust-clippy/pull/6078) +* [`needless_arbitrary_self_type`]: Correctly handle expanded code + [#6093](https://github.com/rust-lang/rust-clippy/pull/6093) +* [`useless_format`]: Preserve raw strings in suggestion + [#6151](https://github.com/rust-lang/rust-clippy/pull/6151) +* [`empty_loop`]: Suggest alternatives + [#6162](https://github.com/rust-lang/rust-clippy/pull/6162) +* [`borrowed_box`]: Correctly add parentheses in suggestion + [#6200](https://github.com/rust-lang/rust-clippy/pull/6200) +* [`unused_unit`]: Improve suggestion formatting + [#6247](https://github.com/rust-lang/rust-clippy/pull/6247) + +### Documentation Improvements + +* Some doc improvements: + * [`rc_buffer`] [#6090](https://github.com/rust-lang/rust-clippy/pull/6090) + * [`empty_loop`] [#6162](https://github.com/rust-lang/rust-clippy/pull/6162) +* [`doc_markdown`]: Document problematic link text style + [#6107](https://github.com/rust-lang/rust-clippy/pull/6107) + +## Rust 1.48 + +Released 2020-11-19 + +[09bd400...e636b88](https://github.com/rust-lang/rust-clippy/compare/09bd400...e636b88) + +### New lints + +* [`self_assignment`] [#5894](https://github.com/rust-lang/rust-clippy/pull/5894) +* [`unnecessary_lazy_evaluations`] [#5720](https://github.com/rust-lang/rust-clippy/pull/5720) +* [`manual_strip`] [#6038](https://github.com/rust-lang/rust-clippy/pull/6038) +* [`map_err_ignore`] [#5998](https://github.com/rust-lang/rust-clippy/pull/5998) +* [`rc_buffer`] [#6044](https://github.com/rust-lang/rust-clippy/pull/6044) +* [`to_string_in_display`] [#5831](https://github.com/rust-lang/rust-clippy/pull/5831) +* `single_char_push_str` [#5881](https://github.com/rust-lang/rust-clippy/pull/5881) + +### Moves and Deprecations + +* Downgrade [`verbose_bit_mask`] to pedantic + [#6036](https://github.com/rust-lang/rust-clippy/pull/6036) + +### Enhancements + +* Extend [`precedence`] to handle chains of methods combined with unary negation + [#5928](https://github.com/rust-lang/rust-clippy/pull/5928) +* [`useless_vec`]: add a configuration value for the maximum allowed size on the stack + [#5907](https://github.com/rust-lang/rust-clippy/pull/5907) +* [`suspicious_arithmetic_impl`]: extend to implementations of `BitAnd`, `BitOr`, `BitXor`, `Rem`, `Shl`, and `Shr` + [#5884](https://github.com/rust-lang/rust-clippy/pull/5884) +* [`invalid_atomic_ordering`]: detect misuse of `compare_exchange`, `compare_exchange_weak`, and `fetch_update` + [#6025](https://github.com/rust-lang/rust-clippy/pull/6025) +* Avoid [`redundant_pattern_matching`] triggering in macros + [#6069](https://github.com/rust-lang/rust-clippy/pull/6069) +* [`option_if_let_else`]: distinguish pure from impure `else` expressions + [#5937](https://github.com/rust-lang/rust-clippy/pull/5937) +* [`needless_doctest_main`]: parse doctests instead of using textual search + [#5912](https://github.com/rust-lang/rust-clippy/pull/5912) +* [`wildcard_imports`]: allow `prelude` to appear in any segment of an import + [#5929](https://github.com/rust-lang/rust-clippy/pull/5929) +* Re-enable [`len_zero`] for ranges now that `range_is_empty` is stable + [#5961](https://github.com/rust-lang/rust-clippy/pull/5961) +* [`option_as_ref_deref`]: catch fully-qualified calls to `Deref::deref` and `DerefMut::deref_mut` + [#5933](https://github.com/rust-lang/rust-clippy/pull/5933) + +### False Positive Fixes + +* [`useless_attribute`]: permit allowing [`wildcard_imports`] and [`enum_glob_use`] + [#5994](https://github.com/rust-lang/rust-clippy/pull/5994) +* [`transmute_ptr_to_ptr`]: avoid suggesting dereferencing raw pointers in const contexts + [#5999](https://github.com/rust-lang/rust-clippy/pull/5999) +* [`redundant_closure_call`]: take into account usages of the closure in nested functions and closures + [#5920](https://github.com/rust-lang/rust-clippy/pull/5920) +* Fix false positive in [`borrow_interior_mutable_const`] when referencing a field behind a pointer + [#5949](https://github.com/rust-lang/rust-clippy/pull/5949) +* [`doc_markdown`]: allow using "GraphQL" without backticks + [#5996](https://github.com/rust-lang/rust-clippy/pull/5996) +* [`to_string_in_display`]: avoid linting when calling `to_string()` on anything that is not `self` + [#5971](https://github.com/rust-lang/rust-clippy/pull/5971) +* [`indexing_slicing`] and [`out_of_bounds_indexing`] treat references to arrays as arrays + [#6034](https://github.com/rust-lang/rust-clippy/pull/6034) +* [`should_implement_trait`]: ignore methods with lifetime parameters + [#5725](https://github.com/rust-lang/rust-clippy/pull/5725) +* [`needless_return`]: avoid linting if a temporary borrows a local variable + [#5903](https://github.com/rust-lang/rust-clippy/pull/5903) +* Restrict [`unnecessary_sort_by`] to non-reference, Copy types + [#6006](https://github.com/rust-lang/rust-clippy/pull/6006) +* Avoid suggesting `from_bits`/`to_bits` in const contexts in [`transmute_int_to_float`] + [#5919](https://github.com/rust-lang/rust-clippy/pull/5919) +* [`declare_interior_mutable_const`] and [`borrow_interior_mutable_const`]: improve detection of interior mutable types + [#6046](https://github.com/rust-lang/rust-clippy/pull/6046) + +### Suggestion Fixes/Improvements + +* [`let_and_return`]: add a cast to the suggestion when the return expression has adjustments + [#5946](https://github.com/rust-lang/rust-clippy/pull/5946) +* [`useless_conversion`]: show the type in the error message + [#6035](https://github.com/rust-lang/rust-clippy/pull/6035) +* [`unnecessary_mut_passed`]: discriminate between functions and methods in the error message + [#5892](https://github.com/rust-lang/rust-clippy/pull/5892) +* [`float_cmp`] and [`float_cmp_const`]: change wording to make margin of error less ambiguous + [#6043](https://github.com/rust-lang/rust-clippy/pull/6043) +* [`default_trait_access`]: do not use unnecessary type parameters in the suggestion + [#5993](https://github.com/rust-lang/rust-clippy/pull/5993) +* [`collapsible_if`]: don't use expanded code in the suggestion + [#5992](https://github.com/rust-lang/rust-clippy/pull/5992) +* Do not suggest empty format strings in [`print_with_newline`] and [`write_with_newline`] + [#6042](https://github.com/rust-lang/rust-clippy/pull/6042) +* [`unit_arg`]: improve the readability of the suggestion + [#5931](https://github.com/rust-lang/rust-clippy/pull/5931) +* [`stable_sort_primitive`]: print the type that is being sorted in the lint message + [#5935](https://github.com/rust-lang/rust-clippy/pull/5935) +* Show line count and max lines in [`too_many_lines`] lint message + [#6009](https://github.com/rust-lang/rust-clippy/pull/6009) +* Keep parentheses in the suggestion of [`useless_conversion`] where applicable + [#5900](https://github.com/rust-lang/rust-clippy/pull/5900) +* [`option_map_unit_fn`] and [`result_map_unit_fn`]: print the unit type `()` explicitly + [#6024](https://github.com/rust-lang/rust-clippy/pull/6024) +* [`redundant_allocation`]: suggest replacing `Rc>` with `Rc` + [#5899](https://github.com/rust-lang/rust-clippy/pull/5899) +* Make lint messages adhere to rustc dev guide conventions + [#5893](https://github.com/rust-lang/rust-clippy/pull/5893) + +### ICE Fixes + +* Fix ICE in [`repeat_once`] + [#5948](https://github.com/rust-lang/rust-clippy/pull/5948) + +### Documentation Improvements + +* [`mutable_key_type`]: explain potential for false positives when the interior mutable type is not accessed in the `Hash` implementation + [#6019](https://github.com/rust-lang/rust-clippy/pull/6019) +* [`unnecessary_mut_passed`]: fix typo + [#5913](https://github.com/rust-lang/rust-clippy/pull/5913) +* Add example of false positive to [`ptr_arg`] docs. + [#5885](https://github.com/rust-lang/rust-clippy/pull/5885) +* [`box_vec`], [`vec_box`] and [`borrowed_box`]: add link to the documentation of `Box` + [#6023](https://github.com/rust-lang/rust-clippy/pull/6023) + +## Rust 1.47 + +Released 2020-10-08 + +[c2c07fa...09bd400](https://github.com/rust-lang/rust-clippy/compare/c2c07fa...09bd400) + +### New lints + +* [`derive_ord_xor_partial_ord`] [#5848](https://github.com/rust-lang/rust-clippy/pull/5848) +* [`trait_duplication_in_bounds`] [#5852](https://github.com/rust-lang/rust-clippy/pull/5852) +* [`map_identity`] [#5694](https://github.com/rust-lang/rust-clippy/pull/5694) +* [`unit_return_expecting_ord`] [#5737](https://github.com/rust-lang/rust-clippy/pull/5737) +* [`pattern_type_mismatch`] [#4841](https://github.com/rust-lang/rust-clippy/pull/4841) +* [`repeat_once`] [#5773](https://github.com/rust-lang/rust-clippy/pull/5773) +* [`same_item_push`] [#5825](https://github.com/rust-lang/rust-clippy/pull/5825) +* [`needless_arbitrary_self_type`] [#5869](https://github.com/rust-lang/rust-clippy/pull/5869) +* [`match_like_matches_macro`] [#5769](https://github.com/rust-lang/rust-clippy/pull/5769) +* [`stable_sort_primitive`] [#5809](https://github.com/rust-lang/rust-clippy/pull/5809) +* [`blanket_clippy_restriction_lints`] [#5750](https://github.com/rust-lang/rust-clippy/pull/5750) +* [`option_if_let_else`] [#5301](https://github.com/rust-lang/rust-clippy/pull/5301) + +### Moves and Deprecations + +* Deprecate [`regex_macro`] lint + [#5760](https://github.com/rust-lang/rust-clippy/pull/5760) +* Move [`range_minus_one`] to `pedantic` + [#5752](https://github.com/rust-lang/rust-clippy/pull/5752) + +### Enhancements + +* Improve [`needless_collect`] by catching `collect` calls followed by `iter` or `into_iter` calls + [#5837](https://github.com/rust-lang/rust-clippy/pull/5837) +* [`panic`], [`todo`], [`unimplemented`] and [`unreachable`] now detect calls with formatting + [#5811](https://github.com/rust-lang/rust-clippy/pull/5811) +* Detect more cases of [`suboptimal_flops`] and [`imprecise_flops`] + [#5443](https://github.com/rust-lang/rust-clippy/pull/5443) +* Handle asymmetrical implementations of `PartialEq` in [`cmp_owned`] + [#5701](https://github.com/rust-lang/rust-clippy/pull/5701) +* Make it possible to allow [`unsafe_derive_deserialize`] + [#5870](https://github.com/rust-lang/rust-clippy/pull/5870) +* Catch `ord.min(a).max(b)` where a < b in [`min_max`] + [#5871](https://github.com/rust-lang/rust-clippy/pull/5871) +* Make [`clone_on_copy`] suggestion machine applicable + [#5745](https://github.com/rust-lang/rust-clippy/pull/5745) +* Enable [`len_zero`] on ranges now that `is_empty` is stable on them + [#5961](https://github.com/rust-lang/rust-clippy/pull/5961) + +### False Positive Fixes + +* Avoid triggering [`or_fun_call`] with const fns that take no arguments + [#5889](https://github.com/rust-lang/rust-clippy/pull/5889) +* Fix [`redundant_closure_call`] false positive for closures that have multiple calls + [#5800](https://github.com/rust-lang/rust-clippy/pull/5800) +* Don't lint cases involving `ManuallyDrop` in [`redundant_clone`] + [#5824](https://github.com/rust-lang/rust-clippy/pull/5824) +* Treat a single expression the same as a single statement in the 2nd arm of a match in [`single_match_else`] + [#5771](https://github.com/rust-lang/rust-clippy/pull/5771) +* Don't trigger [`unnested_or_patterns`] if the feature `or_patterns` is not enabled + [#5758](https://github.com/rust-lang/rust-clippy/pull/5758) +* Avoid linting if key borrows in [`unnecessary_sort_by`] + [#5756](https://github.com/rust-lang/rust-clippy/pull/5756) +* Consider `Try` impl for `Poll` when generating suggestions in [`try_err`] + [#5857](https://github.com/rust-lang/rust-clippy/pull/5857) +* Take input lifetimes into account in `manual_async_fn` + [#5859](https://github.com/rust-lang/rust-clippy/pull/5859) +* Fix multiple false positives in [`type_repetition_in_bounds`] and add a configuration option + [#5761](https://github.com/rust-lang/rust-clippy/pull/5761) +* Limit the [`suspicious_arithmetic_impl`] lint to one binary operation + [#5820](https://github.com/rust-lang/rust-clippy/pull/5820) + +### Suggestion Fixes/Improvements + +* Improve readability of [`shadow_unrelated`] suggestion by truncating the RHS snippet + [#5788](https://github.com/rust-lang/rust-clippy/pull/5788) +* Suggest `filter_map` instead of `flat_map` when mapping to `Option` in [`map_flatten`] + [#5846](https://github.com/rust-lang/rust-clippy/pull/5846) +* Ensure suggestion is shown correctly for long method call chains in [`iter_nth_zero`] + [#5793](https://github.com/rust-lang/rust-clippy/pull/5793) +* Drop borrow operator in suggestions of [`redundant_pattern_matching`] + [#5815](https://github.com/rust-lang/rust-clippy/pull/5815) +* Add suggestion for [`iter_skip_next`] + [#5843](https://github.com/rust-lang/rust-clippy/pull/5843) +* Improve [`collapsible_if`] fix suggestion + [#5732](https://github.com/rust-lang/rust-clippy/pull/5732) + +### ICE Fixes + +* Fix ICE caused by [`needless_collect`] + [#5877](https://github.com/rust-lang/rust-clippy/pull/5877) +* Fix ICE caused by [`unnested_or_patterns`] + [#5784](https://github.com/rust-lang/rust-clippy/pull/5784) + +### Documentation Improvements + +* Fix grammar of [`await_holding_lock`] documentation + [#5748](https://github.com/rust-lang/rust-clippy/pull/5748) + +### Others + +* Make lints adhere to the rustc dev guide + [#5888](https://github.com/rust-lang/rust-clippy/pull/5888) + +## Rust 1.46 + +Released 2020-08-27 + +[7ea7cd1...c2c07fa](https://github.com/rust-lang/rust-clippy/compare/7ea7cd1...c2c07fa) + +### New lints + +* [`unnested_or_patterns`] [#5378](https://github.com/rust-lang/rust-clippy/pull/5378) +* [`iter_next_slice`] [#5597](https://github.com/rust-lang/rust-clippy/pull/5597) +* [`unnecessary_sort_by`] [#5623](https://github.com/rust-lang/rust-clippy/pull/5623) +* [`vec_resize_to_zero`] [#5637](https://github.com/rust-lang/rust-clippy/pull/5637) + +### Moves and Deprecations + +* Move [`cast_ptr_alignment`] to pedantic [#5667](https://github.com/rust-lang/rust-clippy/pull/5667) + +### Enhancements + +* Improve [`mem_replace_with_uninit`] lint [#5695](https://github.com/rust-lang/rust-clippy/pull/5695) + +### False Positive Fixes + +* [`len_zero`]: Avoid linting ranges when the `range_is_empty` feature is not enabled + [#5656](https://github.com/rust-lang/rust-clippy/pull/5656) +* [`let_and_return`]: Don't lint if a temporary borrow is involved + [#5680](https://github.com/rust-lang/rust-clippy/pull/5680) +* [`reversed_empty_ranges`]: Avoid linting `N..N` in for loop arguments in + [#5692](https://github.com/rust-lang/rust-clippy/pull/5692) +* [`if_same_then_else`]: Don't assume multiplication is always commutative + [#5702](https://github.com/rust-lang/rust-clippy/pull/5702) +* [`blacklisted_name`]: Remove `bar` from the default configuration + [#5712](https://github.com/rust-lang/rust-clippy/pull/5712) +* [`redundant_pattern_matching`]: Avoid suggesting non-`const fn` calls in const contexts + [#5724](https://github.com/rust-lang/rust-clippy/pull/5724) + +### Suggestion Fixes/Improvements + +* Fix suggestion of [`unit_arg`] lint, so that it suggest semantic equivalent code + [#4455](https://github.com/rust-lang/rust-clippy/pull/4455) +* Add auto applicable suggestion to [`macro_use_imports`] + [#5279](https://github.com/rust-lang/rust-clippy/pull/5279) + +### ICE Fixes + +* Fix ICE in the `consts` module of Clippy [#5709](https://github.com/rust-lang/rust-clippy/pull/5709) + +### Documentation Improvements + +* Improve code examples across multiple lints [#5664](https://github.com/rust-lang/rust-clippy/pull/5664) + +### Others + +* Introduce a `--rustc` flag to `clippy-driver`, which turns `clippy-driver` + into `rustc` and passes all the given arguments to `rustc`. This is especially + useful for tools that need the `rustc` version Clippy was compiled with, + instead of the Clippy version. E.g. `clippy-driver --rustc --version` will + print the output of `rustc --version`. + [#5178](https://github.com/rust-lang/rust-clippy/pull/5178) +* New issue templates now make it easier to complain if Clippy is too annoying + or not annoying enough! [#5735](https://github.com/rust-lang/rust-clippy/pull/5735) + +## Rust 1.45 + +Released 2020-07-16 + +[891e1a8...7ea7cd1](https://github.com/rust-lang/rust-clippy/compare/891e1a8...7ea7cd1) + +### New lints + +* [`match_wildcard_for_single_variants`] [#5582](https://github.com/rust-lang/rust-clippy/pull/5582) +* [`unsafe_derive_deserialize`] [#5493](https://github.com/rust-lang/rust-clippy/pull/5493) +* [`if_let_mutex`] [#5332](https://github.com/rust-lang/rust-clippy/pull/5332) +* [`mismatched_target_os`] [#5506](https://github.com/rust-lang/rust-clippy/pull/5506) +* [`await_holding_lock`] [#5439](https://github.com/rust-lang/rust-clippy/pull/5439) +* [`match_on_vec_items`] [#5522](https://github.com/rust-lang/rust-clippy/pull/5522) +* [`manual_async_fn`] [#5576](https://github.com/rust-lang/rust-clippy/pull/5576) +* [`reversed_empty_ranges`] [#5583](https://github.com/rust-lang/rust-clippy/pull/5583) +* [`manual_non_exhaustive`] [#5550](https://github.com/rust-lang/rust-clippy/pull/5550) + +### Moves and Deprecations + +* Downgrade [`match_bool`] to pedantic [#5408](https://github.com/rust-lang/rust-clippy/pull/5408) +* Downgrade [`match_wild_err_arm`] to pedantic and update help messages. [#5622](https://github.com/rust-lang/rust-clippy/pull/5622) +* Downgrade [`useless_let_if_seq`] to nursery. [#5599](https://github.com/rust-lang/rust-clippy/pull/5599) +* Generalize `option_and_then_some` and rename to [`bind_instead_of_map`]. [#5529](https://github.com/rust-lang/rust-clippy/pull/5529) +* Rename `identity_conversion` to [`useless_conversion`]. [#5568](https://github.com/rust-lang/rust-clippy/pull/5568) +* Merge `block_in_if_condition_expr` and `block_in_if_condition_stmt` into [`blocks_in_if_conditions`]. +[#5563](https://github.com/rust-lang/rust-clippy/pull/5563) +* Merge `option_map_unwrap_or`, `option_map_unwrap_or_else` and `result_map_unwrap_or_else` into [`map_unwrap_or`]. +[#5563](https://github.com/rust-lang/rust-clippy/pull/5563) +* Merge `option_unwrap_used` and `result_unwrap_used` into [`unwrap_used`]. +[#5563](https://github.com/rust-lang/rust-clippy/pull/5563) +* Merge `option_expect_used` and `result_expect_used` into [`expect_used`]. +[#5563](https://github.com/rust-lang/rust-clippy/pull/5563) +* Merge `for_loop_over_option` and `for_loop_over_result` into [`for_loops_over_fallibles`]. +[#5563](https://github.com/rust-lang/rust-clippy/pull/5563) + +### Enhancements + +* Avoid running cargo lints when not enabled to improve performance. [#5505](https://github.com/rust-lang/rust-clippy/pull/5505) +* Extend [`useless_conversion`] with `TryFrom` and `TryInto`. [#5631](https://github.com/rust-lang/rust-clippy/pull/5631) +* Lint also in type parameters and where clauses in [`unused_unit`]. [#5592](https://github.com/rust-lang/rust-clippy/pull/5592) +* Do not suggest deriving `Default` in [`new_without_default`]. [#5616](https://github.com/rust-lang/rust-clippy/pull/5616) + +### False Positive Fixes + +* [`while_let_on_iterator`] [#5525](https://github.com/rust-lang/rust-clippy/pull/5525) +* [`empty_line_after_outer_attr`] [#5609](https://github.com/rust-lang/rust-clippy/pull/5609) +* [`unnecessary_unwrap`] [#5558](https://github.com/rust-lang/rust-clippy/pull/5558) +* [`comparison_chain`] [#5596](https://github.com/rust-lang/rust-clippy/pull/5596) +* Don't trigger [`used_underscore_binding`] in await desugaring. [#5535](https://github.com/rust-lang/rust-clippy/pull/5535) +* Don't trigger [`borrowed_box`] on mutable references. [#5491](https://github.com/rust-lang/rust-clippy/pull/5491) +* Allow `1 << 0` in [`identity_op`]. [#5602](https://github.com/rust-lang/rust-clippy/pull/5602) +* Allow `use super::*;` glob imports in [`wildcard_imports`]. [#5564](https://github.com/rust-lang/rust-clippy/pull/5564) +* Whitelist more words in [`doc_markdown`]. [#5611](https://github.com/rust-lang/rust-clippy/pull/5611) +* Skip dev and build deps in [`multiple_crate_versions`]. [#5636](https://github.com/rust-lang/rust-clippy/pull/5636) +* Honor `allow` attribute on arguments in [`ptr_arg`]. [#5647](https://github.com/rust-lang/rust-clippy/pull/5647) +* Honor lint level attributes for [`redundant_field_names`], [`just_underscores_and_digits`], [`many_single_char_names`] +and [`similar_names`]. [#5651](https://github.com/rust-lang/rust-clippy/pull/5651) +* Ignore calls to `len` in [`or_fun_call`]. [#4429](https://github.com/rust-lang/rust-clippy/pull/4429) + +### Suggestion Improvements + +* Simplify suggestions in [`manual_memcpy`]. [#5536](https://github.com/rust-lang/rust-clippy/pull/5536) +* Fix suggestion in [`redundant_pattern_matching`] for macros. [#5511](https://github.com/rust-lang/rust-clippy/pull/5511) +* Avoid suggesting `copied()` for mutable references in [`map_clone`]. [#5530](https://github.com/rust-lang/rust-clippy/pull/5530) +* Improve help message for [`clone_double_ref`]. [#5547](https://github.com/rust-lang/rust-clippy/pull/5547) + +### ICE Fixes + +* Fix ICE caused in unwrap module. [#5590](https://github.com/rust-lang/rust-clippy/pull/5590) +* Fix ICE on rustc test issue-69020-assoc-const-arith-overflow.rs [#5499](https://github.com/rust-lang/rust-clippy/pull/5499) + +### Documentation + +* Clarify the documentation of [`unnecessary_mut_passed`]. [#5639](https://github.com/rust-lang/rust-clippy/pull/5639) +* Extend example for [`unneeded_field_pattern`]. [#5541](https://github.com/rust-lang/rust-clippy/pull/5541) + +## Rust 1.44 + +Released 2020-06-04 + +[204bb9b...891e1a8](https://github.com/rust-lang/rust-clippy/compare/204bb9b...891e1a8) + +### New lints + +* [`explicit_deref_methods`] [#5226](https://github.com/rust-lang/rust-clippy/pull/5226) +* [`implicit_saturating_sub`] [#5427](https://github.com/rust-lang/rust-clippy/pull/5427) +* [`macro_use_imports`] [#5230](https://github.com/rust-lang/rust-clippy/pull/5230) +* [`verbose_file_reads`] [#5272](https://github.com/rust-lang/rust-clippy/pull/5272) +* [`future_not_send`] [#5423](https://github.com/rust-lang/rust-clippy/pull/5423) +* [`redundant_pub_crate`] [#5319](https://github.com/rust-lang/rust-clippy/pull/5319) +* [`large_const_arrays`] [#5248](https://github.com/rust-lang/rust-clippy/pull/5248) +* [`result_map_or_into_option`] [#5415](https://github.com/rust-lang/rust-clippy/pull/5415) +* [`redundant_allocation`] [#5349](https://github.com/rust-lang/rust-clippy/pull/5349) +* [`fn_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294) +* [`vtable_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294) + + +### Moves and Deprecations + +* Deprecate [`replace_consts`] lint [#5380](https://github.com/rust-lang/rust-clippy/pull/5380) +* Move [`cognitive_complexity`] to nursery [#5428](https://github.com/rust-lang/rust-clippy/pull/5428) +* Move [`useless_transmute`] to nursery [#5364](https://github.com/rust-lang/rust-clippy/pull/5364) +* Downgrade [`inefficient_to_string`] to pedantic [#5412](https://github.com/rust-lang/rust-clippy/pull/5412) +* Downgrade [`option_option`] to pedantic [#5401](https://github.com/rust-lang/rust-clippy/pull/5401) +* Downgrade [`unreadable_literal`] to pedantic [#5419](https://github.com/rust-lang/rust-clippy/pull/5419) +* Downgrade [`let_unit_value`] to pedantic [#5409](https://github.com/rust-lang/rust-clippy/pull/5409) +* Downgrade [`trivially_copy_pass_by_ref`] to pedantic [#5410](https://github.com/rust-lang/rust-clippy/pull/5410) +* Downgrade [`implicit_hasher`] to pedantic [#5411](https://github.com/rust-lang/rust-clippy/pull/5411) + +### Enhancements + +* On _nightly_ you can now use `cargo clippy --fix -Z unstable-options` to + auto-fix lints that support this [#5363](https://github.com/rust-lang/rust-clippy/pull/5363) +* Make [`redundant_clone`] also trigger on cases where the cloned value is not + consumed. [#5304](https://github.com/rust-lang/rust-clippy/pull/5304) +* Expand [`integer_arithmetic`] to also disallow bit-shifting [#5430](https://github.com/rust-lang/rust-clippy/pull/5430) +* [`option_as_ref_deref`] now detects more deref cases [#5425](https://github.com/rust-lang/rust-clippy/pull/5425) +* [`large_enum_variant`] now report the sizes of the largest and second-largest variants [#5466](https://github.com/rust-lang/rust-clippy/pull/5466) +* [`bool_comparison`] now also checks for inequality comparisons that can be + written more concisely [#5365](https://github.com/rust-lang/rust-clippy/pull/5365) +* Expand [`clone_on_copy`] to work in method call arguments as well [#5441](https://github.com/rust-lang/rust-clippy/pull/5441) +* [`redundant_pattern_matching`] now also handles `while let` [#5483](https://github.com/rust-lang/rust-clippy/pull/5483) +* [`integer_arithmetic`] now also lints references of integers [#5329](https://github.com/rust-lang/rust-clippy/pull/5329) +* Expand [`float_cmp_const`] to also work on arrays [#5345](https://github.com/rust-lang/rust-clippy/pull/5345) +* Trigger [`map_flatten`] when map is called on an `Option` [#5473](https://github.com/rust-lang/rust-clippy/pull/5473) + +### False Positive Fixes + +* [`many_single_char_names`] [#5468](https://github.com/rust-lang/rust-clippy/pull/5468) +* [`should_implement_trait`] [#5437](https://github.com/rust-lang/rust-clippy/pull/5437) +* [`unused_self`] [#5387](https://github.com/rust-lang/rust-clippy/pull/5387) +* [`redundant_clone`] [#5453](https://github.com/rust-lang/rust-clippy/pull/5453) +* [`precedence`] [#5445](https://github.com/rust-lang/rust-clippy/pull/5445) +* [`suspicious_op_assign_impl`] [#5424](https://github.com/rust-lang/rust-clippy/pull/5424) +* [`needless_lifetimes`] [#5293](https://github.com/rust-lang/rust-clippy/pull/5293) +* [`redundant_pattern`] [#5287](https://github.com/rust-lang/rust-clippy/pull/5287) +* [`inconsistent_digit_grouping`] [#5451](https://github.com/rust-lang/rust-clippy/pull/5451) + + +### Suggestion Improvements + +* Improved [`question_mark`] lint suggestion so that it doesn't add redundant `as_ref()` [#5481](https://github.com/rust-lang/rust-clippy/pull/5481) +* Improve the suggested placeholder in [`option_map_unit_fn`] [#5292](https://github.com/rust-lang/rust-clippy/pull/5292) +* Improve suggestion for [`match_single_binding`] when triggered inside a closure [#5350](https://github.com/rust-lang/rust-clippy/pull/5350) + +### ICE Fixes + +* Handle the unstable `trivial_bounds` feature [#5296](https://github.com/rust-lang/rust-clippy/pull/5296) +* `shadow_*` lints [#5297](https://github.com/rust-lang/rust-clippy/pull/5297) + +### Documentation + +* Fix documentation generation for configurable lints [#5353](https://github.com/rust-lang/rust-clippy/pull/5353) +* Update documentation for [`new_ret_no_self`] [#5448](https://github.com/rust-lang/rust-clippy/pull/5448) +* The documentation for [`option_option`] now suggest using a tri-state enum [#5403](https://github.com/rust-lang/rust-clippy/pull/5403) +* Fix bit mask example in [`verbose_bit_mask`] documentation [#5454](https://github.com/rust-lang/rust-clippy/pull/5454) +* [`wildcard_imports`] documentation now mentions that `use ...::prelude::*` is + not linted [#5312](https://github.com/rust-lang/rust-clippy/pull/5312) + +## Rust 1.43 + +Released 2020-04-23 + +[4ee1206...204bb9b](https://github.com/rust-lang/rust-clippy/compare/4ee1206...204bb9b) + +### New lints + +* [`imprecise_flops`] [#4897](https://github.com/rust-lang/rust-clippy/pull/4897) +* [`suboptimal_flops`] [#4897](https://github.com/rust-lang/rust-clippy/pull/4897) +* [`wildcard_imports`] [#5029](https://github.com/rust-lang/rust-clippy/pull/5029) +* [`single_component_path_imports`] [#5058](https://github.com/rust-lang/rust-clippy/pull/5058) +* [`match_single_binding`] [#5061](https://github.com/rust-lang/rust-clippy/pull/5061) +* [`let_underscore_lock`] [#5101](https://github.com/rust-lang/rust-clippy/pull/5101) +* [`struct_excessive_bools`] [#5125](https://github.com/rust-lang/rust-clippy/pull/5125) +* [`fn_params_excessive_bools`] [#5125](https://github.com/rust-lang/rust-clippy/pull/5125) +* [`option_env_unwrap`] [#5148](https://github.com/rust-lang/rust-clippy/pull/5148) +* [`lossy_float_literal`] [#5202](https://github.com/rust-lang/rust-clippy/pull/5202) +* [`rest_pat_in_fully_bound_structs`] [#5258](https://github.com/rust-lang/rust-clippy/pull/5258) + +### Moves and Deprecations + +* Move [`unneeded_field_pattern`] to pedantic group [#5200](https://github.com/rust-lang/rust-clippy/pull/5200) + +### Enhancements + +* Make [`missing_errors_doc`] lint also trigger on `async` functions + [#5181](https://github.com/rust-lang/rust-clippy/pull/5181) +* Add more constants to [`approx_constant`] [#5193](https://github.com/rust-lang/rust-clippy/pull/5193) +* Extend [`question_mark`] lint [#5266](https://github.com/rust-lang/rust-clippy/pull/5266) + +### False Positive Fixes + +* [`use_debug`] [#5047](https://github.com/rust-lang/rust-clippy/pull/5047) +* [`unnecessary_unwrap`] [#5132](https://github.com/rust-lang/rust-clippy/pull/5132) +* [`zero_prefixed_literal`] [#5170](https://github.com/rust-lang/rust-clippy/pull/5170) +* [`missing_const_for_fn`] [#5216](https://github.com/rust-lang/rust-clippy/pull/5216) + +### Suggestion Improvements + +* Improve suggestion when blocks of code are suggested [#5134](https://github.com/rust-lang/rust-clippy/pull/5134) + +### ICE Fixes + +* `misc_early` lints [#5129](https://github.com/rust-lang/rust-clippy/pull/5129) +* [`missing_errors_doc`] [#5213](https://github.com/rust-lang/rust-clippy/pull/5213) +* Fix ICE when evaluating `usize`s [#5256](https://github.com/rust-lang/rust-clippy/pull/5256) + +### Documentation + +* Improve documentation of [`iter_nth_zero`] +* Add documentation pages for stable releases [#5171](https://github.com/rust-lang/rust-clippy/pull/5171) + +### Others + +* Clippy now completely runs on GitHub Actions [#5190](https://github.com/rust-lang/rust-clippy/pull/5190) + + +## Rust 1.42 + +Released 2020-03-12 + +[69f99e7...4ee1206](https://github.com/rust-lang/rust-clippy/compare/69f99e7...4ee1206) + +### New lints + +* [`filetype_is_file`] [#4543](https://github.com/rust-lang/rust-clippy/pull/4543) +* [`let_underscore_must_use`] [#4823](https://github.com/rust-lang/rust-clippy/pull/4823) +* [`modulo_arithmetic`] [#4867](https://github.com/rust-lang/rust-clippy/pull/4867) +* [`mem_replace_with_default`] [#4881](https://github.com/rust-lang/rust-clippy/pull/4881) +* [`mutable_key_type`] [#4885](https://github.com/rust-lang/rust-clippy/pull/4885) +* [`option_as_ref_deref`] [#4945](https://github.com/rust-lang/rust-clippy/pull/4945) +* [`wildcard_in_or_patterns`] [#4960](https://github.com/rust-lang/rust-clippy/pull/4960) +* [`iter_nth_zero`] [#4966](https://github.com/rust-lang/rust-clippy/pull/4966) +* [`invalid_atomic_ordering`] [#4999](https://github.com/rust-lang/rust-clippy/pull/4999) +* [`skip_while_next`] [#5067](https://github.com/rust-lang/rust-clippy/pull/5067) + +### Moves and Deprecations + +* Move [`transmute_float_to_int`] from nursery to complexity group + [#5015](https://github.com/rust-lang/rust-clippy/pull/5015) +* Move [`range_plus_one`] to pedantic group [#5057](https://github.com/rust-lang/rust-clippy/pull/5057) +* Move [`debug_assert_with_mut_call`] to nursery group [#5106](https://github.com/rust-lang/rust-clippy/pull/5106) +* Deprecate [`unused_label`] [#4930](https://github.com/rust-lang/rust-clippy/pull/4930) + +### Enhancements + +* Lint vectored IO in [`unused_io_amount`] [#5027](https://github.com/rust-lang/rust-clippy/pull/5027) +* Make [`vec_box`] configurable by adding a size threshold [#5081](https://github.com/rust-lang/rust-clippy/pull/5081) +* Also lint constants in [`cmp_nan`] [#4910](https://github.com/rust-lang/rust-clippy/pull/4910) +* Fix false negative in [`expect_fun_call`] [#4915](https://github.com/rust-lang/rust-clippy/pull/4915) +* Fix false negative in [`redundant_clone`] [#5017](https://github.com/rust-lang/rust-clippy/pull/5017) + +### False Positive Fixes + +* [`map_clone`] [#4937](https://github.com/rust-lang/rust-clippy/pull/4937) +* [`replace_consts`] [#4977](https://github.com/rust-lang/rust-clippy/pull/4977) +* [`let_and_return`] [#5008](https://github.com/rust-lang/rust-clippy/pull/5008) +* [`eq_op`] [#5079](https://github.com/rust-lang/rust-clippy/pull/5079) +* [`possible_missing_comma`] [#5083](https://github.com/rust-lang/rust-clippy/pull/5083) +* [`debug_assert_with_mut_call`] [#5106](https://github.com/rust-lang/rust-clippy/pull/5106) +* Don't trigger [`let_underscore_must_use`] in external macros + [#5082](https://github.com/rust-lang/rust-clippy/pull/5082) +* Don't trigger [`empty_loop`] in `no_std` crates [#5086](https://github.com/rust-lang/rust-clippy/pull/5086) + +### Suggestion Improvements + +* `option_map_unwrap_or` [#4634](https://github.com/rust-lang/rust-clippy/pull/4634) +* [`wildcard_enum_match_arm`] [#4934](https://github.com/rust-lang/rust-clippy/pull/4934) +* [`cognitive_complexity`] [#4935](https://github.com/rust-lang/rust-clippy/pull/4935) +* [`decimal_literal_representation`] [#4956](https://github.com/rust-lang/rust-clippy/pull/4956) +* [`unknown_clippy_lints`] [#4963](https://github.com/rust-lang/rust-clippy/pull/4963) +* [`explicit_into_iter_loop`] [#4978](https://github.com/rust-lang/rust-clippy/pull/4978) +* [`useless_attribute`] [#5022](https://github.com/rust-lang/rust-clippy/pull/5022) +* [`if_let_some_result`] [#5032](https://github.com/rust-lang/rust-clippy/pull/5032) + +### ICE fixes + +* [`unsound_collection_transmute`] [#4975](https://github.com/rust-lang/rust-clippy/pull/4975) + +### Documentation + +* Improve documentation of [`empty_enum`], [`replace_consts`], [`redundant_clone`], and [`iterator_step_by_zero`] + + +## Rust 1.41 + +Released 2020-01-30 + +[c8e3cfb...69f99e7](https://github.com/rust-lang/rust-clippy/compare/c8e3cfb...69f99e7) + +* New Lints: + * [`exit`] [#4697](https://github.com/rust-lang/rust-clippy/pull/4697) + * [`to_digit_is_some`] [#4801](https://github.com/rust-lang/rust-clippy/pull/4801) + * [`tabs_in_doc_comments`] [#4806](https://github.com/rust-lang/rust-clippy/pull/4806) + * [`large_stack_arrays`] [#4807](https://github.com/rust-lang/rust-clippy/pull/4807) + * [`same_functions_in_if_condition`] [#4814](https://github.com/rust-lang/rust-clippy/pull/4814) + * [`zst_offset`] [#4816](https://github.com/rust-lang/rust-clippy/pull/4816) + * [`as_conversions`] [#4821](https://github.com/rust-lang/rust-clippy/pull/4821) + * [`missing_errors_doc`] [#4884](https://github.com/rust-lang/rust-clippy/pull/4884) + * [`transmute_float_to_int`] [#4889](https://github.com/rust-lang/rust-clippy/pull/4889) +* Remove plugin interface, see + [Inside Rust Blog](https://blog.rust-lang.org/inside-rust/2019/11/04/Clippy-removes-plugin-interface.html) for + details [#4714](https://github.com/rust-lang/rust-clippy/pull/4714) +* Move [`use_self`] to nursery group [#4863](https://github.com/rust-lang/rust-clippy/pull/4863) +* Deprecate [`into_iter_on_array`] [#4788](https://github.com/rust-lang/rust-clippy/pull/4788) +* Expand [`string_lit_as_bytes`] to also trigger when literal has escapes + [#4808](https://github.com/rust-lang/rust-clippy/pull/4808) +* Fix false positive in `comparison_chain` [#4842](https://github.com/rust-lang/rust-clippy/pull/4842) +* Fix false positive in `while_immutable_condition` [#4730](https://github.com/rust-lang/rust-clippy/pull/4730) +* Fix false positive in `explicit_counter_loop` [#4803](https://github.com/rust-lang/rust-clippy/pull/4803) +* Fix false positive in `must_use_candidate` [#4794](https://github.com/rust-lang/rust-clippy/pull/4794) +* Fix false positive in `print_with_newline` and `write_with_newline` + [#4769](https://github.com/rust-lang/rust-clippy/pull/4769) +* Fix false positive in `derive_hash_xor_eq` [#4766](https://github.com/rust-lang/rust-clippy/pull/4766) +* Fix false positive in `missing_inline_in_public_items` [#4870](https://github.com/rust-lang/rust-clippy/pull/4870) +* Fix false positive in `string_add` [#4880](https://github.com/rust-lang/rust-clippy/pull/4880) +* Fix false positive in `float_arithmetic` [#4851](https://github.com/rust-lang/rust-clippy/pull/4851) +* Fix false positive in `cast_sign_loss` [#4883](https://github.com/rust-lang/rust-clippy/pull/4883) +* Fix false positive in `manual_swap` [#4877](https://github.com/rust-lang/rust-clippy/pull/4877) +* Fix ICEs occurring while checking some block expressions [#4772](https://github.com/rust-lang/rust-clippy/pull/4772) +* Fix ICE in `use_self` [#4776](https://github.com/rust-lang/rust-clippy/pull/4776) +* Fix ICEs related to `const_generics` [#4780](https://github.com/rust-lang/rust-clippy/pull/4780) +* Display help when running `clippy-driver` without arguments, instead of ICEing + [#4810](https://github.com/rust-lang/rust-clippy/pull/4810) +* Clippy has its own ICE message now [#4588](https://github.com/rust-lang/rust-clippy/pull/4588) +* Show deprecated lints in the documentation again [#4757](https://github.com/rust-lang/rust-clippy/pull/4757) +* Improve Documentation by adding positive examples to some lints + [#4832](https://github.com/rust-lang/rust-clippy/pull/4832) + +## Rust 1.40 + +Released 2019-12-19 + +[4e7e71b...c8e3cfb](https://github.com/rust-lang/rust-clippy/compare/4e7e71b...c8e3cfb) + +* New Lints: + * [`unneeded_wildcard_pattern`] [#4537](https://github.com/rust-lang/rust-clippy/pull/4537) + * [`needless_doctest_main`] [#4603](https://github.com/rust-lang/rust-clippy/pull/4603) + * [`suspicious_unary_op_formatting`] [#4615](https://github.com/rust-lang/rust-clippy/pull/4615) + * [`debug_assert_with_mut_call`] [#4680](https://github.com/rust-lang/rust-clippy/pull/4680) + * [`unused_self`] [#4619](https://github.com/rust-lang/rust-clippy/pull/4619) + * [`inefficient_to_string`] [#4683](https://github.com/rust-lang/rust-clippy/pull/4683) + * [`must_use_unit`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560) + * [`must_use_candidate`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560) + * [`double_must_use`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560) + * [`comparison_chain`] [#4569](https://github.com/rust-lang/rust-clippy/pull/4569) + * [`unsound_collection_transmute`] [#4592](https://github.com/rust-lang/rust-clippy/pull/4592) + * [`panic`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) + * [`unreachable`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) + * [`todo`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) + * `option_expect_used` [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) + * `result_expect_used` [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) +* Move `redundant_clone` to perf group [#4509](https://github.com/rust-lang/rust-clippy/pull/4509) +* Move `manual_mul_add` to nursery group [#4736](https://github.com/rust-lang/rust-clippy/pull/4736) +* Expand `unit_cmp` to also work with `assert_eq!`, `debug_assert_eq!`, `assert_ne!` and `debug_assert_ne!` [#4613](https://github.com/rust-lang/rust-clippy/pull/4613) +* Expand `integer_arithmetic` to also detect mutating arithmetic like `+=` [#4585](https://github.com/rust-lang/rust-clippy/pull/4585) +* Fix false positive in `nonminimal_bool` [#4568](https://github.com/rust-lang/rust-clippy/pull/4568) +* Fix false positive in `missing_safety_doc` [#4611](https://github.com/rust-lang/rust-clippy/pull/4611) +* Fix false positive in `cast_sign_loss` [#4614](https://github.com/rust-lang/rust-clippy/pull/4614) +* Fix false positive in `redundant_clone` [#4509](https://github.com/rust-lang/rust-clippy/pull/4509) +* Fix false positive in `try_err` [#4721](https://github.com/rust-lang/rust-clippy/pull/4721) +* Fix false positive in `toplevel_ref_arg` [#4570](https://github.com/rust-lang/rust-clippy/pull/4570) +* Fix false positive in `multiple_inherent_impl` [#4593](https://github.com/rust-lang/rust-clippy/pull/4593) +* Improve more suggestions and tests in preparation for the unstable `cargo fix --clippy` [#4575](https://github.com/rust-lang/rust-clippy/pull/4575) +* Improve suggestion for `zero_ptr` [#4599](https://github.com/rust-lang/rust-clippy/pull/4599) +* Improve suggestion for `explicit_counter_loop` [#4691](https://github.com/rust-lang/rust-clippy/pull/4691) +* Improve suggestion for `mul_add` [#4602](https://github.com/rust-lang/rust-clippy/pull/4602) +* Improve suggestion for `assertions_on_constants` [#4635](https://github.com/rust-lang/rust-clippy/pull/4635) +* Fix ICE in `use_self` [#4671](https://github.com/rust-lang/rust-clippy/pull/4671) +* Fix ICE when encountering const casts [#4590](https://github.com/rust-lang/rust-clippy/pull/4590) + +## Rust 1.39 + +Released 2019-11-07 + +[3aea860...4e7e71b](https://github.com/rust-lang/rust-clippy/compare/3aea860...4e7e71b) + +* New Lints: + * [`uninit_assumed_init`] [#4479](https://github.com/rust-lang/rust-clippy/pull/4479) + * [`flat_map_identity`] [#4231](https://github.com/rust-lang/rust-clippy/pull/4231) + * [`missing_safety_doc`] [#4535](https://github.com/rust-lang/rust-clippy/pull/4535) + * [`mem_replace_with_uninit`] [#4511](https://github.com/rust-lang/rust-clippy/pull/4511) + * [`suspicious_map`] [#4394](https://github.com/rust-lang/rust-clippy/pull/4394) + * `option_and_then_some` [#4386](https://github.com/rust-lang/rust-clippy/pull/4386) + * [`manual_saturating_arithmetic`] [#4498](https://github.com/rust-lang/rust-clippy/pull/4498) +* Deprecate `unused_collect` lint. This is fully covered by rustc's `#[must_use]` on `collect` [#4348](https://github.com/rust-lang/rust-clippy/pull/4348) +* Move `type_repetition_in_bounds` to pedantic group [#4403](https://github.com/rust-lang/rust-clippy/pull/4403) +* Move `cast_lossless` to pedantic group [#4539](https://github.com/rust-lang/rust-clippy/pull/4539) +* `temporary_cstring_as_ptr` now catches more cases [#4425](https://github.com/rust-lang/rust-clippy/pull/4425) +* `use_self` now works in constructors, too [#4525](https://github.com/rust-lang/rust-clippy/pull/4525) +* `cargo_common_metadata` now checks for license files [#4518](https://github.com/rust-lang/rust-clippy/pull/4518) +* `cognitive_complexity` now includes the measured complexity in the warning message [#4469](https://github.com/rust-lang/rust-clippy/pull/4469) +* Fix false positives in `block_in_if_*` lints [#4458](https://github.com/rust-lang/rust-clippy/pull/4458) +* Fix false positive in `cast_lossless` [#4473](https://github.com/rust-lang/rust-clippy/pull/4473) +* Fix false positive in `clone_on_copy` [#4411](https://github.com/rust-lang/rust-clippy/pull/4411) +* Fix false positive in `deref_addrof` [#4487](https://github.com/rust-lang/rust-clippy/pull/4487) +* Fix false positive in `too_many_lines` [#4490](https://github.com/rust-lang/rust-clippy/pull/4490) +* Fix false positive in `new_ret_no_self` [#4365](https://github.com/rust-lang/rust-clippy/pull/4365) +* Fix false positive in `manual_swap` [#4478](https://github.com/rust-lang/rust-clippy/pull/4478) +* Fix false positive in `missing_const_for_fn` [#4450](https://github.com/rust-lang/rust-clippy/pull/4450) +* Fix false positive in `extra_unused_lifetimes` [#4477](https://github.com/rust-lang/rust-clippy/pull/4477) +* Fix false positive in `inherent_to_string` [#4460](https://github.com/rust-lang/rust-clippy/pull/4460) +* Fix false positive in `map_entry` [#4495](https://github.com/rust-lang/rust-clippy/pull/4495) +* Fix false positive in `unused_unit` [#4445](https://github.com/rust-lang/rust-clippy/pull/4445) +* Fix false positive in `redundant_pattern` [#4489](https://github.com/rust-lang/rust-clippy/pull/4489) +* Fix false positive in `wrong_self_convention` [#4369](https://github.com/rust-lang/rust-clippy/pull/4369) +* Improve various suggestions and tests in preparation for the unstable `cargo fix --clippy` [#4558](https://github.com/rust-lang/rust-clippy/pull/4558) +* Improve suggestions for `redundant_pattern_matching` [#4352](https://github.com/rust-lang/rust-clippy/pull/4352) +* Improve suggestions for `explicit_write` [#4544](https://github.com/rust-lang/rust-clippy/pull/4544) +* Improve suggestion for `or_fun_call` [#4522](https://github.com/rust-lang/rust-clippy/pull/4522) +* Improve suggestion for `match_as_ref` [#4446](https://github.com/rust-lang/rust-clippy/pull/4446) +* Improve suggestion for `unnecessary_fold_span` [#4382](https://github.com/rust-lang/rust-clippy/pull/4382) +* Add suggestions for `unseparated_literal_suffix` [#4401](https://github.com/rust-lang/rust-clippy/pull/4401) +* Add suggestions for `char_lit_as_u8` [#4418](https://github.com/rust-lang/rust-clippy/pull/4418) + +## Rust 1.38 + +Released 2019-09-26 + +[e3cb40e...3aea860](https://github.com/rust-lang/rust-clippy/compare/e3cb40e...3aea860) + +* New Lints: + * [`main_recursion`] [#4203](https://github.com/rust-lang/rust-clippy/pull/4203) + * [`inherent_to_string`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259) + * [`inherent_to_string_shadow_display`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259) + * [`type_repetition_in_bounds`] [#3766](https://github.com/rust-lang/rust-clippy/pull/3766) + * [`try_err`] [#4222](https://github.com/rust-lang/rust-clippy/pull/4222) +* Move `{unnnecessary,panicking}_unwrap` out of nursery [#4307](https://github.com/rust-lang/rust-clippy/pull/4307) +* Extend the `use_self` lint to suggest uses of `Self::Variant` [#4308](https://github.com/rust-lang/rust-clippy/pull/4308) +* Improve suggestion for needless return [#4262](https://github.com/rust-lang/rust-clippy/pull/4262) +* Add auto-fixable suggestion for `let_unit` [#4337](https://github.com/rust-lang/rust-clippy/pull/4337) +* Fix false positive in `pub_enum_variant_names` and `enum_variant_names` [#4345](https://github.com/rust-lang/rust-clippy/pull/4345) +* Fix false positive in `cast_ptr_alignment` [#4257](https://github.com/rust-lang/rust-clippy/pull/4257) +* Fix false positive in `string_lit_as_bytes` [#4233](https://github.com/rust-lang/rust-clippy/pull/4233) +* Fix false positive in `needless_lifetimes` [#4266](https://github.com/rust-lang/rust-clippy/pull/4266) +* Fix false positive in `float_cmp` [#4275](https://github.com/rust-lang/rust-clippy/pull/4275) +* Fix false positives in `needless_return` [#4274](https://github.com/rust-lang/rust-clippy/pull/4274) +* Fix false negative in `match_same_arms` [#4246](https://github.com/rust-lang/rust-clippy/pull/4246) +* Fix incorrect suggestion for `needless_bool` [#4335](https://github.com/rust-lang/rust-clippy/pull/4335) +* Improve suggestion for `cast_ptr_alignment` [#4257](https://github.com/rust-lang/rust-clippy/pull/4257) +* Improve suggestion for `single_char_literal` [#4361](https://github.com/rust-lang/rust-clippy/pull/4361) +* Improve suggestion for `len_zero` [#4314](https://github.com/rust-lang/rust-clippy/pull/4314) +* Fix ICE in `implicit_hasher` [#4268](https://github.com/rust-lang/rust-clippy/pull/4268) +* Fix allow bug in `trivially_copy_pass_by_ref` [#4250](https://github.com/rust-lang/rust-clippy/pull/4250) + +## Rust 1.37 + +Released 2019-08-15 + +[082cfa7...e3cb40e](https://github.com/rust-lang/rust-clippy/compare/082cfa7...e3cb40e) + +* New Lints: + * [`checked_conversions`] [#4088](https://github.com/rust-lang/rust-clippy/pull/4088) + * [`get_last_with_len`] [#3832](https://github.com/rust-lang/rust-clippy/pull/3832) + * [`integer_division`] [#4195](https://github.com/rust-lang/rust-clippy/pull/4195) +* Renamed Lint: `const_static_lifetime` is now called [`redundant_static_lifetimes`]. + The lint now covers statics in addition to consts [#4162](https://github.com/rust-lang/rust-clippy/pull/4162) +* [`match_same_arms`] now warns for all identical arms, instead of only the first one [#4102](https://github.com/rust-lang/rust-clippy/pull/4102) +* [`needless_return`] now works with void functions [#4220](https://github.com/rust-lang/rust-clippy/pull/4220) +* Fix false positive in [`redundant_closure`] [#4190](https://github.com/rust-lang/rust-clippy/pull/4190) +* Fix false positive in [`useless_attribute`] [#4107](https://github.com/rust-lang/rust-clippy/pull/4107) +* Fix incorrect suggestion for [`float_cmp`] [#4214](https://github.com/rust-lang/rust-clippy/pull/4214) +* Add suggestions for [`print_with_newline`] and [`write_with_newline`] [#4136](https://github.com/rust-lang/rust-clippy/pull/4136) +* Improve suggestions for `option_map_unwrap_or_else` and `result_map_unwrap_or_else` [#4164](https://github.com/rust-lang/rust-clippy/pull/4164) +* Improve suggestions for [`non_ascii_literal`] [#4119](https://github.com/rust-lang/rust-clippy/pull/4119) +* Improve diagnostics for [`let_and_return`] [#4137](https://github.com/rust-lang/rust-clippy/pull/4137) +* Improve diagnostics for [`trivially_copy_pass_by_ref`] [#4071](https://github.com/rust-lang/rust-clippy/pull/4071) +* Add macro check for [`unreadable_literal`] [#4099](https://github.com/rust-lang/rust-clippy/pull/4099) + +## Rust 1.36 + +Released 2019-07-04 + +[eb9f9b1...082cfa7](https://github.com/rust-lang/rust-clippy/compare/eb9f9b1...082cfa7) + +* New lints: [`find_map`], [`filter_map_next`] [#4039](https://github.com/rust-lang/rust-clippy/pull/4039) +* New lint: [`path_buf_push_overwrite`] [#3954](https://github.com/rust-lang/rust-clippy/pull/3954) +* Move `path_buf_push_overwrite` to the nursery [#4013](https://github.com/rust-lang/rust-clippy/pull/4013) +* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101) +* Allow allowing of [`toplevel_ref_arg`] lint [#4007](https://github.com/rust-lang/rust-clippy/pull/4007) +* Fix false negative in [`or_fun_call`] pertaining to nested constructors [#4084](https://github.com/rust-lang/rust-clippy/pull/4084) +* Fix false positive in [`or_fun_call`] pertaining to enum variant constructors [#4018](https://github.com/rust-lang/rust-clippy/pull/4018) +* Fix false positive in [`useless_let_if_seq`] pertaining to interior mutability [#4035](https://github.com/rust-lang/rust-clippy/pull/4035) +* Fix false positive in [`redundant_closure`] pertaining to non-function types [#4008](https://github.com/rust-lang/rust-clippy/pull/4008) +* Fix false positive in [`let_and_return`] pertaining to attributes on `let`s [#4024](https://github.com/rust-lang/rust-clippy/pull/4024) +* Fix false positive in [`module_name_repetitions`] lint pertaining to attributes [#4006](https://github.com/rust-lang/rust-clippy/pull/4006) +* Fix false positive on [`assertions_on_constants`] pertaining to `debug_assert!` [#3989](https://github.com/rust-lang/rust-clippy/pull/3989) +* Improve suggestion in [`map_clone`] to suggest `.copied()` where applicable [#3970](https://github.com/rust-lang/rust-clippy/pull/3970) [#4043](https://github.com/rust-lang/rust-clippy/pull/4043) +* Improve suggestion for [`search_is_some`] [#4049](https://github.com/rust-lang/rust-clippy/pull/4049) +* Improve suggestion applicability for [`naive_bytecount`] [#3984](https://github.com/rust-lang/rust-clippy/pull/3984) +* Improve suggestion applicability for [`while_let_loop`] [#3975](https://github.com/rust-lang/rust-clippy/pull/3975) +* Improve diagnostics for [`too_many_arguments`] [#4053](https://github.com/rust-lang/rust-clippy/pull/4053) +* Improve diagnostics for [`cast_lossless`] [#4021](https://github.com/rust-lang/rust-clippy/pull/4021) +* Deal with macro checks in desugarings better [#4082](https://github.com/rust-lang/rust-clippy/pull/4082) +* Add macro check for [`unnecessary_cast`] [#4026](https://github.com/rust-lang/rust-clippy/pull/4026) +* Remove [`approx_constant`]'s documentation's "Known problems" section. [#4027](https://github.com/rust-lang/rust-clippy/pull/4027) +* Fix ICE in [`suspicious_else_formatting`] [#3960](https://github.com/rust-lang/rust-clippy/pull/3960) +* Fix ICE in [`decimal_literal_representation`] [#3931](https://github.com/rust-lang/rust-clippy/pull/3931) + + +## Rust 1.35 + +Released 2019-05-20 + +[1fac380..37f5c1e](https://github.com/rust-lang/rust-clippy/compare/1fac380...37f5c1e) + +* New lint: [`drop_bounds`] to detect `T: Drop` bounds +* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101) +* Rename `cyclomatic_complexity` to [`cognitive_complexity`], start work on making lint more practical for Rust code +* Move [`get_unwrap`] to the restriction category +* Improve suggestions for [`iter_cloned_collect`] +* Improve suggestions for [`cast_lossless`] to suggest suffixed literals +* Fix false positives in [`print_with_newline`] and [`write_with_newline`] pertaining to raw strings +* Fix false positive in [`needless_range_loop`] pertaining to structs without a `.iter()` +* Fix false positive in [`bool_comparison`] pertaining to non-bool types +* Fix false positive in [`redundant_closure`] pertaining to differences in borrows +* Fix false positive in `option_map_unwrap_or` on non-copy types +* Fix false positives in [`missing_const_for_fn`] pertaining to macros and trait method impls +* Fix false positive in [`needless_pass_by_value`] pertaining to procedural macros +* Fix false positive in [`needless_continue`] pertaining to loop labels +* Fix false positive for [`boxed_local`] pertaining to arguments moved into closures +* Fix false positive for [`use_self`] in nested functions +* Fix suggestion for [`expect_fun_call`] (https://github.com/rust-lang/rust-clippy/pull/3846) +* Fix suggestion for [`explicit_counter_loop`] to deal with parenthesizing range variables +* Fix suggestion for [`single_char_pattern`] to correctly escape single quotes +* Avoid triggering [`redundant_closure`] in macros +* ICE fixes: [#3805](https://github.com/rust-lang/rust-clippy/pull/3805), [#3772](https://github.com/rust-lang/rust-clippy/pull/3772), [#3741](https://github.com/rust-lang/rust-clippy/pull/3741) + +## Rust 1.34 + +Released 2019-04-10 + +[1b89724...1fac380](https://github.com/rust-lang/rust-clippy/compare/1b89724...1fac380) + +* New lint: [`assertions_on_constants`] to detect for example `assert!(true)` +* New lint: [`dbg_macro`] to detect uses of the `dbg!` macro +* New lint: [`missing_const_for_fn`] that can suggest functions to be made `const` +* New lint: [`too_many_lines`] to detect functions with excessive LOC. It can be + configured using the `too-many-lines-threshold` configuration. +* New lint: [`wildcard_enum_match_arm`] to check for wildcard enum matches using `_` +* Expand `redundant_closure` to also work for methods (not only functions) +* Fix ICEs in `vec_box`, `needless_pass_by_value` and `implicit_hasher` +* Fix false positive in `cast_sign_loss` +* Fix false positive in `integer_arithmetic` +* Fix false positive in `unit_arg` +* Fix false positives in `implicit_return` +* Add suggestion to `explicit_write` +* Improve suggestions for `question_mark` lint +* Fix incorrect suggestion for `cast_lossless` +* Fix incorrect suggestion for `expect_fun_call` +* Fix incorrect suggestion for `needless_bool` +* Fix incorrect suggestion for `needless_range_loop` +* Fix incorrect suggestion for `use_self` +* Fix incorrect suggestion for `while_let_on_iterator` +* Clippy is now slightly easier to invoke in non-cargo contexts. See + [#3665][pull3665] for more details. +* We now have [improved documentation][adding_lints] on how to add new lints + +## Rust 1.33 + +Released 2019-02-26 + +[b2601be...1b89724](https://github.com/rust-lang/rust-clippy/compare/b2601be...1b89724) + +* New lints: [`implicit_return`], [`vec_box`], [`cast_ref_to_mut`] +* The `rust-clippy` repository is now part of the `rust-lang` org. +* Rename `stutter` to `module_name_repetitions` +* Merge `new_without_default_derive` into `new_without_default` lint +* Move `large_digit_groups` from `style` group to `pedantic` +* Expand `bool_comparison` to check for `<`, `<=`, `>`, `>=`, and `!=` + comparisons against booleans +* Expand `no_effect` to detect writes to constants such as `A_CONST.field = 2` +* Expand `redundant_clone` to work on struct fields +* Expand `suspicious_else_formatting` to detect `if .. {..} {..}` +* Expand `use_self` to work on tuple structs and also in local macros +* Fix ICE in `result_map_unit_fn` and `option_map_unit_fn` +* Fix false positives in `implicit_return` +* Fix false positives in `use_self` +* Fix false negative in `clone_on_copy` +* Fix false positive in `doc_markdown` +* Fix false positive in `empty_loop` +* Fix false positive in `if_same_then_else` +* Fix false positive in `infinite_iter` +* Fix false positive in `question_mark` +* Fix false positive in `useless_asref` +* Fix false positive in `wildcard_dependencies` +* Fix false positive in `write_with_newline` +* Add suggestion to `explicit_write` +* Improve suggestions for `question_mark` lint +* Fix incorrect suggestion for `get_unwrap` + +## Rust 1.32 + +Released 2019-01-17 + +[2e26fdc2...b2601be](https://github.com/rust-lang/rust-clippy/compare/2e26fdc2...b2601be) + +* New lints: [`slow_vector_initialization`], [`mem_discriminant_non_enum`], + [`redundant_clone`], [`wildcard_dependencies`], + [`into_iter_on_ref`], [`into_iter_on_array`], [`deprecated_cfg_attr`], + [`mem_discriminant_non_enum`], [`cargo_common_metadata`] +* Add support for `u128` and `i128` to integer related lints +* Add float support to `mistyped_literal_suffixes` +* Fix false positives in `use_self` +* Fix false positives in `missing_comma` +* Fix false positives in `new_ret_no_self` +* Fix false positives in `possible_missing_comma` +* Fix false positive in `integer_arithmetic` in constant items +* Fix false positive in `needless_borrow` +* Fix false positive in `out_of_bounds_indexing` +* Fix false positive in `new_without_default_derive` +* Fix false positive in `string_lit_as_bytes` +* Fix false negative in `out_of_bounds_indexing` +* Fix false negative in `use_self`. It will now also check existential types +* Fix incorrect suggestion for `redundant_closure_call` +* Fix various suggestions that contained expanded macros +* Fix `bool_comparison` triggering 3 times on on on the same code +* Expand `trivially_copy_pass_by_ref` to work on trait methods +* Improve suggestion for `needless_range_loop` +* Move `needless_pass_by_value` from `pedantic` group to `style` + +## Rust 1.31 + +Released 2018-12-06 + +[125907ad..2e26fdc2](https://github.com/rust-lang/rust-clippy/compare/125907ad..2e26fdc2) + +* Clippy has been relicensed under a dual MIT / Apache license. + See [#3093](https://github.com/rust-lang/rust-clippy/issues/3093) for more + information. +* With Rust 1.31, Clippy is no longer available via crates.io. The recommended + installation method is via `rustup component add clippy`. +* New lints: [`redundant_pattern_matching`], [`unnecessary_filter_map`], + [`unused_unit`], [`map_flatten`], [`mem_replace_option_with_none`] +* Fix ICE in `if_let_redundant_pattern_matching` +* Fix ICE in `needless_pass_by_value` when encountering a generic function + argument with a lifetime parameter +* Fix ICE in `needless_range_loop` +* Fix ICE in `single_char_pattern` when encountering a constant value +* Fix false positive in `assign_op_pattern` +* Fix false positive in `boxed_local` on trait implementations +* Fix false positive in `cmp_owned` +* Fix false positive in `collapsible_if` when conditionals have comments +* Fix false positive in `double_parens` +* Fix false positive in `excessive_precision` +* Fix false positive in `explicit_counter_loop` +* Fix false positive in `fn_to_numeric_cast_with_truncation` +* Fix false positive in `map_clone` +* Fix false positive in `new_ret_no_self` +* Fix false positive in `new_without_default` when `new` is unsafe +* Fix false positive in `type_complexity` when using extern types +* Fix false positive in `useless_format` +* Fix false positive in `wrong_self_convention` +* Fix incorrect suggestion for `excessive_precision` +* Fix incorrect suggestion for `expect_fun_call` +* Fix incorrect suggestion for `get_unwrap` +* Fix incorrect suggestion for `useless_format` +* `fn_to_numeric_cast_with_truncation` lint can be disabled again +* Improve suggestions for `manual_memcpy` +* Improve help message for `needless_lifetimes` + +## Rust 1.30 + +Released 2018-10-25 + +[14207503...125907ad](https://github.com/rust-lang/rust-clippy/compare/14207503...125907ad) + +* Deprecate `assign_ops` lint +* New lints: [`mistyped_literal_suffixes`], [`ptr_offset_with_cast`], + [`needless_collect`], [`copy_iterator`] +* `cargo clippy -V` now includes the Clippy commit hash of the Rust + Clippy component +* Fix ICE in `implicit_hasher` +* Fix ICE when encountering `println!("{}" a);` +* Fix ICE when encountering a macro call in match statements +* Fix false positive in `default_trait_access` +* Fix false positive in `trivially_copy_pass_by_ref` +* Fix false positive in `similar_names` +* Fix false positive in `redundant_field_name` +* Fix false positive in `expect_fun_call` +* Fix false negative in `identity_conversion` +* Fix false negative in `explicit_counter_loop` +* Fix `range_plus_one` suggestion and false negative +* `print_with_newline` / `write_with_newline`: don't warn about string with several `\n`s in them +* Fix `useless_attribute` to also whitelist `unused_extern_crates` +* Fix incorrect suggestion for `single_char_pattern` +* Improve suggestion for `identity_conversion` lint +* Move `explicit_iter_loop` and `explicit_into_iter_loop` from `style` group to `pedantic` +* Move `range_plus_one` and `range_minus_one` from `nursery` group to `complexity` +* Move `shadow_unrelated` from `restriction` group to `pedantic` +* Move `indexing_slicing` from `pedantic` group to `restriction` + +## Rust 1.29 + +Released 2018-09-13 + +[v0.0.212...14207503](https://github.com/rust-lang/rust-clippy/compare/v0.0.212...14207503) + +* :tada: :tada: **Rust 1.29 is the first stable Rust that includes a bundled Clippy** :tada: + :tada: + You can now run `rustup component add clippy-preview` and then `cargo + clippy` to run Clippy. This should put an end to the continuous nightly + upgrades for Clippy users. +* Clippy now follows the Rust versioning scheme instead of its own +* Fix ICE when encountering a `while let (..) = x.iter()` construct +* Fix false positives in `use_self` +* Fix false positive in `trivially_copy_pass_by_ref` +* Fix false positive in `useless_attribute` lint +* Fix false positive in `print_literal` +* Fix `use_self` regressions +* Improve lint message for `neg_cmp_op_on_partial_ord` +* Improve suggestion highlight for `single_char_pattern` +* Improve suggestions for various print/write macro lints +* Improve website header + +## 0.0.212 (2018-07-10) +* Rustup to *rustc 1.29.0-nightly (e06c87544 2018-07-06)* + +## 0.0.211 +* Rustup to *rustc 1.28.0-nightly (e3bf634e0 2018-06-28)* + +## 0.0.210 +* Rustup to *rustc 1.28.0-nightly (01cc982e9 2018-06-24)* + +## 0.0.209 +* Rustup to *rustc 1.28.0-nightly (523097979 2018-06-18)* + +## 0.0.208 +* Rustup to *rustc 1.28.0-nightly (86a8f1a63 2018-06-17)* + +## 0.0.207 +* Rustup to *rustc 1.28.0-nightly (2a0062974 2018-06-09)* + +## 0.0.206 +* Rustup to *rustc 1.28.0-nightly (5bf68db6e 2018-05-28)* + +## 0.0.205 +* Rustup to *rustc 1.28.0-nightly (990d8aa74 2018-05-25)* +* Rename `unused_lifetimes` to `extra_unused_lifetimes` because of naming conflict with new rustc lint + +## 0.0.204 +* Rustup to *rustc 1.28.0-nightly (71e87be38 2018-05-22)* + +## 0.0.203 +* Rustup to *rustc 1.28.0-nightly (a3085756e 2018-05-19)* +* Clippy attributes are now of the form `clippy::cyclomatic_complexity` instead of `clippy(cyclomatic_complexity)` + +## 0.0.202 +* Rustup to *rustc 1.28.0-nightly (952f344cd 2018-05-18)* + +## 0.0.201 +* Rustup to *rustc 1.27.0-nightly (2f2a11dfc 2018-05-16)* + +## 0.0.200 +* Rustup to *rustc 1.27.0-nightly (9fae15374 2018-05-13)* + +## 0.0.199 +* Rustup to *rustc 1.27.0-nightly (ff2ac35db 2018-05-12)* + +## 0.0.198 +* Rustup to *rustc 1.27.0-nightly (acd3871ba 2018-05-10)* + +## 0.0.197 +* Rustup to *rustc 1.27.0-nightly (428ea5f6b 2018-05-06)* + +## 0.0.196 +* Rustup to *rustc 1.27.0-nightly (e82261dfb 2018-05-03)* + +## 0.0.195 +* Rustup to *rustc 1.27.0-nightly (ac3c2288f 2018-04-18)* + +## 0.0.194 +* Rustup to *rustc 1.27.0-nightly (bd40cbbe1 2018-04-14)* +* New lints: [`cast_ptr_alignment`], [`transmute_ptr_to_ptr`], [`write_literal`], [`write_with_newline`], [`writeln_empty_string`] + +## 0.0.193 +* Rustup to *rustc 1.27.0-nightly (eeea94c11 2018-04-06)* + +## 0.0.192 +* Rustup to *rustc 1.27.0-nightly (fb44b4c0e 2018-04-04)* +* New lint: [`print_literal`] + +## 0.0.191 +* Rustup to *rustc 1.26.0-nightly (ae544ee1c 2018-03-29)* +* Lint audit; categorize lints as style, correctness, complexity, pedantic, nursery, restriction. + +## 0.0.190 +* Fix a bunch of intermittent cargo bugs + +## 0.0.189 +* Rustup to *rustc 1.26.0-nightly (5508b2714 2018-03-18)* + +## 0.0.188 +* Rustup to *rustc 1.26.0-nightly (392645394 2018-03-15)* +* New lint: [`while_immutable_condition`] + +## 0.0.187 +* Rustup to *rustc 1.26.0-nightly (322d7f7b9 2018-02-25)* +* New lints: [`redundant_field_names`], [`suspicious_arithmetic_impl`], [`suspicious_op_assign_impl`] + +## 0.0.186 +* Rustup to *rustc 1.25.0-nightly (0c6091fbd 2018-02-04)* +* Various false positive fixes + +## 0.0.185 +* Rustup to *rustc 1.25.0-nightly (56733bc9f 2018-02-01)* +* New lint: [`question_mark`] + +## 0.0.184 +* Rustup to *rustc 1.25.0-nightly (90eb44a58 2018-01-29)* +* New lints: [`double_comparisons`], [`empty_line_after_outer_attr`] + +## 0.0.183 +* Rustup to *rustc 1.25.0-nightly (21882aad7 2018-01-28)* +* New lint: [`misaligned_transmute`] + +## 0.0.182 +* Rustup to *rustc 1.25.0-nightly (a0dcecff9 2018-01-24)* +* New lint: [`decimal_literal_representation`] + +## 0.0.181 +* Rustup to *rustc 1.25.0-nightly (97520ccb1 2018-01-21)* +* New lints: [`else_if_without_else`], [`option_option`], [`unit_arg`], [`unnecessary_fold`] +* Removed `unit_expr` +* Various false positive fixes for [`needless_pass_by_value`] + +## 0.0.180 +* Rustup to *rustc 1.25.0-nightly (3f92e8d89 2018-01-14)* + +## 0.0.179 +* Rustup to *rustc 1.25.0-nightly (61452e506 2018-01-09)* + +## 0.0.178 +* Rustup to *rustc 1.25.0-nightly (ee220daca 2018-01-07)* + +## 0.0.177 +* Rustup to *rustc 1.24.0-nightly (250b49205 2017-12-21)* +* New lint: [`match_as_ref`] + +## 0.0.176 +* Rustup to *rustc 1.24.0-nightly (0077d128d 2017-12-14)* + +## 0.0.175 +* Rustup to *rustc 1.24.0-nightly (bb42071f6 2017-12-01)* + +## 0.0.174 +* Rustup to *rustc 1.23.0-nightly (63739ab7b 2017-11-21)* + +## 0.0.173 +* Rustup to *rustc 1.23.0-nightly (33374fa9d 2017-11-20)* + +## 0.0.172 +* Rustup to *rustc 1.23.0-nightly (d0f8e2913 2017-11-16)* + +## 0.0.171 +* Rustup to *rustc 1.23.0-nightly (ff0f5de3b 2017-11-14)* + +## 0.0.170 +* Rustup to *rustc 1.23.0-nightly (d6b06c63a 2017-11-09)* + +## 0.0.169 +* Rustup to *rustc 1.23.0-nightly (3b82e4c74 2017-11-05)* +* New lints: [`just_underscores_and_digits`], `result_map_unwrap_or_else`, [`transmute_bytes_to_str`] + +## 0.0.168 +* Rustup to *rustc 1.23.0-nightly (f0fe716db 2017-10-30)* + +## 0.0.167 +* Rustup to *rustc 1.23.0-nightly (90ef3372e 2017-10-29)* +* New lints: `const_static_lifetime`, [`erasing_op`], [`fallible_impl_from`], [`println_empty_string`], [`useless_asref`] + +## 0.0.166 +* Rustup to *rustc 1.22.0-nightly (b7960878b 2017-10-18)* +* New lints: [`explicit_write`], `identity_conversion`, [`implicit_hasher`], [`invalid_ref`], [`option_map_or_none`], + [`range_minus_one`], [`range_plus_one`], [`transmute_int_to_bool`], [`transmute_int_to_char`], + [`transmute_int_to_float`] + +## 0.0.165 +* Rust upgrade to rustc 1.22.0-nightly (0e6f4cf51 2017-09-27) +* New lint: [`mut_range_bound`] + +## 0.0.164 +* Update to *rustc 1.22.0-nightly (6c476ce46 2017-09-25)* +* New lint: [`int_plus_one`] + +## 0.0.163 +* Update to *rustc 1.22.0-nightly (14039a42a 2017-09-22)* + +## 0.0.162 +* Update to *rustc 1.22.0-nightly (0701b37d9 2017-09-18)* +* New lint: [`chars_last_cmp`] +* Improved suggestions for [`needless_borrow`], [`ptr_arg`], + +## 0.0.161 +* Update to *rustc 1.22.0-nightly (539f2083d 2017-09-13)* + +## 0.0.160 +* Update to *rustc 1.22.0-nightly (dd08c3070 2017-09-12)* + +## 0.0.159 +* Update to *rustc 1.22.0-nightly (eba374fb2 2017-09-11)* +* New lint: [`clone_on_ref_ptr`] + +## 0.0.158 +* New lint: [`manual_memcpy`] +* [`cast_lossless`] no longer has redundant parentheses in its suggestions +* Update to *rustc 1.22.0-nightly (dead08cb3 2017-09-08)* + +## 0.0.157 - 2017-09-04 +* Update to *rustc 1.22.0-nightly (981ce7d8d 2017-09-03)* +* New lint: `unit_expr` + +## 0.0.156 - 2017-09-03 +* Update to *rustc 1.22.0-nightly (744dd6c1d 2017-09-02)* + +## 0.0.155 +* Update to *rustc 1.21.0-nightly (c11f689d2 2017-08-29)* +* New lint: [`infinite_iter`], [`maybe_infinite_iter`], [`cast_lossless`] + +## 0.0.154 +* Update to *rustc 1.21.0-nightly (2c0558f63 2017-08-24)* +* Fix [`use_self`] triggering inside derives +* Add support for linting an entire workspace with `cargo clippy --all` +* New lint: [`naive_bytecount`] + +## 0.0.153 +* Update to *rustc 1.21.0-nightly (8c303ed87 2017-08-20)* +* New lint: [`use_self`] + +## 0.0.152 +* Update to *rustc 1.21.0-nightly (df511d554 2017-08-14)* + +## 0.0.151 +* Update to *rustc 1.21.0-nightly (13d94d5fa 2017-08-10)* + +## 0.0.150 +* Update to *rustc 1.21.0-nightly (215e0b10e 2017-08-08)* + +## 0.0.148 +* Update to *rustc 1.21.0-nightly (37c7d0ebb 2017-07-31)* +* New lints: [`unreadable_literal`], [`inconsistent_digit_grouping`], [`large_digit_groups`] + +## 0.0.147 +* Update to *rustc 1.21.0-nightly (aac223f4f 2017-07-30)* + +## 0.0.146 +* Update to *rustc 1.21.0-nightly (52a330969 2017-07-27)* +* Fixes false positives in `inline_always` +* Fixes false negatives in `panic_params` + +## 0.0.145 +* Update to *rustc 1.20.0-nightly (afe145d22 2017-07-23)* + +## 0.0.144 +* Update to *rustc 1.20.0-nightly (086eaa78e 2017-07-15)* + +## 0.0.143 +* Update to *rustc 1.20.0-nightly (d84693b93 2017-07-09)* +* Fix `cargo clippy` crashing on `dylib` projects +* Fix false positives around `nested_while_let` and `never_loop` + +## 0.0.142 +* Update to *rustc 1.20.0-nightly (067971139 2017-07-02)* + +## 0.0.141 +* Rewrite of the `doc_markdown` lint. +* Deprecated [`range_step_by_zero`] +* New lint: [`iterator_step_by_zero`] +* New lint: [`needless_borrowed_reference`] +* Update to *rustc 1.20.0-nightly (69c65d296 2017-06-28)* + +## 0.0.140 - 2017-06-16 +* Update to *rustc 1.19.0-nightly (258ae6dd9 2017-06-15)* + +## 0.0.139 — 2017-06-10 +* Update to *rustc 1.19.0-nightly (4bf5c99af 2017-06-10)* +* Fix bugs with for loop desugaring +* Check for [`AsRef`]/[`AsMut`] arguments in [`wrong_self_convention`] + +## 0.0.138 — 2017-06-05 +* Update to *rustc 1.19.0-nightly (0418fa9d3 2017-06-04)* + +## 0.0.137 — 2017-06-05 +* Update to *rustc 1.19.0-nightly (6684d176c 2017-06-03)* + +## 0.0.136 — 2017—05—26 +* Update to *rustc 1.19.0-nightly (557967766 2017-05-26)* + +## 0.0.135 — 2017—05—24 +* Update to *rustc 1.19.0-nightly (5b13bff52 2017-05-23)* + +## 0.0.134 — 2017—05—19 +* Update to *rustc 1.19.0-nightly (0ed1ec9f9 2017-05-18)* + +## 0.0.133 — 2017—05—14 +* Update to *rustc 1.19.0-nightly (826d8f385 2017-05-13)* + +## 0.0.132 — 2017—05—05 +* Fix various bugs and some ices + +## 0.0.131 — 2017—05—04 +* Update to *rustc 1.19.0-nightly (2d4ed8e0c 2017-05-03)* + +## 0.0.130 — 2017—05—03 +* Update to *rustc 1.19.0-nightly (6a5fc9eec 2017-05-02)* + +## 0.0.129 — 2017-05-01 +* Update to *rustc 1.19.0-nightly (06fb4d256 2017-04-30)* + +## 0.0.128 — 2017-04-28 +* Update to *rustc 1.18.0-nightly (94e884b63 2017-04-27)* + +## 0.0.127 — 2017-04-27 +* Update to *rustc 1.18.0-nightly (036983201 2017-04-26)* +* New lint: [`needless_continue`] + +## 0.0.126 — 2017-04-24 +* Update to *rustc 1.18.0-nightly (2bd4b5c6d 2017-04-23)* + +## 0.0.125 — 2017-04-19 +* Update to *rustc 1.18.0-nightly (9f2abadca 2017-04-18)* + +## 0.0.124 — 2017-04-16 +* Update to *rustc 1.18.0-nightly (d5cf1cb64 2017-04-15)* + +## 0.0.123 — 2017-04-07 +* Fix various false positives + +## 0.0.122 — 2017-04-07 +* Rustup to *rustc 1.18.0-nightly (91ae22a01 2017-04-05)* +* New lint: [`op_ref`] + +## 0.0.121 — 2017-03-21 +* Rustup to *rustc 1.17.0-nightly (134c4a0f0 2017-03-20)* + +## 0.0.120 — 2017-03-17 +* Rustup to *rustc 1.17.0-nightly (0aeb9c129 2017-03-15)* + +## 0.0.119 — 2017-03-13 +* Rustup to *rustc 1.17.0-nightly (824c9ebbd 2017-03-12)* + +## 0.0.118 — 2017-03-05 +* Rustup to *rustc 1.17.0-nightly (b1e31766d 2017-03-03)* + +## 0.0.117 — 2017-03-01 +* Rustup to *rustc 1.17.0-nightly (be760566c 2017-02-28)* + +## 0.0.116 — 2017-02-28 +* Fix `cargo clippy` on 64 bit windows systems + +## 0.0.115 — 2017-02-27 +* Rustup to *rustc 1.17.0-nightly (60a0edc6c 2017-02-26)* +* New lints: [`zero_ptr`], [`never_loop`], [`mut_from_ref`] + +## 0.0.114 — 2017-02-08 +* Rustup to *rustc 1.17.0-nightly (c49d10207 2017-02-07)* +* Tests are now ui tests (testing the exact output of rustc) + +## 0.0.113 — 2017-02-04 +* Rustup to *rustc 1.16.0-nightly (eedaa94e3 2017-02-02)* +* New lint: [`large_enum_variant`] +* `explicit_into_iter_loop` provides suggestions + +## 0.0.112 — 2017-01-27 +* Rustup to *rustc 1.16.0-nightly (df8debf6d 2017-01-25)* + +## 0.0.111 — 2017-01-21 +* Rustup to *rustc 1.16.0-nightly (a52da95ce 2017-01-20)* + +## 0.0.110 — 2017-01-20 +* Add badges and categories to `Cargo.toml` + +## 0.0.109 — 2017-01-19 +* Update to *rustc 1.16.0-nightly (c07a6ae77 2017-01-17)* + +## 0.0.108 — 2017-01-12 +* Update to *rustc 1.16.0-nightly (2782e8f8f 2017-01-12)* + +## 0.0.107 — 2017-01-11 +* Update regex dependency +* Fix FP when matching `&&mut` by `&ref` +* Reintroduce `for (_, x) in &mut hash_map` -> `for x in hash_map.values_mut()` +* New lints: [`unused_io_amount`], [`forget_ref`], [`short_circuit_statement`] + +## 0.0.106 — 2017-01-04 +* Fix FP introduced by rustup in [`wrong_self_convention`] + +## 0.0.105 — 2017-01-04 +* Update to *rustc 1.16.0-nightly (468227129 2017-01-03)* +* New lints: [`deref_addrof`], [`double_parens`], [`pub_enum_variant_names`] +* Fix suggestion in [`new_without_default`] +* FP fix in [`absurd_extreme_comparisons`] + +## 0.0.104 — 2016-12-15 +* Update to *rustc 1.15.0-nightly (8f02c429a 2016-12-15)* + +## 0.0.103 — 2016-11-25 +* Update to *rustc 1.15.0-nightly (d5814b03e 2016-11-23)* + +## 0.0.102 — 2016-11-24 +* Update to *rustc 1.15.0-nightly (3bf2be9ce 2016-11-22)* + +## 0.0.101 — 2016-11-23 +* Update to *rustc 1.15.0-nightly (7b3eeea22 2016-11-21)* +* New lint: [`string_extend_chars`] + +## 0.0.100 — 2016-11-20 +* Update to *rustc 1.15.0-nightly (ac635aa95 2016-11-18)* + +## 0.0.99 — 2016-11-18 +* Update to rustc 1.15.0-nightly (0ed951993 2016-11-14) +* New lint: [`get_unwrap`] + +## 0.0.98 — 2016-11-08 +* Fixes an issue due to a change in how cargo handles `--sysroot`, which broke `cargo clippy` + +## 0.0.97 — 2016-11-03 +* For convenience, `cargo clippy` defines a `cargo-clippy` feature. This was + previously added for a short time under the name `clippy` but removed for + compatibility. +* `cargo clippy --help` is more helping (and less helpful :smile:) +* Rustup to *rustc 1.14.0-nightly (5665bdf3e 2016-11-02)* +* New lints: [`if_let_redundant_pattern_matching`], [`partialeq_ne_impl`] + +## 0.0.96 — 2016-10-22 +* Rustup to *rustc 1.14.0-nightly (f09420685 2016-10-20)* +* New lint: [`iter_skip_next`] + +## 0.0.95 — 2016-10-06 +* Rustup to *rustc 1.14.0-nightly (3210fd5c2 2016-10-05)* + +## 0.0.94 — 2016-10-04 +* Fixes bustage on Windows due to forbidden directory name + +## 0.0.93 — 2016-10-03 +* Rustup to *rustc 1.14.0-nightly (144af3e97 2016-10-02)* +* `option_map_unwrap_or` and `option_map_unwrap_or_else` are now + allowed by default. +* New lint: [`explicit_into_iter_loop`] + +## 0.0.92 — 2016-09-30 +* Rustup to *rustc 1.14.0-nightly (289f3a4ca 2016-09-29)* + +## 0.0.91 — 2016-09-28 +* Rustup to *rustc 1.13.0-nightly (d0623cf7b 2016-09-26)* + +## 0.0.90 — 2016-09-09 +* Rustup to *rustc 1.13.0-nightly (f1f40f850 2016-09-09)* + +## 0.0.89 — 2016-09-06 +* Rustup to *rustc 1.13.0-nightly (cbe4de78e 2016-09-05)* + +## 0.0.88 — 2016-09-04 +* Rustup to *rustc 1.13.0-nightly (70598e04f 2016-09-03)* +* The following lints are not new but were only usable through the `clippy` + lint groups: [`filter_next`], `for_loop_over_option`, + `for_loop_over_result` and [`match_overlapping_arm`]. You should now be + able to `#[allow/deny]` them individually and they are available directly + through `cargo clippy`. + +## 0.0.87 — 2016-08-31 +* Rustup to *rustc 1.13.0-nightly (eac41469d 2016-08-30)* +* New lints: [`builtin_type_shadow`] +* Fix FP in [`zero_prefixed_literal`] and `0b`/`0o` + +## 0.0.86 — 2016-08-28 +* Rustup to *rustc 1.13.0-nightly (a23064af5 2016-08-27)* +* New lints: [`missing_docs_in_private_items`], [`zero_prefixed_literal`] + +## 0.0.85 — 2016-08-19 +* Fix ICE with [`useless_attribute`] +* [`useless_attribute`] ignores `unused_imports` on `use` statements + +## 0.0.84 — 2016-08-18 +* Rustup to *rustc 1.13.0-nightly (aef6971ca 2016-08-17)* + +## 0.0.83 — 2016-08-17 +* Rustup to *rustc 1.12.0-nightly (1bf5fa326 2016-08-16)* +* New lints: [`print_with_newline`], [`useless_attribute`] + +## 0.0.82 — 2016-08-17 +* Rustup to *rustc 1.12.0-nightly (197be89f3 2016-08-15)* +* New lint: [`module_inception`] + +## 0.0.81 — 2016-08-14 +* Rustup to *rustc 1.12.0-nightly (1deb02ea6 2016-08-12)* +* New lints: [`eval_order_dependence`], [`mixed_case_hex_literals`], [`unseparated_literal_suffix`] +* False positive fix in [`too_many_arguments`] +* Addition of functionality to [`needless_borrow`] +* Suggestions for [`clone_on_copy`] +* Bug fix in [`wrong_self_convention`] +* Doc improvements + +## 0.0.80 — 2016-07-31 +* Rustup to *rustc 1.12.0-nightly (1225e122f 2016-07-30)* +* New lints: [`misrefactored_assign_op`], [`serde_api_misuse`] + +## 0.0.79 — 2016-07-10 +* Rustup to *rustc 1.12.0-nightly (f93aaf84c 2016-07-09)* +* Major suggestions refactoring + +## 0.0.78 — 2016-07-02 +* Rustup to *rustc 1.11.0-nightly (01411937f 2016-07-01)* +* New lints: [`wrong_transmute`], [`double_neg`], [`filter_map`] +* For compatibility, `cargo clippy` does not defines the `clippy` feature + introduced in 0.0.76 anymore +* [`collapsible_if`] now considers `if let` + +## 0.0.77 — 2016-06-21 +* Rustup to *rustc 1.11.0-nightly (5522e678b 2016-06-20)* +* New lints: `stutter` and [`iter_nth`] + +## 0.0.76 — 2016-06-10 +* Rustup to *rustc 1.11.0-nightly (7d2f75a95 2016-06-09)* +* `cargo clippy` now automatically defines the `clippy` feature +* New lint: [`not_unsafe_ptr_arg_deref`] + +## 0.0.75 — 2016-06-08 +* Rustup to *rustc 1.11.0-nightly (763f9234b 2016-06-06)* + +## 0.0.74 — 2016-06-07 +* Fix bug with `cargo-clippy` JSON parsing +* Add the `CLIPPY_DISABLE_DOCS_LINKS` environment variable to deactivate the + “for further information visit *lint-link*” message. + +## 0.0.73 — 2016-06-05 +* Fix false positives in [`useless_let_if_seq`] + +## 0.0.72 — 2016-06-04 +* Fix false positives in [`useless_let_if_seq`] + +## 0.0.71 — 2016-05-31 +* Rustup to *rustc 1.11.0-nightly (a967611d8 2016-05-30)* +* New lint: [`useless_let_if_seq`] + +## 0.0.70 — 2016-05-28 +* Rustup to *rustc 1.10.0-nightly (7bddce693 2016-05-27)* +* [`invalid_regex`] and [`trivial_regex`] can now warn on `RegexSet::new`, + `RegexBuilder::new` and byte regexes + +## 0.0.69 — 2016-05-20 +* Rustup to *rustc 1.10.0-nightly (476fe6eef 2016-05-21)* +* [`used_underscore_binding`] has been made `Allow` temporarily + +## 0.0.68 — 2016-05-17 +* Rustup to *rustc 1.10.0-nightly (cd6a40017 2016-05-16)* +* New lint: [`unnecessary_operation`] + +## 0.0.67 — 2016-05-12 +* Rustup to *rustc 1.10.0-nightly (22ac88f1a 2016-05-11)* + +## 0.0.66 — 2016-05-11 +* New `cargo clippy` subcommand +* New lints: [`assign_op_pattern`], [`assign_ops`], [`needless_borrow`] + +## 0.0.65 — 2016-05-08 +* Rustup to *rustc 1.10.0-nightly (62e2b2fb7 2016-05-06)* +* New lints: [`float_arithmetic`], [`integer_arithmetic`] + +## 0.0.64 — 2016-04-26 +* Rustup to *rustc 1.10.0-nightly (645dd013a 2016-04-24)* +* New lints: [`temporary_cstring_as_ptr`], [`unsafe_removed_from_name`], and [`mem_forget`] + +## 0.0.63 — 2016-04-08 +* Rustup to *rustc 1.9.0-nightly (7979dd608 2016-04-07)* + +## 0.0.62 — 2016-04-07 +* Rustup to *rustc 1.9.0-nightly (bf5da36f1 2016-04-06)* + +## 0.0.61 — 2016-04-03 +* Rustup to *rustc 1.9.0-nightly (5ab11d72c 2016-04-02)* +* New lint: [`invalid_upcast_comparisons`] + +## 0.0.60 — 2016-04-01 +* Rustup to *rustc 1.9.0-nightly (e1195c24b 2016-03-31)* + +## 0.0.59 — 2016-03-31 +* Rustup to *rustc 1.9.0-nightly (30a3849f2 2016-03-30)* +* New lints: [`logic_bug`], [`nonminimal_bool`] +* Fixed: [`match_same_arms`] now ignores arms with guards +* Improved: [`useless_vec`] now warns on `for … in vec![…]` + +## 0.0.58 — 2016-03-27 +* Rustup to *rustc 1.9.0-nightly (d5a91e695 2016-03-26)* +* New lint: [`doc_markdown`] + +## 0.0.57 — 2016-03-27 +* Update to *rustc 1.9.0-nightly (a1e29daf1 2016-03-25)* +* Deprecated lints: [`str_to_string`], [`string_to_string`], [`unstable_as_slice`], [`unstable_as_mut_slice`] +* New lint: [`crosspointer_transmute`] + +## 0.0.56 — 2016-03-23 +* Update to *rustc 1.9.0-nightly (0dcc413e4 2016-03-22)* +* New lints: [`many_single_char_names`] and [`similar_names`] + +## 0.0.55 — 2016-03-21 +* Update to *rustc 1.9.0-nightly (02310fd31 2016-03-19)* + +## 0.0.54 — 2016-03-16 +* Update to *rustc 1.9.0-nightly (c66d2380a 2016-03-15)* + +## 0.0.53 — 2016-03-15 +* Add a [configuration file] + +## ~~0.0.52~~ + +## 0.0.51 — 2016-03-13 +* Add `str` to types considered by [`len_zero`] +* New lints: [`indexing_slicing`] + +## 0.0.50 — 2016-03-11 +* Update to *rustc 1.9.0-nightly (c9629d61c 2016-03-10)* + +## 0.0.49 — 2016-03-09 +* Update to *rustc 1.9.0-nightly (eabfc160f 2016-03-08)* +* New lints: [`overflow_check_conditional`], [`unused_label`], [`new_without_default`] + +## 0.0.48 — 2016-03-07 +* Fixed: ICE in [`needless_range_loop`] with globals + +## 0.0.47 — 2016-03-07 +* Update to *rustc 1.9.0-nightly (998a6720b 2016-03-07)* +* New lint: [`redundant_closure_call`] + +[`AsMut`]: https://doc.rust-lang.org/std/convert/trait.AsMut.html +[`AsRef`]: https://doc.rust-lang.org/std/convert/trait.AsRef.html +[configuration file]: ./rust-clippy#configuration +[pull3665]: https://github.com/rust-lang/rust-clippy/pull/3665 +[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md + + + +[`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons +[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped +[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant +[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions +[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants +[`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern +[`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops +[`async_yields_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#async_yields_async +[`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock +[`await_holding_refcell_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_refcell_ref +[`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask +[`bind_instead_of_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#bind_instead_of_map +[`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name +[`blanket_clippy_restriction_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints +[`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions +[`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison +[`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const +[`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box +[`box_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_vec +[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local +[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow +[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth +[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata +[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons +[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless +[`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation +[`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap +[`cast_precision_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_precision_loss +[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment +[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut +[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss +[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8 +[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp +[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp +[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions +[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref +[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy +[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr +[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan +[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null +[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned +[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity +[`collapsible_else_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if +[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if +[`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match +[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain +[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty +[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator +[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir +[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute +[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro +[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call +[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation +[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const +[`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback +[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access +[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr +[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver +[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof +[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq +[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord +[`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method +[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression +[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown +[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons +[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use +[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg +[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens +[`drop_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_bounds +[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy +[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref +[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument +[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec +[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else +[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum +[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr +[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop +[`enum_clike_unportable_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_clike_unportable_variant +[`enum_glob_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_glob_use +[`enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names +[`eq_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#eq_op +[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op +[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence +[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision +[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums +[`exhaustive_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_structs +[`exit`]: https://rust-lang.github.io/rust-clippy/master/index.html#exit +[`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call +[`expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_used +[`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy +[`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop +[`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods +[`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop +[`explicit_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop +[`explicit_write`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_write +[`extend_from_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_from_slice +[`extra_unused_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_lifetimes +[`fallible_impl_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#fallible_impl_from +[`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default +[`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file +[`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map +[`filter_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_identity +[`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next +[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next +[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map +[`flat_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_identity +[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic +[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp +[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const +[`float_equality_without_abs`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_equality_without_abs +[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons +[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools +[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast +[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation +[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map +[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles +[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy +[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref +[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect +[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into +[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10 +[`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send +[`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len +[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap +[`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op +[`if_let_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_mutex +[`if_let_redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_redundant_pattern_matching +[`if_let_some_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_some_result +[`if_not_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else +[`if_same_then_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_same_then_else +[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond +[`implicit_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_clone +[`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher +[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return +[`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub +[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops +[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping +[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor +[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing +[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask +[`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string +[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match +[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter +[`inherent_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string +[`inherent_to_string_shadow_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string_shadow_display +[`inline_always`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_always +[`inline_asm_x86_att_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_att_syntax +[`inline_asm_x86_intel_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_intel_syntax +[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body +[`inspect_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#inspect_for_each +[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one +[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic +[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division +[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array +[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref +[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering +[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref +[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex +[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons +[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters +[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements +[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect +[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count +[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop +[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice +[`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth +[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero +[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next +[`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero +[`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits +[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays +[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups +[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant +[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays +[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value +[`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty +[`len_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_zero +[`let_and_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return +[`let_underscore_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_drop +[`let_underscore_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_lock +[`let_underscore_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_must_use +[`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value +[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist +[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug +[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal +[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports +[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion +[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn +[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map +[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map +[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten +[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map +[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy +[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive +[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or +[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains +[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic +[`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip +[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap +[`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or +[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names +[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone +[`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit +[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry +[`map_err_ignore`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_err_ignore +[`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten +[`map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_identity +[`map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or +[`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref +[`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool +[`match_like_matches_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro +[`match_on_vec_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_on_vec_items +[`match_overlapping_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_overlapping_arm +[`match_ref_pats`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_ref_pats +[`match_same_arms`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_same_arms +[`match_single_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_single_binding +[`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm +[`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants +[`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter +[`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum +[`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget +[`mem_replace_option_with_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_option_with_none +[`mem_replace_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default +[`mem_replace_with_uninit`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_uninit +[`min_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_max +[`misaligned_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#misaligned_transmute +[`mismatched_target_os`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_target_os +[`misrefactored_assign_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#misrefactored_assign_op +[`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn +[`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items +[`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc +[`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items +[`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc +[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc +[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes +[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals +[`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception +[`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions +[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic +[`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one +[`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions +[`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl +[`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate +[`must_use_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_unit +[`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref +[`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut +[`mut_mutex_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mutex_lock +[`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound +[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type +[`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic +[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer +[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount +[`needless_arbitrary_self_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_arbitrary_self_type +[`needless_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool +[`needless_borrow`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow +[`needless_borrowed_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrowed_reference +[`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect +[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue +[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main +[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes +[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value +[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark +[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop +[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return +[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update +[`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord +[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply +[`never_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_loop +[`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self +[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default +[`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect +[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal +[`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool +[`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options +[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref +[`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect +[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref +[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref +[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap +[`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else +[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none +[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn +[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option +[`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call +[`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing +[`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional +[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic +[`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn +[`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params +[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap +[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl +[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite +[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch +[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma +[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence +[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal +[`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr +[`print_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout +[`print_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_with_newline +[`println_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string +[`ptr_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg +[`ptr_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr +[`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq +[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast +[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names +[`question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark +[`range_minus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_minus_one +[`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one +[`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero +[`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len +[`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer +[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation +[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone +[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure +[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call +[`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls +[`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else +[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names +[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern +[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching +[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate +[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing +[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes +[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref +[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref +[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro +[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once +[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts +[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs +[`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option +[`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn +[`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err +[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges +[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition +[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push +[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some +[`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment +[`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned +[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse +[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse +[`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same +[`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated +[`short_circuit_statement`]: https://rust-lang.github.io/rust-clippy/master/index.html#short_circuit_statement +[`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq +[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait +[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names +[`single_char_add_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_add_str +[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern +[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports +[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop +[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match +[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else +[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count +[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next +[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization +[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive +[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string +[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add +[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign +[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars +[`string_from_utf8_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_from_utf8_as_bytes +[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes +[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string +[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools +[`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops +[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl +[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting +[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting +[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map +[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl +[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings +[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting +[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments +[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment +[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr +[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some +[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display +[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo +[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments +[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines +[`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg +[`trait_duplication_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#trait_duplication_in_bounds +[`transmute_bytes_to_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_bytes_to_str +[`transmute_float_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_float_to_int +[`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool +[`transmute_int_to_char`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_char +[`transmute_int_to_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_float +[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr +[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref +[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts +[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null +[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex +[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref +[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err +[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity +[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds +[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops +[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc +[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented +[`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init +[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg +[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp +[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord +[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints +[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast +[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map +[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold +[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations +[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed +[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation +[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by +[`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap +[`unnecessary_wraps`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps +[`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern +[`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern +[`unnested_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns +[`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable +[`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal +[`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize +[`unsafe_removed_from_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_removed_from_name +[`unsafe_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_vector_initialization +[`unseparated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#unseparated_literal_suffix +[`unsound_collection_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsound_collection_transmute +[`unstable_as_mut_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_mut_slice +[`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice +[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect +[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount +[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label +[`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self +[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit +[`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings +[`unwrap_in_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_in_result +[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used +[`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms +[`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug +[`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self +[`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding +[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref +[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute +[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion +[`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format +[`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq +[`useless_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_transmute +[`useless_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec +[`vec_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_box +[`vec_init_then_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_init_then_push +[`vec_resize_to_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_resize_to_zero +[`verbose_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask +[`verbose_file_reads`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_file_reads +[`vtable_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons +[`while_immutable_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_immutable_condition +[`while_let_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_loop +[`while_let_on_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_on_iterator +[`wildcard_dependencies`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_dependencies +[`wildcard_enum_match_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_enum_match_arm +[`wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports +[`wildcard_in_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_in_or_patterns +[`write_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_literal +[`write_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_with_newline +[`writeln_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#writeln_empty_string +[`wrong_pub_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_pub_self_convention +[`wrong_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention +[`wrong_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_transmute +[`zero_divided_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_divided_by_zero +[`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal +[`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr +[`zero_sized_map_values`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_sized_map_values +[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset + diff --git a/src/tools/clippy/CODE_OF_CONDUCT.md b/src/tools/clippy/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..dec13e44a1 --- /dev/null +++ b/src/tools/clippy/CODE_OF_CONDUCT.md @@ -0,0 +1,70 @@ +# The Rust Code of Conduct + +A version of this document [can be found online](https://www.rust-lang.org/conduct.html). + +## Conduct + +**Contact**: [rust-mods@rust-lang.org](mailto:rust-mods@rust-lang.org) + +* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, + gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, + religion, nationality, or other similar characteristic. +* On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and + welcoming environment for all. +* Please be kind and courteous. There's no need to be mean or rude. +* Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and + numerous costs. There is seldom a right answer. +* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and + see how it works. +* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We + interpret the term "harassment" as including the definition in the Citizen + Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their + definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. +* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or + made uncomfortable by a community member, please contact one of the channel ops or any of the [Rust moderation + team][mod_team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a + safe place for you and we've got your back. +* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. + +## Moderation + + +These are the policies for upholding our community's standards of conduct. If you feel that a thread needs moderation, +please contact the [Rust moderation team][mod_team]. + +1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, + are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) +2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. +3. Moderators will first respond to such remarks with a warning. +4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off. +5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. +6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended + party a genuine apology. +7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a + different moderator, **in private**. Complaints about bans in-channel are not allowed. +8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate + situation, they should expect less leeway than others. + +In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically +unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly +if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can +drive people away from the community entirely. + +And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was +they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good +there was something you could've communicated better — remember that it's your responsibility to make your fellow +Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about +cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their +trust. + +The enforcement policies listed above apply to all official Rust venues; including official IRC channels (#rust, +#rust-internals, #rust-tools, #rust-libs, #rustc, #rust-beginners, #rust-docs, #rust-community, #rust-lang, and #cargo); +GitHub repositories under rust-lang, rust-lang-nursery, and rust-lang-deprecated; and all forums under rust-lang.org +(users.rust-lang.org, internals.rust-lang.org). For other projects adopting the Rust Code of Conduct, please contact the +maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider +explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion. + +*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the +[Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* + +[mod_team]: https://www.rust-lang.org/team.html#Moderation-team diff --git a/src/tools/clippy/CONTRIBUTING.md b/src/tools/clippy/CONTRIBUTING.md new file mode 100644 index 0000000000..5954ab25d1 --- /dev/null +++ b/src/tools/clippy/CONTRIBUTING.md @@ -0,0 +1,359 @@ +# 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 [Zulip]. + +All contributors are expected to follow the [Rust Code of Conduct]. + +- [Contributing to Clippy](#contributing-to-clippy) + - [Getting started](#getting-started) + - [High level approach](#high-level-approach) + - [Finding something to fix/improve](#finding-something-to-fiximprove) + - [Writing code](#writing-code) + - [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work) + - [How Clippy works](#how-clippy-works) + - [Syncing changes between Clippy and `rust-lang/rust`](#syncing-changes-between-clippy-and-rust-langrust) + - [Patching git-subtree to work with big repos](#patching-git-subtree-to-work-with-big-repos) + - [Performing the sync from `rust-lang/rust` to Clippy](#performing-the-sync-from-rust-langrust-to-clippy) + - [Performing the sync from Clippy to `rust-lang/rust`](#performing-the-sync-from-clippy-to-rust-langrust) + - [Defining remotes](#defining-remotes) + - [Issue and PR triage](#issue-and-pr-triage) + - [Bors and Homu](#bors-and-homu) + - [Contributions](#contributions) + +[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy +[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct + +## Getting started + +**Note: If this is your first time contributing to Clippy, you should +first read the [Basics docs](doc/basics.md).** + +### 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 [Basics docs](doc/basics.md) to get set up +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 simply ask @Manishearth, @flip1995, @phansch +or @llogiq directly by mentioning them in the issue or over on [Zulip]. This list may be out of date. +All currently active mentors can be found [here](https://github.com/rust-lang/highfive/blob/master/highfive/configs/rust-lang/rust-clippy.json#L3) + +Some issues are easier than others. The [`good-first-issue`] label can be used to find the easy +issues. You can use `@rustbot claim` to assign the issue to yourself. + +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 [`good-first-issue`] +first. Sometimes they are only somewhat involved code wise, but not difficult per-se. +Note that [`E-medium`] issues may require some knowledge of Clippy internals or some +debugging to find the actual problem behind the issue. + +[`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-first-issue +[`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 + +## Getting code-completion for rustc internals to work + +Unfortunately, [`rust-analyzer`][ra_homepage] does not (yet?) understand how Clippy uses compiler-internals +using `extern crate` and it also needs to be able to read the source files of the rustc-compiler which are not +available via a `rustup` component at the time of writing. +To work around this, you need to have a copy of the [rustc-repo][rustc_repo] available which can be obtained via +`git clone https://github.com/rust-lang/rust/`. +Then you can run a `cargo dev` command to automatically make Clippy use the rustc-repo via path-dependencies +which rust-analyzer will be able to understand. +Run `cargo dev ra_setup --repo-path ` where `` is an absolute path to the rustc repo +you just cloned. +The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to +Clippys `Cargo.toml`s and should allow rust-analyzer to understand most of the types that Clippy uses. +Just make sure to remove the dependencies again before finally making a pull request! + +[ra_homepage]: https://rust-analyzer.github.io/ +[rustc_repo]: https://github.com/rust-lang/rust/ + +## 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 + +## Syncing changes between Clippy and [`rust-lang/rust`] + +Clippy currently gets built with a pinned nightly version. + +In the `rust-lang/rust` repository, where rustc resides, there's a copy of Clippy +that compiler hackers modify from time to time to adapt to changes in the unstable +API of the compiler. + +We need to sync these changes back to this repository periodically, and the changes +made to this repository in the meantime also need to be synced to the `rust-lang/rust` repository. + +To avoid flooding the `rust-lang/rust` PR queue, this two-way sync process is done +in a bi-weekly basis if there's no urgent changes. This is done starting on the day of +the Rust stable release and then every other week. That way we guarantee that we keep +this repo up to date with the latest compiler API, and every feature in Clippy is available +for 2 weeks in nightly, before it can get to beta. For reference, the first sync +following this cadence was performed the 2020-08-27. + +This process is described in detail in the following sections. For general information +about `subtree`s in the Rust repository see [Rust's `CONTRIBUTING.md`][subtree]. + +### Patching git-subtree to work with big repos + +Currently there's a bug in `git-subtree` that prevents it from working properly +with the [`rust-lang/rust`] repo. There's an open PR to fix that, but it's stale. +Before continuing with the following steps, we need to manually apply that fix to +our local copy of `git-subtree`. + +You can get the patched version of `git-subtree` from [here][gitgitgadget-pr]. +Put this file under `/usr/lib/git-core` (taking a backup of the previous file) +and make sure it has the proper permissions: + +```bash +sudo cp --backup /path/to/patched/git-subtree.sh /usr/lib/git-core/git-subtree +sudo chmod --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree +sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree +``` + +_Note:_ The first time running `git subtree push` a cache has to be built. This +involves going through the complete Clippy history once. For this you have to +increase the stack limit though, which you can do with `ulimit -s 60000`. +Make sure to run the `ulimit` command from the same session you call git subtree. + +_Note:_ If you are a Debian user, `dash` is the shell used by default for scripts instead of `sh`. +This shell has a hardcoded recursion limit set to 1000. In order to make this process work, +you need to force the script to run `bash` instead. You can do this by editing the first +line of the `git-subtree` script and changing `sh` to `bash`. + +### Performing the sync from [`rust-lang/rust`] to Clippy + +Here is a TL;DR version of the sync process (all of the following commands have +to be run inside the `rust` directory): + +1. Clone the [`rust-lang/rust`] repository or make sure it is up to date. +2. Checkout the commit from the latest available nightly. You can get it using `rustup check`. +3. Sync the changes to the rust-copy of Clippy to your Clippy fork: + ```bash + # Make sure to change `your-github-name` to your github name in the following command + git subtree push -P src/tools/clippy git@github.com:your-github-name/rust-clippy sync-from-rust + ``` + + _Note:_ This will directly push to the remote repository. You can also push + to your local copy by replacing the remote address with `/path/to/rust-clippy` + directory. + + _Note:_ Most of the time you have to create a merge commit in the + `rust-clippy` repo (this has to be done in the Clippy repo, not in the + rust-copy of Clippy): + ```bash + git fetch origin && git fetch upstream + git checkout sync-from-rust + git merge upstream/master + ``` +4. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to + accelerate the process ping the `@rust-lang/clippy` team in your PR and/or + ~~annoy~~ ask them in the [Zulip] stream.) + +### Performing the sync from Clippy to [`rust-lang/rust`] + +All of the following commands have to be run inside the `rust` directory. + +1. Make sure Clippy itself is up-to-date by following the steps outlined in the previous +section if necessary. + +2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy: + ```bash + git checkout -b sync-from-clippy + git subtree pull -P src/tools/clippy https://github.com/rust-lang/rust-clippy master + ``` +3. Open a PR to [`rust-lang/rust`] + +### Defining remotes + +You may want to define remotes, so you don't have to type out the remote +addresses on every sync. You can do this with the following commands (these +commands still have to be run inside the `rust` directory): + +```bash +# Set clippy-upstream remote for pulls +$ git remote add clippy-upstream https://github.com/rust-lang/rust-clippy +# Make sure to not push to the upstream repo +$ git remote set-url --push clippy-upstream DISABLED +# Set clippy-origin remote to your fork for pushes +$ git remote add clippy-origin git@github.com:your-github-name/rust-clippy +# Set a local remote +$ git remote add clippy-local /path/to/rust-clippy +``` + +You can then sync with the remote names from above, e.g.: + +```bash +$ git subtree push -P src/tools/clippy clippy-local sync-from-rust +``` + +[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493 +[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree +[`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], for example +an ICE in a popular crate that many other crates depend on. 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. + +We have prioritization labels and a sync-blocker label, which are described below. +- [P-low][p-low]: Requires attention (fix/response/evaluation) by a team member but isn't urgent. +- [P-medium][p-medium]: Should be addressed by a team member until the next sync. +- [P-high][p-high]: Should be immediately addressed and will require an out-of-cycle sync or a backport. +- [L-sync-blocker][l-sync-blocker]: An issue that "blocks" a sync. +Or rather: before the sync this should be addressed, +e.g. by removing a lint again, so it doesn't hit beta/stable. + +## 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 +[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug +[p-low]: https://github.com/rust-lang/rust-clippy/labels/P-low +[p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium +[p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high +[l-sync-blocker]: https://github.com/rust-lang/rust-clippy/labels/L-sync-blocker +[homu]: https://github.com/rust-lang/homu +[homu_instructions]: https://bors.rust-lang.org/ +[homu_queue]: https://bors.rust-lang.org/queue/clippy + +## Contributions + +Contributions to Clippy should be made in the form of GitHub pull requests. Each pull request will +be reviewed by a core contributor (someone with permission to land patches) and either landed in the +main tree or given feedback for changes that would be required. + +All code in this repository is under the [Apache-2.0] or the [MIT] license. + + + +[Apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0 +[MIT]: https://opensource.org/licenses/MIT diff --git a/src/tools/clippy/COPYRIGHT b/src/tools/clippy/COPYRIGHT new file mode 100644 index 0000000000..80d64472c7 --- /dev/null +++ b/src/tools/clippy/COPYRIGHT @@ -0,0 +1,7 @@ +Copyright 2014-2020 The Rust Project Developers + +Licensed under the Apache License, Version 2.0 or the MIT license +, at your +option. All files in the project carrying such notice may not be +copied, modified, or distributed except according to those terms. diff --git a/src/tools/clippy/Cargo.toml b/src/tools/clippy/Cargo.toml new file mode 100644 index 0000000000..2b9488de28 --- /dev/null +++ b/src/tools/clippy/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "clippy" +version = "0.1.52" +authors = ["The Rust Clippy Developers"] +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.1.50", path = "clippy_lints" } +# end automatic update +semver = "0.11" +rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" } +tempfile = { version = "3.1.0", optional = true } + +[dev-dependencies] +cargo_metadata = "0.12" +compiletest_rs = { version = "0.6.0", features = ["tmp"] } +tester = "0.9" +clippy-mini-macro-test = { version = "0.2", path = "mini-macro" } +serde = { version = "1.0", features = ["derive"] } +derive-new = "0.5" +regex = "1.4" + +# 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"] +internal-lints = ["clippy_lints/internal-lints"] + +[package.metadata.rust-analyzer] +# This package uses #[feature(rustc_private)] +rustc_private = true diff --git a/src/tools/clippy/LICENSE-APACHE b/src/tools/clippy/LICENSE-APACHE new file mode 100644 index 0000000000..d821a4de2b --- /dev/null +++ b/src/tools/clippy/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2014-2020 The Rust Project Developers + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/tools/clippy/LICENSE-MIT b/src/tools/clippy/LICENSE-MIT new file mode 100644 index 0000000000..b7c70dd402 --- /dev/null +++ b/src/tools/clippy/LICENSE-MIT @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2014-2020 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/tools/clippy/README.md b/src/tools/clippy/README.md new file mode 100644 index 0000000000..63057609bb --- /dev/null +++ b/src/tools/clippy/README.md @@ -0,0 +1,245 @@ +# 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 400 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) + +Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). +You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. + +| Category | Description | Default level | +| --------------------- | ----------------------------------------------------------------------- | ------------- | +| `clippy::all` | all lints that are on by default (correctness, style, complexity, perf) | **warn/deny** | +| `clippy::correctness` | code that is outright wrong or very useless | **deny** | +| `clippy::style` | code that should be written in a more idiomatic way | **warn** | +| `clippy::complexity` | code that does something simple but in a complex way | **warn** | +| `clippy::perf` | code that can be written to run faster | **warn** | +| `clippy::pedantic` | lints which are rather strict or might have false positives | allow | +| `clippy::nursery` | new lints that are still under development | allow | +| `clippy::cargo` | lints for the cargo manifest | allow | + +More to come, please [file an issue](https://github.com/rust-lang/rust-clippy/issues) if you have ideas! + +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 + +Below are instructions on how to use Clippy as a subcommand, compiled from source +or in Travis CI. + +### 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 +``` + +#### Workspaces + +All the usual workspace options should work with Clippy. For example the following command +will run Clippy on the `example` crate: + +```terminal +cargo clippy -p example +``` + +As with `cargo check`, this includes dependencies that are members of the workspace, like path dependencies. +If you want to run Clippy **only** on the given crate, use the `--no-deps` option like this: + +```terminal +cargo clippy -p example -- --no-deps +``` + +### As a rustc replacement (`clippy-driver`) + +Clippy can also be used in projects that do not use cargo. To do so, you will need to replace +your `rustc` compilation commands with `clippy-driver`. For example, if your project runs: + +```terminal +rustc --edition 2018 -Cpanic=abort foo.rs +``` + +Then, to enable Clippy, you will need to call: + +```terminal +clippy-driver --edition 2018 -Cpanic=abort foo.rs +``` + +Note that `rustc` will still run, i.e. it will still emit the output files it normally does. + +### 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. +``` + +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: `allow` means to suppress the lint for your code. With `warn` the lint +will only emit a warning, while with `deny` the lint will emit an error, when +triggering for your code. An error causes clippy to exit with an error code, so +is useful in scripts like CI/CD. + +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: + +To allow `lint_name`, run + +```terminal +cargo clippy -- -A clippy::lint_name +``` + +And to warn on `lint_name`, run + +```terminal +cargo clippy -- -W clippy::lint_name +``` + +This also works with lint groups. For example you +can run Clippy with warnings for all lints enabled: +```terminal +cargo clippy -- -W clippy::pedantic +``` + +If you care only about a single lint, you can allow all others and then explicitly warn on +the lint(s) you are interested in: +```terminal +cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::... +``` + +### Specifying the minimum supported Rust version + +Projects that intend to support old versions of Rust can disable lints pertaining to newer features by +specifying the minimum supported Rust version (MSRV) in the clippy configuration file. + +```toml +msrv = "1.30.0" +``` + +The MSRV can also be specified as an inner attribute, like below. + +```rust +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.30.0"] + +fn main() { + ... +} +``` + +You can also omit the patch version when specifying the MSRV, so `msrv = 1.30` +is equivalent to `msrv = 1.30.0`. + +Note: `custom_inner_attributes` is an unstable feature so it has to be enabled explicitly. + +Lints that recognize this configuration option can be found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv) + +## Contributing + +If you want to contribute to Clippy, you can find more information in [CONTRIBUTING.md](https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md). + +## License + +Copyright 2014-2020 The Rust Project Developers + +Licensed under the Apache License, Version 2.0 or the MIT license +, at your +option. Files in the project may not be +copied, modified, or distributed except according to those terms. diff --git a/src/tools/clippy/build.rs b/src/tools/clippy/build.rs new file mode 100644 index 0000000000..018375dbad --- /dev/null +++ b/src/tools/clippy/build.rs @@ -0,0 +1,19 @@ +fn main() { + // Forward the profile to the main compilation + println!("cargo:rustc-env=PROFILE={}", std::env::var("PROFILE").unwrap()); + // Don't rebuild even if nothing changed + println!("cargo:rerun-if-changed=build.rs"); + // forward git repo hashes we build at + println!( + "cargo:rustc-env=GIT_HASH={}", + rustc_tools_util::get_commit_hash().unwrap_or_default() + ); + println!( + "cargo:rustc-env=COMMIT_DATE={}", + rustc_tools_util::get_commit_date().unwrap_or_default() + ); + println!( + "cargo:rustc-env=RUSTC_RELEASE_CHANNEL={}", + rustc_tools_util::get_channel().unwrap_or_default() + ); +} diff --git a/src/tools/clippy/clippy_dev/Cargo.toml b/src/tools/clippy/clippy_dev/Cargo.toml new file mode 100644 index 0000000000..b1844e29b3 --- /dev/null +++ b/src/tools/clippy/clippy_dev/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "clippy_dev" +version = "0.0.1" +authors = ["The Rust Clippy Developers"] +edition = "2018" + +[dependencies] +bytecount = "0.6" +clap = "2.33" +itertools = "0.9" +opener = "0.4" +regex = "1" +shell-escape = "0.1" +walkdir = "2" + +[features] +deny-warnings = [] diff --git a/src/tools/clippy/clippy_dev/src/bless.rs b/src/tools/clippy/clippy_dev/src/bless.rs new file mode 100644 index 0000000000..c4fa0a9aca --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/bless.rs @@ -0,0 +1,103 @@ +//! `bless` updates the reference files in the repo with changed output files +//! from the last test run. + +use std::env; +use std::ffi::OsStr; +use std::fs; +use std::lazy::SyncLazy; +use std::path::{Path, PathBuf}; +use walkdir::WalkDir; + +use crate::clippy_project_root; + +// NOTE: this is duplicated with tests/cargo/mod.rs What to do? +pub static CARGO_TARGET_DIR: SyncLazy = SyncLazy::new(|| match env::var_os("CARGO_TARGET_DIR") { + Some(v) => v.into(), + None => env::current_dir().unwrap().join("target"), +}); + +static CLIPPY_BUILD_TIME: SyncLazy> = SyncLazy::new(|| { + let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".to_string()); + let mut path = PathBuf::from(&**CARGO_TARGET_DIR); + path.push(profile); + path.push("cargo-clippy"); + fs::metadata(path).ok()?.modified().ok() +}); + +/// # Panics +/// +/// Panics if the path to a test file is broken +pub fn bless(ignore_timestamp: bool) { + let test_suite_dirs = [ + clippy_project_root().join("tests").join("ui"), + clippy_project_root().join("tests").join("ui-internal"), + clippy_project_root().join("tests").join("ui-toml"), + clippy_project_root().join("tests").join("ui-cargo"), + ]; + for test_suite_dir in &test_suite_dirs { + WalkDir::new(test_suite_dir) + .into_iter() + .filter_map(Result::ok) + .filter(|f| f.path().extension() == Some(OsStr::new("rs"))) + .for_each(|f| { + let test_name = f.path().strip_prefix(test_suite_dir).unwrap(); + for &ext in &["stdout", "stderr", "fixed"] { + let test_name_ext = format!("stage-id.{}", ext); + update_reference_file( + f.path().with_extension(ext), + test_name.with_extension(test_name_ext), + ignore_timestamp, + ); + } + }); + } +} + +fn update_reference_file(reference_file_path: PathBuf, test_name: PathBuf, ignore_timestamp: bool) { + let test_output_path = build_dir().join(test_name); + let relative_reference_file_path = reference_file_path.strip_prefix(clippy_project_root()).unwrap(); + + // If compiletest did not write any changes during the test run, + // we don't have to update anything + if !test_output_path.exists() { + return; + } + + // If the test output was not updated since the last clippy build, it may be outdated + if !ignore_timestamp && !updated_since_clippy_build(&test_output_path).unwrap_or(true) { + return; + } + + let test_output_file = fs::read(&test_output_path).expect("Unable to read test output file"); + let reference_file = fs::read(&reference_file_path).unwrap_or_default(); + + if test_output_file != reference_file { + // If a test run caused an output file to change, update the reference file + println!("updating {}", &relative_reference_file_path.display()); + fs::copy(test_output_path, &reference_file_path).expect("Could not update reference file"); + + // We need to re-read the file now because it was potentially updated from copying + let reference_file = fs::read(&reference_file_path).unwrap_or_default(); + + if reference_file.is_empty() { + // If we copied over an empty output file, we remove the now empty reference file + println!("removing {}", &relative_reference_file_path.display()); + fs::remove_file(reference_file_path).expect("Could not remove reference file"); + } + } +} + +fn updated_since_clippy_build(path: &Path) -> Option { + let clippy_build_time = (*CLIPPY_BUILD_TIME)?; + let modified = fs::metadata(path).ok()?.modified().ok()?; + Some(modified >= clippy_build_time) +} + +fn build_dir() -> PathBuf { + let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".to_string()); + let mut path = PathBuf::new(); + path.push(CARGO_TARGET_DIR.clone()); + path.push(profile); + path.push("test_build_base"); + path +} diff --git a/src/tools/clippy/clippy_dev/src/fmt.rs b/src/tools/clippy/clippy_dev/src/fmt.rs new file mode 100644 index 0000000000..a26d6aba10 --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/fmt.rs @@ -0,0 +1,202 @@ +use crate::clippy_project_root; +use shell_escape::escape; +use std::ffi::OsStr; +use std::path::Path; +use std::process::{self, Command}; +use std::{fs, io}; +use walkdir::WalkDir; + +#[derive(Debug)] +pub enum CliError { + CommandFailed(String, String), + IoError(io::Error), + RustfmtNotInstalled, + WalkDirError(walkdir::Error), + RaSetupActive, +} + +impl From for CliError { + fn from(error: io::Error) -> Self { + Self::IoError(error) + } +} + +impl From for CliError { + fn from(error: walkdir::Error) -> Self { + Self::WalkDirError(error) + } +} + +struct FmtContext { + check: bool, + verbose: bool, +} + +// the "main" function of cargo dev fmt +pub fn run(check: bool, verbose: bool) { + fn try_run(context: &FmtContext) -> Result { + let mut success = true; + + let project_root = clippy_project_root(); + + // if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to + // format because rustfmt would also format the entire rustc repo as it is a local + // dependency + if fs::read_to_string(project_root.join("Cargo.toml")) + .expect("Failed to read clippy Cargo.toml") + .contains(&"[target.'cfg(NOT_A_PLATFORM)'.dependencies]") + { + return Err(CliError::RaSetupActive); + } + + 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"))?; + success &= cargo_fmt(context, &project_root.join("lintcheck"))?; + + 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, stderr) => { + eprintln!("error: A command failed! `{}`\nstderr: {}", command, stderr); + }, + CliError::IoError(err) => { + eprintln!("error: {}", err); + }, + CliError::RustfmtNotInstalled => { + eprintln!("error: rustfmt nightly is not installed."); + }, + CliError::WalkDirError(err) => { + eprintln!("error: {}", err); + }, + CliError::RaSetupActive => { + eprintln!( + "error: a local rustc repo is enabled as path dependency via `cargo dev ra_setup`. +Not formatting because that would format the local repo as well! +Please revert the changes to Cargo.tomls first." + ); + }, + } + } + + let context = FmtContext { check, verbose }; + let result = try_run(&context); + let code = match result { + Ok(true) => 0, + Ok(false) => { + eprintln!(); + eprintln!("Formatting check failed."); + eprintln!("Run `cargo dev fmt` to update formatting."); + 1 + }, + Err(err) => { + output_err(err); + 1 + }, + }; + process::exit(code); +} + +fn format_command(program: impl AsRef, dir: impl AsRef, args: &[impl AsRef]) -> String { + let arg_display: Vec<_> = args.iter().map(|a| escape(a.as_ref().to_string_lossy())).collect(); + + format!( + "cd {} && {} {}", + escape(dir.as_ref().to_string_lossy()), + escape(program.as_ref().to_string_lossy()), + arg_display.join(" ") + ) +} + +fn exec( + context: &FmtContext, + program: impl AsRef, + dir: impl AsRef, + args: &[impl AsRef], +) -> Result { + if context.verbose { + println!("{}", format_command(&program, &dir, args)); + } + + let child = Command::new(&program).current_dir(&dir).args(args.iter()).spawn()?; + let output = child.wait_with_output()?; + let success = output.status.success(); + + if !context.check && !success { + let stderr = std::str::from_utf8(&output.stderr).unwrap_or(""); + return Err(CliError::CommandFailed( + format_command(&program, &dir, args), + String::from(stderr), + )); + } + + Ok(success) +} + +fn cargo_fmt(context: &FmtContext, path: &Path) -> Result { + let mut args = vec!["+nightly", "fmt", "--all"]; + if context.check { + args.push("--"); + args.push("--check"); + } + let success = exec(context, "cargo", path, &args)?; + + Ok(success) +} + +fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { + let program = "rustfmt"; + let dir = std::env::current_dir()?; + let args = &["+nightly", "--version"]; + + if context.verbose { + println!("{}", format_command(&program, &dir, args)); + } + + let output = Command::new(&program).current_dir(&dir).args(args.iter()).output()?; + + if output.status.success() { + Ok(()) + } else if std::str::from_utf8(&output.stderr) + .unwrap_or("") + .starts_with("error: 'rustfmt' is not installed") + { + Err(CliError::RustfmtNotInstalled) + } else { + Err(CliError::CommandFailed( + format_command(&program, &dir, args), + std::str::from_utf8(&output.stderr).unwrap_or("").to_string(), + )) + } +} + +fn rustfmt(context: &FmtContext, path: &Path) -> Result { + let mut args = vec!["+nightly".as_ref(), path.as_os_str()]; + if context.check { + args.push("--check".as_ref()); + } + let success = exec(context, "rustfmt", std::env::current_dir()?, &args)?; + if !success { + eprintln!("rustfmt failed on {}", path.display()); + } + Ok(success) +} diff --git a/src/tools/clippy/clippy_dev/src/lib.rs b/src/tools/clippy/clippy_dev/src/lib.rs new file mode 100644 index 0000000000..a95abfacea --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/lib.rs @@ -0,0 +1,558 @@ +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![feature(once_cell)] + +use itertools::Itertools; +use regex::Regex; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fs; +use std::lazy::SyncLazy; +use std::path::{Path, PathBuf}; +use walkdir::WalkDir; + +pub mod bless; +pub mod fmt; +pub mod new_lint; +pub mod ra_setup; +pub mod serve; +pub mod stderr_length_check; +pub mod update_lints; + +static DEC_CLIPPY_LINT_RE: SyncLazy = SyncLazy::new(|| { + Regex::new( + r#"(?x) + declare_clippy_lint!\s*[\{(] + (?:\s+///.*)* + \s+pub\s+(?P[A-Z_][A-Z_0-9]*)\s*,\s* + (?P[a-z_]+)\s*,\s* + "(?P(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})] +"#, + ) + .unwrap() +}); + +static DEC_DEPRECATED_LINT_RE: SyncLazy = SyncLazy::new(|| { + Regex::new( + r#"(?x) + declare_deprecated_lint!\s*[{(]\s* + (?:\s+///.*)* + \s+pub\s+(?P[A-Z_][A-Z_0-9]*)\s*,\s* + "(?P(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})] +"#, + ) + .unwrap() +}); +static NL_ESCAPE_RE: SyncLazy = SyncLazy::new(|| Regex::new(r#"\\\n\s*"#).unwrap()); + +pub static DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; + +/// Lint data parsed from the Clippy source code. +#[derive(Clone, PartialEq, Debug)] +pub struct Lint { + pub name: String, + pub group: String, + pub desc: String, + pub deprecation: Option, + pub module: String, +} + +impl Lint { + #[must_use] + pub fn new(name: &str, group: &str, desc: &str, deprecation: Option<&str>, module: &str) -> Self { + Self { + name: name.to_lowercase(), + group: group.to_string(), + desc: NL_ESCAPE_RE.replace(&desc.replace("\\\"", "\""), "").to_string(), + deprecation: deprecation.map(ToString::to_string), + module: module.to_string(), + } + } + + /// Returns all non-deprecated lints and non-internal lints + #[must_use] + pub fn usable_lints(lints: &[Self]) -> Vec { + lints + .iter() + .filter(|l| l.deprecation.is_none() && !l.group.starts_with("internal")) + .cloned() + .collect() + } + + /// Returns all internal lints (not `internal_warn` lints) + #[must_use] + pub fn internal_lints(lints: &[Self]) -> Vec { + lints.iter().filter(|l| l.group == "internal").cloned().collect() + } + + /// Returns all deprecated lints + #[must_use] + pub fn deprecated_lints(lints: &[Self]) -> Vec { + lints.iter().filter(|l| l.deprecation.is_some()).cloned().collect() + } + + /// Returns the lints in a `HashMap`, grouped by the different lint groups + #[must_use] + pub fn by_lint_group(lints: impl Iterator) -> HashMap> { + lints.map(|lint| (lint.group.to_string(), lint)).into_group_map() + } +} + +/// Generates the Vec items for `register_lint_group` calls in `clippy_lints/src/lib.rs`. +#[must_use] +pub fn gen_lint_group_list<'a>(lints: impl Iterator) -> Vec { + lints + .map(|l| format!(" LintId::of(&{}::{}),", l.module, l.name.to_uppercase())) + .sorted() + .collect::>() +} + +/// Generates the `pub mod module_name` list in `clippy_lints/src/lib.rs`. +#[must_use] +pub fn gen_modules_list<'a>(lints: impl Iterator) -> Vec { + lints + .map(|l| &l.module) + .unique() + .map(|module| format!("mod {};", module)) + .sorted() + .collect::>() +} + +/// Generates the list of lint links at the bottom of the README +#[must_use] +pub fn gen_changelog_lint_list<'a>(lints: impl Iterator) -> Vec { + lints + .sorted_by_key(|l| &l.name) + .map(|l| format!("[`{}`]: {}#{}", l.name, DOCS_LINK, l.name)) + .collect() +} + +/// Generates the `register_removed` code in `./clippy_lints/src/lib.rs`. +#[must_use] +pub fn gen_deprecated<'a>(lints: impl Iterator) -> Vec { + lints + .flat_map(|l| { + l.deprecation + .clone() + .map(|depr_text| { + vec![ + " store.register_removed(".to_string(), + format!(" \"clippy::{}\",", l.name), + format!(" \"{}\",", depr_text), + " );".to_string(), + ] + }) + .expect("only deprecated lints should be passed") + }) + .collect::>() +} + +#[must_use] +pub fn gen_register_lint_list<'a>( + internal_lints: impl Iterator, + usable_lints: impl Iterator, +) -> Vec { + let header = " store.register_lints(&[".to_string(); + let footer = " ]);".to_string(); + let internal_lints = internal_lints + .sorted_by_key(|l| format!(" &{}::{},", l.module, l.name.to_uppercase())) + .map(|l| { + format!( + " #[cfg(feature = \"internal-lints\")]\n &{}::{},", + l.module, + l.name.to_uppercase() + ) + }); + let other_lints = usable_lints + .sorted_by_key(|l| format!(" &{}::{},", l.module, l.name.to_uppercase())) + .map(|l| format!(" &{}::{},", l.module, l.name.to_uppercase())) + .sorted(); + let mut lint_list = vec![header]; + lint_list.extend(internal_lints); + lint_list.extend(other_lints); + lint_list.push(footer); + lint_list +} + +/// Gathers all files in `src/clippy_lints` and gathers all lints inside +pub fn gather_all() -> impl Iterator { + lint_files().flat_map(|f| gather_from_file(&f)) +} + +fn gather_from_file(dir_entry: &walkdir::DirEntry) -> impl Iterator { + let content = fs::read_to_string(dir_entry.path()).unwrap(); + let path = dir_entry.path(); + let filename = path.file_stem().unwrap(); + let path_buf = path.with_file_name(filename); + let mut rel_path = path_buf + .strip_prefix(clippy_project_root().join("clippy_lints/src")) + .expect("only files in `clippy_lints/src` should be looked at"); + // If the lints are stored in mod.rs, we get the module name from + // the containing directory: + if filename == "mod" { + rel_path = rel_path.parent().unwrap(); + } + + let module = rel_path + .components() + .map(|c| c.as_os_str().to_str().unwrap()) + .collect::>() + .join("::"); + + parse_contents(&content, &module) +} + +fn parse_contents(content: &str, module: &str) -> impl Iterator { + let lints = DEC_CLIPPY_LINT_RE + .captures_iter(content) + .map(|m| Lint::new(&m["name"], &m["cat"], &m["desc"], None, module)); + let deprecated = DEC_DEPRECATED_LINT_RE + .captures_iter(content) + .map(|m| Lint::new(&m["name"], "Deprecated", &m["desc"], Some(&m["desc"]), module)); + // Removing the `.collect::>().into_iter()` causes some lifetime issues due to the map + lints.chain(deprecated).collect::>().into_iter() +} + +/// Collects all .rs files in the `clippy_lints/src` directory +fn lint_files() -> impl Iterator { + // We use `WalkDir` instead of `fs::read_dir` here in order to recurse into subdirectories. + // Otherwise we would not collect all the lints, for example in `clippy_lints/src/methods/`. + let path = clippy_project_root().join("clippy_lints/src"); + WalkDir::new(path) + .into_iter() + .filter_map(Result::ok) + .filter(|f| f.path().extension() == Some(OsStr::new("rs"))) +} + +/// Whether a file has had its text changed or not +#[derive(PartialEq, Debug)] +pub struct FileChange { + pub changed: bool, + pub new_lines: String, +} + +/// Replaces a region in a file delimited by two lines matching regexes. +/// +/// `path` is the relative path to the file on which you want to perform the replacement. +/// +/// See `replace_region_in_text` for documentation of the other options. +/// +/// # Panics +/// +/// Panics if the path could not read or then written +pub fn replace_region_in_file( + path: &Path, + start: &str, + end: &str, + replace_start: bool, + write_back: bool, + replacements: F, +) -> FileChange +where + F: FnOnce() -> Vec, +{ + let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from {}: {}", path.display(), e)); + let file_change = replace_region_in_text(&contents, start, end, replace_start, replacements); + + if write_back { + if let Err(e) = fs::write(path, file_change.new_lines.as_bytes()) { + panic!("Cannot write to {}: {}", path.display(), e); + } + } + file_change +} + +/// Replaces a region in a text delimited by two lines matching regexes. +/// +/// * `text` is the input text on which you want to perform the replacement +/// * `start` is a `&str` that describes the delimiter line before the region you want to replace. +/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too. +/// * `end` is a `&str` that describes the delimiter line until where the replacement should happen. +/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too. +/// * If `replace_start` is true, the `start` delimiter line is replaced as well. The `end` +/// delimiter line is never replaced. +/// * `replacements` is a closure that has to return a `Vec` which contains the new text. +/// +/// If you want to perform the replacement on files instead of already parsed text, +/// use `replace_region_in_file`. +/// +/// # Example +/// +/// ``` +/// let the_text = "replace_start\nsome text\nthat will be replaced\nreplace_end"; +/// let result = +/// clippy_dev::replace_region_in_text(the_text, "replace_start", "replace_end", false, || { +/// vec!["a different".to_string(), "text".to_string()] +/// }) +/// .new_lines; +/// assert_eq!("replace_start\na different\ntext\nreplace_end", result); +/// ``` +/// +/// # Panics +/// +/// Panics if start or end is not valid regex +pub fn replace_region_in_text(text: &str, start: &str, end: &str, replace_start: bool, replacements: F) -> FileChange +where + F: FnOnce() -> Vec, +{ + let replace_it = replacements(); + let mut in_old_region = false; + let mut found = false; + let mut new_lines = vec![]; + let start = Regex::new(start).unwrap(); + let end = Regex::new(end).unwrap(); + + for line in text.lines() { + if in_old_region { + if end.is_match(line) { + in_old_region = false; + new_lines.extend(replace_it.clone()); + new_lines.push(line.to_string()); + } + } else if start.is_match(line) { + if !replace_start { + new_lines.push(line.to_string()); + } + in_old_region = true; + found = true; + } else { + new_lines.push(line.to_string()); + } + } + + if !found { + // This happens if the provided regex in `clippy_dev/src/main.rs` does not match in the + // given text or file. Most likely this is an error on the programmer's side and the Regex + // is incorrect. + eprintln!("error: regex \n{:?}\ndoesn't match. You may have to update it.", start); + std::process::exit(1); + } + + let mut new_lines = new_lines.join("\n"); + if text.ends_with('\n') { + new_lines.push('\n'); + } + let changed = new_lines != text; + FileChange { changed, new_lines } +} + +/// Returns the path to the Clippy project directory +/// +/// # Panics +/// +/// Panics if the current directory could not be retrieved, there was an error reading any of the +/// Cargo.toml files or ancestor directory is the clippy root directory +#[must_use] +pub fn clippy_project_root() -> PathBuf { + let current_dir = std::env::current_dir().unwrap(); + for path in current_dir.ancestors() { + let result = std::fs::read_to_string(path.join("Cargo.toml")); + if let Err(err) = &result { + if err.kind() == std::io::ErrorKind::NotFound { + continue; + } + } + + let content = result.unwrap(); + if content.contains("[package]\nname = \"clippy\"") { + return path.to_path_buf(); + } + } + panic!("error: Can't determine root of project. Please run inside a Clippy working dir."); +} + +#[test] +fn test_parse_contents() { + let result: Vec = parse_contents( + r#" +declare_clippy_lint! { + pub PTR_ARG, + style, + "really long \ + text" +} + +declare_clippy_lint!{ + pub DOC_MARKDOWN, + pedantic, + "single line" +} + +/// some doc comment +declare_deprecated_lint! { + pub SHOULD_ASSERT_EQ, + "`assert!()` will be more flexible with RFC 2011" +} + "#, + "module_name", + ) + .collect(); + + let expected = vec![ + Lint::new("ptr_arg", "style", "really long text", None, "module_name"), + Lint::new("doc_markdown", "pedantic", "single line", None, "module_name"), + Lint::new( + "should_assert_eq", + "Deprecated", + "`assert!()` will be more flexible with RFC 2011", + Some("`assert!()` will be more flexible with RFC 2011"), + "module_name", + ), + ]; + assert_eq!(expected, result); +} + +#[test] +fn test_replace_region() { + let text = "\nabc\n123\n789\ndef\nghi"; + let expected = FileChange { + changed: true, + new_lines: "\nabc\nhello world\ndef\nghi".to_string(), + }; + let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, false, || { + vec!["hello world".to_string()] + }); + assert_eq!(expected, result); +} + +#[test] +fn test_replace_region_with_start() { + let text = "\nabc\n123\n789\ndef\nghi"; + let expected = FileChange { + changed: true, + new_lines: "\nhello world\ndef\nghi".to_string(), + }; + let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, true, || { + vec!["hello world".to_string()] + }); + assert_eq!(expected, result); +} + +#[test] +fn test_replace_region_no_changes() { + let text = "123\n456\n789"; + let expected = FileChange { + changed: false, + new_lines: "123\n456\n789".to_string(), + }; + let result = replace_region_in_text(text, r#"^\s*123$"#, r#"^\s*456"#, false, Vec::new); + assert_eq!(expected, result); +} + +#[test] +fn test_usable_lints() { + let lints = vec![ + Lint::new("should_assert_eq", "Deprecated", "abc", Some("Reason"), "module_name"), + Lint::new("should_assert_eq2", "Not Deprecated", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "internal", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "internal_style", "abc", None, "module_name"), + ]; + let expected = vec![Lint::new( + "should_assert_eq2", + "Not Deprecated", + "abc", + None, + "module_name", + )]; + assert_eq!(expected, Lint::usable_lints(&lints)); +} + +#[test] +fn test_by_lint_group() { + let lints = vec![ + Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"), + Lint::new("incorrect_match", "group1", "abc", None, "module_name"), + ]; + let mut expected: HashMap> = HashMap::new(); + expected.insert( + "group1".to_string(), + vec![ + Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), + Lint::new("incorrect_match", "group1", "abc", None, "module_name"), + ], + ); + expected.insert( + "group2".to_string(), + vec![Lint::new("should_assert_eq2", "group2", "abc", None, "module_name")], + ); + assert_eq!(expected, Lint::by_lint_group(lints.into_iter())); +} + +#[test] +fn test_gen_changelog_lint_list() { + let lints = vec![ + Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"), + ]; + let expected = vec![ + format!("[`should_assert_eq`]: {}#should_assert_eq", DOCS_LINK.to_string()), + format!("[`should_assert_eq2`]: {}#should_assert_eq2", DOCS_LINK.to_string()), + ]; + assert_eq!(expected, gen_changelog_lint_list(lints.iter())); +} + +#[test] +fn test_gen_deprecated() { + let lints = vec![ + Lint::new( + "should_assert_eq", + "group1", + "abc", + Some("has been superseded by should_assert_eq2"), + "module_name", + ), + Lint::new( + "another_deprecated", + "group2", + "abc", + Some("will be removed"), + "module_name", + ), + ]; + let expected: Vec = vec![ + " store.register_removed(", + " \"clippy::should_assert_eq\",", + " \"has been superseded by should_assert_eq2\",", + " );", + " store.register_removed(", + " \"clippy::another_deprecated\",", + " \"will be removed\",", + " );", + ] + .into_iter() + .map(String::from) + .collect(); + assert_eq!(expected, gen_deprecated(lints.iter())); +} + +#[test] +#[should_panic] +fn test_gen_deprecated_fail() { + let lints = vec![Lint::new("should_assert_eq2", "group2", "abc", None, "module_name")]; + let _deprecated_lints = gen_deprecated(lints.iter()); +} + +#[test] +fn test_gen_modules_list() { + let lints = vec![ + Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), + Lint::new("incorrect_stuff", "group3", "abc", None, "another_module"), + ]; + let expected = vec!["mod another_module;".to_string(), "mod module_name;".to_string()]; + assert_eq!(expected, gen_modules_list(lints.iter())); +} + +#[test] +fn test_gen_lint_group_list() { + let lints = vec![ + Lint::new("abc", "group1", "abc", None, "module_name"), + Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), + Lint::new("internal", "internal_style", "abc", None, "module_name"), + ]; + let expected = vec![ + " LintId::of(&module_name::ABC),".to_string(), + " LintId::of(&module_name::INTERNAL),".to_string(), + " LintId::of(&module_name::SHOULD_ASSERT_EQ),".to_string(), + ]; + assert_eq!(expected, gen_lint_group_list(lints.iter())); +} diff --git a/src/tools/clippy/clippy_dev/src/main.rs b/src/tools/clippy/clippy_dev/src/main.rs new file mode 100644 index 0000000000..2a9f3e5348 --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/main.rs @@ -0,0 +1,167 @@ +#![cfg_attr(feature = "deny-warnings", deny(warnings))] + +use clap::{App, Arg, ArgMatches, SubCommand}; +use clippy_dev::{bless, fmt, new_lint, ra_setup, serve, stderr_length_check, update_lints}; +fn main() { + let matches = get_clap_config(); + + match matches.subcommand() { + ("bless", Some(matches)) => { + bless::bless(matches.is_present("ignore-timestamp")); + }, + ("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(); + }, + ("ra_setup", Some(matches)) => ra_setup::run(matches.value_of("rustc-repo-path")), + ("serve", Some(matches)) => { + let port = matches.value_of("port").unwrap().parse().unwrap(); + let lint = matches.value_of("lint"); + serve::run(port, lint); + }, + _ => {}, + } +} + +fn get_clap_config<'a>() -> ArgMatches<'a> { + App::new("Clippy developer tooling") + .subcommand( + SubCommand::with_name("bless") + .about("bless the test output changes") + .arg( + Arg::with_name("ignore-timestamp") + .long("ignore-timestamp") + .help("Include files updated before clippy was built"), + ), + ) + .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/lifb.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."), + ) + .subcommand( + SubCommand::with_name("ra_setup") + .about("Alter dependencies so rust-analyzer can find rustc internals") + .arg( + Arg::with_name("rustc-repo-path") + .long("repo-path") + .short("r") + .help("The path to a rustc repo that will be used for setting the dependencies") + .takes_value(true) + .value_name("path") + .required(true), + ), + ) + .subcommand( + SubCommand::with_name("serve") + .about("Launch a local 'ALL the Clippy Lints' website in a browser") + .arg( + Arg::with_name("port") + .long("port") + .short("p") + .help("Local port for the http server") + .default_value("8000") + .validator_os(serve::validate_port), + ) + .arg(Arg::with_name("lint").help("Which lint's page to load initially (optional)")), + ) + .get_matches() +} diff --git a/src/tools/clippy/clippy_dev/src/new_lint.rs b/src/tools/clippy/clippy_dev/src/new_lint.rs new file mode 100644 index 0000000000..d951ca0e63 --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/new_lint.rs @@ -0,0 +1,219 @@ +use crate::clippy_project_root; +use std::fs::{self, OpenOptions}; +use std::io::prelude::*; +use std::io::{self, ErrorKind}; +use std::path::{Path, PathBuf}; + +struct LintData<'a> { + pass: &'a str, + name: &'a str, + category: &'a str, + project_root: PathBuf, +} + +trait Context { + fn context>(self, text: C) -> Self; +} + +impl Context for io::Result { + fn context>(self, text: C) -> Self { + match self { + Ok(t) => Ok(t), + Err(e) => { + let message = format!("{}: {}", text.as_ref(), e); + Err(io::Error::new(ErrorKind::Other, message)) + }, + } + } +} + +/// Creates the files required to implement and test a new lint and runs `update_lints`. +/// +/// # Errors +/// +/// This function errors out if the files couldn't be created or written to. +pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>) -> io::Result<()> { + let lint = LintData { + pass: pass.expect("`pass` argument is validated by clap"), + name: lint_name.expect("`name` argument is validated by clap"), + category: category.expect("`category` argument is validated by clap"), + project_root: clippy_project_root(), + }; + + create_lint(&lint).context("Unable to create lint implementation")?; + create_test(&lint).context("Unable to create a test for the new lint") +} + +fn create_lint(lint: &LintData) -> io::Result<()> { + let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.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); + let lint_contents = get_lint_file_contents( + pass_type, + pass_lifetimes, + lint.name, + &camel_case_name, + lint.category, + pass_import, + context_import, + ); + + let lint_path = format!("clippy_lints/src/{}.rs", lint.name); + write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes()) +} + +fn create_test(lint: &LintData) -> io::Result<()> { + fn create_project_layout>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> { + let mut path = location.into().join(case); + fs::create_dir(&path)?; + write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?; + + path.push("src"); + fs::create_dir(&path)?; + let header = format!("// compile-flags: --crate-name={}", lint_name); + write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?; + + Ok(()) + } + + if lint.category == "cargo" { + let relative_test_dir = format!("tests/ui-cargo/{}", lint.name); + let test_dir = lint.project_root.join(relative_test_dir); + fs::create_dir(&test_dir)?; + + create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?; + create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint") + } else { + let test_path = format!("tests/ui/{}.rs", lint.name); + let test_contents = get_test_file_contents(lint.name, None); + write_file(lint.project_root.join(test_path), test_contents) + } +} + +fn write_file, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> { + fn inner(path: &Path, contents: &[u8]) -> io::Result<()> { + OpenOptions::new() + .write(true) + .create_new(true) + .open(path)? + .write_all(contents) + } + + inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display())) +} + +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, header_commands: Option<&str>) -> String { + let mut contents = format!( + "#![warn(clippy::{})] + +fn main() {{ + // test code goes here +}} +", + lint_name + ); + + if let Some(header) = header_commands { + contents = format!("{}\n{}", header, contents); + } + + contents +} + +fn get_manifest_contents(lint_name: &str, hint: &str) -> String { + format!( + r#" +# {} + +[package] +name = "{}" +version = "0.1.0" +publish = false + +[workspace] +"#, + hint, lint_name + ) +} + +fn get_lint_file_contents( + pass_type: &str, + pass_lifetimes: &str, + lint_name: &str, + camel_case_name: &str, + category: &str, + pass_import: &str, + context_import: &str, +) -> String { + format!( + "use rustc_lint::{{{type}, {context_import}}}; +use rustc_session::{{declare_lint_pass, declare_tool_lint}}; +{pass_import} + +declare_clippy_lint! {{ + /// **What it does:** + /// + /// **Why is this bad?** + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // example code where clippy issues a warning + /// ``` + /// Use instead: + /// ```rust + /// // example code which does not raise clippy warning + /// ``` + pub {name_upper}, + {category}, + \"default lint description\" +}} + +declare_lint_pass!({name_camel} => [{name_upper}]); + +impl {type}{lifetimes} for {name_camel} {{}} +", + type=pass_type, + lifetimes=pass_lifetimes, + name_upper=lint_name.to_uppercase(), + name_camel=camel_case_name, + category=category, + pass_import=pass_import, + context_import=context_import + ) +} + +#[test] +fn test_camel_case() { + let s = "a_lint"; + let s2 = to_camel_case(s); + assert_eq!(s2, "ALint"); + + let name = "a_really_long_new_lint"; + let name2 = to_camel_case(name); + assert_eq!(name2, "AReallyLongNewLint"); + + let name3 = "lint__name"; + let name4 = to_camel_case(name3); + assert_eq!(name4, "LintName"); +} diff --git a/src/tools/clippy/clippy_dev/src/ra_setup.rs b/src/tools/clippy/clippy_dev/src/ra_setup.rs new file mode 100644 index 0000000000..d0e2193ddc --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/ra_setup.rs @@ -0,0 +1,103 @@ +use std::fs; +use std::fs::File; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; + +// This module takes an absolute path to a rustc repo and alters the dependencies to point towards +// the respective rustc subcrates instead of using extern crate xyz. +// This allows rust analyzer to analyze rustc internals and show proper information inside clippy +// code. See https://github.com/rust-analyzer/rust-analyzer/issues/3517 and https://github.com/rust-lang/rust-clippy/issues/5514 for details + +/// # Panics +/// +/// Panics if `rustc_path` does not lead to a rustc repo or the files could not be read +pub fn run(rustc_path: Option<&str>) { + // we can unwrap here because the arg is required by clap + let rustc_path = PathBuf::from(rustc_path.unwrap()) + .canonicalize() + .expect("failed to get the absolute repo path"); + assert!(rustc_path.is_dir(), "path is not a directory"); + let rustc_source_basedir = rustc_path.join("compiler"); + assert!( + rustc_source_basedir.is_dir(), + "are you sure the path leads to a rustc repo?" + ); + + let clippy_root_manifest = fs::read_to_string("Cargo.toml").expect("failed to read ./Cargo.toml"); + let clippy_root_lib_rs = fs::read_to_string("src/driver.rs").expect("failed to read ./src/driver.rs"); + inject_deps_into_manifest( + &rustc_source_basedir, + "Cargo.toml", + &clippy_root_manifest, + &clippy_root_lib_rs, + ) + .expect("Failed to inject deps into ./Cargo.toml"); + + let clippy_lints_manifest = + fs::read_to_string("clippy_lints/Cargo.toml").expect("failed to read ./clippy_lints/Cargo.toml"); + let clippy_lints_lib_rs = + fs::read_to_string("clippy_lints/src/lib.rs").expect("failed to read ./clippy_lints/src/lib.rs"); + inject_deps_into_manifest( + &rustc_source_basedir, + "clippy_lints/Cargo.toml", + &clippy_lints_manifest, + &clippy_lints_lib_rs, + ) + .expect("Failed to inject deps into ./clippy_lints/Cargo.toml"); +} + +fn inject_deps_into_manifest( + rustc_source_dir: &Path, + manifest_path: &str, + cargo_toml: &str, + lib_rs: &str, +) -> std::io::Result<()> { + // do not inject deps if we have aleady done so + if cargo_toml.contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]") { + eprintln!( + "cargo dev ra_setup: warning: deps already found inside {}, doing nothing.", + manifest_path + ); + return Ok(()); + } + + let extern_crates = lib_rs + .lines() + // get the deps + .filter(|line| line.starts_with("extern crate")) + // we have something like "extern crate foo;", we only care about the "foo" + // ↓ ↓ + // extern crate rustc_middle; + .map(|s| &s[13..(s.len() - 1)]); + + let new_deps = extern_crates.map(|dep| { + // format the dependencies that are going to be put inside the Cargo.toml + format!( + "{dep} = {{ path = \"{source_path}/{dep}\" }}\n", + dep = dep, + source_path = rustc_source_dir.display() + ) + }); + + // format a new [dependencies]-block with the new deps we need to inject + let mut all_deps = String::from("[target.'cfg(NOT_A_PLATFORM)'.dependencies]\n"); + new_deps.for_each(|dep_line| { + all_deps.push_str(&dep_line); + }); + all_deps.push_str("\n[dependencies]\n"); + + // replace "[dependencies]" with + // [dependencies] + // dep1 = { path = ... } + // dep2 = { path = ... } + // etc + let new_manifest = cargo_toml.replacen("[dependencies]\n", &all_deps, 1); + + // println!("{}", new_manifest); + let mut file = File::create(manifest_path)?; + file.write_all(new_manifest.as_bytes())?; + + println!("Dependency paths injected: {}", manifest_path); + + Ok(()) +} diff --git a/src/tools/clippy/clippy_dev/src/serve.rs b/src/tools/clippy/clippy_dev/src/serve.rs new file mode 100644 index 0000000000..d13c27a195 --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/serve.rs @@ -0,0 +1,67 @@ +use std::ffi::{OsStr, OsString}; +use std::path::Path; +use std::process::Command; +use std::thread; +use std::time::{Duration, SystemTime}; + +/// # Panics +/// +/// Panics if the python commands could not be spawned +pub fn run(port: u16, lint: Option<&str>) -> ! { + let mut url = Some(match lint { + None => format!("http://localhost:{}", port), + Some(lint) => format!("http://localhost:{}/#{}", port, lint), + }); + + loop { + if mtime("util/gh-pages/lints.json") < mtime("clippy_lints/src") { + Command::new("python3") + .arg("util/export.py") + .spawn() + .unwrap() + .wait() + .unwrap(); + } + if let Some(url) = url.take() { + thread::spawn(move || { + Command::new("python3") + .arg("-m") + .arg("http.server") + .arg(port.to_string()) + .current_dir("util/gh-pages") + .spawn() + .unwrap(); + // Give some time for python to start + thread::sleep(Duration::from_millis(500)); + // Launch browser after first export.py has completed and http.server is up + let _result = opener::open(url); + }); + } + thread::sleep(Duration::from_millis(1000)); + } +} + +fn mtime(path: impl AsRef) -> SystemTime { + let path = path.as_ref(); + if path.is_dir() { + path.read_dir() + .into_iter() + .flatten() + .flatten() + .map(|entry| mtime(&entry.path())) + .max() + .unwrap_or(SystemTime::UNIX_EPOCH) + } else { + path.metadata() + .and_then(|metadata| metadata.modified()) + .unwrap_or(SystemTime::UNIX_EPOCH) + } +} + +#[allow(clippy::missing_errors_doc)] +pub fn validate_port(arg: &OsStr) -> Result<(), OsString> { + match arg.to_string_lossy().parse::() { + Ok(_port) => Ok(()), + Err(err) => Err(OsString::from(err.to_string())), + } +} diff --git a/src/tools/clippy/clippy_dev/src/stderr_length_check.rs b/src/tools/clippy/clippy_dev/src/stderr_length_check.rs new file mode 100644 index 0000000000..e02b6f7da5 --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/stderr_length_check.rs @@ -0,0 +1,51 @@ +use crate::clippy_project_root; +use std::ffi::OsStr; +use std::fs; +use std::path::{Path, PathBuf}; +use walkdir::WalkDir; + +// The maximum length allowed for stderr files. +// +// We limit this because small files are easier to deal with than bigger files. +const LENGTH_LIMIT: usize = 200; + +pub fn check() { + let exceeding_files: Vec<_> = exceeding_stderr_files(); + + if !exceeding_files.is_empty() { + eprintln!("Error: stderr files exceeding limit of {} lines:", LENGTH_LIMIT); + for (path, count) in exceeding_files { + println!("{}: {}", path.display(), count); + } + std::process::exit(1); + } +} + +fn exceeding_stderr_files() -> Vec<(PathBuf, usize)> { + // We use `WalkDir` instead of `fs::read_dir` here in order to recurse into subdirectories. + WalkDir::new(clippy_project_root().join("tests/ui")) + .into_iter() + .filter_map(Result::ok) + .filter(|f| !f.file_type().is_dir()) + .filter_map(|e| { + let p = e.into_path(); + let count = count_linenumbers(&p); + if p.extension() == Some(OsStr::new("stderr")) && count > LENGTH_LIMIT { + Some((p, count)) + } else { + None + } + }) + .collect() +} + +#[must_use] +fn count_linenumbers(filepath: &Path) -> usize { + match fs::read(filepath) { + Ok(content) => bytecount::count(&content, b'\n'), + Err(e) => { + eprintln!("Failed to read file: {}", e); + 0 + }, + } +} diff --git a/src/tools/clippy/clippy_dev/src/update_lints.rs b/src/tools/clippy/clippy_dev/src/update_lints.rs new file mode 100644 index 0000000000..edf6c5f57a --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/update_lints.rs @@ -0,0 +1,149 @@ +use crate::{ + gather_all, gen_changelog_lint_list, gen_deprecated, gen_lint_group_list, gen_modules_list, gen_register_lint_list, + replace_region_in_file, Lint, DOCS_LINK, +}; +use std::path::Path; + +#[derive(Clone, Copy, PartialEq)] +pub enum UpdateMode { + Check, + Change, +} + +#[allow(clippy::too_many_lines)] +pub fn run(update_mode: UpdateMode) { + let lint_list: Vec = gather_all().collect(); + + let internal_lints = Lint::internal_lints(&lint_list); + let deprecated_lints = Lint::deprecated_lints(&lint_list); + let usable_lints = Lint::usable_lints(&lint_list); + let mut sorted_usable_lints = usable_lints.clone(); + sorted_usable_lints.sort_by_key(|lint| lint.name.clone()); + + let usable_lint_count = round_to_fifty(usable_lints.len()); + + let mut file_change = false; + + file_change |= replace_region_in_file( + Path::new("README.md"), + &format!( + r#"\[There are over \d+ lints included in this crate!\]\({}\)"#, + DOCS_LINK + ), + "", + true, + update_mode == UpdateMode::Change, + || { + vec![format!( + "[There are over {} lints included in this crate!]({})", + usable_lint_count, DOCS_LINK + )] + }, + ) + .changed; + + file_change |= replace_region_in_file( + Path::new("CHANGELOG.md"), + "", + "", + false, + update_mode == UpdateMode::Change, + || gen_changelog_lint_list(usable_lints.iter().chain(deprecated_lints.iter())), + ) + .changed; + + file_change |= replace_region_in_file( + Path::new("clippy_lints/src/lib.rs"), + "begin deprecated lints", + "end deprecated lints", + false, + update_mode == UpdateMode::Change, + || gen_deprecated(deprecated_lints.iter()), + ) + .changed; + + file_change |= replace_region_in_file( + Path::new("clippy_lints/src/lib.rs"), + "begin register lints", + "end register lints", + false, + update_mode == UpdateMode::Change, + || gen_register_lint_list(internal_lints.iter(), usable_lints.iter()), + ) + .changed; + + file_change |= replace_region_in_file( + Path::new("clippy_lints/src/lib.rs"), + "begin lints modules", + "end lints modules", + false, + update_mode == UpdateMode::Change, + || gen_modules_list(usable_lints.iter()), + ) + .changed; + + // Generate lists of lints in the clippy::all lint group + file_change |= replace_region_in_file( + Path::new("clippy_lints/src/lib.rs"), + r#"store.register_group\(true, "clippy::all""#, + r#"\]\);"#, + false, + update_mode == UpdateMode::Change, + || { + // clippy::all should only include the following lint groups: + let all_group_lints = usable_lints.iter().filter(|l| { + l.group == "correctness" || l.group == "style" || l.group == "complexity" || l.group == "perf" + }); + + gen_lint_group_list(all_group_lints) + }, + ) + .changed; + + // Generate the list of lints for all other lint groups + for (lint_group, lints) in Lint::by_lint_group(usable_lints.into_iter().chain(internal_lints)) { + file_change |= replace_region_in_file( + Path::new("clippy_lints/src/lib.rs"), + &format!("store.register_group\\(true, \"clippy::{}\"", lint_group), + r#"\]\);"#, + false, + update_mode == UpdateMode::Change, + || gen_lint_group_list(lints.iter()), + ) + .changed; + } + + if update_mode == UpdateMode::Check && file_change { + println!( + "Not all lints defined properly. \ + Please run `cargo dev update_lints` to make sure all lints are defined properly." + ); + std::process::exit(1); + } +} + +pub fn print_lints() { + let lint_list: Vec = gather_all().collect(); + let usable_lints = Lint::usable_lints(&lint_list); + let usable_lint_count = usable_lints.len(); + let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter()); + + for (lint_group, mut lints) in grouped_by_lint_group { + if lint_group == "Deprecated" { + continue; + } + println!("\n## {}", lint_group); + + lints.sort_by_key(|l| l.name.clone()); + + for lint in lints { + println!("* [{}]({}#{}) ({})", lint.name, DOCS_LINK, lint.name, lint.desc); + } + } + + println!("there are {} lints", usable_lint_count); +} + +fn round_to_fifty(count: usize) -> usize { + count / 50 * 50 +} diff --git a/src/tools/clippy/clippy_dummy/Cargo.toml b/src/tools/clippy/clippy_dummy/Cargo.toml new file mode 100644 index 0000000000..6959de7ffe --- /dev/null +++ b/src/tools/clippy/clippy_dummy/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "clippy_dummy" # rename to clippy before publishing +version = "0.0.303" +authors = ["The Rust Clippy Developers"] +edition = "2018" +readme = "crates-readme.md" +description = "A bunch of helpful lints to avoid common pitfalls in Rust." +build = 'build.rs' + +repository = "https://github.com/rust-lang/rust-clippy" + +license = "MIT OR Apache-2.0" +keywords = ["clippy", "lint", "plugin"] +categories = ["development-tools", "development-tools::cargo-plugins"] + +[build-dependencies] +term = "0.6" diff --git a/src/tools/clippy/clippy_dummy/PUBLISH.md b/src/tools/clippy/clippy_dummy/PUBLISH.md new file mode 100644 index 0000000000..8e420ec959 --- /dev/null +++ b/src/tools/clippy/clippy_dummy/PUBLISH.md @@ -0,0 +1,6 @@ +This is a dummy crate to publish to crates.io. It primarily exists to ensure +that folks trying to install clippy from crates.io get redirected to the +`rustup` technique. + +Before publishing, be sure to rename `clippy_dummy` to `clippy` in `Cargo.toml`, +it has a different name to avoid workspace issues. diff --git a/src/tools/clippy/clippy_dummy/build.rs b/src/tools/clippy/clippy_dummy/build.rs new file mode 100644 index 0000000000..21af4f8244 --- /dev/null +++ b/src/tools/clippy/clippy_dummy/build.rs @@ -0,0 +1,42 @@ +use term::color::{GREEN, RED, WHITE}; +use term::{Attr, Error, Result}; + +fn main() { + if foo().is_err() { + eprintln!( + "error: Clippy is no longer available via crates.io\n\n\ + help: please run `rustup component add clippy` instead" + ); + } + std::process::exit(1); +} + +fn foo() -> Result<()> { + let mut t = term::stderr().ok_or(Error::NotSupported)?; + + t.attr(Attr::Bold)?; + t.fg(RED)?; + write!(t, "\nerror: ")?; + + t.reset()?; + t.fg(WHITE)?; + writeln!(t, "Clippy is no longer available via crates.io\n")?; + + t.attr(Attr::Bold)?; + t.fg(GREEN)?; + write!(t, "help: ")?; + + t.reset()?; + t.fg(WHITE)?; + write!(t, "please run `")?; + + t.attr(Attr::Bold)?; + write!(t, "rustup component add clippy")?; + + t.reset()?; + t.fg(WHITE)?; + writeln!(t, "` instead")?; + + t.reset()?; + Ok(()) +} diff --git a/src/tools/clippy/clippy_dummy/crates-readme.md b/src/tools/clippy/clippy_dummy/crates-readme.md new file mode 100644 index 0000000000..0decae8b91 --- /dev/null +++ b/src/tools/clippy/clippy_dummy/crates-readme.md @@ -0,0 +1,9 @@ +Installing clippy via crates.io is deprecated. Please use the following: + +```terminal +rustup component add clippy +``` + +on a Rust version 1.29 or later. You may need to run `rustup self update` if it complains about a missing clippy binary. + +See [the homepage](https://github.com/rust-lang/rust-clippy/#clippy) for more information diff --git a/src/tools/clippy/clippy_dummy/src/main.rs b/src/tools/clippy/clippy_dummy/src/main.rs new file mode 100644 index 0000000000..a118834f1f --- /dev/null +++ b/src/tools/clippy/clippy_dummy/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + panic!("This shouldn't even compile") +} diff --git a/src/tools/clippy/clippy_lints/Cargo.toml b/src/tools/clippy/clippy_lints/Cargo.toml new file mode 100644 index 0000000000..6bd6c07927 --- /dev/null +++ b/src/tools/clippy/clippy_lints/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "clippy_lints" +# begin automatic update +version = "0.1.52" +# end automatic update +authors = ["The Rust Clippy Developers"] +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.12" +clippy_utils = { path = "../clippy_utils" } +if_chain = "1.0.0" +itertools = "0.9" +pulldown-cmark = { version = "0.8", 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.11" +rustc-semver = "1.1.0" +# NOTE: cargo requires serde feat in its url dep +# see +url = { version = "2.1.0", features = ["serde"] } +quote = "1" +syn = { version = "1", features = ["full"] } + +[features] +deny-warnings = [] +# build clippy with internal lints enabled, off by default +internal-lints = ["clippy_utils/internal-lints"] + +[package.metadata.rust-analyzer] +# This crate uses #[feature(rustc_private)] +rustc_private = true diff --git a/src/tools/clippy/clippy_lints/README.md b/src/tools/clippy/clippy_lints/README.md new file mode 100644 index 0000000000..513583b7e3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/README.md @@ -0,0 +1 @@ +This crate contains Clippy lints. For the main crate, check [GitHub](https://github.com/rust-lang/rust-clippy). diff --git a/src/tools/clippy/clippy_lints/src/approx_const.rs b/src/tools/clippy/clippy_lints/src/approx_const.rs new file mode 100644 index 0000000000..1d511a86c9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/approx_const.rs @@ -0,0 +1,117 @@ +use crate::utils::span_lint; +use rustc_ast::ast::{FloatTy, LitFloatType, LitKind}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol; +use std::f64::consts as f64; + +declare_clippy_lint! { + /// **What it does:** Checks for floating point literals that approximate + /// constants which are defined in + /// [`std::f32::consts`](https://doc.rust-lang.org/stable/std/f32/consts/#constants) + /// or + /// [`std::f64::consts`](https://doc.rust-lang.org/stable/std/f64/consts/#constants), + /// respectively, suggesting to use the predefined constant. + /// + /// **Why is this bad?** Usually, the definition in the standard library is more + /// precise than what people come up with. If you find that your definition is + /// actually more precise, please [file a Rust + /// issue](https://github.com/rust-lang/rust/issues). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 3.14; + /// let y = 1_f64 / x; + /// ``` + /// Use predefined constants instead: + /// ```rust + /// let x = std::f32::consts::PI; + /// let y = std::f64::consts::FRAC_1_PI; + /// ``` + pub APPROX_CONSTANT, + correctness, + "the approximate of a known float constant (in `std::fXX::consts`)" +} + +// Tuples are of the form (constant, name, min_digits) +const KNOWN_CONSTS: [(f64, &str, usize); 18] = [ + (f64::E, "E", 4), + (f64::FRAC_1_PI, "FRAC_1_PI", 4), + (f64::FRAC_1_SQRT_2, "FRAC_1_SQRT_2", 5), + (f64::FRAC_2_PI, "FRAC_2_PI", 5), + (f64::FRAC_2_SQRT_PI, "FRAC_2_SQRT_PI", 5), + (f64::FRAC_PI_2, "FRAC_PI_2", 5), + (f64::FRAC_PI_3, "FRAC_PI_3", 5), + (f64::FRAC_PI_4, "FRAC_PI_4", 5), + (f64::FRAC_PI_6, "FRAC_PI_6", 5), + (f64::FRAC_PI_8, "FRAC_PI_8", 5), + (f64::LN_10, "LN_10", 5), + (f64::LN_2, "LN_2", 5), + (f64::LOG10_E, "LOG10_E", 5), + (f64::LOG2_E, "LOG2_E", 5), + (f64::LOG2_10, "LOG2_10", 5), + (f64::LOG10_2, "LOG10_2", 5), + (f64::PI, "PI", 3), + (f64::SQRT_2, "SQRT_2", 5), +]; + +declare_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); + +impl<'tcx> LateLintPass<'tcx> for ApproxConstant { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Lit(lit) = &e.kind { + check_lit(cx, &lit.node, e); + } + } +} + +fn check_lit(cx: &LateContext<'_>, lit: &LitKind, e: &Expr<'_>) { + match *lit { + LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty { + FloatTy::F32 => check_known_consts(cx, e, s, "f32"), + FloatTy::F64 => check_known_consts(cx, e, s, "f64"), + }, + LitKind::Float(s, LitFloatType::Unsuffixed) => check_known_consts(cx, e, s, "f{32, 64}"), + _ => (), + } +} + +fn check_known_consts(cx: &LateContext<'_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) { + let s = s.as_str(); + if s.parse::().is_ok() { + for &(constant, name, min_digits) in &KNOWN_CONSTS { + if is_approx_const(constant, &s, min_digits) { + span_lint( + cx, + APPROX_CONSTANT, + e.span, + &format!( + "approximate value of `{}::consts::{}` found. \ + Consider using it directly", + module, &name + ), + ); + return; + } + } + } +} + +/// Returns `false` if the number of significant figures in `value` are +/// less than `min_digits`; otherwise, returns true if `value` is equal +/// to `constant`, rounded to the number of digits present in `value`. +#[must_use] +fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool { + if value.len() <= min_digits { + false + } else if constant.to_string().starts_with(value) { + // The value is a truncated constant + true + } else { + let round_const = format!("{:.*}", value.len() - 2, constant); + value == round_const + } +} diff --git a/src/tools/clippy/clippy_lints/src/arithmetic.rs b/src/tools/clippy/clippy_lints/src/arithmetic.rs new file mode 100644 index 0000000000..61fdf9495b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/arithmetic.rs @@ -0,0 +1,168 @@ +use crate::consts::constant_simple; +use crate::utils::span_lint; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for integer arithmetic operations which could overflow or panic. + /// + /// Specifically, checks for any operators (`+`, `-`, `*`, `<<`, etc) which are capable + /// of overflowing according to the [Rust + /// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow), + /// or which can panic (`/`, `%`). No bounds analysis or sophisticated reasoning is + /// attempted. + /// + /// **Why is this bad?** Integer overflow will trigger a panic in debug builds or will wrap in + /// release mode. Division by zero will cause a panic in either mode. In some applications one + /// wants explicitly checked, wrapping or saturating arithmetic. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let a = 0; + /// a + 1; + /// ``` + pub INTEGER_ARITHMETIC, + restriction, + "any integer arithmetic expression which could overflow or panic" +} + +declare_clippy_lint! { + /// **What it does:** Checks for float arithmetic. + /// + /// **Why is this bad?** For some embedded systems or kernel development, it + /// can be useful to rule out floating-point numbers. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let a = 0.0; + /// a + 1.0; + /// ``` + pub FLOAT_ARITHMETIC, + restriction, + "any floating-point arithmetic statement" +} + +#[derive(Copy, Clone, Default)] +pub struct Arithmetic { + expr_span: Option, + /// This field is used to check whether expressions are constants, such as in enum discriminants + /// and consts + const_span: Option, +} + +impl_lint_pass!(Arithmetic => [INTEGER_ARITHMETIC, FLOAT_ARITHMETIC]); + +impl<'tcx> LateLintPass<'tcx> for Arithmetic { + fn check_expr(&mut self, cx: &LateContext<'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.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r)); + if l_ty.peel_refs().is_integral() && r_ty.peel_refs().is_integral() { + match op.node { + hir::BinOpKind::Div | hir::BinOpKind::Rem => match &r.kind { + hir::ExprKind::Lit(_lit) => (), + hir::ExprKind::Unary(hir::UnOp::Neg, expr) => { + if let hir::ExprKind::Lit(lit) = &expr.kind { + if let rustc_ast::ast::LitKind::Int(1, _) = lit.node { + span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); + self.expr_span = Some(expr.span); + } + } + }, + _ => { + span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); + self.expr_span = Some(expr.span); + }, + }, + _ => { + span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); + self.expr_span = Some(expr.span); + }, + } + } else if r_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::Neg, arg) => { + let ty = cx.typeck_results().expr_ty(arg); + if constant_simple(cx, cx.typeck_results(), 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<'tcx>, expr: &'tcx hir::Expr<'_>) { + if Some(expr.span) == self.expr_span { + self.expr_span = None; + } + } + + fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) { + let body_owner = cx.tcx.hir().body_owner(body.id()); + + match cx.tcx.hir().body_owner_kind(body_owner) { + hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const => { + let body_span = cx.tcx.hir().span(body_owner); + + if let Some(span) = self.const_span { + if span.contains(body_span) { + return; + } + } + self.const_span = Some(body_span); + }, + hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => (), + } + } + + fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) { + let body_owner = cx.tcx.hir().body_owner(body.id()); + let body_span = cx.tcx.hir().span(body_owner); + + if let Some(span) = self.const_span { + if span.contains(body_span) { + return; + } + } + self.const_span = None; + } +} diff --git a/src/tools/clippy/clippy_lints/src/as_conversions.rs b/src/tools/clippy/clippy_lints/src/as_conversions.rs new file mode 100644 index 0000000000..c30d65bbc5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/as_conversions.rs @@ -0,0 +1,66 @@ +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. + /// + /// Note that this lint is specialized in linting *every single* use of `as` + /// regardless of whether good alternatives exist or not. + /// If you want more precise lints for `as`, please consider using these separate lints: + /// `unnecessary_cast`, `cast_lossless/possible_truncation/possible_wrap/precision_loss/sign_loss`, + /// `fn_to_numeric_cast(_with_truncation)`, `char_lit_as_u8`, `ref_to_mut` and `ptr_as_ptr`. + /// There is a good explanation the reason why this lint should work in this way and how it is useful + /// [in this issue](https://github.com/rust-lang/rust-clippy/issues/5122). + /// + /// **Why is this bad?** `as` conversions will perform many kinds of + /// conversions, including silently lossy conversions and dangerous coercions. + /// There are cases when it makes sense to use `as`, so the lint is + /// Allow by default. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// let a: u32; + /// ... + /// f(a as u16); + /// ``` + /// + /// Usually better represents the semantics you expect: + /// ```rust,ignore + /// f(a.try_into()?); + /// ``` + /// or + /// ```rust,ignore + /// f(a.try_into().expect("Unexpected u16 overflow in f")); + /// ``` + /// + pub AS_CONVERSIONS, + restriction, + "using a potentially dangerous silent `as` conversion" +} + +declare_lint_pass!(AsConversions => [AS_CONVERSIONS]); + +impl EarlyLintPass for AsConversions { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if let ExprKind::Cast(_, _) = expr.kind { + span_lint_and_help( + cx, + AS_CONVERSIONS, + expr.span, + "using a potentially dangerous silent `as` conversion", + None, + "consider using a safe wrapper for this conversion", + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/asm_syntax.rs b/src/tools/clippy/clippy_lints/src/asm_syntax.rs new file mode 100644 index 0000000000..ef1f1a14af --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/asm_syntax.rs @@ -0,0 +1,125 @@ +use std::fmt; + +use crate::utils::span_lint_and_help; +use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum AsmStyle { + Intel, + Att, +} + +impl fmt::Display for AsmStyle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AsmStyle::Intel => f.write_str("Intel"), + AsmStyle::Att => f.write_str("AT&T"), + } + } +} + +impl std::ops::Not for AsmStyle { + type Output = AsmStyle; + + fn not(self) -> AsmStyle { + match self { + AsmStyle::Intel => AsmStyle::Att, + AsmStyle::Att => AsmStyle::Intel, + } + } +} + +fn check_expr_asm_syntax(lint: &'static Lint, cx: &EarlyContext<'_>, expr: &Expr, check_for: AsmStyle) { + if let ExprKind::InlineAsm(ref inline_asm) = expr.kind { + let style = if inline_asm.options.contains(InlineAsmOptions::ATT_SYNTAX) { + AsmStyle::Att + } else { + AsmStyle::Intel + }; + + if style == check_for { + span_lint_and_help( + cx, + lint, + expr.span, + &format!("{} x86 assembly syntax used", style), + None, + &format!("use {} x86 assembly syntax", !style), + ); + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of Intel x86 assembly syntax. + /// + /// **Why is this bad?** The lint has been enabled to indicate a preference + /// for AT&T x86 assembly syntax. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,no_run + /// # #![feature(asm)] + /// # unsafe { let ptr = "".as_ptr(); + /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); + /// # } + /// ``` + /// Use instead: + /// ```rust,no_run + /// # #![feature(asm)] + /// # unsafe { let ptr = "".as_ptr(); + /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); + /// # } + /// ``` + pub INLINE_ASM_X86_INTEL_SYNTAX, + restriction, + "prefer AT&T x86 assembly syntax" +} + +declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]); + +impl EarlyLintPass for InlineAsmX86IntelSyntax { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Intel); + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of AT&T x86 assembly syntax. + /// + /// **Why is this bad?** The lint has been enabled to indicate a preference + /// for Intel x86 assembly syntax. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,no_run + /// # #![feature(asm)] + /// # unsafe { let ptr = "".as_ptr(); + /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); + /// # } + /// ``` + /// Use instead: + /// ```rust,no_run + /// # #![feature(asm)] + /// # unsafe { let ptr = "".as_ptr(); + /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); + /// # } + /// ``` + pub INLINE_ASM_X86_ATT_SYNTAX, + restriction, + "prefer Intel x86 assembly syntax" +} + +declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]); + +impl EarlyLintPass for InlineAsmX86AttSyntax { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Att); + } +} diff --git a/src/tools/clippy/clippy_lints/src/assertions_on_constants.rs b/src/tools/clippy/clippy_lints/src/assertions_on_constants.rs new file mode 100644 index 0000000000..77b26faaa5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/assertions_on_constants.rs @@ -0,0 +1,144 @@ +use crate::consts::{constant, Constant}; +use crate::utils::{is_direct_expn_of, is_expn_of, match_panic_call, snippet_opt, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind, 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<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { + fn check_expr(&mut self, cx: &LateContext<'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.typeck_results(), 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 +/// if !c { +/// { +/// ::std::rt::begin_panic(message, _) +/// } +/// } +/// ``` +/// +/// where `message` is any expression and `c` is a constant bool. +fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option { + if_chain! { + if let ExprKind::If(ref cond, ref then, _) = expr.kind; + if let ExprKind::Unary(UnOp::Not, ref expr) = cond.kind; + // bind the first argument of the `assert!` macro + if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr); + // block + if let ExprKind::Block(ref block, _) = then.kind; + if block.stmts.is_empty(); + if let Some(block_expr) = &block.expr; + // inner block is optional. unwrap it if it exists, or use the expression as is otherwise. + if let Some(begin_panic_call) = match block_expr.kind { + ExprKind::Block(ref inner_block, _) => &inner_block.expr, + _ => &block.expr, + }; + // function call + if let Some(args) = match_panic_call(cx, begin_panic_call); + if args.len() == 1; + // bind the second argument of the `assert!` macro if it exists + if let panic_message = snippet_opt(cx, args[0].span); + // second argument of begin_panic is irrelevant + // as is the second match arm + then { + // an empty message occurs when it was generated by the macro + // (and not passed by the user) + return panic_message + .filter(|msg| !msg.is_empty()) + .map(|msg| AssertKind::WithMessage(msg, is_true)) + .or(Some(AssertKind::WithoutMessage(is_true))); + } + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/assign_ops.rs b/src/tools/clippy/clippy_lints/src/assign_ops.rs new file mode 100644 index 0000000000..e13f62d042 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/assign_ops.rs @@ -0,0 +1,263 @@ +use crate::utils::{ + eq_expr_value, get_trait_def_id, implements_trait, snippet_opt, span_lint_and_then, trait_ref_of_method, +}; +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; + /// // ... + /// // Bad + /// a = a + b; + /// + /// // Good + /// 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<'tcx> LateLintPass<'tcx> for AssignOps { + #[allow(clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'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 eq_expr_value(cx, lhs, l) { + lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, r); + } + // lhs op= l commutative_op r + if is_commutative(op.node) && eq_expr_value(cx, 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.typeck_results().expr_ty(assignee); + let rty = cx.typeck_results().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 eq_expr_value(cx, assignee, l) { + lint(assignee, r); + } + // a = b commutative_op a + // Limited to primitive type as these ops are know to be commutative + if eq_expr_value(cx, assignee, r) && cx.typeck_results().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<'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + if eq_expr_value(self.cx, self.assignee, expr) { + self.counter += 1; + } + + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/async_yields_async.rs b/src/tools/clippy/clippy_lints/src/async_yields_async.rs new file mode 100644 index 0000000000..869a5c28d0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/async_yields_async.rs @@ -0,0 +1,85 @@ +use crate::utils::{implements_trait, snippet, span_lint_and_then}; +use rustc_errors::Applicability; +use rustc_hir::{AsyncGeneratorKind, Body, BodyId, ExprKind, GeneratorKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for async blocks that yield values of types + /// that can themselves be awaited. + /// + /// **Why is this bad?** An await is likely missing. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// async fn foo() {} + /// + /// fn bar() { + /// let x = async { + /// foo() + /// }; + /// } + /// ``` + /// Use instead: + /// ```rust + /// async fn foo() {} + /// + /// fn bar() { + /// let x = async { + /// foo().await + /// }; + /// } + /// ``` + pub ASYNC_YIELDS_ASYNC, + correctness, + "async blocks that return a type that can be awaited" +} + +declare_lint_pass!(AsyncYieldsAsync => [ASYNC_YIELDS_ASYNC]); + +impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync { + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { + use AsyncGeneratorKind::{Block, Closure}; + // For functions, with explicitly defined types, don't warn. + // XXXkhuey maybe we should? + if let Some(GeneratorKind::Async(Block | Closure)) = body.generator_kind { + if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() { + let body_id = BodyId { + hir_id: body.value.hir_id, + }; + let typeck_results = cx.tcx.typeck_body(body_id); + let expr_ty = typeck_results.expr_ty(&body.value); + + if implements_trait(cx, expr_ty, future_trait_def_id, &[]) { + let return_expr_span = match &body.value.kind { + // XXXkhuey there has to be a better way. + ExprKind::Block(block, _) => block.expr.map(|e| e.span), + ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span), + _ => None, + }; + if let Some(return_expr_span) = return_expr_span { + span_lint_and_then( + cx, + ASYNC_YIELDS_ASYNC, + return_expr_span, + "an async construct yields a type which is itself awaitable", + |db| { + db.span_label(body.value.span, "outer async construct"); + db.span_label(return_expr_span, "awaitable value not awaited"); + db.span_suggestion( + return_expr_span, + "consider awaiting this value", + format!("{}.await", snippet(cx, return_expr_span, "..")), + Applicability::MaybeIncorrect, + ); + }, + ); + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/atomic_ordering.rs b/src/tools/clippy/clippy_lints/src/atomic_ordering.rs new file mode 100644 index 0000000000..703d8a6f62 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/atomic_ordering.rs @@ -0,0 +1,229 @@ +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/exchanges/updates 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, AtomicU8, Ordering}; + /// + /// let x = AtomicU8::new(0); + /// + /// // Bad: `Release` and `AcqRel` cannot be used for `load`. + /// let _ = x.load(Ordering::Release); + /// let _ = x.load(Ordering::AcqRel); + /// + /// // Bad: `Acquire` and `AcqRel` cannot be used for `store`. + /// x.store(1, Ordering::Acquire); + /// x.store(2, Ordering::AcqRel); + /// + /// // Bad: `Relaxed` cannot be used as a fence's ordering. + /// atomic::fence(Ordering::Relaxed); + /// atomic::compiler_fence(Ordering::Relaxed); + /// + /// // Bad: `Release` and `AcqRel` are both always invalid + /// // for the failure ordering (the last arg). + /// let _ = x.compare_exchange(1, 2, Ordering::SeqCst, Ordering::Release); + /// let _ = x.compare_exchange_weak(2, 3, Ordering::AcqRel, Ordering::AcqRel); + /// + /// // Bad: The failure ordering is not allowed to be + /// // stronger than the success order, and `SeqCst` is + /// // stronger than `Relaxed`. + /// let _ = x.fetch_update(Ordering::Relaxed, Ordering::SeqCst, |val| Some(val + val)); + /// ``` + pub INVALID_ATOMIC_ORDERING, + correctness, + "usage of invalid atomic ordering in atomic operations 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.typeck_results().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.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.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.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`" + ); + } + } +} + +fn opt_ordering_defid(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option { + if let ExprKind::Path(ref ord_qpath) = ord_arg.kind { + cx.qpath_res(ord_qpath, ord_arg.hir_id).opt_def_id() + } else { + None + } +} + +fn check_atomic_compare_exchange(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 == "compare_exchange" || method == "compare_exchange_weak" || method == "fetch_update"; + let (success_order_arg, failure_order_arg) = if method == "fetch_update" { + (&args[1], &args[2]) + } else { + (&args[3], &args[4]) + }; + if let Some(fail_ordering_def_id) = opt_ordering_defid(cx, failure_order_arg); + then { + // Helper type holding on to some checking and error reporting data. Has + // - (success ordering name, + // - list of failure orderings forbidden by the success order, + // - suggestion message) + type OrdLintInfo = (&'static str, &'static [&'static str], &'static str); + let relaxed: OrdLintInfo = ("Relaxed", &["SeqCst", "Acquire"], "ordering mode `Relaxed`"); + let acquire: OrdLintInfo = ("Acquire", &["SeqCst"], "ordering modes `Acquire` or `Relaxed`"); + let seq_cst: OrdLintInfo = ("SeqCst", &[], "ordering modes `Acquire`, `SeqCst` or `Relaxed`"); + let release = ("Release", relaxed.1, relaxed.2); + let acqrel = ("AcqRel", acquire.1, acquire.2); + let search = [relaxed, acquire, seq_cst, release, acqrel]; + + let success_lint_info = opt_ordering_defid(cx, success_order_arg) + .and_then(|success_ord_def_id| -> Option { + search + .iter() + .find(|(ordering, ..)| { + match_def_path(cx, success_ord_def_id, + &["core", "sync", "atomic", "Ordering", ordering]) + }) + .copied() + }); + + if match_ordering_def_path(cx, fail_ordering_def_id, &["Release", "AcqRel"]) { + // If we don't know the success order is, use what we'd suggest + // if it were maximally permissive. + let suggested = success_lint_info.unwrap_or(seq_cst).2; + span_lint_and_help( + cx, + INVALID_ATOMIC_ORDERING, + failure_order_arg.span, + &format!( + "{}'s failure ordering may not be `Release` or `AcqRel`", + method, + ), + None, + &format!("consider using {} instead", suggested), + ); + } else if let Some((success_ord_name, bad_ords_given_success, suggested)) = success_lint_info { + if match_ordering_def_path(cx, fail_ordering_def_id, bad_ords_given_success) { + span_lint_and_help( + cx, + INVALID_ATOMIC_ORDERING, + failure_order_arg.span, + &format!( + "{}'s failure ordering may not be stronger than the success ordering of `{}`", + method, + success_ord_name, + ), + None, + &format!("consider using {} instead", suggested), + ); + } + } + } + } +} + +impl<'tcx> LateLintPass<'tcx> for AtomicOrdering { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + check_atomic_load_store(cx, expr); + check_memory_fence(cx, expr); + check_atomic_compare_exchange(cx, expr); + } +} diff --git a/src/tools/clippy/clippy_lints/src/attrs.rs b/src/tools/clippy/clippy_lints/src/attrs.rs new file mode 100644 index 0000000000..6250810bc4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/attrs.rs @@ -0,0 +1,650 @@ +//! checks for attributes + +use crate::utils::{ + first_line_of_span, is_present_in_source, match_panic_def_id, snippet_opt, span_lint, span_lint_and_help, + span_lint_and_sugg, span_lint_and_then, without_block_comments, +}; +use if_chain::if_chain; +use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem}; +use rustc_errors::Applicability; +use rustc_hir::{ + Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind, +}; +use rustc_lint::{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::sym; +use rustc_span::symbol::{Symbol, SymbolStr}; +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] = &["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 permits `#[allow(unused_imports)]`, `#[allow(deprecated)]`, + /// `#[allow(unreachable_pub)]`, `#[allow(clippy::wildcard_imports)]` and + /// `#[allow(clippy::enum_glob_use)]` 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) + /// #![allow(dead_code)] + /// + /// fn this_is_fine() { } + /// + /// // Bad + /// #[allow(dead_code)] + /// + /// fn not_quite_good_code() { } + /// + /// // Good (as outer attribute) + /// #[allow(dead_code)] + /// 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 `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category. + /// + /// **Why is this bad?** Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust. + /// These lints should only be enabled on a lint-by-lint basis and with careful consideration. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust + /// #![deny(clippy::restriction)] + /// ``` + /// + /// Good: + /// ```rust + /// #![deny(clippy::as_conversions)] + /// ``` + pub BLANKET_CLIPPY_RESTRICTION_LINTS, + style, + "enabling the complete restriction group" +} + +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, + BLANKET_CLIPPY_RESTRICTION_LINTS, +]); + +impl<'tcx> LateLintPass<'tcx> for Attributes { + fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) { + if let Some(items) = &attr.meta_item_list() { + if let Some(ident) = attr.ident() { + let ident = &*ident.as_str(); + match ident { + "allow" | "warn" | "deny" | "forbid" => { + check_clippy_lint_names(cx, ident, items); + }, + _ => {}, + } + if items.is_empty() || !attr.has_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.has_name(sym::since); + then { + check_semver(cx, item.span(), lit); + } + } + } + } + } + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + if is_relevant_item(cx, item) { + check_attrs(cx, item.span, item.ident.name, attrs) + } + match item.kind { + ItemKind::ExternCrate(..) | ItemKind::Use(..) => { + let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use)); + + for attr in 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" => { + // permit `unused_imports`, `deprecated`, `unreachable_pub`, + // `clippy::wildcard_imports`, and `clippy::enum_glob_use` 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)) + || extract_clippy_lint(lint) + .map_or(false, |s| s == "wildcard_imports") + || extract_clippy_lint(lint).map_or(false, |s| s == "enum_glob_use") + { + 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<'tcx>, item: &'tcx ImplItem<'_>) { + if is_relevant_impl(cx, item) { + check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id())) + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + if is_relevant_trait(cx, item) { + check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id())) + } + } +} + +/// Returns the lint name if it is clippy lint. +fn extract_clippy_lint(lint: &NestedMetaItem) -> Option { + 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.name == sym::clippy; + let lint_name = meta_item.path.segments.last().unwrap().ident.name; + then { + return Some(lint_name.as_str()); + } + } + None +} + +fn check_clippy_lint_names(cx: &LateContext<'_>, ident: &str, items: &[NestedMetaItem]) { + for lint in items { + if let Some(lint_name) = extract_clippy_lint(lint) { + if lint_name == "restriction" && ident != "allow" { + span_lint_and_help( + cx, + BLANKET_CLIPPY_RESTRICTION_LINTS, + lint.span(), + "restriction lints are not meant to be all enabled", + None, + "try enabling only the lints you really need", + ); + } + } + } +} + +fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool { + if let ItemKind::Fn(_, _, eid) = item.kind { + is_relevant_expr(cx, cx.tcx.typeck_body(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.typeck_body(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.typeck_body(eid), &cx.tcx.hir().body(eid).value) + }, + _ => false, + } +} + +fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool { + block.stmts.first().map_or( + block + .expr + .as_ref() + .map_or(false, |e| is_relevant_expr(cx, typeck_results, e)), + |stmt| match &stmt.kind { + StmtKind::Local(_) => true, + StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr), + _ => false, + }, + ) +} + +fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool { + match &expr.kind { + ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block), + ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e), + ExprKind::Ret(None) | ExprKind::Break(_, None) => false, + ExprKind::Call(path_expr, _) => { + if let ExprKind::Path(qpath) = &path_expr.kind { + typeck_results + .qpath_res(qpath, path_expr.hir_id) + .opt_def_id() + .map_or(true, |fun_id| !match_panic_def_id(cx, fun_id)) + } else { + true + } + }, + _ => true, + } +} + +fn check_attrs(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) { + if span.from_expansion() { + return; + } + + for attr in attrs { + if let Some(values) = attr.meta_item_list() { + if values.len() != 1 || !attr.has_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.has_name(expected) + } else { + false + } +} + +declare_lint_pass!(EarlyAttributes => [ + DEPRECATED_CFG_ATTR, + MISMATCHED_TARGET_OS, + EMPTY_LINE_AFTER_OUTER_ATTR, +]); + +impl EarlyLintPass for EarlyAttributes { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) { + check_empty_line_after_outer_attr(cx, item); + } + + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { + check_deprecated_cfg_attr(cx, attr); + check_mismatched_target_os(cx, attr); + } +} + +fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) { + for attr in &item.attrs { + let attr_item = if let AttrKind::Normal(ref attr, _) = attr.kind { + attr + } else { + return; + }; + + 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(), item.span.lo(), item.span.ctxt()); + let end_of_attr_to_item = Span::new(attr.span.hi(), item.span.lo(), item.span.ctxt()); + + if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) { + let lines = snippet.split('\n').collect::>(); + let lines = without_block_comments(lines); + + if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 { + span_lint( + cx, + EMPTY_LINE_AFTER_OUTER_ATTR, + begin_of_attr_to_item, + "found an empty line after an outer attribute. \ + Perhaps you forgot to add a `!` to make it an inner attribute?", + ); + } + } + } + } +} + +fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute) { + if_chain! { + // check cfg_attr + if attr.has_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.has_name(sym::rustfmt); + // check for `rustfmt_skip` and `rustfmt::skip` + if let Some(skip_item) = &items[1].meta_item(); + if skip_item.has_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.has_name(sym::cfg); + if let Some(list) = attr.meta_item_list(); + let mismatched = find_mismatched_target_os(&list); + if !mismatched.is_empty(); + then { + let mess = "operating system used in target family position"; + + span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, &mess, |diag| { + // Avoid showing the unix suggestion multiple times in case + // we have more than one mismatch for unix-like systems + let mut unix_suggested = false; + + for (os, span) in mismatched { + let sugg = format!("target_os = \"{}\"", os); + diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect); + + if !unix_suggested && is_unix(os) { + diag.help("did you mean `unix`?"); + unix_suggested = true; + } + } + }); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs b/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs new file mode 100644 index 0000000000..14b6a156c6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs @@ -0,0 +1,148 @@ +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 std::sync and parking_lot + /// are not designed to operate 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:** Will report false positive for explicitly dropped guards ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). + /// + /// **Example:** + /// + /// ```rust,ignore + /// use std::sync::Mutex; + /// + /// async fn foo(x: &Mutex) { + /// let guard = x.lock().unwrap(); + /// *guard += 1; + /// bar.await; + /// } + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// use std::sync::Mutex; + /// + /// async fn foo(x: &Mutex) { + /// { + /// let guard = x.lock().unwrap(); + /// *guard += 1; + /// } + /// bar.await; + /// } + /// ``` + pub AWAIT_HOLDING_LOCK, + pedantic, + "Inside an async function, holding a MutexGuard while calling await" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to await while holding a + /// `RefCell` `Ref` or `RefMut`. + /// + /// **Why is this bad?** `RefCell` refs only check for exclusive mutable access + /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point + /// risks panics from a mutable ref shared while other refs are outstanding. + /// + /// **Known problems:** Will report false positive for explicitly dropped refs ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). + /// + /// **Example:** + /// + /// ```rust,ignore + /// use std::cell::RefCell; + /// + /// async fn foo(x: &RefCell) { + /// let mut y = x.borrow_mut(); + /// *y += 1; + /// bar.await; + /// } + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// use std::cell::RefCell; + /// + /// async fn foo(x: &RefCell) { + /// { + /// let mut y = x.borrow_mut(); + /// *y += 1; + /// } + /// bar.await; + /// } + /// ``` + pub AWAIT_HOLDING_REFCELL_REF, + pedantic, + "Inside an async function, holding a RefCell ref while calling await" +} + +declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]); + +impl LateLintPass<'_> for AwaitHolding { + fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) { + use AsyncGeneratorKind::{Block, Closure, Fn}; + if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind { + let body_id = BodyId { + hir_id: body.value.hir_id, + }; + let typeck_results = cx.tcx.typeck_body(body_id); + check_interior_types( + cx, + &typeck_results.generator_interior_types.as_ref().skip_binder(), + 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", + ); + } + if is_refcell_ref(cx, adt.did) { + span_lint_and_note( + cx, + AWAIT_HOLDING_REFCELL_REF, + ty_cause.span, + "this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await", + ty_cause.scope_span.or(Some(span)), + "these are all the await points this ref 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) +} + +fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool { + match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT) +} diff --git a/src/tools/clippy/clippy_lints/src/bit_mask.rs b/src/tools/clippy/clippy_lints/src/bit_mask.rs new file mode 100644 index 0000000000..a4ee54076e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/bit_mask.rs @@ -0,0 +1,326 @@ +use crate::consts::{constant, Constant}; +use crate::utils::sugg::Sugg; +use crate::utils::{span_lint, span_lint_and_then}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for incompatible bit masks in comparisons. + /// + /// The formula for detecting if an expression of the type `_ m + /// c` (where `` is one of {`&`, `|`} and `` is one of + /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following + /// table: + /// + /// |Comparison |Bit Op|Example |is always|Formula | + /// |------------|------|------------|---------|----------------------| + /// |`==` or `!=`| `&` |`x & 2 == 3`|`false` |`c & m != c` | + /// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` | + /// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` | + /// |`==` or `!=`| `|` |`x | 1 == 0`|`false` |`c | m != c` | + /// |`<` or `>=`| `|` |`x | 1 < 1` |`false` |`m >= c` | + /// |`<=` or `>` | `|` |`x | 1 > 0` |`true` |`m > c` | + /// + /// **Why is this bad?** If the bits that the comparison cares about are always + /// set to zero or one by the bit mask, the comparison is constant `true` or + /// `false` (depending on mask, compared value, and operators). + /// + /// So the code is actively misleading, and the only reason someone would write + /// this intentionally is to win an underhanded Rust contest or create a + /// test-case for this lint. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// if (x & 1 == 2) { } + /// ``` + pub BAD_BIT_MASK, + correctness, + "expressions of the form `_ & mask == select` that will only ever return `true` or `false`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for bit masks in comparisons which can be removed + /// without changing the outcome. The basic structure can be seen in the + /// following table: + /// + /// |Comparison| Bit Op |Example |equals | + /// |----------|---------|-----------|-------| + /// |`>` / `<=`|`|` / `^`|`x | 2 > 3`|`x > 3`| + /// |`<` / `>=`|`|` / `^`|`x ^ 1 < 4`|`x < 4`| + /// + /// **Why is this bad?** Not equally evil as [`bad_bit_mask`](#bad_bit_mask), + /// but still a bit misleading, because the bit mask is ineffective. + /// + /// **Known problems:** False negatives: This lint will only match instances + /// where we have figured out the math (which is for a power-of-two compared + /// value). This means things like `x | 1 >= 7` (which would be better written + /// as `x >= 6`) will not be reported (but bit masks like this are fairly + /// uncommon). + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// if (x | 1 > 3) { } + /// ``` + pub INEFFECTIVE_BIT_MASK, + correctness, + "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for bit masks that can be replaced by a call + /// to `trailing_zeros` + /// + /// **Why is this bad?** `x.trailing_zeros() > 4` is much clearer than `x & 15 + /// == 0` + /// + /// **Known problems:** llvm generates better code for `x & 15 == 0` on x86 + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// if x & 0b1111 == 0 { } + /// ``` + pub VERBOSE_BIT_MASK, + pedantic, + "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<'tcx> LateLintPass<'tcx> for BitMask { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Binary(cmp, left, right) = &e.kind { + if cmp.node.is_comparison() { + if let Some(cmp_opt) = fetch_int_literal(cx, right) { + check_compare(cx, left, cmp.node, cmp_opt, e.span) + } else if let Some(cmp_val) = fetch_int_literal(cx, left) { + check_compare(cx, right, invert_cmp(cmp.node), cmp_val, e.span) + } + } + } + if_chain! { + if let ExprKind::Binary(op, left, right) = &e.kind; + if BinOpKind::Eq == op.node; + if let ExprKind::Binary(op1, left1, right1) = &left.kind; + if BinOpKind::BitAnd == op1.node; + if let ExprKind::Lit(lit) = &right1.kind; + if let LitKind::Int(n, _) = lit.node; + if let ExprKind::Lit(lit1) = &right.kind; + if let LitKind::Int(0, _) = lit1.node; + if n.leading_zeros() == n.count_zeros(); + if n > u128::from(self.verbose_bit_mask_threshold); + then { + span_lint_and_then(cx, + VERBOSE_BIT_MASK, + e.span, + "bit mask could be simplified with a call to `trailing_zeros`", + |diag| { + let sugg = Sugg::hir(cx, left1, "...").maybe_par(); + diag.span_suggestion( + e.span, + "try", + format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()), + Applicability::MaybeIncorrect, + ); + }); + } + } + } +} + +#[must_use] +fn invert_cmp(cmp: BinOpKind) -> BinOpKind { + match cmp { + BinOpKind::Eq => BinOpKind::Eq, + BinOpKind::Ne => BinOpKind::Ne, + BinOpKind::Lt => BinOpKind::Gt, + BinOpKind::Gt => BinOpKind::Lt, + BinOpKind::Le => BinOpKind::Ge, + BinOpKind::Ge => BinOpKind::Le, + _ => BinOpKind::Or, // Dummy + } +} + +fn check_compare(cx: &LateContext<'_>, bit_op: &Expr<'_>, cmp_op: BinOpKind, cmp_value: u128, span: Span) { + if let ExprKind::Binary(op, left, right) = &bit_op.kind { + if op.node != BinOpKind::BitAnd && op.node != BinOpKind::BitOr { + return; + } + fetch_int_literal(cx, right) + .or_else(|| fetch_int_literal(cx, left)) + .map_or((), |mask| check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span)) + } +} + +#[allow(clippy::too_many_lines)] +fn check_bit_mask( + cx: &LateContext<'_>, + bit_op: BinOpKind, + cmp_op: BinOpKind, + mask_value: u128, + cmp_value: u128, + span: Span, +) { + match cmp_op { + BinOpKind::Eq | BinOpKind::Ne => match bit_op { + BinOpKind::BitAnd => { + if mask_value & cmp_value != cmp_value { + if cmp_value != 0 { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ & {}` can never be equal to `{}`", + mask_value, cmp_value + ), + ); + } + } else if mask_value == 0 { + span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero"); + } + }, + BinOpKind::BitOr => { + if mask_value | cmp_value != cmp_value { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ | {}` can never be equal to `{}`", + mask_value, cmp_value + ), + ); + } + }, + _ => (), + }, + BinOpKind::Lt | BinOpKind::Ge => match bit_op { + BinOpKind::BitAnd => { + if mask_value < cmp_value { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ & {}` will always be lower than `{}`", + mask_value, cmp_value + ), + ); + } else if mask_value == 0 { + span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero"); + } + }, + BinOpKind::BitOr => { + if mask_value >= cmp_value { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ | {}` will never be lower than `{}`", + mask_value, cmp_value + ), + ); + } else { + check_ineffective_lt(cx, span, mask_value, cmp_value, "|"); + } + }, + BinOpKind::BitXor => check_ineffective_lt(cx, span, mask_value, cmp_value, "^"), + _ => (), + }, + BinOpKind::Le | BinOpKind::Gt => match bit_op { + BinOpKind::BitAnd => { + if mask_value <= cmp_value { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ & {}` will never be higher than `{}`", + mask_value, cmp_value + ), + ); + } else if mask_value == 0 { + span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero"); + } + }, + BinOpKind::BitOr => { + if mask_value > cmp_value { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ | {}` will always be higher than `{}`", + mask_value, cmp_value + ), + ); + } else { + check_ineffective_gt(cx, span, mask_value, cmp_value, "|"); + } + }, + BinOpKind::BitXor => check_ineffective_gt(cx, span, mask_value, cmp_value, "^"), + _ => (), + }, + _ => (), + } +} + +fn check_ineffective_lt(cx: &LateContext<'_>, span: Span, m: u128, c: u128, op: &str) { + if c.is_power_of_two() && m < c { + span_lint( + cx, + INEFFECTIVE_BIT_MASK, + span, + &format!( + "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly", + op, m, c + ), + ); + } +} + +fn check_ineffective_gt(cx: &LateContext<'_>, span: Span, m: u128, c: u128, op: &str) { + if (c + 1).is_power_of_two() && m <= c { + span_lint( + cx, + INEFFECTIVE_BIT_MASK, + span, + &format!( + "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly", + op, m, c + ), + ); + } +} + +fn fetch_int_literal(cx: &LateContext<'_>, lit: &Expr<'_>) -> Option { + match constant(cx, cx.typeck_results(), lit)?.0 { + Constant::Int(n) => Some(n), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/blacklisted_name.rs b/src/tools/clippy/clippy_lints/src/blacklisted_name.rs new file mode 100644 index 0000000000..153870fb41 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/blacklisted_name.rs @@ -0,0 +1,51 @@ +use crate::utils::span_lint; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::{Pat, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of blacklisted names for variables, such + /// as `foo`. + /// + /// **Why is this bad?** These names are usually placeholder names and should be + /// avoided. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let foo = 3.14; + /// ``` + pub BLACKLISTED_NAME, + style, + "usage of a blacklisted/placeholder name" +} + +#[derive(Clone, Debug)] +pub struct BlacklistedName { + blacklist: FxHashSet, +} + +impl BlacklistedName { + pub fn new(blacklist: FxHashSet) -> Self { + Self { blacklist } + } +} + +impl_lint_pass!(BlacklistedName => [BLACKLISTED_NAME]); + +impl<'tcx> LateLintPass<'tcx> for BlacklistedName { + fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { + if let PatKind::Binding(.., ident, _) = pat.kind { + if self.blacklist.contains(&ident.name.to_string()) { + span_lint( + cx, + BLACKLISTED_NAME, + ident.span, + &format!("use of a blacklisted/placeholder name `{}`", ident.name), + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs b/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs new file mode 100644 index 0000000000..b53f80fd8b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs @@ -0,0 +1,160 @@ +use crate::utils::{ + differing_macro_contexts, get_parent_expr, get_trait_def_id, implements_trait, paths, + snippet_block_with_applicability, span_lint, span_lint_and_sugg, +}; +use if_chain::if_chain; +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 containing an + /// expression, statements or conditions that use closures with blocks. + /// + /// **Why is this bad?** Style, using blocks in the condition makes it hard to read. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + /// // Bad + /// if { true } { /* ... */ } + /// + /// // Good + /// if true { /* ... */ } + /// ``` + /// + /// // or + /// + /// ```rust + /// # fn somefunc() -> bool { true }; + /// // Bad + /// if { let x = somefunc(); x } { /* ... */ } + /// + /// // Good + /// let res = { let x = somefunc(); x }; + /// if res { /* ... */ } + /// ``` + pub BLOCKS_IN_IF_CONDITIONS, + style, + "useless or complex blocks that can be eliminated in conditions" +} + +declare_lint_pass!(BlocksInIfConditions => [BLOCKS_IN_IF_CONDITIONS]); + +struct ExVisitor<'a, 'tcx> { + found_block: Option<&'tcx Expr<'tcx>>, + cx: &'a LateContext<'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 { + // do not lint if the closure is called using an iterator (see #1141) + if_chain! { + if let Some(parent) = get_parent_expr(self.cx, expr); + if let ExprKind::MethodCall(_, _, args, _) = parent.kind; + let caller = self.cx.typeck_results().expr_ty(&args[0]); + if let Some(iter_id) = get_trait_def_id(self.cx, &paths::ITERATOR); + if implements_trait(self.cx, caller, iter_id, &[]); + then { + return; + } + } + + let body = self.cx.tcx.hir().body(eid); + let ex = &body.value; + if matches!(ex.kind, ExprKind::Block(_, _)) && !body.value.span.from_expansion() { + self.found_block = Some(ex); + return; + } + } + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition"; +const COMPLEX_BLOCK_MESSAGE: &str = "in an `if` condition, avoid complex blocks or closures with blocks; \ + instead, move the block or closure higher and bind it with a `let`"; + +impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + if let ExprKind::If(cond, _, _) = &expr.kind { + 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, + BLOCKS_IN_IF_CONDITIONS, + 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, + BLOCKS_IN_IF_CONDITIONS, + 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, BLOCKS_IN_IF_CONDITIONS, block.span, COMPLEX_BLOCK_MESSAGE); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/booleans.rs b/src/tools/clippy/clippy_lints/src/booleans.rs new file mode 100644 index 0000000000..0713303ec4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/booleans.rs @@ -0,0 +1,504 @@ +use crate::utils::{ + eq_expr_value, get_trait_def_id, implements_trait, in_macro, is_type_diagnostic_item, paths, snippet_opt, + span_lint_and_sugg, span_lint_and_then, +}; +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; +use rustc_span::sym; + +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<'tcx> LateLintPass<'tcx> for NonminimalBool { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: FnKind<'tcx>, + _: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + _: HirId, + ) { + NonminimalBoolVisitor { cx }.visit_body(body) + } +} + +struct NonminimalBoolVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, +} + +use quine_mc_cluskey::Bool; +struct Hir2Qmm<'a, 'tcx, 'v> { + terminals: Vec<&'v Expr<'v>>, + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> { + fn extract(&mut self, op: BinOpKind, a: &[&'v Expr<'_>], mut v: Vec) -> Result, String> { + for a in a { + if let ExprKind::Binary(binop, lhs, rhs) = &a.kind { + if binop.node == op { + v = self.extract(op, &[lhs, rhs], v)?; + continue; + } + } + v.push(self.run(a)?); + } + Ok(v) + } + + fn run(&mut self, e: &'v Expr<'_>) -> Result { + fn negate(bin_op_kind: BinOpKind) -> Option { + match bin_op_kind { + BinOpKind::Eq => Some(BinOpKind::Ne), + BinOpKind::Ne => Some(BinOpKind::Eq), + BinOpKind::Gt => Some(BinOpKind::Le), + BinOpKind::Ge => Some(BinOpKind::Lt), + BinOpKind::Lt => Some(BinOpKind::Ge), + BinOpKind::Le => Some(BinOpKind::Gt), + _ => None, + } + } + + // prevent folding of `cfg!` macros and the like + if !e.span.from_expansion() { + match &e.kind { + ExprKind::Unary(UnOp::Not, 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 eq_expr_value(self.cx, 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 eq_expr_value(self.cx, e_lhs, expr_lhs); + if eq_expr_value(self.cx, 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<'tcx>, + output: String, +} + +impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> { + fn recurse(&mut self, suggestion: &Bool) -> Option<()> { + use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True}; + match suggestion { + True => { + self.output.push_str("true"); + }, + False => { + self.output.push_str("false"); + }, + Not(inner) => match **inner { + And(_) | Or(_) => { + self.output.push('!'); + self.output.push('('); + self.recurse(inner); + self.output.push(')'); + }, + Term(n) => { + let terminal = self.terminals[n as usize]; + if let Some(str) = simplify_not(self.cx, terminal) { + self.output.push_str(&str) + } else { + self.output.push('!'); + let snip = snippet_opt(self.cx, terminal.span)?; + self.output.push_str(&snip); + } + }, + True | False | Not(_) => { + self.output.push('!'); + self.recurse(inner)?; + }, + }, + And(v) => { + for (index, inner) in v.iter().enumerate() { + if index > 0 { + self.output.push_str(" && "); + } + if let Or(_) = *inner { + self.output.push('('); + self.recurse(inner); + self.output.push(')'); + } else { + self.recurse(inner); + } + } + }, + Or(v) => { + for (index, inner) in v.iter().rev().enumerate() { + if index > 0 { + self.output.push_str(" || "); + } + self.recurse(inner); + } + }, + &Term(n) => { + let snip = snippet_opt(self.cx, self.terminals[n as usize].span)?; + self.output.push_str(&snip); + }, + } + Some(()) + } +} + +fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { + match &expr.kind { + ExprKind::Binary(binop, lhs, rhs) => { + if !implements_ord(cx, lhs) { + return None; + } + + match binop.node { + BinOpKind::Eq => Some(" != "), + BinOpKind::Ne => Some(" == "), + BinOpKind::Lt => Some(" >= "), + BinOpKind::Gt => Some(" <= "), + BinOpKind::Le => Some(" > "), + BinOpKind::Ge => Some(" < "), + _ => None, + } + .and_then(|op| { + Some(format!( + "{}{}{}", + snippet_opt(cx, lhs.span)?, + op, + snippet_opt(cx, rhs.span)? + )) + }) + }, + ExprKind::MethodCall(path, _, args, _) if args.len() == 1 => { + let type_of_receiver = cx.typeck_results().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::Not, inner) => { + if self.cx.typeck_results().node_types()[inner.hir_id].is_bool() { + self.bool_expr(e); + } else { + walk_expr(self, e); + } + }, + _ => walk_expr(self, e), + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn implements_ord<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool { + let ty = cx.typeck_results().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<'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::Not, inner) = &expr.kind { + if let Some(suggestion) = simplify_not(self.cx, inner) { + span_lint_and_sugg( + self.cx, + NONMINIMAL_BOOL, + expr.span, + "this boolean expression can be simplified", + "try", + suggestion, + Applicability::MachineApplicable, + ); + } + } + + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/bytecount.rs b/src/tools/clippy/clippy_lints/src/bytecount.rs new file mode 100644 index 0000000000..eb5dc7ceec --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/bytecount.rs @@ -0,0 +1,117 @@ +use crate::utils::{ + contains_name, get_pat_name, match_type, paths, single_segment_path, snippet_with_applicability, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, UintTy}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; +use rustc_span::Symbol; + +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, + pedantic, + "use of naive `.filter(|&x| x == y).count()` to count byte values" +} + +declare_lint_pass!(ByteCount => [NAIVE_BYTECOUNT]); + +impl<'tcx> LateLintPass<'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, + cx.typeck_results().expr_ty(&filter_args[0]).peel_refs(), + &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) != *cx.typeck_results().expr_ty(needle).peel_refs().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: Symbol, arg: Symbol, needle: &Expr<'_>) -> bool { + name == arg && !contains_name(name, needle) +} + +fn get_path_name(expr: &Expr<'_>) -> Option { + match expr.kind { + ExprKind::Box(ref e) | ExprKind::AddrOf(BorrowKind::Ref, _, ref e) | ExprKind::Unary(UnOp::Deref, ref e) => { + get_path_name(e) + }, + ExprKind::Block(ref b, _) => { + if b.stmts.is_empty() { + b.expr.as_ref().and_then(|p| get_path_name(p)) + } else { + None + } + }, + ExprKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/cargo_common_metadata.rs b/src/tools/clippy/clippy_lints/src/cargo_common_metadata.rs new file mode 100644 index 0000000000..cc2869ab49 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/cargo_common_metadata.rs @@ -0,0 +1,129 @@ +//! 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_tool_lint, impl_lint_pass}; +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"] + /// ``` + /// + /// Should include an authors field like: + /// + /// ```toml + /// # This `Cargo.toml` includes all common metadata + /// [package] + /// name = "clippy" + /// version = "0.0.212" + /// authors = ["Someone "] + /// 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`" +} + +#[derive(Copy, Clone, Debug)] +pub struct CargoCommonMetadata { + ignore_publish: bool, +} + +impl CargoCommonMetadata { + pub fn new(ignore_publish: bool) -> Self { + Self { ignore_publish } + } +} + +impl_lint_pass!(CargoCommonMetadata => [ + CARGO_COMMON_METADATA +]); + +fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) { + let message = format!("package `{}` is missing `{}` metadata", package.name, field); + span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message); +} + +fn is_empty_str(value: &Option) -> bool { + value.as_ref().map_or(true, String::is_empty) +} + +fn is_empty_path(value: &Option) -> bool { + value.as_ref().and_then(|x| x.to_str()).map_or(true, str::is_empty) +} + +fn is_empty_vec(value: &[String]) -> bool { + // This works because empty iterators return true + value.iter().all(String::is_empty) +} + +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 = unwrap_cargo_metadata!(cx, CARGO_COMMON_METADATA, false); + + for package in metadata.packages { + // only run the lint if publish is `None` (`publish = true` or skipped entirely) + // or if the vector isn't empty (`publish = ["something"]`) + if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || self.ignore_publish { + if is_empty_vec(&package.authors) { + missing_warning(cx, &package, "package.authors"); + } + + if is_empty_str(&package.description) { + missing_warning(cx, &package, "package.description"); + } + + if is_empty_str(&package.license) && is_empty_path(&package.license_file) { + missing_warning(cx, &package, "either package.license or package.license_file"); + } + + if is_empty_str(&package.repository) { + missing_warning(cx, &package, "package.repository"); + } + + if is_empty_path(&package.readme) { + missing_warning(cx, &package, "package.readme"); + } + + if is_empty_vec(&package.keywords) { + missing_warning(cx, &package, "package.keywords"); + } + + if is_empty_vec(&package.categories) { + missing_warning(cx, &package, "package.categories"); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/case_sensitive_file_extension_comparisons.rs b/src/tools/clippy/clippy_lints/src/case_sensitive_file_extension_comparisons.rs new file mode 100644 index 0000000000..b15fe65352 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/case_sensitive_file_extension_comparisons.rs @@ -0,0 +1,85 @@ +use crate::utils::span_lint_and_help; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_hir::{Expr, ExprKind, PathSegment}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{source_map::Spanned, symbol::sym, Span}; + +declare_clippy_lint! { + /// **What it does:** + /// Checks for calls to `ends_with` with possible file extensions + /// and suggests to use a case-insensitive approach instead. + /// + /// **Why is this bad?** + /// `ends_with` is case-sensitive and may not detect files with a valid extension. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// fn is_rust_file(filename: &str) -> bool { + /// filename.ends_with(".rs") + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn is_rust_file(filename: &str) -> bool { + /// filename.rsplit('.').next().map(|ext| ext.eq_ignore_ascii_case("rs")) == Some(true) + /// } + /// ``` + pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + pedantic, + "Checks for calls to ends_with with case-sensitive file extensions" +} + +declare_lint_pass!(CaseSensitiveFileExtensionComparisons => [CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS]); + +fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &Expr<'_>) -> Option { + if_chain! { + if let ExprKind::MethodCall(PathSegment { ident, .. }, _, [obj, extension, ..], span) = expr.kind; + if ident.as_str() == "ends_with"; + if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = extension.kind; + if (2..=6).contains(&ext_literal.as_str().len()); + if ext_literal.as_str().starts_with('.'); + if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_digit(10)) + || ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_digit(10)); + then { + let mut ty = ctx.typeck_results().expr_ty(obj); + ty = match ty.kind() { + ty::Ref(_, ty, ..) => ty, + _ => ty + }; + + match ty.kind() { + ty::Str => { + return Some(span); + }, + ty::Adt(&ty::AdtDef { did, .. }, _) => { + if ctx.tcx.is_diagnostic_item(sym::string_type, did) { + return Some(span); + } + }, + _ => { return None; } + } + } + } + None +} + +impl LateLintPass<'tcx> for CaseSensitiveFileExtensionComparisons { + fn check_expr(&mut self, ctx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let Some(span) = check_case_sensitive_file_extension_comparison(ctx, expr) { + span_lint_and_help( + ctx, + CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + span, + "case-sensitive file extension comparison", + None, + "consider using a case-insensitive comparison instead", + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_lossless.rs b/src/tools/clippy/clippy_lints/src/casts/cast_lossless.rs new file mode 100644 index 0000000000..478832a516 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/cast_lossless.rs @@ -0,0 +1,87 @@ +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, FloatTy, Ty}; + +use crate::utils::{in_constant, is_isize_or_usize, snippet_opt, span_lint_and_sugg}; + +use super::{utils, CAST_LOSSLESS}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + if !should_lint(cx, expr, cast_from, cast_to) { + return; + } + + // The suggestion is to use a function call, so if the original expression + // has parens on the outside, they are no longer needed. + let mut applicability = Applicability::MachineApplicable; + let opt = snippet_opt(cx, cast_op.span); + let sugg = opt.as_ref().map_or_else( + || { + applicability = Applicability::HasPlaceholders; + ".." + }, + |snip| { + if should_strip_parens(cast_op, snip) { + &snip[1..snip.len() - 1] + } else { + snip.as_str() + } + }, + ); + + span_lint_and_sugg( + cx, + CAST_LOSSLESS, + expr.span, + &format!( + "casting `{}` to `{}` may become silently lossy if you later change the type", + cast_from, cast_to + ), + "try", + format!("{}::from({})", cast_to, sugg), + applicability, + ); +} + +fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool { + // Do not suggest using From in consts/statics until it is valid to do so (see #2267). + if in_constant(cx, expr.hir_id) { + return false; + } + + match (cast_from.is_integral(), cast_to.is_integral()) { + (true, true) => { + let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed(); + let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + !is_isize_or_usize(cast_from) + && !is_isize_or_usize(cast_to) + && from_nbits < to_nbits + && !cast_signed_to_unsigned + }, + + (true, false) => { + let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() { + 32 + } else { + 64 + }; + from_nbits < to_nbits + }, + + (_, _) => { + matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64)) + }, + } +} + +fn should_strip_parens(cast_expr: &Expr<'_>, snip: &str) -> bool { + if let ExprKind::Binary(_, _, _) = cast_expr.kind { + if snip.starts_with('(') && snip.ends_with(')') { + return true; + } + } + false +} diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs new file mode 100644 index 0000000000..33b06b8fe7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs @@ -0,0 +1,54 @@ +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, FloatTy, Ty}; + +use crate::utils::{is_isize_or_usize, span_lint}; + +use super::{utils, CAST_POSSIBLE_TRUNCATION}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + let msg = match (cast_from.is_integral(), cast_to.is_integral()) { + (true, true) => { + let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + + let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) { + (true, true) | (false, false) => (to_nbits < from_nbits, ""), + (true, false) => ( + to_nbits <= 32, + if to_nbits == 32 { + " on targets with 64-bit wide pointers" + } else { + "" + }, + ), + (false, true) => (from_nbits == 64, " on targets with 32-bit wide pointers"), + }; + + if !should_lint { + return; + } + + format!( + "casting `{}` to `{}` may truncate the value{}", + cast_from, cast_to, suffix, + ) + }, + + (false, true) => { + format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to) + }, + + (_, _) => { + if matches!(cast_from.kind(), &ty::Float(FloatTy::F64)) + && matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) + { + "casting `f64` to `f32` may truncate the value".to_string() + } else { + return; + } + }, + }; + + span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg); +} diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_possible_wrap.rs b/src/tools/clippy/clippy_lints/src/casts/cast_possible_wrap.rs new file mode 100644 index 0000000000..56d301ed3e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/cast_possible_wrap.rs @@ -0,0 +1,44 @@ +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; + +use crate::utils::{is_isize_or_usize, span_lint}; + +use super::{utils, CAST_POSSIBLE_WRAP}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + if !(cast_from.is_integral() && cast_to.is_integral()) { + return; + } + + let arch_64_suffix = " on targets with 64-bit wide pointers"; + let arch_32_suffix = " on targets with 32-bit wide pointers"; + let cast_unsigned_to_signed = !cast_from.is_signed() && cast_to.is_signed(); + let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + + let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) { + (true, true) | (false, false) => (to_nbits == from_nbits && cast_unsigned_to_signed, ""), + (true, false) => (to_nbits <= 32 && cast_unsigned_to_signed, arch_32_suffix), + (false, true) => ( + cast_unsigned_to_signed, + if from_nbits == 64 { + arch_64_suffix + } else { + arch_32_suffix + }, + ), + }; + + if should_lint { + span_lint( + cx, + CAST_POSSIBLE_WRAP, + expr.span, + &format!( + "casting `{}` to `{}` may wrap around the value{}", + cast_from, cast_to, suffix, + ), + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_precision_loss.rs b/src/tools/clippy/clippy_lints/src/casts/cast_precision_loss.rs new file mode 100644 index 0000000000..a1c3900ce1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/cast_precision_loss.rs @@ -0,0 +1,51 @@ +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, FloatTy, Ty}; + +use crate::utils::{is_isize_or_usize, span_lint}; + +use super::{utils, CAST_PRECISION_LOSS}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + if !cast_from.is_integral() || cast_to.is_integral() { + return; + } + + let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() { + 32 + } else { + 64 + }; + + if !(is_isize_or_usize(cast_from) || from_nbits >= to_nbits) { + return; + } + + let cast_to_f64 = to_nbits == 64; + let mantissa_nbits = if cast_to_f64 { 52 } else { 23 }; + let arch_dependent = is_isize_or_usize(cast_from) && cast_to_f64; + let arch_dependent_str = "on targets with 64-bit wide pointers "; + let from_nbits_str = if arch_dependent { + "64".to_owned() + } else if is_isize_or_usize(cast_from) { + "32 or 64".to_owned() + } else { + utils::int_ty_to_nbits(cast_from, cx.tcx).to_string() + }; + + span_lint( + cx, + CAST_PRECISION_LOSS, + expr.span, + &format!( + "casting `{0}` to `{1}` causes a loss of precision {2}(`{0}` is {3} bits wide, \ + but `{1}`'s mantissa is only {4} bits wide)", + cast_from, + if cast_to_f64 { "f64" } else { "f32" }, + if arch_dependent { arch_dependent_str } else { "" }, + from_nbits_str, + mantissa_nbits + ), + ); +} diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs new file mode 100644 index 0000000000..87fb5557be --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -0,0 +1,81 @@ +use rustc_hir::{Expr, ExprKind, GenericArg}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::symbol::sym; +use rustc_target::abi::LayoutOf; + +use if_chain::if_chain; + +use crate::utils::{is_hir_ty_cfg_dependant, span_lint}; + +use super::CAST_PTR_ALIGNMENT; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Cast(ref cast_expr, cast_to) = expr.kind { + if is_hir_ty_cfg_dependant(cx, cast_to) { + return; + } + let (cast_from, cast_to) = ( + cx.typeck_results().expr_ty(cast_expr), + cx.typeck_results().expr_ty(expr), + ); + lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); + } else if let ExprKind::MethodCall(method_path, _, args, _) = expr.kind { + if_chain! { + if method_path.ident.name == sym!(cast); + if let Some(generic_args) = method_path.args; + if let [GenericArg::Type(cast_to)] = generic_args.args; + // There probably is no obvious reason to do this, just to be consistent with `as` cases. + if !is_hir_ty_cfg_dependant(cx, cast_to); + then { + let (cast_from, cast_to) = + (cx.typeck_results().expr_ty(&args[0]), cx.typeck_results().expr_ty(expr)); + lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); + } + } + } +} + +fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) { + if_chain! { + if let ty::RawPtr(from_ptr_ty) = &cast_from.kind(); + if let ty::RawPtr(to_ptr_ty) = &cast_to.kind(); + if let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty); + if let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty); + if from_layout.align.abi < to_layout.align.abi; + // with c_void, we inherently need to trust the user + if !is_c_void(cx, from_ptr_ty.ty); + // when casting from a ZST, we don't know enough to properly lint + if !from_layout.is_zst(); + then { + span_lint( + cx, + CAST_PTR_ALIGNMENT, + expr.span, + &format!( + "casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)", + cast_from, + cast_to, + from_layout.align.abi.bytes(), + to_layout.align.abi.bytes(), + ), + ); + } + } +} + +/// Check if the given type is either `core::ffi::c_void` or +/// one of the platform specific `libc::::c_void` of libc. +fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + if let ty::Adt(adt, _) = ty.kind() { + let names = cx.get_def_path(adt.did); + + if names.is_empty() { + return false; + } + if names[0] == sym::libc || names[0] == sym::core && *names.last().unwrap() == sym!(c_void) { + return true; + } + } + false +} diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_ref_to_mut.rs b/src/tools/clippy/clippy_lints/src/casts/cast_ref_to_mut.rs new file mode 100644 index 0000000000..3fdc1c6168 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/cast_ref_to_mut.rs @@ -0,0 +1,28 @@ +use rustc_hir::{Expr, ExprKind, MutTy, Mutability, TyKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use if_chain::if_chain; + +use crate::utils::span_lint; + +use super::CAST_REF_TO_MUT; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Unary(UnOp::Deref, e) = &expr.kind; + if let ExprKind::Cast(e, t) = &e.kind; + if let TyKind::Ptr(MutTy { mutbl: Mutability::Mut, .. }) = t.kind; + if let ExprKind::Cast(e, t) = &e.kind; + if let TyKind::Ptr(MutTy { mutbl: Mutability::Not, .. }) = t.kind; + if let ty::Ref(..) = cx.typeck_results().node_type(e.hir_id).kind(); + then { + span_lint( + cx, + CAST_REF_TO_MUT, + expr.span, + "casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell`", + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_sign_loss.rs b/src/tools/clippy/clippy_lints/src/casts/cast_sign_loss.rs new file mode 100644 index 0000000000..9656fbebd7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/cast_sign_loss.rs @@ -0,0 +1,70 @@ +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +use if_chain::if_chain; + +use crate::consts::{constant, Constant}; +use crate::utils::{method_chain_args, sext, span_lint}; + +use super::CAST_SIGN_LOSS; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + if should_lint(cx, cast_op, cast_from, cast_to) { + span_lint( + cx, + CAST_SIGN_LOSS, + expr.span, + &format!( + "casting `{}` to `{}` may lose the sign of the value", + cast_from, cast_to + ), + ); + } +} + +fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool { + match (cast_from.is_integral(), cast_to.is_integral()) { + (true, true) => { + if !cast_from.is_signed() || cast_to.is_signed() { + return false; + } + + // Don't lint for positive constants. + let const_val = constant(cx, &cx.typeck_results(), cast_op); + if_chain! { + if let Some((Constant::Int(n), _)) = const_val; + if let ty::Int(ity) = *cast_from.kind(); + if sext(cx.tcx, n, ity) >= 0; + then { + return false; + } + } + + // Don't lint for the result of methods that always return non-negative values. + if let ExprKind::MethodCall(ref path, _, _, _) = cast_op.kind { + let mut method_name = path.ident.name.as_str(); + let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"]; + + if_chain! { + if method_name == "unwrap"; + if let Some(arglist) = method_chain_args(cast_op, &["unwrap"]); + if let ExprKind::MethodCall(ref inner_path, _, _, _) = &arglist[0][0].kind; + then { + method_name = inner_path.ident.name.as_str(); + } + } + + if allowed_methods.iter().any(|&name| method_name == name) { + return false; + } + } + + true + }, + + (false, true) => !cast_to.is_signed(), + + (_, _) => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/casts/char_lit_as_u8.rs b/src/tools/clippy/clippy_lints/src/casts/char_lit_as_u8.rs new file mode 100644 index 0000000000..ccaad1b8f2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/char_lit_as_u8.rs @@ -0,0 +1,42 @@ +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, UintTy}; + +use if_chain::if_chain; + +use crate::utils::{snippet_with_applicability, span_lint_and_then}; + +use super::CHAR_LIT_AS_U8; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Cast(e, _) = &expr.kind; + if let ExprKind::Lit(l) = &e.kind; + if let LitKind::Char(c) = l.node; + if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind(); + then { + let mut applicability = Applicability::MachineApplicable; + let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability); + + span_lint_and_then( + cx, + CHAR_LIT_AS_U8, + expr.span, + "casting a character literal to `u8` truncates", + |diag| { + diag.note("`char` is four bytes wide, but `u8` is a single byte"); + + if c.is_ascii() { + diag.span_suggestion( + expr.span, + "use a byte literal instead", + format!("b{}", snippet), + applicability, + ); + } + }); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast.rs b/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast.rs new file mode 100644 index 0000000000..a8d508585b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast.rs @@ -0,0 +1,37 @@ +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty, UintTy}; + +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; + +use super::{utils, FN_TO_NUMERIC_CAST}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + // We only want to check casts to `ty::Uint` or `ty::Int` + match cast_to.kind() { + ty::Uint(_) | ty::Int(..) => { /* continue on */ }, + _ => return, + } + + match cast_from.kind() { + ty::FnDef(..) | ty::FnPtr(_) => { + let mut applicability = Applicability::MaybeIncorrect; + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + + if (to_nbits >= cx.tcx.data_layout.pointer_size.bits()) && (*cast_to.kind() != ty::Uint(UintTy::Usize)) { + span_lint_and_sugg( + cx, + FN_TO_NUMERIC_CAST, + expr.span, + &format!("casting function pointer `{}` to `{}`", from_snippet, cast_to), + "try", + format!("{} as usize", from_snippet), + applicability, + ); + } + }, + _ => {}, + } +} diff --git a/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs b/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs new file mode 100644 index 0000000000..0085c7b27b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs @@ -0,0 +1,39 @@ +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; + +use super::{utils, FN_TO_NUMERIC_CAST_WITH_TRUNCATION}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + // We only want to check casts to `ty::Uint` or `ty::Int` + match cast_to.kind() { + ty::Uint(_) | ty::Int(..) => { /* continue on */ }, + _ => return, + } + match cast_from.kind() { + ty::FnDef(..) | ty::FnPtr(_) => { + let mut applicability = Applicability::MaybeIncorrect; + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); + + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + if to_nbits < cx.tcx.data_layout.pointer_size.bits() { + span_lint_and_sugg( + cx, + FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + expr.span, + &format!( + "casting function pointer `{}` to `{}`, which truncates the value", + from_snippet, cast_to + ), + "try", + format!("{} as usize", from_snippet), + applicability, + ); + } + }, + _ => {}, + } +} diff --git a/src/tools/clippy/clippy_lints/src/casts/mod.rs b/src/tools/clippy/clippy_lints/src/casts/mod.rs new file mode 100644 index 0000000000..b726bd75f1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/mod.rs @@ -0,0 +1,407 @@ +mod cast_lossless; +mod cast_possible_truncation; +mod cast_possible_wrap; +mod cast_precision_loss; +mod cast_ptr_alignment; +mod cast_ref_to_mut; +mod cast_sign_loss; +mod char_lit_as_u8; +mod fn_to_numeric_cast; +mod fn_to_numeric_cast_with_truncation; +mod ptr_as_ptr; +mod unnecessary_cast; +mod utils; + +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +use crate::utils::is_hir_ty_cfg_dependant; + +declare_clippy_lint! { + /// **What it does:** Checks for casts from any numerical to a float type where + /// the receiving type cannot store all values from the original type without + /// rounding errors. This possible rounding is to be expected, so this lint is + /// `Allow` by default. + /// + /// Basically, this warns on casting any integer with 32 or more bits to `f32` + /// or any 64-bit integer to `f64`. + /// + /// **Why is this bad?** It's not bad at all. But in some applications it can be + /// helpful to know where precision loss can take place. This lint can help find + /// those places in the code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = u64::MAX; + /// x as f64; + /// ``` + pub CAST_PRECISION_LOSS, + pedantic, + "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts from a signed to an unsigned numerical + /// type. In this case, negative values wrap around to large positive values, + /// which can be quite surprising in practice. However, as the cast works as + /// defined, this lint is `Allow` by default. + /// + /// **Why is this bad?** Possibly surprising results. You can activate this lint + /// as a one-time check to see where numerical wrapping can arise. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let y: i8 = -1; + /// y as u128; // will return 18446744073709551615 + /// ``` + pub CAST_SIGN_LOSS, + pedantic, + "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts between numerical types that may + /// truncate large values. This is expected behavior, so the cast is `Allow` by + /// default. + /// + /// **Why is this bad?** In some problem domains, it is good practice to avoid + /// truncation. This lint can be activated to help assess where additional + /// checks could be beneficial. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn as_u8(x: u64) -> u8 { + /// x as u8 + /// } + /// ``` + pub CAST_POSSIBLE_TRUNCATION, + pedantic, + "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts from an unsigned type to a signed type of + /// the same size. Performing such a cast is a 'no-op' for the compiler, + /// i.e., nothing is changed at the bit level, and the binary representation of + /// the value is reinterpreted. This can cause wrapping if the value is too big + /// for the target signed type. However, the cast works as defined, so this lint + /// is `Allow` by default. + /// + /// **Why is this bad?** While such a cast is not bad in itself, the results can + /// be surprising when this is not the intended behavior, as demonstrated by the + /// example below. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// u32::MAX as i32; // will yield a value of `-1` + /// ``` + pub CAST_POSSIBLE_WRAP, + pedantic, + "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts between numerical types that may + /// be replaced by safe conversion functions. + /// + /// **Why is this bad?** Rust's `as` keyword will perform many kinds of + /// conversions, including silently lossy conversions. Conversion functions such + /// as `i32::from` will only perform lossless conversions. Using the conversion + /// functions prevents conversions from turning into silent lossy conversions if + /// the types of the input expressions ever change, and make it easier for + /// people reading the code to know that the conversion is lossless. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn as_u64(x: u8) -> u64 { + /// x as u64 + /// } + /// ``` + /// + /// Using `::from` would look like this: + /// + /// ```rust + /// fn as_u64(x: u8) -> u64 { + /// u64::from(x) + /// } + /// ``` + pub CAST_LOSSLESS, + pedantic, + "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts to the same type, casts of int literals to integer types + /// and casts of float literals to float types. + /// + /// **Why is this bad?** It's just unnecessary. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let _ = 2i32 as i32; + /// let _ = 0.5 as f32; + /// ``` + /// + /// Better: + /// + /// ```rust + /// let _ = 2_i32; + /// let _ = 0.5_f32; + /// ``` + pub UNNECESSARY_CAST, + complexity, + "cast to the same type, e.g., `x as i32` where `x: i32`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts, using `as` or `pointer::cast`, + /// from a less-strictly-aligned pointer to a more-strictly-aligned pointer + /// + /// **Why is this bad?** Dereferencing the resulting pointer may be undefined + /// behavior. + /// + /// **Known problems:** Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar + /// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like + /// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis. + /// + /// **Example:** + /// ```rust + /// let _ = (&1u8 as *const u8) as *const u16; + /// let _ = (&mut 1u8 as *mut u8) as *mut u16; + /// + /// (&1u8 as *const u8).cast::(); + /// (&mut 1u8 as *mut u8).cast::(); + /// ``` + pub CAST_PTR_ALIGNMENT, + pedantic, + "cast from a pointer to a more-strictly-aligned pointer" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts of function pointers to something other than usize + /// + /// **Why is this bad?** + /// Casting a function pointer to anything other than usize/isize is not portable across + /// architectures, because you end up losing bits if the target type is too small or end up with a + /// bunch of extra bits that waste space and add more instructions to the final binary than + /// strictly necessary for the problem + /// + /// Casting to isize also doesn't make sense since there are no signed addresses. + /// + /// **Example** + /// + /// ```rust + /// // Bad + /// fn fun() -> i32 { 1 } + /// let a = fun as i64; + /// + /// // Good + /// fn fun2() -> i32 { 1 } + /// let a = fun2 as usize; + /// ``` + pub FN_TO_NUMERIC_CAST, + style, + "casting a function pointer to a numeric type other than usize" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts of a function pointer to a numeric type not wide enough to + /// store address. + /// + /// **Why is this bad?** + /// Such a cast discards some bits of the function's address. If this is intended, it would be more + /// clearly expressed by casting to usize first, then casting the usize to the intended type (with + /// a comment) to perform the truncation. + /// + /// **Example** + /// + /// ```rust + /// // Bad + /// fn fn1() -> i16 { + /// 1 + /// }; + /// let _ = fn1 as i32; + /// + /// // Better: Cast to usize first, then comment with the reason for the truncation + /// fn fn2() -> i16 { + /// 1 + /// }; + /// let fn_ptr = fn2 as usize; + /// let fn_ptr_truncated = fn_ptr as i32; + /// ``` + pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + style, + "casting a function pointer to a numeric type not wide enough to store the address" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts of `&T` to `&mut T` anywhere in the code. + /// + /// **Why is this bad?** It’s basically guaranteed to be undefined behaviour. + /// `UnsafeCell` is the only way to obtain aliasable data that is considered + /// mutable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// fn x(r: &i32) { + /// unsafe { + /// *(r as *const _ as *mut _) += 1; + /// } + /// } + /// ``` + /// + /// Instead consider using interior mutability types. + /// + /// ```rust + /// use std::cell::UnsafeCell; + /// + /// fn x(r: &UnsafeCell) { + /// unsafe { + /// *r.get() += 1; + /// } + /// } + /// ``` + pub CAST_REF_TO_MUT, + correctness, + "a cast of reference to a mutable pointer" +} + +declare_clippy_lint! { + /// **What it does:** Checks for expressions where a character literal is cast + /// to `u8` and suggests using a byte literal instead. + /// + /// **Why is this bad?** In general, casting values to smaller types is + /// error-prone and should be avoided where possible. In the particular case of + /// converting a character literal to u8, it is easy to avoid by just using a + /// byte literal instead. As an added bonus, `b'a'` is even slightly shorter + /// than `'a' as u8`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// 'x' as u8 + /// ``` + /// + /// A better version, using the byte literal: + /// + /// ```rust,ignore + /// b'x' + /// ``` + pub CHAR_LIT_AS_U8, + complexity, + "casting a character literal to `u8` truncates" +} + +declare_clippy_lint! { + /// **What it does:** + /// Checks for `as` casts between raw pointers without changing its mutability, + /// namely `*const T` to `*const U` and `*mut T` to `*mut U`. + /// + /// **Why is this bad?** + /// Though `as` casts between raw pointers is not terrible, `pointer::cast` is safer because + /// it cannot accidentally change the pointer's mutability nor cast the pointer to other types like `usize`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let ptr: *const u32 = &42_u32; + /// let mut_ptr: *mut u32 = &mut 42_u32; + /// let _ = ptr as *const i32; + /// let _ = mut_ptr as *mut i32; + /// ``` + /// Use instead: + /// ```rust + /// let ptr: *const u32 = &42_u32; + /// let mut_ptr: *mut u32 = &mut 42_u32; + /// let _ = ptr.cast::(); + /// let _ = mut_ptr.cast::(); + /// ``` + pub PTR_AS_PTR, + pedantic, + "casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`" +} + +pub struct Casts { + msrv: Option, +} + +impl Casts { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(Casts => [ + CAST_PRECISION_LOSS, + CAST_SIGN_LOSS, + CAST_POSSIBLE_TRUNCATION, + CAST_POSSIBLE_WRAP, + CAST_LOSSLESS, + CAST_REF_TO_MUT, + CAST_PTR_ALIGNMENT, + UNNECESSARY_CAST, + FN_TO_NUMERIC_CAST, + FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + CHAR_LIT_AS_U8, + PTR_AS_PTR, +]); + +impl<'tcx> LateLintPass<'tcx> for Casts { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + if let ExprKind::Cast(ref cast_expr, cast_to) = expr.kind { + if is_hir_ty_cfg_dependant(cx, cast_to) { + return; + } + let (cast_from, cast_to) = ( + cx.typeck_results().expr_ty(cast_expr), + cx.typeck_results().expr_ty(expr), + ); + + if unnecessary_cast::check(cx, expr, cast_expr, cast_from, cast_to) { + return; + } + + fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to); + fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to); + if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) { + cast_possible_truncation::check(cx, expr, cast_from, cast_to); + cast_possible_wrap::check(cx, expr, cast_from, cast_to); + cast_precision_loss::check(cx, expr, cast_from, cast_to); + cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to); + cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to); + } + } + + cast_ref_to_mut::check(cx, expr); + cast_ptr_alignment::check(cx, expr); + char_lit_as_u8::check(cx, expr); + ptr_as_ptr::check(cx, expr, &self.msrv); + } + + extract_msrv_attr!(LateContext); +} diff --git a/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs b/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs new file mode 100644 index 0000000000..abfbadf364 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs @@ -0,0 +1,52 @@ +use std::borrow::Cow; + +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Mutability, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, TypeAndMut}; +use rustc_semver::RustcVersion; + +use if_chain::if_chain; + +use crate::utils::sugg::Sugg; +use crate::utils::{meets_msrv, span_lint_and_sugg}; + +use super::PTR_AS_PTR; + +const PTR_AS_PTR_MSRV: RustcVersion = RustcVersion::new(1, 38, 0); + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: &Option) { + if !meets_msrv(msrv.as_ref(), &PTR_AS_PTR_MSRV) { + return; + } + + if_chain! { + if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind; + let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr)); + if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, .. }) = cast_from.kind(); + if let ty::RawPtr(TypeAndMut { ty: to_pointee_ty, mutbl: to_mutbl }) = cast_to.kind(); + if matches!((from_mutbl, to_mutbl), + (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut)); + // The `U` in `pointer::cast` have to be `Sized` + // as explained here: https://github.com/rust-lang/rust/issues/60602. + if to_pointee_ty.is_sized(cx.tcx.at(expr.span), cx.param_env); + then { + let mut applicability = Applicability::MachineApplicable; + let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut applicability); + let turbofish = match &cast_to_hir_ty.kind { + TyKind::Infer => Cow::Borrowed(""), + TyKind::Ptr(mut_ty) if matches!(mut_ty.ty.kind, TyKind::Infer) => Cow::Borrowed(""), + _ => Cow::Owned(format!("::<{}>", to_pointee_ty)), + }; + span_lint_and_sugg( + cx, + PTR_AS_PTR, + expr.span, + "`as` casting between raw pointers without changing its mutability", + "try `pointer::cast`, a safer alternative", + format!("{}.cast{}()", cast_expr_sugg.maybe_par(), turbofish), + applicability, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs b/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs new file mode 100644 index 0000000000..fa2a07ef1d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs @@ -0,0 +1,106 @@ +use rustc_ast::{LitFloatType, LitIntType, LitKind}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Lit, UnOp}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, FloatTy, InferTy, Ty}; + +use if_chain::if_chain; + +use crate::utils::{numeric_literal::NumericLiteral, snippet_opt, span_lint, span_lint_and_sugg}; + +use super::UNNECESSARY_CAST; + +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + cast_expr: &Expr<'_>, + cast_from: Ty<'_>, + cast_to: Ty<'_>, +) -> bool { + if let Some(lit) = get_numeric_literal(cast_expr) { + let literal_str = snippet_opt(cx, cast_expr.span).unwrap_or_default(); + + if_chain! { + if let LitKind::Int(n, _) = lit.node; + if let Some(src) = snippet_opt(cx, lit.span); + if cast_to.is_floating_point(); + if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node); + let from_nbits = 128 - n.leading_zeros(); + let to_nbits = fp_ty_mantissa_nbits(cast_to); + if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal(); + then { + let literal_str = if is_unary_neg(cast_expr) { format!("-{}", num_lit.integer) } else { num_lit.integer.into() }; + lint_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); + return true + } + } + + match lit.node { + LitKind::Int(_, LitIntType::Unsuffixed) if cast_to.is_integral() => { + lint_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); + }, + LitKind::Float(_, LitFloatType::Unsuffixed) if cast_to.is_floating_point() => { + lint_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); + }, + LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) => {}, + _ => { + if cast_from.kind() == cast_to.kind() && !in_external_macro(cx.sess(), expr.span) { + span_lint( + cx, + UNNECESSARY_CAST, + expr.span, + &format!( + "casting to the same type is unnecessary (`{}` -> `{}`)", + cast_from, cast_to + ), + ); + return true; + } + }, + } + } + + false +} + +fn lint_unnecessary_cast(cx: &LateContext<'_>, expr: &Expr<'_>, literal_str: &str, cast_from: Ty<'_>, cast_to: Ty<'_>) { + let literal_kind_name = if cast_from.is_integral() { "integer" } else { "float" }; + span_lint_and_sugg( + cx, + UNNECESSARY_CAST, + expr.span, + &format!("casting {} literal to `{}` is unnecessary", literal_kind_name, cast_to), + "try", + format!("{}_{}", literal_str.trim_end_matches('.'), cast_to), + Applicability::MachineApplicable, + ); +} + +fn get_numeric_literal<'e>(expr: &'e Expr<'e>) -> Option<&'e Lit> { + match expr.kind { + ExprKind::Lit(ref lit) => Some(lit), + ExprKind::Unary(UnOp::Neg, e) => { + if let ExprKind::Lit(ref lit) = e.kind { + Some(lit) + } else { + None + } + }, + _ => None, + } +} + +/// Returns the mantissa bits wide of a fp type. +/// Will return 0 if the type is not a fp +fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 { + match typ.kind() { + ty::Float(FloatTy::F32) => 23, + ty::Float(FloatTy::F64) | ty::Infer(InferTy::FloatVar(_)) => 52, + _ => 0, + } +} + +fn is_unary_neg(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Unary(UnOp::Neg, _)) +} diff --git a/src/tools/clippy/clippy_lints/src/casts/utils.rs b/src/tools/clippy/clippy_lints/src/casts/utils.rs new file mode 100644 index 0000000000..00fd0b3473 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/casts/utils.rs @@ -0,0 +1,25 @@ +use rustc_middle::ty::{self, IntTy, Ty, TyCtxt, UintTy}; + +/// Returns the size in bits of an integral type. +/// Will return 0 if the type is not an int or uint variant +pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 { + match typ.kind() { + ty::Int(i) => match i { + IntTy::Isize => tcx.data_layout.pointer_size.bits(), + IntTy::I8 => 8, + IntTy::I16 => 16, + IntTy::I32 => 32, + IntTy::I64 => 64, + IntTy::I128 => 128, + }, + ty::Uint(i) => match i { + UintTy::Usize => tcx.data_layout.pointer_size.bits(), + UintTy::U8 => 8, + UintTy::U16 => 16, + UintTy::U32 => 32, + UintTy::U64 => 64, + UintTy::U128 => 128, + }, + _ => 0, + } +} diff --git a/src/tools/clippy/clippy_lints/src/checked_conversions.rs b/src/tools/clippy/clippy_lints/src/checked_conversions.rs new file mode 100644 index 0000000000..54bc69e058 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/checked_conversions.rs @@ -0,0 +1,360 @@ +//! 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_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +use crate::utils::{meets_msrv, snippet_with_applicability, span_lint_and_sugg, SpanlessEq}; + +const CHECKED_CONVERSIONS_MSRV: RustcVersion = RustcVersion::new(1, 34, 0); + +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" +} + +pub struct CheckedConversions { + msrv: Option, +} + +impl CheckedConversions { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); + +impl<'tcx> LateLintPass<'tcx> for CheckedConversions { + fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) { + if !meets_msrv(self.msrv.as_ref(), &CHECKED_CONVERSIONS_MSRV) { + return; + } + + 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 let Some(cv) = result { + if let Some(to_type) = cv.to_type { + 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, + ); + } + } + } + + extract_msrv_attr!(LateContext); +} + +/// Searches for a single check from unsigned to _ is done +/// todo: check for case signed -> larger unsigned == only x >= 0 +fn single_check<'tcx>(expr: &'tcx Expr<'tcx>) -> Option> { + check_upper_bound(expr).filter(|cv| cv.cvt == ConversionType::FromUnsigned) +} + +/// Searches for a combination of upper & lower bound checks +fn double_check<'a>(cx: &LateContext<'_>, left: &'a Expr<'_>, right: &'a Expr<'_>) -> Option> { + let upper_lower = |l, r| { + let upper = check_upper_bound(l); + let lower = check_lower_bound(r); + + upper.zip(lower).and_then(|(l, r)| l.combine(r, cx)) + }; + + upper_lower(left, right).or_else(|| upper_lower(right, left)) +} + +/// Contains the result of a tried conversion check +#[derive(Clone, Debug)] +struct Conversion<'a> { + cvt: ConversionType, + expr_to_cast: &'a Expr<'a>, + to_type: Option<&'a str>, +} + +/// The kind of conversion that is checked +#[derive(Copy, Clone, Debug, PartialEq)] +enum ConversionType { + SignedToUnsigned, + SignedToSigned, + FromUnsigned, +} + +impl<'a> Conversion<'a> { + /// Combine multiple conversions if the are compatible + pub fn combine(self, other: Self, cx: &LateContext<'_>) -> Option> { + if self.is_compatible(&other, cx) { + // Prefer a Conversion that contains a type-constraint + Some(if self.to_type.is_some() { self } else { other }) + } else { + None + } + } + + /// Checks if two conversions are compatible + /// same type of conversion, same 'castee' and same 'to type' + pub fn is_compatible(&self, other: &Self, cx: &LateContext<'_>) -> bool { + (self.cvt == other.cvt) + && (SpanlessEq::new(cx).eq_expr(self.expr_to_cast, other.expr_to_cast)) + && (self.has_compatible_to_type(other)) + } + + /// Checks if the to-type is the same (if there is a type constraint) + fn has_compatible_to_type(&self, other: &Self) -> bool { + match (self.to_type, other.to_type) { + (Some(l), Some(r)) => l == r, + _ => true, + } + } + + /// Try to construct a new conversion if the conversion type is valid + fn try_new(expr_to_cast: &'a Expr<'_>, from_type: &str, to_type: &'a str) -> Option> { + ConversionType::try_new(from_type, to_type).map(|cvt| Conversion { + cvt, + expr_to_cast, + to_type: Some(to_type), + }) + } + + /// Construct a new conversion without type constraint + fn new_any(expr_to_cast: &'a Expr<'_>) -> Conversion<'a> { + Conversion { + cvt: ConversionType::SignedToUnsigned, + expr_to_cast, + to_type: None, + } + } +} + +impl ConversionType { + /// Creates a conversion type if the type is allowed & conversion is valid + #[must_use] + fn try_new(from: &str, to: &str) -> Option { + if UINTS.contains(&from) { + Some(Self::FromUnsigned) + } else if SINTS.contains(&from) { + if UINTS.contains(&to) { + Some(Self::SignedToUnsigned) + } else if SINTS.contains(&to) { + Some(Self::SignedToSigned) + } else { + None + } + } else { + None + } + } +} + +/// Check for `expr <= (to_type::MAX as from_type)` +fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option> { + if_chain! { + if let ExprKind::Binary(ref op, ref left, ref right) = &expr.kind; + if let Some((candidate, check)) = normalize_le_ge(op, left, right); + if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX"); + + then { + Conversion::try_new(candidate, from, to) + } else { + None + } + } +} + +/// Check for `expr >= 0|(to_type::MIN as from_type)` +fn check_lower_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option> { + fn check_function<'a>(candidate: &'a Expr<'a>, check: &'a Expr<'a>) -> Option> { + (check_lower_bound_zero(candidate, check)).or_else(|| (check_lower_bound_min(candidate, check))) + } + + // First of we need a binary containing the expression & the cast + if let ExprKind::Binary(ref op, ref left, ref right) = &expr.kind { + normalize_le_ge(op, right, left).and_then(|(l, r)| check_function(l, r)) + } else { + None + } +} + +/// Check for `expr >= 0` +fn check_lower_bound_zero<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option> { + if_chain! { + if let ExprKind::Lit(ref lit) = &check.kind; + if let LitKind::Int(0, _) = &lit.node; + + then { + Some(Conversion::new_any(candidate)) + } else { + None + } + } +} + +/// Check for `expr >= (to_type::MIN as from_type)` +fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option> { + if let Some((from, to)) = get_types_from_cast(check, SINTS, "min_value", "MIN") { + 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<'_>, + types: &'a [&str], + func: &'a str, + assoc_const: &'a str, +) -> Option<(&'a str, &'a str)> { + // `to_type::max_value() as from_type` + // or `to_type::MAX as from_type` + let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! { + // to_type::max_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::max_value())` + let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| { + if_chain! { + // `from_type::from, to_type::max_value()` + if let ExprKind::Call(ref from_func, ref args) = &expr.kind; + // `to_type::max_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 { + match limit.kind { + // `from_type::from(_)` + ExprKind::Call(path, _) => { + if let ExprKind::Path(ref path) = path.kind { + // `to_type` + if let Some(to_type) = get_implementing_type(path, types, func) { + return Some((from_type, to_type)); + } + } + }, + // `to_type::MAX` + ExprKind::Path(ref path) => { + if let Some(to_type) = get_implementing_type(path, types, assoc_const) { + return Some((from_type, to_type)); + } + }, + _ => {}, + } + }; + 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 + } + } +} + +/// 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 UINTS: &[&str] = &["u8", "u16", "u32", "u64", "usize"]; +const SINTS: &[&str] = &["i8", "i16", "i32", "i64", "isize"]; +const INTS: &[&str] = &["u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize"]; diff --git a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs new file mode 100644 index 0000000000..658d445dfe --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs @@ -0,0 +1,166 @@ +//! 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::{sym, 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<'tcx>( + &mut self, + cx: &LateContext<'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.typeck_results().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<'tcx> LateLintPass<'tcx> for CognitiveComplexity { + fn check_fn( + &mut self, + cx: &LateContext<'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<'tcx>, attrs: &'tcx [Attribute]) { + self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity"); + } + fn exit_lint_attrs(&mut self, cx: &LateContext<'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::If(_, _, _) => { + self.cc += 1; + }, + ExprKind::Match(_, ref arms, _) => { + if arms.len() > 1 { + self.cc += 1; + } + self.cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64; + }, + ExprKind::Ret(_) => self.returns += 1, + _ => {}, + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/collapsible_if.rs b/src/tools/clippy/clippy_lints/src/collapsible_if.rs new file mode 100644 index 0000000000..34f0e6ab02 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/collapsible_if.rs @@ -0,0 +1,183 @@ +//! 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. + /// + /// **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 { + /// … + /// } + /// } + /// + /// ``` + /// + /// Should be written: + /// + /// ```rust.ignore + /// if x && y { + /// … + /// } + /// ``` + pub COLLAPSIBLE_IF, + style, + "nested `if`s that can be collapsed (e.g., `if x { if y { ... } }`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for collapsible `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 { + /// … + /// } else { + /// if y { + /// … + /// } + /// } + /// ``` + /// + /// Should be written: + /// + /// ```rust.ignore + /// if x { + /// … + /// } else if y { + /// … + /// } + /// ``` + pub COLLAPSIBLE_ELSE_IF, + style, + "nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)" +} + +declare_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_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_.attrs.is_empty(); + if !else_.span.from_expansion(); + if let ast::ExprKind::If(..) = else_.kind; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + COLLAPSIBLE_ELSE_IF, + block.span, + "this `else { if .. }` block can be collapsed", + "collapse nested if block", + 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 inner.attrs.is_empty(); + if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind; + // Prevent triggering on `if c { if let a = b { .. } }`. + if !matches!(check_inner.kind, ast::ExprKind::Let(..)); + if expr.span.ctxt() == inner.span.ctxt(); + then { + 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, + "collapse nested if block", + format!( + "if {} {}", + lhs.and(&rhs), + snippet_block(cx, content.span, "..", Some(expr.span)), + ), + Applicability::MachineApplicable, // snippet + ); + }); + } + } +} + +/// If the block contains only one expression, return it. +fn expr_block(block: &ast::Block) -> Option<&ast::Expr> { + let mut it = block.stmts.iter(); + + if let (Some(stmt), None) = (it.next(), it.next()) { + match stmt.kind { + ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => Some(expr), + _ => None, + } + } else { + None + } +} diff --git a/src/tools/clippy/clippy_lints/src/collapsible_match.rs b/src/tools/clippy/clippy_lints/src/collapsible_match.rs new file mode 100644 index 0000000000..3c45525684 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/collapsible_match.rs @@ -0,0 +1,188 @@ +use crate::utils::visitors::LocalUsedVisitor; +use crate::utils::{path_to_local, span_lint_and_then, SpanlessEq}; +use if_chain::if_chain; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; +use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, QPath, StmtKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{DefIdTree, TyCtxt, TypeckResults}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{MultiSpan, Span}; + +declare_clippy_lint! { + /// **What it does:** Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together + /// without adding any branches. + /// + /// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only + /// cases where merging would most likely make the code more readable. + /// + /// **Why is this bad?** It is unnecessarily verbose and complex. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// fn func(opt: Option>) { + /// let n = match opt { + /// Some(n) => match n { + /// Ok(n) => n, + /// _ => return, + /// } + /// None => return, + /// }; + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn func(opt: Option>) { + /// let n = match opt { + /// Some(Ok(n)) => n, + /// _ => return, + /// }; + /// } + /// ``` + pub COLLAPSIBLE_MATCH, + style, + "Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together." +} + +declare_lint_pass!(CollapsibleMatch => [COLLAPSIBLE_MATCH]); + +impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if let ExprKind::Match(_expr, arms, _source) = expr.kind { + if let Some(wild_arm) = arms.iter().rfind(|arm| arm_is_wild_like(arm, cx.tcx)) { + for arm in arms { + check_arm(arm, wild_arm, cx); + } + } + } + } +} + +fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext<'tcx>) { + if_chain! { + let expr = strip_singleton_blocks(arm.body); + if let ExprKind::Match(expr_in, arms_inner, _) = expr.kind; + // the outer arm pattern and the inner match + if expr_in.span.ctxt() == arm.pat.span.ctxt(); + // there must be no more than two arms in the inner match for this lint + if arms_inner.len() == 2; + // no if guards on the inner match + if arms_inner.iter().all(|arm| arm.guard.is_none()); + // match expression must be a local binding + // match { .. } + if let Some(binding_id) = path_to_local(strip_ref_operators(expr_in, cx.typeck_results())); + // one of the branches must be "wild-like" + if let Some(wild_inner_arm_idx) = arms_inner.iter().rposition(|arm_inner| arm_is_wild_like(arm_inner, cx.tcx)); + let (wild_inner_arm, non_wild_inner_arm) = + (&arms_inner[wild_inner_arm_idx], &arms_inner[1 - wild_inner_arm_idx]); + if !pat_contains_or(non_wild_inner_arm.pat); + // the binding must come from the pattern of the containing match arm + // .... => match { .. } + if let Some(binding_span) = find_pat_binding(arm.pat, binding_id); + // the "wild-like" branches must be equal + if SpanlessEq::new(cx).eq_expr(wild_inner_arm.body, wild_outer_arm.body); + // the binding must not be used in the if guard + let mut used_visitor = LocalUsedVisitor::new(cx, binding_id); + if match arm.guard { + None => true, + Some(Guard::If(expr) | Guard::IfLet(_, expr)) => !used_visitor.check_expr(expr), + }; + // ...or anywhere in the inner match + if !arms_inner.iter().any(|arm| used_visitor.check_arm(arm)); + then { + span_lint_and_then( + cx, + COLLAPSIBLE_MATCH, + expr.span, + "unnecessary nested match", + |diag| { + let mut help_span = MultiSpan::from_spans(vec![binding_span, non_wild_inner_arm.pat.span]); + help_span.push_span_label(binding_span, "replace this binding".into()); + help_span.push_span_label(non_wild_inner_arm.pat.span, "with this pattern".into()); + diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern"); + }, + ); + } + } +} + +fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> { + while let ExprKind::Block(block, _) = expr.kind { + match (block.stmts, block.expr) { + ([stmt], None) => match stmt.kind { + StmtKind::Expr(e) | StmtKind::Semi(e) => expr = e, + _ => break, + }, + ([], Some(e)) => expr = e, + _ => break, + } + } + expr +} + +/// A "wild-like" pattern is wild ("_") or `None`. +/// For this lint to apply, both the outer and inner match expressions +/// must have "wild-like" branches that can be combined. +fn arm_is_wild_like(arm: &Arm<'_>, tcx: TyCtxt<'_>) -> bool { + if arm.guard.is_some() { + return false; + } + match arm.pat.kind { + PatKind::Binding(..) | PatKind::Wild => true, + PatKind::Path(QPath::Resolved(None, path)) if is_none_ctor(path.res, tcx) => true, + _ => false, + } +} + +fn find_pat_binding(pat: &Pat<'_>, hir_id: HirId) -> Option { + let mut span = None; + pat.walk_short(|p| match &p.kind { + // ignore OR patterns + PatKind::Or(_) => false, + PatKind::Binding(_bm, _, _ident, _) => { + let found = p.hir_id == hir_id; + if found { + span = Some(p.span); + } + !found + }, + _ => true, + }); + span +} + +fn pat_contains_or(pat: &Pat<'_>) -> bool { + let mut result = false; + pat.walk(|p| { + let is_or = matches!(p.kind, PatKind::Or(_)); + result |= is_or; + !is_or + }); + result +} + +fn is_none_ctor(res: Res, tcx: TyCtxt<'_>) -> bool { + if let Some(none_id) = tcx.lang_items().option_none_variant() { + if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = res { + if let Some(variant_id) = tcx.parent(id) { + return variant_id == none_id; + } + } + } + false +} + +/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is +/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed. +fn strip_ref_operators<'hir>(mut expr: &'hir Expr<'hir>, typeck_results: &TypeckResults<'_>) -> &'hir Expr<'hir> { + loop { + match expr.kind { + ExprKind::AddrOf(_, _, e) => expr = e, + ExprKind::Unary(UnOp::Deref, e) if typeck_results.expr_ty(e).is_ref() => expr = e, + _ => break, + } + } + expr +} diff --git a/src/tools/clippy/clippy_lints/src/comparison_chain.rs b/src/tools/clippy/clippy_lints/src/comparison_chain.rs new file mode 100644 index 0000000000..e309db2599 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/comparison_chain.rs @@ -0,0 +1,127 @@ +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:** The match statement may be slower due to the compiler + /// not inlining the call to cmp. See issue [#5354](https://github.com/rust-lang/rust-clippy/issues/5354) + /// + /// **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<'tcx> LateLintPass<'tcx> for ComparisonChain { + fn check_expr(&mut self, cx: &LateContext<'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); + let same_fixed_operands = spanless_eq.eq_expr(lhs1, lhs2) && spanless_eq.eq_expr(rhs1, rhs2); + let same_transposed_operands = spanless_eq.eq_expr(lhs1, rhs2) && spanless_eq.eq_expr(rhs1, lhs2); + + if !same_fixed_operands && !same_transposed_operands { + return; + } + + // Check that if the operation is the same, either it's not `==` or the operands are transposed + if kind1.node == kind2.node { + if kind1.node == BinOpKind::Eq { + return; + } + if !same_transposed_operands { + return; + } + } + + // Check that the type being compared implements `core::cmp::Ord` + let ty = cx.typeck_results().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 { + matches!(kind, BinOpKind::Lt | BinOpKind::Gt | BinOpKind::Eq) +} diff --git a/src/tools/clippy/clippy_lints/src/consts.rs b/src/tools/clippy/clippy_lints/src/consts.rs new file mode 100644 index 0000000000..7e87f53e3f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/consts.rs @@ -0,0 +1 @@ +pub use clippy_utils::consts::*; diff --git a/src/tools/clippy/clippy_lints/src/copies.rs b/src/tools/clippy/clippy_lints/src/copies.rs new file mode 100644 index 0000000000..944aaafb46 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/copies.rs @@ -0,0 +1,211 @@ +use crate::utils::{eq_expr_value, in_macro, search_same, SpanlessEq, SpanlessHash}; +use crate::utils::{get_parent_expr, if_sequence, span_lint_and_note}; +use rustc_hir::{Block, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +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_lint_pass!(CopyAndPaste => [IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE]); + +impl<'tcx> LateLintPass<'tcx> for CopyAndPaste { + fn check_expr(&mut self, cx: &LateContext<'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 { + kind: ExprKind::If(_, _, Some(ref else_expr)), + .. + }) = get_parent_expr(cx, 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); + } + } +} + +/// 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); + h.hash_expr(expr); + h.finish() + }; + + let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { eq_expr_value(cx, 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); + h.hash_expr(expr); + h.finish() + }; + + let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { + // Do not lint if any expr originates from a macro + if in_macro(lhs.span) || in_macro(rhs.span) { + return false; + } + // Do not spawn warning if `IFS_SAME_COND` already produced it. + if eq_expr_value(cx, 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", + ); + } +} + +fn search_same_sequenced(exprs: &[T], eq: Eq) -> Option<(&T, &T)> +where + Eq: Fn(&T, &T) -> bool, +{ + for win in exprs.windows(2) { + if eq(&win[0], &win[1]) { + return Some((&win[0], &win[1])); + } + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/copy_iterator.rs b/src/tools/clippy/clippy_lints/src/copy_iterator.rs new file mode 100644 index 0000000000..004bce5f62 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/copy_iterator.rs @@ -0,0 +1,55 @@ +use crate::utils::{is_copy, match_path, paths, span_lint_and_note}; +use rustc_hir::{Impl, 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<'tcx> LateLintPass<'tcx> for CopyIterator { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Impl(Impl { + of_trait: Some(ref trait_ref), + .. + }) = item.kind + { + let ty = cx.tcx.type_of(item.def_id); + + if is_copy(cx, ty) && match_path(&trait_ref.path, &paths::ITERATOR) { + span_lint_and_note( + cx, + COPY_ITERATOR, + item.span, + "you are implementing `Iterator` on a `Copy` type", + None, + "consider implementing `IntoIterator` instead", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/create_dir.rs b/src/tools/clippy/clippy_lints/src/create_dir.rs new file mode 100644 index 0000000000..200b6a565c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/create_dir.rs @@ -0,0 +1,51 @@ +use crate::utils::{match_def_path, paths, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks usage of `std::fs::create_dir` and suggest using `std::fs::create_dir_all` instead. + /// + /// **Why is this bad?** Sometimes `std::fs::create_dir` is mistakenly chosen over `std::fs::create_dir_all`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// std::fs::create_dir("foo"); + /// ``` + /// Use instead: + /// ```rust + /// std::fs::create_dir_all("foo"); + /// ``` + pub CREATE_DIR, + restriction, + "calling `std::fs::create_dir` instead of `std::fs::create_dir_all`" +} + +declare_lint_pass!(CreateDir => [CREATE_DIR]); + +impl LateLintPass<'_> for CreateDir { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref func, ref args) = expr.kind; + if let ExprKind::Path(ref path) = func.kind; + if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::STD_FS_CREATE_DIR); + then { + span_lint_and_sugg( + cx, + CREATE_DIR, + expr.span, + "calling `std::fs::create_dir` where there may be a better way", + "consider calling `std::fs::create_dir_all` instead", + format!("create_dir_all({})", snippet(cx, args[0].span, "..")), + Applicability::MaybeIncorrect, + ) + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/dbg_macro.rs b/src/tools/clippy/clippy_lints/src/dbg_macro.rs new file mode 100644 index 0000000000..e513dcce64 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/dbg_macro.rs @@ -0,0 +1,65 @@ +use crate::utils::{snippet_opt, span_lint_and_help, span_lint_and_sugg}; +use rustc_ast::ast; +use rustc_ast::tokenstream::TokenStream; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of dbg!() macro. + /// + /// **Why is this bad?** `dbg!` macro is intended as a debugging tool. It + /// should not be in version control. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// dbg!(true) + /// + /// // Good + /// true + /// ``` + pub DBG_MACRO, + restriction, + "`dbg!` macro is intended as a debugging tool" +} + +declare_lint_pass!(DbgMacro => [DBG_MACRO]); + +impl EarlyLintPass for DbgMacro { + fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) { + if mac.path == sym!(dbg) { + if let Some(sugg) = tts_span(mac.args.inner_tokens()).and_then(|span| snippet_opt(cx, span)) { + span_lint_and_sugg( + cx, + DBG_MACRO, + mac.span(), + "`dbg!` macro is intended as a debugging tool", + "ensure to avoid having uses of it in version control", + sugg, + Applicability::MaybeIncorrect, + ); + } else { + span_lint_and_help( + cx, + DBG_MACRO, + mac.span(), + "`dbg!` macro is intended as a debugging tool", + None, + "ensure to avoid having uses of it in version control", + ); + } + } + } +} + +// Get span enclosing entire the token stream. +fn tts_span(tts: TokenStream) -> Option { + let mut cursor = tts.into_trees(); + let first = cursor.next()?.span(); + let span = cursor.last().map_or(first, |tree| first.to(tree.span())); + Some(span) +} diff --git a/src/tools/clippy/clippy_lints/src/default.rs b/src/tools/clippy/clippy_lints/src/default.rs new file mode 100644 index 0000000000..6fa1378b8c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/default.rs @@ -0,0 +1,262 @@ +use crate::utils::{ + any_parent_is_automatically_derived, contains_name, match_def_path, paths, snippet_with_macro_callsite, +}; +use crate::utils::{span_lint_and_note, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_hir::def::Res; +use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind}; +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::symbol::{Ident, Symbol}; +use rustc_span::Span; + +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_clippy_lint! { + /// **What it does:** Checks for immediate reassignment of fields initialized + /// with Default::default(). + /// + /// **Why is this bad?**It's more idiomatic to use the [functional update syntax](https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax). + /// + /// **Known problems:** Assignments to patterns that are of tuple type are not linted. + /// + /// **Example:** + /// Bad: + /// ``` + /// # #[derive(Default)] + /// # struct A { i: i32 } + /// let mut a: A = Default::default(); + /// a.i = 42; + /// ``` + /// Use instead: + /// ``` + /// # #[derive(Default)] + /// # struct A { i: i32 } + /// let a = A { + /// i: 42, + /// .. Default::default() + /// }; + /// ``` + pub FIELD_REASSIGN_WITH_DEFAULT, + style, + "binding initialized with Default should have its fields set in the initializer" +} + +#[derive(Default)] +pub struct Default { + // Spans linted by `field_reassign_with_default`. + reassigned_linted: FxHashSet, +} + +impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]); + +impl LateLintPass<'_> for Default { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + // Avoid cases already linted by `field_reassign_with_default` + if !self.reassigned_linted.contains(&expr.span); + 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.qpath_res(qpath, path.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD); + // Detect and ignore ::default() because these calls do explicitly name the type. + if let QPath::Resolved(None, _path) = qpath; + then { + let expr_ty = cx.typeck_results().expr_ty(expr); + if let ty::Adt(def, ..) = expr_ty.kind() { + // 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 replacement = format!("{}::default()", cx.tcx.def_path_str(def.did)); + 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 + ); + } + } + } + } + + fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) { + // start from the `let mut _ = _::default();` and look at all the following + // statements, see if they re-assign the fields of the binding + let stmts_head = match block.stmts { + // Skip the last statement since there cannot possibly be any following statements that re-assign fields. + [head @ .., _] if !head.is_empty() => head, + _ => return, + }; + for (stmt_idx, stmt) in stmts_head.iter().enumerate() { + // find all binding statements like `let mut _ = T::default()` where `T::default()` is the + // `default` method of the `Default` trait, and store statement index in current block being + // checked and the name of the bound variable + let (local, variant, binding_name, binding_type, span) = if_chain! { + // only take `let ...` statements + if let StmtKind::Local(local) = stmt.kind; + if let Some(expr) = local.init; + if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id); + if !in_external_macro(cx.tcx.sess, expr.span); + // only take bindings to identifiers + if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind; + // only when assigning `... = Default::default()` + if is_expr_default(expr, cx); + let binding_type = cx.typeck_results().node_type(binding_id); + if let Some(adt) = binding_type.ty_adt_def(); + if adt.is_struct(); + let variant = adt.non_enum_variant(); + if adt.did.is_local() || !variant.is_field_list_non_exhaustive(); + let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id(); + if variant + .fields + .iter() + .all(|field| field.vis.is_accessible_from(module_did, cx.tcx)); + then { + (local, variant, ident.name, binding_type, expr.span) + } else { + continue; + } + }; + + // find all "later statement"'s where the fields of the binding set as + // Default::default() get reassigned, unless the reassignment refers to the original binding + let mut first_assign = None; + let mut assigned_fields = Vec::new(); + let mut cancel_lint = false; + for consecutive_statement in &block.stmts[stmt_idx + 1..] { + // find out if and which field was set by this `consecutive_statement` + if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) { + // interrupt and cancel lint if assign_rhs references the original binding + if contains_name(binding_name, assign_rhs) { + cancel_lint = true; + break; + } + + // if the field was previously assigned, replace the assignment, otherwise insert the assignment + if let Some(prev) = assigned_fields + .iter_mut() + .find(|(field_name, _)| field_name == &field_ident.name) + { + *prev = (field_ident.name, assign_rhs); + } else { + assigned_fields.push((field_ident.name, assign_rhs)); + } + + // also set first instance of error for help message + if first_assign.is_none() { + first_assign = Some(consecutive_statement); + } + } + // interrupt if no field was assigned, since we only want to look at consecutive statements + else { + break; + } + } + + // if there are incorrectly assigned fields, do a span_lint_and_note to suggest + // construction using `Ty { fields, ..Default::default() }` + if !assigned_fields.is_empty() && !cancel_lint { + // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion. + let ext_with_default = !variant + .fields + .iter() + .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.ident.name)); + + let field_list = assigned_fields + .into_iter() + .map(|(field, rhs)| { + // extract and store the assigned value for help message + let value_snippet = snippet_with_macro_callsite(cx, rhs.span, ".."); + format!("{}: {}", field, value_snippet) + }) + .collect::>() + .join(", "); + + let sugg = if ext_with_default { + if field_list.is_empty() { + format!("{}::default()", binding_type) + } else { + format!("{} {{ {}, ..Default::default() }}", binding_type, field_list) + } + } else { + format!("{} {{ {} }}", binding_type, field_list) + }; + + // span lint once per statement that binds default + span_lint_and_note( + cx, + FIELD_REASSIGN_WITH_DEFAULT, + first_assign.unwrap().span, + "field assignment outside of initializer for an instance created with Default::default()", + Some(local.span), + &format!( + "consider initializing the variable with `{}` and removing relevant reassignments", + sugg + ), + ); + self.reassigned_linted.insert(span); + } + } + } +} + +/// Checks if the given expression is the `default` method belonging to the `Default` trait. +fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool { + if_chain! { + if let ExprKind::Call(ref fn_expr, _) = &expr.kind; + if let ExprKind::Path(qpath) = &fn_expr.kind; + if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id); + then { + // right hand side of assignment is `Default::default` + match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD) + } else { + false + } + } +} + +/// Returns the reassigned field and the assigning expression (right-hand side of assign). +fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> { + if_chain! { + // only take assignments + if let StmtKind::Semi(ref later_expr) = this.kind; + if let ExprKind::Assign(ref assign_lhs, ref assign_rhs, _) = later_expr.kind; + // only take assignments to fields where the left-hand side field is a field of + // the same binding as the previous statement + if let ExprKind::Field(ref binding, field_ident) = assign_lhs.kind; + if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind; + if let Some(second_binding_name) = path.segments.last(); + if second_binding_name.ident.name == binding_name; + then { + Some((field_ident, assign_rhs)) + } else { + None + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/default_numeric_fallback.rs b/src/tools/clippy/clippy_lints/src/default_numeric_fallback.rs new file mode 100644 index 0000000000..369efacc9b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/default_numeric_fallback.rs @@ -0,0 +1,236 @@ +use rustc_ast::ast::{LitFloatType, LitIntType, LitKind}; +use rustc_errors::Applicability; +use rustc_hir::{ + intravisit::{walk_expr, walk_stmt, NestedVisitorMap, Visitor}, + Body, Expr, ExprKind, HirId, Lit, Stmt, StmtKind, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::{ + hir::map::Map, + ty::{self, FloatTy, IntTy, PolyFnSig, Ty}, +}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use if_chain::if_chain; + +use crate::utils::{snippet, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of unconstrained numeric literals which may cause default numeric fallback in type + /// inference. + /// + /// Default numeric fallback means that if numeric types have not yet been bound to concrete + /// types at the end of type inference, then integer type is bound to `i32`, and similarly + /// floating type is bound to `f64`. + /// + /// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback. + /// + /// **Why is this bad?** For those who are very careful about types, default numeric fallback + /// can be a pitfall that cause unexpected runtime behavior. + /// + /// **Known problems:** This lint can only be allowed at the function level or above. + /// + /// **Example:** + /// ```rust + /// let i = 10; + /// let f = 1.23; + /// ``` + /// + /// Use instead: + /// ```rust + /// let i = 10i32; + /// let f = 1.23f64; + /// ``` + pub DEFAULT_NUMERIC_FALLBACK, + restriction, + "usage of unconstrained numeric literals which may cause default numeric fallback." +} + +declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]); + +impl LateLintPass<'_> for DefaultNumericFallback { + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { + let mut visitor = NumericFallbackVisitor::new(cx); + visitor.visit_body(body); + } +} + +struct NumericFallbackVisitor<'a, 'tcx> { + /// Stack manages type bound of exprs. The top element holds current expr type. + ty_bounds: Vec>, + + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { + ty_bounds: vec![TyBound::Nothing], + cx, + } + } + + /// Check whether a passed literal has potential to cause fallback or not. + fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>) { + if_chain! { + if let Some(ty_bound) = self.ty_bounds.last(); + if matches!(lit.node, + LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)); + if !ty_bound.is_integral(); + then { + let suffix = match lit_ty.kind() { + ty::Int(IntTy::I32) => "i32", + ty::Float(FloatTy::F64) => "f64", + // Default numeric fallback never results in other types. + _ => return, + }; + + let sugg = format!("{}_{}", snippet(self.cx, lit.span, ""), suffix); + span_lint_and_sugg( + self.cx, + DEFAULT_NUMERIC_FALLBACK, + lit.span, + "default numeric fallback might occur", + "consider adding suffix", + sugg, + Applicability::MaybeIncorrect, + ); + } + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + #[allow(clippy::too_many_lines)] + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + match &expr.kind { + ExprKind::Call(func, args) => { + if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) { + for (expr, bound) in args.iter().zip(fn_sig.skip_binder().inputs().iter()) { + // Push found arg type, then visit arg. + self.ty_bounds.push(TyBound::Ty(bound)); + self.visit_expr(expr); + self.ty_bounds.pop(); + } + return; + } + }, + + ExprKind::MethodCall(_, _, args, _) => { + if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) { + let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder(); + for (expr, bound) in args.iter().zip(fn_sig.inputs().iter()) { + self.ty_bounds.push(TyBound::Ty(bound)); + self.visit_expr(expr); + self.ty_bounds.pop(); + } + return; + } + }, + + ExprKind::Struct(_, fields, base) => { + if_chain! { + let ty = self.cx.typeck_results().expr_ty(expr); + if let Some(adt_def) = ty.ty_adt_def(); + if adt_def.is_struct(); + if let Some(variant) = adt_def.variants.iter().next(); + then { + let fields_def = &variant.fields; + + // Push field type then visit each field expr. + for field in fields.iter() { + let bound = + fields_def + .iter() + .find_map(|f_def| { + if f_def.ident == field.ident + { Some(self.cx.tcx.type_of(f_def.did)) } + else { None } + }); + self.ty_bounds.push(bound.into()); + self.visit_expr(field.expr); + self.ty_bounds.pop(); + } + + // Visit base with no bound. + if let Some(base) = base { + self.ty_bounds.push(TyBound::Nothing); + self.visit_expr(base); + self.ty_bounds.pop(); + } + return; + } + } + }, + + ExprKind::Lit(lit) => { + let ty = self.cx.typeck_results().expr_ty(expr); + self.check_lit(lit, ty); + return; + }, + + _ => {}, + } + + walk_expr(self, expr); + } + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + match stmt.kind { + StmtKind::Local(local) => { + if local.ty.is_some() { + self.ty_bounds.push(TyBound::Any) + } else { + self.ty_bounds.push(TyBound::Nothing) + } + }, + + _ => self.ty_bounds.push(TyBound::Nothing), + } + + walk_stmt(self, stmt); + self.ty_bounds.pop(); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option> { + let node_ty = cx.typeck_results().node_type_opt(hir_id)?; + // We can't use `TyS::fn_sig` because it automatically performs substs, this may result in FNs. + match node_ty.kind() { + ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id)), + ty::FnPtr(fn_sig) => Some(*fn_sig), + _ => None, + } +} + +#[derive(Debug, Clone, Copy)] +enum TyBound<'tcx> { + Any, + Ty(Ty<'tcx>), + Nothing, +} + +impl<'tcx> TyBound<'tcx> { + fn is_integral(self) -> bool { + match self { + TyBound::Any => true, + TyBound::Ty(t) => t.is_integral(), + TyBound::Nothing => false, + } + } +} + +impl<'tcx> From>> for TyBound<'tcx> { + fn from(v: Option>) -> Self { + match v { + Some(t) => TyBound::Ty(t), + None => TyBound::Nothing, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/deprecated_lints.rs b/src/tools/clippy/clippy_lints/src/deprecated_lints.rs new file mode 100644 index 0000000000..89088c533e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/deprecated_lints.rs @@ -0,0 +1,190 @@ +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 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 preferred to `{min,max}_value()` and module constants" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** The regex! macro does not exist anymore. + pub REGEX_MACRO, + "the regex! macro has been removed from the regex crate in 2018" +} + +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 + /// `drop_bounds`. + pub DROP_BOUNDS, + "this lint has been uplifted to rustc and is now called `drop_bounds`" +} + +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 + /// `temporary_cstring_as_ptr`. + pub TEMPORARY_CSTRING_AS_PTR, + "this lint has been uplifted to rustc and is now called `temporary_cstring_as_ptr`" +} + +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 + /// `panic_fmt`. + pub PANIC_PARAMS, + "this lint has been uplifted to rustc and is now called `panic_fmt`" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint has been integrated into the `unknown_lints` + /// rustc lint. + pub UNKNOWN_CLIPPY_LINTS, + "this lint has been integrated into the `unknown_lints` rustc lint" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint has been replaced by `manual_find_map`, a + /// more specific lint. + pub FIND_MAP, + "this lint has been replaced by `manual_find_map`, a more specific lint" +} diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs new file mode 100644 index 0000000000..b5fb51af1c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -0,0 +1,109 @@ +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?** Dereferencing 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<'tcx> LateLintPass<'tcx> for Dereferencing { + fn check_expr(&mut self, cx: &LateContext<'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" => { + let impls_deref_trait = cx.tcx.lang_items().deref_trait().map_or(false, |id| { + implements_trait(cx, cx.typeck_results().expr_ty(&call_expr), id, &[]) + }); + if impls_deref_trait { + 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" => { + let impls_deref_mut_trait = cx.tcx.lang_items().deref_mut_trait().map_or(false, |id| { + implements_trait(cx, cx.typeck_results().expr_ty(&call_expr), id, &[]) + }); + if impls_deref_mut_trait { + span_lint_and_sugg( + cx, + EXPLICIT_DEREF_METHODS, + expr_span, + "explicit deref_mut method call", + "try this", + format!("&mut *{}", &snippet(cx, var_span, "..")), + Applicability::MachineApplicable, + ); + } + }, + _ => (), + } +} diff --git a/src/tools/clippy/clippy_lints/src/derive.rs b/src/tools/clippy/clippy_lints/src/derive.rs new file mode 100644 index 0000000000..6d3094ed6b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/derive.rs @@ -0,0 +1,429 @@ +use crate::utils::paths; +use crate::utils::{ + get_trait_def_id, is_allowed, is_automatically_derived, is_copy, match_def_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, Impl, Item, ItemKind, TraitRef, UnsafeSource, Unsafety, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for deriving `Hash` but implementing `PartialEq` + /// explicitly or vice versa. + /// + /// **Why is this bad?** The implementation of these traits must agree (for + /// example for use with `HashMap`) so it’s probably a bad idea to use a + /// default-generated `Hash` implementation with an explicitly defined + /// `PartialEq`. In particular, the following must hold for any type: + /// + /// ```text + /// k1 == k2 ⇒ hash(k1) == hash(k2) + /// ``` + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// #[derive(Hash)] + /// struct Foo; + /// + /// impl PartialEq for Foo { + /// ... + /// } + /// ``` + pub DERIVE_HASH_XOR_EQ, + correctness, + "deriving `Hash` but implementing `PartialEq` explicitly" +} + +declare_clippy_lint! { + /// **What it does:** Checks for deriving `Ord` but implementing `PartialOrd` + /// explicitly or vice versa. + /// + /// **Why is this bad?** The implementation of these traits must agree (for + /// example for use with `sort`) so it’s probably a bad idea to use a + /// default-generated `Ord` implementation with an explicitly defined + /// `PartialOrd`. In particular, the following must hold for any type + /// implementing `Ord`: + /// + /// ```text + /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap() + /// ``` + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,ignore + /// #[derive(Ord, PartialEq, Eq)] + /// struct Foo; + /// + /// impl PartialOrd for Foo { + /// ... + /// } + /// ``` + /// Use instead: + /// ```rust,ignore + /// #[derive(PartialEq, Eq)] + /// struct Foo; + /// + /// impl PartialOrd for Foo { + /// fn partial_cmp(&self, other: &Foo) -> Option { + /// Some(self.cmp(other)) + /// } + /// } + /// + /// impl Ord for Foo { + /// ... + /// } + /// ``` + /// or, if you don't need a custom ordering: + /// ```rust,ignore + /// #[derive(Ord, PartialOrd, PartialEq, Eq)] + /// struct Foo; + /// ``` + pub DERIVE_ORD_XOR_PARTIAL_ORD, + correctness, + "deriving `Ord` but implementing `PartialOrd` 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, + DERIVE_ORD_XOR_PARTIAL_ORD, + UNSAFE_DERIVE_DESERIALIZE +]); + +impl<'tcx> LateLintPass<'tcx> for Derive { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Impl(Impl { + of_trait: Some(ref trait_ref), + .. + }) = item.kind + { + let ty = cx.tcx.type_of(item.def_id); + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let is_automatically_derived = is_automatically_derived(attrs); + + check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); + check_ord_partial_ord(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<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &TraitRef<'_>, + ty: Ty<'tcx>, + hash_is_automatically_derived: bool, +) { + if_chain! { + if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait(); + if let Some(def_id) = trait_ref.trait_def_id(); + if match_def_path(cx, def_id, &paths::HASH); + then { + // Look for the PartialEq implementations for `ty` + cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { + let peq_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id)); + + if peq_is_automatically_derived == hash_is_automatically_derived { + return; + } + + let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); + + // Only care about `impl PartialEq for Foo` + // For `impl PartialEq for A, input_types is [A, B] + if trait_ref.substs.type_at(1) == ty { + let mess = if peq_is_automatically_derived { + "you are implementing `Hash` explicitly but have derived `PartialEq`" + } else { + "you are deriving `Hash` but have implemented `PartialEq` explicitly" + }; + + span_lint_and_then( + cx, + DERIVE_HASH_XOR_EQ, + span, + mess, + |diag| { + if let Some(local_def_id) = impl_id.as_local() { + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id); + diag.span_note( + cx.tcx.hir().span(hir_id), + "`PartialEq` implemented here" + ); + } + } + ); + } + }); + } + } +} + +/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint. +fn check_ord_partial_ord<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &TraitRef<'_>, + ty: Ty<'tcx>, + ord_is_automatically_derived: bool, +) { + if_chain! { + if let Some(ord_trait_def_id) = get_trait_def_id(cx, &paths::ORD); + if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait(); + if let Some(def_id) = &trait_ref.trait_def_id(); + if *def_id == ord_trait_def_id; + then { + // Look for the PartialOrd implementations for `ty` + cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { + let partial_ord_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id)); + + if partial_ord_is_automatically_derived == ord_is_automatically_derived { + return; + } + + let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); + + // Only care about `impl PartialOrd for Foo` + // For `impl PartialOrd for A, input_types is [A, B] + if trait_ref.substs.type_at(1) == ty { + let mess = if partial_ord_is_automatically_derived { + "you are implementing `Ord` explicitly but have derived `PartialOrd`" + } else { + "you are deriving `Ord` but have implemented `PartialOrd` explicitly" + }; + + span_lint_and_then( + cx, + DERIVE_ORD_XOR_PARTIAL_ORD, + span, + mess, + |diag| { + if let Some(local_def_id) = impl_id.as_local() { + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id); + diag.span_note( + cx.tcx.hir().span(hir_id), + "`PartialOrd` implemented here" + ); + } + } + ); + } + }); + } + } +} + +/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. +fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) { + if cx + .tcx + .lang_items() + .clone_trait() + .map_or(false, |id| Some(id) == trait_ref.trait_def_id()) + { + 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<'tcx>( + cx: &LateContext<'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().local_def_id_to_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 let Some(trait_def_id) = trait_ref.trait_def_id(); + if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE); + if let ty::Adt(def, _) = ty.kind(); + if let Some(local_def_id) = def.did.as_local(); + let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id); + if !is_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id); + 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<'tcx>, + has_unsafe: bool, +} + +impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { + type Map = Map<'tcx>; + + fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, span: Span, id: HirId) { + if self.has_unsafe { + return; + } + + if_chain! { + if let Some(header) = kind.header(); + if let Unsafety::Unsafe = header.unsafety; + then { + self.has_unsafe = true; + } + } + + walk_fn(self, kind, decl, body_id, span, id); + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.has_unsafe { + return; + } + + if let ExprKind::Block(block, _) = expr.kind { + match block.rules { + BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + | BlockCheckMode::PushUnsafeBlock(UnsafeSource::UserProvided) + | BlockCheckMode::PopUnsafeBlock(UnsafeSource::UserProvided) => { + self.has_unsafe = true; + }, + _ => {}, + } + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} diff --git a/src/tools/clippy/clippy_lints/src/disallowed_method.rs b/src/tools/clippy/clippy_lints/src/disallowed_method.rs new file mode 100644 index 0000000000..56dc6d18a5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/disallowed_method.rs @@ -0,0 +1,89 @@ +use crate::utils::{fn_def_id, span_lint}; + +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::Expr; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Symbol; + +declare_clippy_lint! { + /// **What it does:** Denies the configured methods and functions in clippy.toml + /// + /// **Why is this bad?** Some methods are undesirable in certain contexts, + /// and it's beneficial to lint for them as needed. + /// + /// **Known problems:** Currently, you must write each function as a + /// fully-qualified path. This lint doesn't support aliases or reexported + /// names; be aware that many types in `std` are actually reexports. + /// + /// For example, if you want to disallow `Duration::as_secs`, your clippy.toml + /// configuration would look like + /// `disallowed-methods = ["core::time::Duration::as_secs"]` and not + /// `disallowed-methods = ["std::time::Duration::as_secs"]` as you might expect. + /// + /// **Example:** + /// + /// An example clippy.toml configuration: + /// ```toml + /// # clippy.toml + /// disallowed-methods = ["alloc::vec::Vec::leak", "std::time::Instant::now"] + /// ``` + /// + /// ```rust,ignore + /// // Example code where clippy issues a warning + /// let xs = vec![1, 2, 3, 4]; + /// xs.leak(); // Vec::leak is disallowed in the config. + /// + /// let _now = Instant::now(); // Instant::now is disallowed in the config. + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// // Example code which does not raise clippy warning + /// let mut xs = Vec::new(); // Vec::new is _not_ disallowed in the config. + /// xs.push(123); // Vec::push is _not_ disallowed in the config. + /// ``` + pub DISALLOWED_METHOD, + nursery, + "use of a disallowed method call" +} + +#[derive(Clone, Debug)] +pub struct DisallowedMethod { + disallowed: FxHashSet>, +} + +impl DisallowedMethod { + pub fn new(disallowed: &FxHashSet) -> Self { + Self { + disallowed: disallowed + .iter() + .map(|s| s.split("::").map(|seg| Symbol::intern(seg)).collect::>()) + .collect(), + } + } +} + +impl_lint_pass!(DisallowedMethod => [DISALLOWED_METHOD]); + +impl<'tcx> LateLintPass<'tcx> for DisallowedMethod { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(def_id) = fn_def_id(cx, expr) { + let func_path = cx.get_def_path(def_id); + if self.disallowed.contains(&func_path) { + let func_path_string = func_path + .into_iter() + .map(Symbol::to_ident_string) + .collect::>() + .join("::"); + + span_lint( + cx, + DISALLOWED_METHOD, + expr.span, + &format!("use of a disallowed method `{}`", func_path_string), + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/doc.rs b/src/tools/clippy/clippy_lints/src/doc.rs new file mode 100644 index 0000000000..90b02d52f8 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/doc.rs @@ -0,0 +1,741 @@ +use crate::utils::{ + implements_trait, is_entrypoint_fn, is_expn_of, is_type_diagnostic_item, match_panic_def_id, method_chain_args, + return_ty, span_lint, span_lint_and_note, +}; +use if_chain::if_chain; +use itertools::Itertools; +use rustc_ast::ast::{Async, AttrKind, Attribute, FnKind, FnRetTy, ItemKind}; +use rustc_ast::token::CommentKind; +use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::sync::Lrc; +use rustc_errors::emitter::EmitterWriter; +use rustc_errors::Handler; +use rustc_hir as hir; +use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_parse::maybe_new_parser_from_source_str; +use rustc_parse::parser::ForceCollect; +use rustc_session::parse::ParseSess; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::edition::Edition; +use rustc_span::source_map::{BytePos, FilePathMapping, MultiSpan, SourceMap, Span}; +use rustc_span::{sym, FileName, Pos}; +use std::io; +use std::ops::Range; +use url::Url; + +declare_clippy_lint! { + /// **What it does:** Checks for the presence of `_`, `::` or camel-case words + /// outside ticks in documentation. + /// + /// **Why is this bad?** *Rustdoc* supports markdown formatting, `_`, `::` and + /// camel-case probably indicates some code which should be included between + /// ticks. `_` can also be used for emphasis in markdown, this lint tries to + /// consider that. + /// + /// **Known problems:** Lots of bad docs won’t be fixed, what the lint checks + /// for is limited, and there are still false positives. + /// + /// In addition, when writing documentation comments, including `[]` brackets + /// inside a link text would trip the parser. Therfore, documenting link with + /// `[`SmallVec<[T; INLINE_CAPACITY]>`]` and then [`SmallVec<[T; INLINE_CAPACITY]>`]: SmallVec + /// would fail. + /// + /// **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) {} + /// ``` + /// + /// ```rust + /// // Link text with `[]` brackets should be written as following: + /// /// Consume the array and return the inner + /// /// [`SmallVec<[T; INLINE_CAPACITY]>`][SmallVec]. + /// /// [SmallVec]: SmallVec + /// fn main() {} + /// ``` + pub DOC_MARKDOWN, + pedantic, + "presence of `_`, `::` or camel-case outside backticks in documentation" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the doc comments of publicly visible + /// unsafe functions and warns if there is no `# Safety` section. + /// + /// **Why is this bad?** Unsafe functions should document their safety + /// preconditions, so that users can be sure they are using them safely. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + ///# type Universe = (); + /// /// This function should really be documented + /// pub unsafe fn start_apocalypse(u: &mut Universe) { + /// unimplemented!(); + /// } + /// ``` + /// + /// At least write a line about safety: + /// + /// ```rust + ///# type Universe = (); + /// /// # Safety + /// /// + /// /// This function should not be called before the horsemen are ready. + /// pub unsafe fn start_apocalypse(u: &mut Universe) { + /// unimplemented!(); + /// } + /// ``` + pub MISSING_SAFETY_DOC, + style, + "`pub unsafe fn` without `# Safety` docs" +} + +declare_clippy_lint! { + /// **What it does:** Checks the doc comments of publicly visible functions that + /// return a `Result` type and warns if there is no `# Errors` section. + /// + /// **Why is this bad?** Documenting the type of errors that can be returned from a + /// function can help callers write code to handle the errors appropriately. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// + /// Since the following function returns a `Result` it has an `# Errors` section in + /// its doc comment: + /// + /// ```rust + ///# use std::io; + /// /// # Errors + /// /// + /// /// Will return `Err` if `filename` does not exist or the user does not have + /// /// permission to read it. + /// pub fn read(filename: String) -> io::Result { + /// unimplemented!(); + /// } + /// ``` + pub MISSING_ERRORS_DOC, + pedantic, + "`pub fn` returns `Result` without `# Errors` in doc comment" +} + +declare_clippy_lint! { + /// **What it does:** Checks the doc comments of publicly visible functions that + /// may panic and warns if there is no `# Panics` section. + /// + /// **Why is this bad?** Documenting the scenarios in which panicking occurs + /// can help callers who do not want to panic to avoid those situations. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// + /// Since the following function may panic it has a `# Panics` section in + /// its doc comment: + /// + /// ```rust + /// /// # Panics + /// /// + /// /// Will panic if y is 0 + /// pub fn divide_by(x: i32, y: i32) -> i32 { + /// if y == 0 { + /// panic!("Cannot divide by 0") + /// } else { + /// x / y + /// } + /// } + /// ``` + pub MISSING_PANICS_DOC, + pedantic, + "`pub fn` may panic without `# Panics` in doc comment" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `fn main() { .. }` in doctests + /// + /// **Why is this bad?** The test can be shorter (and likely more readable) + /// if the `fn main()` is left implicit. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ``````rust + /// /// An example of a doctest with a `main()` function + /// /// + /// /// # Examples + /// /// + /// /// ``` + /// /// fn main() { + /// /// // this needs not be in an `fn` + /// /// } + /// /// ``` + /// fn needless_main() { + /// unimplemented!(); + /// } + /// `````` + pub NEEDLESS_DOCTEST_MAIN, + style, + "presence of `fn main() {` in code examples" +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Clone)] +pub struct DocMarkdown { + valid_idents: FxHashSet, + in_trait_impl: bool, +} + +impl DocMarkdown { + pub fn new(valid_idents: FxHashSet) -> Self { + Self { + valid_idents, + in_trait_impl: false, + } + } +} + +impl_lint_pass!(DocMarkdown => + [DOC_MARKDOWN, MISSING_SAFETY_DOC, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, NEEDLESS_DOCTEST_MAIN] +); + +impl<'tcx> LateLintPass<'tcx> for DocMarkdown { + fn check_crate(&mut self, cx: &LateContext<'tcx>, _: &'tcx hir::Crate<'_>) { + let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID); + check_attrs(cx, &self.valid_idents, attrs); + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let headers = check_attrs(cx, &self.valid_idents, attrs); + match item.kind { + hir::ItemKind::Fn(ref sig, _, body_id) => { + if !(is_entrypoint_fn(cx, item.def_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) { + let body = cx.tcx.hir().body(body_id); + let mut fpu = FindPanicUnwrap { + cx, + typeck_results: cx.tcx.typeck(item.def_id), + panic_span: None, + }; + fpu.visit_expr(&body.value); + lint_for_missing_headers( + cx, + item.hir_id(), + item.span, + sig, + headers, + Some(body_id), + fpu.panic_span, + ); + } + }, + hir::ItemKind::Impl(ref impl_) => { + self.in_trait_impl = impl_.of_trait.is_some(); + }, + _ => {}, + } + } + + fn check_item_post(&mut self, _cx: &LateContext<'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<'tcx>, item: &'tcx hir::TraitItem<'_>) { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let headers = check_attrs(cx, &self.valid_idents, 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, None); + } + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let headers = check_attrs(cx, &self.valid_idents, 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 { + let body = cx.tcx.hir().body(body_id); + let mut fpu = FindPanicUnwrap { + cx, + typeck_results: cx.tcx.typeck(item.def_id), + panic_span: None, + }; + fpu.visit_expr(&body.value); + lint_for_missing_headers( + cx, + item.hir_id(), + item.span, + sig, + headers, + Some(body_id), + fpu.panic_span, + ); + } + } +} + +fn lint_for_missing_headers<'tcx>( + cx: &LateContext<'tcx>, + hir_id: hir::HirId, + span: impl Into + Copy, + sig: &hir::FnSig<'_>, + headers: DocHeaders, + body_id: Option, + panic_span: Option, +) { + if !cx.access_levels.is_exported(hir_id) { + return; // Private functions do not require doc comments + } + if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe { + span_lint( + cx, + MISSING_SAFETY_DOC, + span, + "unsafe function's docs miss `# Safety` section", + ); + } + if !headers.panics && panic_span.is_some() { + span_lint_and_note( + cx, + MISSING_PANICS_DOC, + span, + "docs for function which may panic missing `# Panics` section", + panic_span, + "first possible panic found here", + ); + } + 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 typeck = cx.tcx.typeck_body(body_id); + let body = cx.tcx.hir().body(body_id); + let ret_ty = typeck.expr_ty(&body.value); + 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. +/// +/// 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(doc: &str, comment_kind: CommentKind, span: Span) -> (String, Vec<(usize, Span)>) { + // one-line comments lose their prefix + if comment_kind == CommentKind::Line { + let mut doc = doc.to_owned(); + doc.push('\n'); + let len = doc.len(); + // +3 skips the opening delimiter + return (doc, vec![(len, span.with_lo(span.lo() + BytePos(3)))]); + } + + let mut sizes = vec![]; + let mut contains_initial_stars = false; + for line in doc.lines() { + let offset = line.as_ptr() as usize - doc.as_ptr() as usize; + debug_assert_eq!(offset as u32 as usize, offset); + contains_initial_stars |= line.trim_start().starts_with('*'); + // +1 adds the newline, +3 skips the opening delimiter + sizes.push((line.len() + 1, span.with_lo(span.lo() + BytePos(3 + 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'); + } + + (no_stars, sizes) +} + +#[derive(Copy, Clone)] +struct DocHeaders { + safety: bool, + errors: bool, + panics: bool, +} + +fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &'a [Attribute]) -> DocHeaders { + let mut doc = String::new(); + let mut spans = vec![]; + + for attr in attrs { + if let AttrKind::DocComment(comment_kind, comment) = attr.kind { + let (comment, current_spans) = strip_doc_comment_decoration(&comment.as_str(), comment_kind, attr.span); + spans.extend_from_slice(¤t_spans); + doc.push_str(&comment); + } else if attr.has_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, + panics: 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, + panics: false, + }; + } + + let parser = pulldown_cmark::Parser::new(&doc).into_offset_iter(); + // Iterate over all `Events` and combine consecutive events into one + let events = parser.coalesce(|previous, current| { + use pulldown_cmark::Event::Text; + + let previous_range = previous.1; + let current_range = current.1; + + match (previous.0, current.0) { + (Text(previous), Text(current)) => { + let mut previous = previous.to_string(); + previous.push_str(¤t); + Ok((Text(previous.into()), previous_range)) + }, + (previous, current) => Err(((previous, previous_range), (current, current_range))), + } + }); + check_doc(cx, valid_idents, events, &spans) +} + +const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"]; + +fn check_doc<'a, Events: Iterator, Range)>>( + cx: &LateContext<'_>, + valid_idents: &FxHashSet, + events: Events, + spans: &[(usize, Span)], +) -> DocHeaders { + // true if a safety header was found + use pulldown_cmark::CodeBlockKind; + use pulldown_cmark::Event::{ + Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text, + }; + use pulldown_cmark::Tag::{CodeBlock, Heading, Link}; + + let mut headers = DocHeaders { + safety: false, + errors: false, + panics: false, + }; + let mut in_code = false; + let mut in_link = None; + let mut in_heading = false; + let mut is_rust = false; + let mut edition = None; + for (event, range) in events { + match event { + Start(CodeBlock(ref kind)) => { + in_code = true; + if let CodeBlockKind::Fenced(lang) = kind { + for item in lang.split(',') { + if item == "ignore" { + is_rust = false; + break; + } + if let Some(stripped) = item.strip_prefix("edition") { + is_rust = true; + edition = stripped.parse::().ok(); + } else if item.is_empty() || RUST_CODE.contains(&item) { + is_rust = true; + } + } + } + }, + End(CodeBlock(_)) => { + in_code = false; + is_rust = false; + }, + Start(Link(_, url, _)) => in_link = Some(url), + End(Link(..)) => in_link = None, + Start(Heading(_)) => in_heading = true, + End(Heading(_)) => in_heading = false, + Start(_tag) | End(_tag) => (), // We don't care about other tags + Html(_html) => (), // HTML is weird, just ignore it + SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (), + FootnoteReference(text) | Text(text) => { + if Some(&text) == in_link.as_ref() { + // Probably a link of the form `` + // Which are represented as a link to "http://example.com" with + // text "http://example.com" by pulldown-cmark + continue; + } + headers.safety |= in_heading && text.trim() == "Safety"; + headers.errors |= in_heading && text.trim() == "Errors"; + headers.panics |= in_heading && text.trim() == "Panics"; + 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 { + let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition()); + check_code(cx, &text, edition, 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 +} + +fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { + fn has_needless_main(code: &str, edition: Edition) -> bool { + rustc_driver::catch_fatal_errors(|| { + rustc_span::with_session_globals(edition, || { + let filename = FileName::anon_source_code(code); + + let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let emitter = EmitterWriter::new(box io::sink(), None, false, false, false, None, false); + let handler = Handler::with_emitter(false, None, box emitter); + let sess = ParseSess::with_span_handler(handler, sm); + + let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code.into()) { + Ok(p) => p, + Err(errs) => { + for mut err in errs { + err.cancel(); + } + return false; + }, + }; + + let mut relevant_main_found = false; + loop { + match parser.parse_item(ForceCollect::No) { + Ok(Some(item)) => match &item.kind { + // Tests with one of these items are ignored + ItemKind::Static(..) + | ItemKind::Const(..) + | ItemKind::ExternCrate(..) + | ItemKind::ForeignMod(..) => return false, + // We found a main function ... + ItemKind::Fn(box FnKind(_, sig, _, Some(block))) if item.ident.name == sym::main => { + let is_async = matches!(sig.header.asyncness, Async::Yes { .. }); + let returns_nothing = match &sig.decl.output { + FnRetTy::Default(..) => true, + FnRetTy::Ty(ty) if ty.kind.is_unit() => true, + _ => false, + }; + + if returns_nothing && !is_async && !block.stmts.is_empty() { + // This main function should be linted, but only if there are no other functions + relevant_main_found = true; + } else { + // This main function should not be linted, we're done + return false; + } + }, + // Another function was found; this case is ignored too + ItemKind::Fn(..) => return false, + _ => {}, + }, + Ok(None) => break, + Err(mut e) => { + e.cancel(); + return false; + }, + } + } + + relevant_main_found + }) + }) + .ok() + .unwrap_or_default() + } + + if has_needless_main(text, edition) { + span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest"); + } +} + +fn check_text(cx: &LateContext<'_>, valid_idents: &FxHashSet, text: &str, span: Span) { + for word in text.split(|c: char| c.is_whitespace() || c == '\'') { + // Trim punctuation as in `some comment (see foo::bar).` + // ^^ + // Or even as in `_foo bar_` which is emphasized. + let word = word.trim_matches(|c: char| !c.is_alphanumeric()); + + if valid_idents.contains(word) { + continue; + } + + // Adjust for the current word + let offset = word.as_ptr() as usize - text.as_ptr() as usize; + let span = Span::new( + span.lo() + BytePos::from_usize(offset), + span.lo() + BytePos::from_usize(offset + word.len()), + span.ctxt(), + ); + + check_word(cx, word, span); + } +} + +fn check_word(cx: &LateContext<'_>, word: &str, span: Span) { + /// Checks if a string is camel-case, i.e., contains at least two uppercase + /// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok). + /// Plurals are also excluded (`IDs` is ok). + fn is_camel_case(s: &str) -> bool { + if s.starts_with(|c: char| c.is_digit(10)) { + return false; + } + + let s = s.strip_suffix('s').unwrap_or(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), + ); + } +} + +struct FindPanicUnwrap<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + panic_span: Option, + typeck_results: &'tcx ty::TypeckResults<'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.panic_span.is_some() { + return; + } + + // 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_panic_def_id(self.cx, path_def_id); + if is_expn_of(expr.span, "unreachable").is_none(); + then { + self.panic_span = Some(expr.span); + } + } + + // check for `unwrap` + if let Some(arglists) = method_chain_args(expr, &["unwrap"]) { + let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); + if is_type_diagnostic_item(self.cx, reciever_ty, sym::option_type) + || is_type_diagnostic_item(self.cx, reciever_ty, sym::result_type) + { + self.panic_span = Some(expr.span); + } + } + + // and check sub-expressions + intravisit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} diff --git a/src/tools/clippy/clippy_lints/src/double_comparison.rs b/src/tools/clippy/clippy_lints/src/double_comparison.rs new file mode 100644 index 0000000000..19f56195ec --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/double_comparison.rs @@ -0,0 +1,94 @@ +//! 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::{eq_expr_value, snippet_with_applicability, span_lint_and_sugg}; + +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<'tcx> DoubleComparisons { + #[allow(clippy::similar_names)] + fn check_binop(cx: &LateContext<'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, + }; + if !(eq_expr_value(cx, &llhs, &rlhs) && eq_expr_value(cx, &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<'tcx> LateLintPass<'tcx> for DoubleComparisons { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = expr.kind { + Self::check_binop(cx, kind.node, lhs, rhs, expr.span); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/double_parens.rs b/src/tools/clippy/clippy_lints/src/double_parens.rs new file mode 100644 index 0000000000..abbcaf43f4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/double_parens.rs @@ -0,0 +1,76 @@ +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 + /// // Bad + /// fn simple_double_parens() -> i32 { + /// ((0)) + /// } + /// + /// // Good + /// fn simple_no_parens() -> i32 { + /// 0 + /// } + /// + /// // or + /// + /// # fn foo(bar: usize) {} + /// // Bad + /// foo((0)); + /// + /// // Good + /// foo(0); + /// ``` + 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; + } + + let msg: &str = "consider removing unnecessary double parentheses"; + + match expr.kind { + ExprKind::Paren(ref in_paren) => match in_paren.kind { + ExprKind::Paren(_) | ExprKind::Tup(_) => { + span_lint(cx, DOUBLE_PARENS, expr.span, &msg); + }, + _ => {}, + }, + ExprKind::Call(_, ref params) => { + if params.len() == 1 { + let param = ¶ms[0]; + if let ExprKind::Paren(_) = param.kind { + span_lint(cx, DOUBLE_PARENS, param.span, &msg); + } + } + }, + ExprKind::MethodCall(_, ref params, _) => { + if params.len() == 2 { + let param = ¶ms[1]; + if let ExprKind::Paren(_) = param.kind { + span_lint(cx, DOUBLE_PARENS, param.span, &msg); + } + } + }, + _ => {}, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/drop_forget_ref.rs b/src/tools/clippy/clippy_lints/src/drop_forget_ref.rs new file mode 100644 index 0000000000..2aea00d883 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/drop_forget_ref.rs @@ -0,0 +1,160 @@ +use crate::utils::{is_copy, match_def_path, paths, 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<'tcx> LateLintPass<'tcx> for DropForgetRef { + fn check_expr(&mut self, cx: &LateContext<'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) = cx.qpath_res(qpath, path.hir_id).opt_def_id(); + then { + let lint; + let msg; + let arg = &args[0]; + let arg_ty = cx.typeck_results().expr_ty(arg); + + if let ty::Ref(..) = arg_ty.kind() { + if match_def_path(cx, def_id, &paths::DROP) { + lint = DROP_REF; + msg = DROP_REF_SUMMARY.to_string(); + } else if match_def_path(cx, def_id, &paths::MEM_FORGET) { + lint = FORGET_REF; + msg = FORGET_REF_SUMMARY.to_string(); + } else { + return; + } + span_lint_and_note(cx, + lint, + expr.span, + &msg, + Some(arg.span), + &format!("argument has type `{}`", arg_ty)); + } else if is_copy(cx, arg_ty) { + if match_def_path(cx, def_id, &paths::DROP) { + lint = DROP_COPY; + msg = DROP_COPY_SUMMARY.to_string(); + } else if match_def_path(cx, def_id, &paths::MEM_FORGET) { + lint = FORGET_COPY; + msg = FORGET_COPY_SUMMARY.to_string(); + } else { + return; + } + span_lint_and_note(cx, + lint, + expr.span, + &msg, + Some(arg.span), + &format!("argument has type {}", arg_ty)); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/duration_subsec.rs b/src/tools/clippy/clippy_lints/src/duration_subsec.rs new file mode 100644 index 0000000000..c0529a34cc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/duration_subsec.rs @@ -0,0 +1,71 @@ +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}; + +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); + /// + /// // Bad + /// let _micros = dur.subsec_nanos() / 1_000; + /// let _millis = dur.subsec_nanos() / 1_000_000; + /// + /// // Good + /// let _micros = dur.subsec_micros(); + /// let _millis = dur.subsec_millis(); + /// ``` + pub DURATION_SUBSEC, + complexity, + "checks for calculation of subsecond microseconds or milliseconds" +} + +declare_lint_pass!(DurationSubsec => [DURATION_SUBSEC]); + +impl<'tcx> LateLintPass<'tcx> for DurationSubsec { + fn check_expr(&mut self, cx: &LateContext<'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, cx.typeck_results().expr_ty(&args[0]).peel_refs(), &paths::DURATION); + if let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right); + then { + let suggested_fn = match (method_path.ident.as_str().as_ref(), divisor) { + ("subsec_micros", 1_000) | ("subsec_nanos", 1_000_000) => "subsec_millis", + ("subsec_nanos", 1_000) => "subsec_micros", + _ => return, + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + DURATION_SUBSEC, + expr.span, + &format!("calling `{}()` is more concise than this calculation", suggested_fn), + "try", + format!( + "{}.{}()", + snippet_with_applicability(cx, args[0].span, "_", &mut applicability), + suggested_fn + ), + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/else_if_without_else.rs b/src/tools/clippy/clippy_lints/src/else_if_without_else.rs new file mode 100644 index 0000000000..95123e6ff6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/else_if_without_else.rs @@ -0,0 +1,72 @@ +//! Lint on if expressions with an else if, but without a final else branch. + +use rustc_ast::ast::{Expr, ExprKind}; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::span_lint_and_help; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of if expressions with an `else if` branch, + /// but without a final `else` branch. + /// + /// **Why is this bad?** Some coding guidelines require this (e.g., MISRA-C:2004 Rule 14.10). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn a() {} + /// # fn b() {} + /// # let x: i32 = 1; + /// if x.is_positive() { + /// a(); + /// } else if x.is_negative() { + /// b(); + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// # fn a() {} + /// # fn b() {} + /// # let x: i32 = 1; + /// if x.is_positive() { + /// a(); + /// } else if x.is_negative() { + /// b(); + /// } else { + /// // We don't care about zero. + /// } + /// ``` + pub ELSE_IF_WITHOUT_ELSE, + restriction, + "`if` expression with an `else if`, but without a final `else` branch" +} + +declare_lint_pass!(ElseIfWithoutElse => [ELSE_IF_WITHOUT_ELSE]); + +impl EarlyLintPass for ElseIfWithoutElse { + fn check_expr(&mut self, cx: &EarlyContext<'_>, mut item: &Expr) { + if in_external_macro(cx.sess(), item.span) { + return; + } + + while let ExprKind::If(_, _, Some(ref els)) = item.kind { + if let ExprKind::If(_, _, None) = els.kind { + span_lint_and_help( + cx, + ELSE_IF_WITHOUT_ELSE, + els.span, + "`if` expression with an `else if`, but without a final `else`", + None, + "add an `else` block here", + ); + } + + item = els; + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/empty_enum.rs b/src/tools/clippy/clippy_lints/src/empty_enum.rs new file mode 100644 index 0000000000..077c3b75fb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/empty_enum.rs @@ -0,0 +1,68 @@ +//! 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. + /// + /// As of this writing, the `never_type` is still a + /// nightly-only experimental API. Therefore, this lint is only triggered + /// if the `never_type` is enabled. + /// + /// **Why is this bad?** If you want to introduce a type which + /// can't be instantiated, you should use `!` (the primitive type "never"), + /// 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<'tcx> LateLintPass<'tcx> for EmptyEnum { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + // Only suggest the `never_type` if the feature is enabled + if !cx.tcx.features().never_type { + return; + } + + if let ItemKind::Enum(..) = item.kind { + let ty = cx.tcx.type_of(item.def_id); + let adt = ty.ty_adt_def().expect("already checked whether this is an enum"); + if adt.variants.is_empty() { + span_lint_and_help( + cx, + EMPTY_ENUM, + item.span, + "enum with no variants", + None, + "consider using the uninhabited type `!` (never type) or a wrapper \ + around it to introduce a type which can't be instantiated", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/entry.rs b/src/tools/clippy/clippy_lints/src/entry.rs new file mode 100644 index 0000000000..5557596992 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/entry.rs @@ -0,0 +1,185 @@ +use crate::utils::SpanlessEq; +use crate::utils::{get_item_name, is_type_diagnostic_item, match_type, paths, snippet, snippet_opt}; +use crate::utils::{snippet_with_applicability, span_lint_and_then}; +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; +use rustc_span::sym; + +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<'tcx> LateLintPass<'tcx> for HashMapPass { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::If(ref check, ref then_block, ref else_block) = expr.kind { + if let ExprKind::Unary(UnOp::Not, 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>(cx: &LateContext<'_>, check: &'a Expr<'a>) -> Option<(&'static str, &'a Expr<'a>, &'a Expr<'a>)> { + if_chain! { + if let ExprKind::MethodCall(ref path, _, ref params, _) = check.kind; + if params.len() >= 2; + if path.ident.name == sym!(contains_key); + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref key) = params[1].kind; + then { + let map = ¶ms[0]; + let obj_ty = cx.typeck_results().expr_ty(map).peel_refs(); + + 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<'tcx>, + span: Span, + ty: &'static str, + map: &'b Expr<'b>, + key: &'b Expr<'b>, + sole_expr: bool, +} + +impl<'a, 'tcx, 'b> Visitor<'tcx> for InsertVisitor<'a, 'tcx, 'b> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::MethodCall(ref path, _, ref params, _) = expr.kind; + if params.len() == 3; + if path.ident.name == sym!(insert); + if get_item_name(self.cx, self.map) == get_item_name(self.cx, ¶ms[0]); + if SpanlessEq::new(self.cx).eq_expr(self.key, ¶ms[1]); + if snippet_opt(self.cx, self.map.span) == snippet_opt(self.cx, params[0].span); + then { + span_lint_and_then(self.cx, MAP_ENTRY, self.span, + &format!("usage of `contains_key` followed by `insert` on a `{}`", self.ty), |diag| { + if self.sole_expr { + let mut app = Applicability::MachineApplicable; + let help = format!("{}.entry({}).or_insert({});", + snippet_with_applicability(self.cx, self.map.span, "map", &mut app), + snippet_with_applicability(self.cx, params[1].span, "..", &mut app), + snippet_with_applicability(self.cx, params[2].span, "..", &mut app)); + + diag.span_suggestion( + self.span, + "consider using", + help, + Applicability::MachineApplicable, // snippet + ); + } + else { + let help = format!("consider using `{}.entry({})`", + snippet(self.cx, self.map.span, "map"), + snippet(self.cx, params[1].span, "..")); + + diag.span_label( + self.span, + &help, + ); + } + }); + } + } + + if !self.sole_expr { + walk_expr(self, expr); + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/enum_clike.rs b/src/tools/clippy/clippy_lints/src/enum_clike.rs new file mode 100644 index 0000000000..aa235642ac --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/enum_clike.rs @@ -0,0 +1,81 @@ +//! 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_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::util::IntTypeExt; +use rustc_middle::ty::{self, IntTy, UintTy}; +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<'tcx> LateLintPass<'tcx> for UnportableVariant { + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_sign_loss)] + fn check_item(&mut self, cx: &LateContext<'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) => {}, + _ => continue, + } + span_lint( + cx, + ENUM_CLIKE_UNPORTABLE_VARIANT, + var.span, + "C-like enum variant discriminant is not portable to 32-bit targets", + ); + }; + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/enum_variants.rs b/src/tools/clippy/clippy_lints/src/enum_variants.rs new file mode 100644 index 0000000000..67a4635385 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/enum_variants.rs @@ -0,0 +1,327 @@ +//! 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, + /// } + /// ``` + /// Could be written as: + /// ```rust + /// enum Cake { + /// BlackForest, + /// Hummingbird, + /// Battenberg, + /// } + /// ``` + pub ENUM_VARIANT_NAMES, + style, + "enums where all variants share a prefix/postfix" +} + +declare_clippy_lint! { + /// **What it does:** Detects public enumeration variants that are + /// prefixed or suffixed by the same characters. + /// + /// **Why is this bad?** Public enumeration variant names should specify their variant, + /// not repeat the enumeration name. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// pub enum Cake { + /// BlackForestCake, + /// HummingbirdCake, + /// BattenbergCake, + /// } + /// ``` + /// Could be written as: + /// ```rust + /// pub enum Cake { + /// BlackForest, + /// Hummingbird, + /// Battenberg, + /// } + /// ``` + pub PUB_ENUM_VARIANT_NAMES, + pedantic, + "public 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; + /// } + /// ``` + /// Could be written as: + /// ```rust + /// mod cake { + /// struct BlackForest; + /// } + /// ``` + 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.kind.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.kind { + VisibilityKind::Public => PUB_ENUM_VARIANT_NAMES, + _ => ENUM_VARIANT_NAMES, + }; + check_variant(cx, self.threshold, def, &item_name, item_name_chars, item.span, lint); + } + self.modules.push((item.ident.name, item_camel)); + } +} diff --git a/src/tools/clippy/clippy_lints/src/eq_op.rs b/src/tools/clippy/clippy_lints/src/eq_op.rs new file mode 100644 index 0000000000..6308f6e2e7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/eq_op.rs @@ -0,0 +1,247 @@ +use crate::utils::{ + ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, implements_trait, in_macro, is_copy, is_expn_of, + multispan_sugg, snippet, span_lint, span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, StmtKind}; +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 list of known pure functions in the future. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// if x + 1 == x + 1 {} + /// ``` + /// or + /// ```rust + /// # let a = 3; + /// # let b = 4; + /// assert_eq!(a, a); + /// ``` + 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 + /// // Bad + /// &x == y + /// + /// // Good + /// x == *y + /// ``` + pub OP_REF, + style, + "taking a reference to satisfy the type constraints on `==`" +} + +declare_lint_pass!(EqOp => [EQ_OP, OP_REF]); + +const ASSERT_MACRO_NAMES: [&str; 4] = ["assert_eq", "assert_ne", "debug_assert_eq", "debug_assert_ne"]; + +impl<'tcx> LateLintPass<'tcx> for EqOp { + #[allow(clippy::similar_names, clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Block(ref block, _) = e.kind { + for stmt in block.stmts { + for amn in &ASSERT_MACRO_NAMES { + if_chain! { + if is_expn_of(stmt.span, amn).is_some(); + if let StmtKind::Semi(ref matchexpr) = stmt.kind; + if let Some(macro_args) = higher::extract_assert_macro_args(matchexpr); + if macro_args.len() == 2; + let (lhs, rhs) = (macro_args[0], macro_args[1]); + if eq_expr_value(cx, lhs, rhs); + + then { + span_lint( + cx, + EQ_OP, + lhs.span.to(rhs.span), + &format!("identical args used in this `{}!` macro call", amn), + ); + } + } + } + } + } + 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_useless_with_eq_exprs(higher::binop(op.node)) && eq_expr_value(cx, 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.typeck_results().expr_ty(l); + let rty = cx.typeck_results().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", + vec![(left.span, lsnip), (right.span, rsnip)], + ); + }, + ) + } else if lcpy + && !rcpy + && implements_trait(cx, lty, trait_id, &[cx.typeck_results().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.typeck_results().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.typeck_results().expr_ty(l); + let lcpy = is_copy(cx, lty); + if (requires_ref || lcpy) + && implements_trait(cx, lty, trait_id, &[cx.typeck_results().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.typeck_results().expr_ty(r); + let rcpy = is_copy(cx, rty); + if (requires_ref || rcpy) + && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()]) + { + span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| { + let rsnip = snippet(cx, r.span, "...").to_string(); + diag.span_suggestion( + right.span, + "use the right value directly", + rsnip, + Applicability::MaybeIncorrect, // FIXME #2597 + ); + }) + } + }, + _ => {}, + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/erasing_op.rs b/src/tools/clippy/clippy_lints/src/erasing_op.rs new file mode 100644 index 0000000000..dbd1ff514f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/erasing_op.rs @@ -0,0 +1,59 @@ +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +use crate::consts::{constant_simple, Constant}; +use crate::utils::span_lint; + +declare_clippy_lint! { + /// **What it does:** Checks for erasing operations, e.g., `x * 0`. + /// + /// **Why is this bad?** The whole expression can be replaced by zero. + /// This is most likely not the intended outcome and should probably be + /// corrected + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 1; + /// 0 / x; + /// 0 * x; + /// x & 0; + /// ``` + pub ERASING_OP, + correctness, + "using erasing operations, e.g., `x * 0` or `y & 0`" +} + +declare_lint_pass!(ErasingOp => [ERASING_OP]); + +impl<'tcx> LateLintPass<'tcx> for ErasingOp { + fn check_expr(&mut self, cx: &LateContext<'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.typeck_results(), e) { + span_lint( + cx, + ERASING_OP, + span, + "this operation will always return zero. This is likely not the intended outcome", + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/escape.rs b/src/tools/clippy/clippy_lints/src/escape.rs new file mode 100644 index 0000000000..9721675754 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/escape.rs @@ -0,0 +1,201 @@ +use rustc_hir::intravisit; +use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::FakeReadCause; +use rustc_middle::ty::{self, TraitRef, Ty}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; +use rustc_span::symbol::kw; +use rustc_target::abi::LayoutOf; +use rustc_target::spec::abi::Abi; +use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; + +use crate::utils::{contains_ty, span_lint}; + +#[derive(Copy, Clone)] +pub struct BoxedLocal { + pub too_large_for_stack: u64, +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `Box` where an unboxed `T` would + /// work fine. + /// + /// **Why is this bad?** This is an unnecessary allocation, and bad for + /// performance. It is only necessary to allocate if you wish to move the box + /// into something. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn foo(bar: usize) {} + /// // Bad + /// let x = Box::new(1); + /// foo(*x); + /// println!("{}", *x); + /// + /// // Good + /// let x = 1; + /// foo(x); + /// println!("{}", x); + /// ``` + pub BOXED_LOCAL, + perf, + "using `Box` where unnecessary" +} + +fn is_non_trait_box(ty: Ty<'_>) -> bool { + ty.is_box() && !ty.boxed_ty().is_trait() +} + +struct EscapeDelegate<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + set: HirIdSet, + trait_self_ty: Option>, + too_large_for_stack: u64, +} + +impl_lint_pass!(BoxedLocal => [BOXED_LOCAL]); + +impl<'tcx> LateLintPass<'tcx> for BoxedLocal { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + fn_kind: intravisit::FnKind<'tcx>, + _: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + hir_id: HirId, + ) { + if let Some(header) = fn_kind.header() { + if header.abi != Abi::Rust { + return; + } + } + + let parent_id = cx.tcx.hir().get_parent_item(hir_id); + let parent_node = cx.tcx.hir().find(parent_id); + + let mut trait_self_ty = None; + if let Some(Node::Item(item)) = parent_node { + // If the method is an impl for a trait, don't warn. + if let ItemKind::Impl(Impl { of_trait: Some(_), .. }) = item.kind { + return; + } + + // find `self` ty for this trait if relevant + if let ItemKind::Trait(_, _, _, _, items) = item.kind { + for trait_item in items { + if trait_item.id.hir_id() == hir_id { + // be sure we have `self` parameter in this function + if let AssocItemKind::Fn { has_self: true } = trait_item.kind { + trait_self_ty = + Some(TraitRef::identity(cx.tcx, trait_item.id.def_id.to_def_id()).self_ty()); + } + } + } + } + } + + let mut v = EscapeDelegate { + cx, + set: HirIdSet::default(), + trait_self_ty, + 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.typeck_results()).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, + } + + matches!(map.find(map.get_parent_node(id)), Some(Node::Param(_))) +} + +impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> { + fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, mode: ConsumeMode) { + if cmt.place.projections.is_empty() { + if let PlaceBase::Local(lid) = cmt.place.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: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) { + if cmt.place.projections.is_empty() { + if let PlaceBase::Local(lid) = cmt.place.base { + self.set.remove(&lid); + } + } + } + + fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { + if cmt.place.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; + } + + // skip if there is a `self` parameter binding to a type + // that contains `Self` (i.e.: `self: Box`), see #4804 + if let Some(trait_self_ty) = self.trait_self_ty { + if map.name(cmt.hir_id) == kw::SelfLower && contains_ty(cmt.place.ty(), trait_self_ty) { + return; + } + } + + if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) { + self.set.insert(cmt.hir_id); + } + } + } + } + + fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _: HirId) { } +} + +impl<'a, 'tcx> EscapeDelegate<'a, 'tcx> { + fn is_large_box(&self, ty: Ty<'tcx>) -> bool { + // Large types need to be boxed to avoid stack overflows. + if ty.is_box() { + self.cx.layout_of(ty.boxed_ty()).map_or(0, |l| l.size.bytes()) > self.too_large_for_stack + } else { + false + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/eta_reduction.rs b/src/tools/clippy/clippy_lints/src/eta_reduction.rs new file mode 100644 index 0000000000..c461732fd3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/eta_reduction.rs @@ -0,0 +1,251 @@ +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, +}; +use clippy_utils::higher; +use clippy_utils::higher::VecArgs; + +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 [#1439](https://github.com/rust-lang/rust-clippy/issues/1439) for more details. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// xs.map(|x| foo(x)) + /// + /// // Good + /// xs.map(foo) + /// ``` + /// 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:** [#3071](https://github.com/rust-lang/rust-clippy/issues/3071), + /// [#3942](https://github.com/rust-lang/rust-clippy/issues/3942), + /// [#4002](https://github.com/rust-lang/rust-clippy/issues/4002) + /// + /// + /// **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<'tcx> LateLintPass<'tcx> for EtaReduction { + fn check_expr(&mut self, cx: &LateContext<'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 { + // skip `foo(macro!())` + if arg.span.ctxt() == expr.span.ctxt() { + 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 ex.span.ctxt() != expr.span.ctxt() { + if let Some(VecArgs::Vec(&[])) = higher::vec_macro(cx, ex) { + // replace `|| vec![]` with `Vec::new` + span_lint_and_sugg( + cx, + REDUNDANT_CLOSURE, + expr.span, + "redundant closure", + "replace the closure with `Vec::new`", + "std::vec::Vec::new".into(), + Applicability::MachineApplicable, + ); + } + // skip `foo(|| macro!())` + return; + } + + 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.typeck_results().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", |diag| { + if let Some(snippet) = snippet_opt(cx, caller.span) { + diag.span_suggestion( + expr.span, + "replace the closure with the function itself", + 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.typeck_results().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", + "replace the closure with the method itself", + format!("{}::{}", name, path.ident.name), + Applicability::MachineApplicable, + ); + } + ); + } +} + +/// Tries to determine the type for universal function call to be used instead of the closure +fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: def_id::DefId, self_arg: &Expr<'_>) -> Option { + let expected_type_of_self = &cx.tcx.fn_sig(method_def_id).inputs_and_output().skip_binder()[0]; + let actual_type_of_self = &cx.typeck_results().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) => !matches!((l, r), (ty::Ref(_, _, _), _) | (_, ty::Ref(_, _, _))), + } +} + +fn match_types(lhs: Ty<'_>, rhs: Ty<'_>) -> bool { + match (&lhs.kind(), &rhs.kind()) { + (ty::Bool, ty::Bool) + | (ty::Char, ty::Char) + | (ty::Int(_), ty::Int(_)) + | (ty::Uint(_), ty::Uint(_)) + | (ty::Str, ty::Str) => true, + (ty::Ref(_, t1, mut1), ty::Ref(_, t2, mut2)) => mut1 == mut2 && match_types(t1, t2), + (ty::Array(t1, _), ty::Array(t2, _)) | (ty::Slice(t1), ty::Slice(t2)) => match_types(t1, t2), + (ty::Adt(def1, _), ty::Adt(def2, _)) => def1 == def2, + (_, _) => false, + } +} + +fn get_type_name(cx: &LateContext<'_>, ty: Ty<'_>) -> String { + match ty.kind() { + ty::Adt(t, _) => cx.tcx.def_path_str(t.did), + ty::Ref(_, r, _) => get_type_name(cx, &r), + _ => ty.to_string(), + } +} + +fn compare_inputs( + closure_inputs: &mut dyn Iterator>, + call_args: &mut dyn Iterator>, +) -> bool { + for (closure_input, function_arg) in closure_inputs.zip(call_args) { + if let PatKind::Binding(_, _, ident, _) = closure_input.pat.kind { + // XXXManishearth Should I be checking the binding mode here? + if let ExprKind::Path(QPath::Resolved(None, ref p)) = function_arg.kind { + if p.segments.len() != 1 { + // If it's a proper path, it can't be a local variable + return false; + } + if p.segments[0].ident.name != ident.name { + // The two idents should be the same + return false; + } + } else { + return false; + } + } else { + return false; + } + } + true +} diff --git a/src/tools/clippy/clippy_lints/src/eval_order_dependence.rs b/src/tools/clippy/clippy_lints/src/eval_order_dependence.rs new file mode 100644 index 0000000000..83cee11c3a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/eval_order_dependence.rs @@ -0,0 +1,350 @@ +use crate::utils::{get_parent_expr, path_to_local, path_to_local_id, span_lint, span_lint_and_note}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, 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; + /// + /// // Bad + /// let a = { + /// x = 1; + /// 1 + /// } + x; + /// // Unclear whether a is 1 or 2. + /// + /// // Good + /// let tmp = { + /// x = 1; + /// 1 + /// }; + /// let a = tmp + x; + /// ``` + 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<'tcx> LateLintPass<'tcx> for EvalOrderDependence { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // Find a write to a local variable. + match expr.kind { + ExprKind::Assign(ref lhs, ..) | ExprKind::AssignOp(_, ref lhs, _) => { + if let Some(var) = path_to_local(lhs) { + 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<'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<'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.typeck_results().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.typeck_results(); + if borrowed_table.expr_ty(e).is_never() { + self.report_diverging_sub_expr(e); + } + }, + _ => { + // do not lint expressions referencing objects of type `!`, as that required a + // diverging expression + // to begin with + }, + } + self.maybe_walk_expr(e); + } + fn visit_block(&mut self, _: &'tcx Block<'_>) { + // don't continue over blocks, LateLintPass already does that + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Walks up the AST from the given write expression (`vis.write_expr`) looking +/// for reads to the same variable that are unsequenced relative to the write. +/// +/// This means reads for which there is a common ancestor between the read and +/// the write such that +/// +/// * evaluating the ancestor necessarily evaluates both the read and the write (for example, `&x` +/// and `|| x = 1` don't necessarily evaluate `x`), and +/// +/// * which one is evaluated first depends on the order of sub-expression evaluation. Blocks, `if`s, +/// loops, `match`es, and the short-circuiting logical operators are considered to have a defined +/// evaluation order. +/// +/// When such a read is found, the lint is triggered. +fn check_for_unsequenced_reads(vis: &mut ReadVisitor<'_, '_>) { + let map = &vis.cx.tcx.hir(); + let mut cur_id = vis.write_expr.hir_id; + loop { + let parent_id = map.get_parent_node(cur_id); + if parent_id == cur_id { + break; + } + let parent_node = match map.find(parent_id) { + Some(parent) => parent, + None => break, + }; + + let stop_early = match parent_node { + Node::Expr(expr) => check_expr(vis, expr), + Node::Stmt(stmt) => check_stmt(vis, stmt), + Node::Item(_) => { + // We reached the top of the function, stop. + break; + }, + _ => StopEarly::KeepGoing, + }; + match stop_early { + StopEarly::Stop => break, + StopEarly::KeepGoing => {}, + } + + cur_id = parent_id; + } +} + +/// Whether to stop early for the loop in `check_for_unsequenced_reads`. (If +/// `check_expr` weren't an independent function, this would be unnecessary and +/// we could just use `break`). +enum StopEarly { + KeepGoing, + Stop, +} + +fn check_expr<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, expr: &'tcx Expr<'_>) -> StopEarly { + if expr.hir_id == vis.last_expr.hir_id { + return StopEarly::KeepGoing; + } + + match expr.kind { + ExprKind::Array(_) + | ExprKind::Tup(_) + | ExprKind::MethodCall(..) + | ExprKind::Call(_, _) + | ExprKind::Assign(..) + | ExprKind::Index(_, _) + | ExprKind::Repeat(_, _) + | ExprKind::Struct(_, _, _) => { + walk_expr(vis, expr); + }, + ExprKind::Binary(op, _, _) | ExprKind::AssignOp(op, _, _) => { + if op.node == BinOpKind::And || op.node == BinOpKind::Or { + // x && y and x || y always evaluate x first, so these are + // strictly sequenced. + } else { + walk_expr(vis, expr); + } + }, + ExprKind::Closure(_, _, _, _, _) => { + // Either + // + // * `var` is defined in the closure body, in which case we've reached the top of the enclosing + // function and can stop, or + // + // * `var` is captured by the closure, in which case, because evaluating a closure does not evaluate + // its body, we don't necessarily have a write, so we need to stop to avoid generating false + // positives. + // + // This is also the only place we need to stop early (grrr). + return StopEarly::Stop; + }, + // All other expressions either have only one child or strictly + // sequence the evaluation order of their sub-expressions. + _ => {}, + } + + vis.last_expr = expr; + + StopEarly::KeepGoing +} + +fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly { + match stmt.kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => check_expr(vis, expr), + // If the declaration is of a local variable, check its initializer + // expression if it has one. Otherwise, keep going. + StmtKind::Local(ref local) => local + .init + .as_ref() + .map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)), + _ => StopEarly::KeepGoing, + } +} + +/// A visitor that looks for reads from a variable. +struct ReadVisitor<'a, 'tcx> { + cx: &'a LateContext<'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; + } + + if path_to_local_id(expr, self.var) { + // Check that this is a read, not a write. + if !is_in_assignment_position(self.cx, expr) { + 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", + ); + } + } + match expr.kind { + // We're about to descend a closure. Since we don't know when (or + // if) the closure will be evaluated, any reads in it might not + // occur here (or ever). Like above, bail to avoid false positives. + ExprKind::Closure(_, _, _, _, _) | + + // We want to avoid a false positive when a variable name occurs + // only to have its address taken, so we stop here. Technically, + // this misses some weird cases, eg. + // + // ```rust + // let mut x = 0; + // let a = foo(&{x = 1; x}, x); + // ``` + // + // TODO: fix this + ExprKind::AddrOf(_, _, _) => { + return; + } + _ => {} + } + + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`. +fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let Some(parent) = get_parent_expr(cx, expr) { + if let ExprKind::Assign(ref lhs, ..) = parent.kind { + return lhs.hir_id == expr.hir_id; + } + } + false +} diff --git a/src/tools/clippy/clippy_lints/src/excessive_bools.rs b/src/tools/clippy/clippy_lints/src/excessive_bools.rs new file mode 100644 index 0000000000..6f22f65dea --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/excessive_bools.rs @@ -0,0 +1,178 @@ +use crate::utils::{attr_by_name, in_macro, match_path_ast, span_lint_and_help}; +use rustc_ast::ast::{ + AssocItemKind, Extern, FnKind, FnSig, ImplKind, Item, ItemKind, TraitKind, Ty, TyKind, +}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; + +use std::convert::TryInto; + +declare_clippy_lint! { + /// **What it does:** Checks for excessive + /// use of bools in structs. + /// + /// **Why is this bad?** Excessive bools in a struct + /// is often a sign that it's used as a state machine, + /// which is much better implemented as an enum. + /// If it's not the case, excessive bools usually benefit + /// from refactoring into two-variant enums for better + /// readability and API. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust + /// struct S { + /// is_pending: bool, + /// is_processing: bool, + /// is_finished: bool, + /// } + /// ``` + /// + /// Good: + /// ```rust + /// enum S { + /// Pending, + /// Processing, + /// Finished, + /// } + /// ``` + pub STRUCT_EXCESSIVE_BOOLS, + pedantic, + "using too many bools in a struct" +} + +declare_clippy_lint! { + /// **What it does:** Checks for excessive use of + /// bools in function definitions. + /// + /// **Why is this bad?** Calls to such functions + /// are confusing and error prone, because it's + /// hard to remember argument order and you have + /// no type system support to back you up. Using + /// two-variant enums instead of bools often makes + /// API easier to use. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// fn f(is_round: bool, is_hot: bool) { ... } + /// ``` + /// + /// Good: + /// ```rust,ignore + /// enum Shape { + /// Round, + /// Spiky, + /// } + /// + /// enum Temperature { + /// Hot, + /// IceCold, + /// } + /// + /// fn f(shape: Shape, temperature: Temperature) { ... } + /// ``` + pub FN_PARAMS_EXCESSIVE_BOOLS, + pedantic, + "using too many bools in function parameters" +} + +pub struct ExcessiveBools { + max_struct_bools: u64, + max_fn_params_bools: u64, +} + +impl ExcessiveBools { + #[must_use] + pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self { + Self { + max_struct_bools, + max_fn_params_bools, + } + } + + fn check_fn_sig(&self, cx: &EarlyContext<'_>, fn_sig: &FnSig, span: Span) { + match fn_sig.header.ext { + Extern::Implicit | Extern::Explicit(_) => return, + Extern::None => (), + } + + let fn_sig_bools = fn_sig + .decl + .inputs + .iter() + .filter(|param| is_bool_ty(¶m.ty)) + .count() + .try_into() + .unwrap(); + if self.max_fn_params_bools < fn_sig_bools { + span_lint_and_help( + cx, + FN_PARAMS_EXCESSIVE_BOOLS, + span, + &format!("more than {} bools in function parameters", self.max_fn_params_bools), + None, + "consider refactoring bools into two-variant enums", + ); + } + } +} + +impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]); + +fn is_bool_ty(ty: &Ty) -> bool { + if let TyKind::Path(None, path) = &ty.kind { + return match_path_ast(path, &["bool"]); + } + false +} + +impl EarlyLintPass for ExcessiveBools { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if in_macro(item.span) { + return; + } + match &item.kind { + ItemKind::Struct(variant_data, _) => { + if attr_by_name(&item.attrs, "repr").is_some() { + return; + } + + let struct_bools = variant_data + .fields() + .iter() + .filter(|field| is_bool_ty(&field.ty)) + .count() + .try_into() + .unwrap(); + if self.max_struct_bools < struct_bools { + span_lint_and_help( + cx, + STRUCT_EXCESSIVE_BOOLS, + item.span, + &format!("more than {} bools in a struct", self.max_struct_bools), + None, + "consider using a state machine or refactoring bools into two-variant enums", + ); + } + }, + ItemKind::Impl(box ImplKind { + of_trait: None, items, .. + }) + | ItemKind::Trait(box TraitKind(.., items)) => { + for item in items { + if let AssocItemKind::Fn(box FnKind(_, fn_sig, _, _)) = &item.kind { + self.check_fn_sig(cx, fn_sig, item.span); + } + } + }, + ItemKind::Fn(box FnKind(_, fn_sig, _, _)) => self.check_fn_sig(cx, fn_sig, item.span), + _ => (), + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/exhaustive_items.rs b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs new file mode 100644 index 0000000000..316f748486 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs @@ -0,0 +1,107 @@ +use crate::utils::{indent_of, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Warns on any exported `enum`s that are not tagged `#[non_exhaustive]` + /// + /// **Why is this bad?** Exhaustive enums are typically fine, but a project which does + /// not wish to make a stability commitment around exported enums may wish to + /// disable them by default. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// enum Foo { + /// Bar, + /// Baz + /// } + /// ``` + /// Use instead: + /// ```rust + /// #[non_exhaustive] + /// enum Foo { + /// Bar, + /// Baz + /// } + /// ``` + pub EXHAUSTIVE_ENUMS, + restriction, + "detects exported enums that have not been marked #[non_exhaustive]" +} + +declare_clippy_lint! { + /// **What it does:** Warns on any exported `structs`s that are not tagged `#[non_exhaustive]` + /// + /// **Why is this bad?** Exhaustive structs are typically fine, but a project which does + /// not wish to make a stability commitment around exported structs may wish to + /// disable them by default. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// struct Foo { + /// bar: u8, + /// baz: String, + /// } + /// ``` + /// Use instead: + /// ```rust + /// #[non_exhaustive] + /// struct Foo { + /// bar: u8, + /// baz: String, + /// } + /// ``` + pub EXHAUSTIVE_STRUCTS, + restriction, + "detects exported structs that have not been marked #[non_exhaustive]" +} + +declare_lint_pass!(ExhaustiveItems => [EXHAUSTIVE_ENUMS, EXHAUSTIVE_STRUCTS]); + +impl LateLintPass<'_> for ExhaustiveItems { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if_chain! { + if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind; + if cx.access_levels.is_exported(item.hir_id()); + let attrs = cx.tcx.hir().attrs(item.hir_id()); + if !attrs.iter().any(|a| a.has_name(sym::non_exhaustive)); + then { + let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind { + if v.fields().iter().any(|f| !f.vis.node.is_pub()) { + // skip structs with private fields + return; + } + (EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive") + } else { + (EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive") + }; + let suggestion_span = item.span.shrink_to_lo(); + let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0)); + span_lint_and_then( + cx, + lint, + item.span, + msg, + |diag| { + let sugg = format!("#[non_exhaustive]\n{}", indent); + diag.span_suggestion(suggestion_span, + "try adding #[non_exhaustive]", + sugg, + Applicability::MaybeIncorrect); + } + ); + + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/exit.rs b/src/tools/clippy/clippy_lints/src/exit.rs new file mode 100644 index 0000000000..9158592700 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/exit.rs @@ -0,0 +1,47 @@ +use crate::utils::{is_entrypoint_fn, match_def_path, paths, 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<'tcx> LateLintPass<'tcx> for Exit { + fn check_expr(&mut self, cx: &LateContext<'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) = cx.qpath_res(path, path_expr.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::EXIT); + then { + let parent = cx.tcx.hir().get_parent_item(e.hir_id); + if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find(parent) { + // If the next item up is a function we check if it is an entry point + // and only then emit a linter warning + let def_id = cx.tcx.hir().local_def_id(parent); + if !is_entrypoint_fn(cx, def_id.to_def_id()) { + span_lint(cx, EXIT, e.span, "usage of `process::exit`"); + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/explicit_write.rs b/src/tools/clippy/clippy_lints/src/explicit_write.rs new file mode 100644 index 0000000000..f8038d06e5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/explicit_write.rs @@ -0,0 +1,150 @@ +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}; +use rustc_span::sym; + +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<'tcx> LateLintPass<'tcx> for ExplicitWrite { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + // match call to unwrap + if let ExprKind::MethodCall(ref unwrap_fun, _, ref unwrap_args, _) = expr.kind; + if unwrap_fun.ident.name == sym::unwrap; + // match call to write_fmt + if !unwrap_args.is_empty(); + if let ExprKind::MethodCall(ref write_fun, _, write_args, _) = + unwrap_args[0].kind; + if write_fun.ident.name == sym!(write_fmt); + // match calls to std::io::stdout() / std::io::stderr () + if !write_args.is_empty(); + if let Some(dest_name) = if match_function_call(cx, &write_args[0], &paths::STDOUT).is_some() { + Some("stdout") + } else if match_function_call(cx, &write_args[0], &paths::STDERR).is_some() { + Some("stderr") + } else { + None + }; + then { + let write_span = unwrap_args[0].span; + let calling_macro = + // ordering is important here, since `writeln!` uses `write!` internally + if is_expn_of(write_span, "writeln").is_some() { + Some("writeln") + } else if is_expn_of(write_span, "write").is_some() { + Some("write") + } else { + None + }; + let prefix = if dest_name == "stderr" { + "e" + } else { + "" + }; + + // We need to remove the last trailing newline from the string because the + // underlying `fmt::write` function doesn't know whether `println!` or `print!` was + // used. + if let Some(mut write_output) = write_output_string(write_args) { + if write_output.ends_with('\n') { + write_output.pop(); + } + + if let Some(macro_name) = calling_macro { + span_lint_and_sugg( + cx, + EXPLICIT_WRITE, + expr.span, + &format!( + "use of `{}!({}(), ...).unwrap()`", + macro_name, + dest_name + ), + "try this", + format!("{}{}!(\"{}\")", prefix, macro_name.replace("write", "print"), write_output.escape_default()), + Applicability::MachineApplicable + ); + } else { + span_lint_and_sugg( + cx, + EXPLICIT_WRITE, + expr.span, + &format!("use of `{}().write_fmt(...).unwrap()`", dest_name), + "try this", + format!("{}print!(\"{}\")", prefix, write_output.escape_default()), + Applicability::MachineApplicable + ); + } + } else { + // We don't have a proper suggestion + if let Some(macro_name) = calling_macro { + span_lint( + cx, + EXPLICIT_WRITE, + expr.span, + &format!( + "use of `{}!({}(), ...).unwrap()`. Consider using `{}{}!` instead", + macro_name, + dest_name, + prefix, + macro_name.replace("write", "print") + ) + ); + } else { + span_lint( + cx, + EXPLICIT_WRITE, + expr.span, + &format!("use of `{}().write_fmt(...).unwrap()`. Consider using `{}print!` instead", dest_name, prefix), + ); + } + } + + } + } + } +} + +// Extract the output string from the given `write_args`. +fn write_output_string(write_args: &[Expr<'_>]) -> Option { + if_chain! { + // Obtain the string that should be printed + if write_args.len() > 1; + if let ExprKind::Call(_, ref output_args) = write_args[1].kind; + if !output_args.is_empty(); + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref output_string_expr) = output_args[0].kind; + if let ExprKind::Array(ref string_exprs) = output_string_expr.kind; + // we only want to provide an automatic suggestion for simple (non-format) strings + if string_exprs.len() == 1; + if let ExprKind::Lit(ref lit) = string_exprs[0].kind; + if let LitKind::Str(ref write_output, _) = lit.node; + then { + return Some(write_output.to_string()) + } + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/fallible_impl_from.rs b/src/tools/clippy/clippy_lints/src/fallible_impl_from.rs new file mode 100644 index 0000000000..f466dddc13 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/fallible_impl_from.rs @@ -0,0 +1,143 @@ +use crate::utils::{is_expn_of, is_type_diagnostic_item, match_panic_def_id, method_chain_args, span_lint_and_then}; +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::{sym, 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); + /// + /// // Bad + /// impl From for Foo { + /// fn from(s: String) -> Self { + /// Foo(s.parse().unwrap()) + /// } + /// } + /// ``` + /// + /// ```rust + /// // Good + /// struct Foo(i32); + /// + /// use std::convert::TryFrom; + /// impl TryFrom for Foo { + /// type Error = (); + /// fn try_from(s: String) -> Result { + /// if let Ok(parsed) = s.parse() { + /// Ok(Foo(parsed)) + /// } else { + /// Err(()) + /// } + /// } + /// } + /// ``` + pub FALLIBLE_IMPL_FROM, + nursery, + "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`" +} + +declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]); + +impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { + // check for `impl From for ..` + if_chain! { + if let hir::ItemKind::Impl(impl_) = &item.kind; + if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.def_id); + if cx.tcx.is_diagnostic_item(sym::from_trait, impl_trait_ref.def_id); + then { + lint_impl_body(cx, item.span, impl_.items); + } + } + } +} + +fn lint_impl_body<'tcx>(cx: &LateContext<'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<'tcx>, + typeck_results: &'tcx ty::TypeckResults<'tcx>, + result: Vec, + } + + impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + // check for `begin_panic` + if_chain! { + if let ExprKind::Call(ref func_expr, _) = expr.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind; + if let Some(path_def_id) = path.res.opt_def_id(); + if match_panic_def_id(self.lcx, path_def_id); + 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 = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); + if is_type_diagnostic_item(self.lcx, reciever_ty, sym::option_type) + || is_type_diagnostic_item(self.lcx, reciever_ty, sym::result_type) + { + self.result.push(expr.span); + } + } + + // and check sub-expressions + intravisit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + } + + for impl_item in impl_items { + if_chain! { + if impl_item.ident.name == sym::from; + if let ImplItemKind::Fn(_, body_id) = + cx.tcx.hir().impl_item(impl_item.id).kind; + then { + // check the body for `begin_panic` or `unwrap` + let body = cx.tcx.hir().body(body_id); + let mut fpu = FindPanicUnwrap { + lcx: cx, + typeck_results: cx.tcx.typeck(impl_item.id.def_id), + result: Vec::new(), + }; + fpu.visit_expr(&body.value); + + // if we've found one, lint + if !fpu.result.is_empty() { + span_lint_and_then( + cx, + FALLIBLE_IMPL_FROM, + impl_span, + "consider implementing `TryFrom` instead", + move |diag| { + diag.help( + "`From` is intended for infallible conversions only. \ + Use `TryFrom` if there's a possibility for the conversion to fail"); + diag.span_note(fpu.result, "potential failure(s)"); + }); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/float_equality_without_abs.rs b/src/tools/clippy/clippy_lints/src/float_equality_without_abs.rs new file mode 100644 index 0000000000..c1c08597ee --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/float_equality_without_abs.rs @@ -0,0 +1,112 @@ +use crate::utils::{match_def_path, paths, span_lint_and_then, sugg}; +use if_chain::if_chain; +use rustc_ast::util::parser::AssocOp; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +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::Spanned; + +declare_clippy_lint! { + /// **What it does:** Checks for statements of the form `(a - b) < f32::EPSILON` or + /// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`. + /// + /// **Why is this bad?** The code without `.abs()` is more likely to have a bug. + /// + /// **Known problems:** If the user can ensure that b is larger than a, the `.abs()` is + /// technically unneccessary. However, it will make the code more robust and doesn't have any + /// large performance implications. If the abs call was deliberately left out for performance + /// reasons, it is probably better to state this explicitly in the code, which then can be done + /// with an allow. + /// + /// **Example:** + /// + /// ```rust + /// pub fn is_roughly_equal(a: f32, b: f32) -> bool { + /// (a - b) < f32::EPSILON + /// } + /// ``` + /// Use instead: + /// ```rust + /// pub fn is_roughly_equal(a: f32, b: f32) -> bool { + /// (a - b).abs() < f32::EPSILON + /// } + /// ``` + pub FLOAT_EQUALITY_WITHOUT_ABS, + correctness, + "float equality check without `.abs()`" +} + +declare_lint_pass!(FloatEqualityWithoutAbs => [FLOAT_EQUALITY_WITHOUT_ABS]); + +impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let lhs; + let rhs; + + // check if expr is a binary expression with a lt or gt operator + if let ExprKind::Binary(op, ref left, ref right) = expr.kind { + match op.node { + BinOpKind::Lt => { + lhs = left; + rhs = right; + }, + BinOpKind::Gt => { + lhs = right; + rhs = left; + }, + _ => return, + }; + } else { + return; + } + + if_chain! { + + // left hand side is a substraction + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Sub, + .. + }, + val_l, + val_r, + ) = lhs.kind; + + // right hand side matches either f32::EPSILON or f64::EPSILON + if let ExprKind::Path(ref epsilon_path) = rhs.kind; + if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id); + if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON); + + // values of the substractions on the left hand side are of the type float + let t_val_l = cx.typeck_results().expr_ty(val_l); + let t_val_r = cx.typeck_results().expr_ty(val_r); + if let ty::Float(_) = t_val_l.kind(); + if let ty::Float(_) = t_val_r.kind(); + + then { + let sug_l = sugg::Sugg::hir(cx, &val_l, ".."); + let sug_r = sugg::Sugg::hir(cx, &val_r, ".."); + // format the suggestion + let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par()); + // spans the lint + span_lint_and_then( + cx, + FLOAT_EQUALITY_WITHOUT_ABS, + expr.span, + "float equality check without `.abs()`", + | diag | { + diag.span_suggestion( + lhs.span, + "add `.abs()`", + suggestion, + Applicability::MaybeIncorrect, + ); + } + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/float_literal.rs b/src/tools/clippy/clippy_lints/src/float_literal.rs new file mode 100644 index 0000000000..8e256f3468 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/float_literal.rs @@ -0,0 +1,178 @@ +use crate::utils::{numeric_literal, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast::{self, LitFloatType, LitKind}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, FloatTy}; +use rustc_session::{declare_lint_pass, 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<'tcx> LateLintPass<'tcx> for FloatLiteral { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if_chain! { + let ty = cx.typeck_results().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(ast::FloatTy::F32) => Some("f32"), + LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"), + LitFloatType::Unsuffixed => None + }; + let (is_whole, mut float_str) = match fty { + FloatTy::F32 => { + let value = sym_str.parse::().unwrap(); + + (value.fract() == 0.0, formatter.format(value)) + }, + FloatTy::F64 => { + let value = sym_str.parse::().unwrap(); + + (value.fract() == 0.0, formatter.format(value)) + }, + }; + + if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') { + // Normalize the literal by stripping the fractional portion + if sym_str.split('.').next().unwrap() != float_str { + // If the type suffix is missing the suggestion would be + // incorrectly interpreted as an integer so adding a `.0` + // suffix to prevent that. + if type_suffix.is_none() { + float_str.push_str(".0"); + } + + span_lint_and_sugg( + cx, + LOSSY_FLOAT_LITERAL, + expr.span, + "literal cannot be represented as the underlying type without loss of precision", + "consider changing the type or replacing it with", + numeric_literal::format(&float_str, type_suffix, true), + Applicability::MachineApplicable, + ); + } + } else if digits > max as usize && sym_str != float_str { + span_lint_and_sugg( + cx, + EXCESSIVE_PRECISION, + expr.span, + "float has excessive precision", + "consider changing the type or truncating it to", + numeric_literal::format(&float_str, type_suffix, true), + Applicability::MachineApplicable, + ); + } + } + } + } +} + +#[must_use] +fn max_digits(fty: FloatTy) -> u32 { + match fty { + FloatTy::F32 => f32::DIGITS, + FloatTy::F64 => f64::DIGITS, + } +} + +/// Counts the digits excluding leading zeros +#[must_use] +fn count_digits(s: &str) -> usize { + // Note that s does not contain the f32/64 suffix, and underscores have been stripped + s.chars() + .filter(|c| *c != '-' && *c != '.') + .take_while(|c| *c != 'e' && *c != 'E') + .fold(0, |count, c| { + // leading zeros + if c == '0' && count == 0 { count } else { count + 1 } + }) +} + +enum FloatFormat { + LowerExp, + UpperExp, + Normal, +} +impl FloatFormat { + #[must_use] + fn new(s: &str) -> Self { + s.chars() + .find_map(|x| match x { + 'e' => Some(Self::LowerExp), + 'E' => Some(Self::UpperExp), + _ => None, + }) + .unwrap_or(Self::Normal) + } + fn format(&self, f: T) -> String + where + T: fmt::UpperExp + fmt::LowerExp + fmt::Display, + { + match self { + Self::LowerExp => format!("{:e}", f), + Self::UpperExp => format!("{:E}", f), + Self::Normal => format!("{}", f), + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs new file mode 100644 index 0000000000..086a791520 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs @@ -0,0 +1,721 @@ +use crate::consts::{ + constant, constant_simple, Constant, + Constant::{Int, F32, F64}, +}; +use crate::utils::{eq_expr_value, get_parent_expr, numeric_literal, span_lint_and_sugg, sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, 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.typeck_results(), 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::Neg, 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.typeck_results().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.typeck_results(), lhs), + constant(cx, cx.typeck_results(), rhs), + ) { + (Some((value, _)), _) if F32(1.0) == value || F64(1.0) == value => rhs, + (_, Some((value, _))) if F32(1.0) == value || F64(1.0) == value => lhs, + _ => return, + }; + + span_lint_and_sugg( + cx, + IMPRECISE_FLOPS, + expr.span, + "ln(1 + x) can be computed more accurately", + "consider using", + format!("{}.ln_1p()", prepare_receiver_sugg(cx, recv)), + Applicability::MachineApplicable, + ); + } +} + +// Returns an integer if the float constant is a whole number and it can be +// converted to an integer without loss of precision. For now we only check +// ranges [-16777215, 16777216) for type f32 as whole number floats outside +// this range are lossy and ambiguous. +#[allow(clippy::cast_possible_truncation)] +fn get_integer_from_float_constant(value: &Constant) -> Option { + match value { + F32(num) if num.fract() == 0.0 => { + if (-16_777_215.0..16_777_216.0).contains(num) { + Some(num.round() as i32) + } else { + None + } + }, + F64(num) if num.fract() == 0.0 => { + if (-2_147_483_648.0..2_147_483_648.0).contains(num) { + Some(num.round() as i32) + } else { + None + } + }, + _ => None, + } +} + +fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { + // Check receiver + if let Some((value, _)) = constant(cx, cx.typeck_results(), &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.typeck_results(), &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, + ); + } +} + +fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { + if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[1]) { + if value == Int(2) { + if let Some(parent) = get_parent_expr(cx, expr) { + if let Some(grandparent) = get_parent_expr(cx, parent) { + if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args, _) = grandparent.kind { + if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() { + return; + } + } + } + + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + ref lhs, + ref rhs, + ) = parent.kind + { + let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; + + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + parent.span, + "square can be computed more efficiently", + "consider using", + format!( + "{}.mul_add({}, {})", + Sugg::hir(cx, &args[0], ".."), + Sugg::hir(cx, &args[0], ".."), + Sugg::hir(cx, &other_addend, ".."), + ), + Applicability::MachineApplicable, + ); + + return; + } + } + + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "square can be computed more efficiently", + "consider using", + format!("{} * {}", Sugg::hir(cx, &args[0], ".."), Sugg::hir(cx, &args[0], "..")), + Applicability::MachineApplicable, + ); + } + } +} + +fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + ref add_lhs, + ref add_rhs, + ) = args[0].kind + { + // check if expression of the form x * x + y * y + if_chain! { + if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lmul_lhs, ref lmul_rhs) = add_lhs.kind; + if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref rmul_lhs, ref rmul_rhs) = add_rhs.kind; + if eq_expr_value(cx, lmul_lhs, lmul_rhs); + if eq_expr_value(cx, rmul_lhs, rmul_rhs); + then { + return Some(format!("{}.hypot({})", Sugg::hir(cx, &lmul_lhs, ".."), Sugg::hir(cx, &rmul_lhs, ".."))); + } + } + + // check if expression of the form x.powi(2) + y.powi(2) + if_chain! { + if let ExprKind::MethodCall( + PathSegment { ident: lmethod_name, .. }, + ref _lspan, + ref largs, + _ + ) = add_lhs.kind; + if let ExprKind::MethodCall( + PathSegment { ident: rmethod_name, .. }, + ref _rspan, + ref rargs, + _ + ) = add_rhs.kind; + if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi"; + if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), &largs[1]); + if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), &rargs[1]); + if Int(2) == lvalue && Int(2) == rvalue; + then { + return Some(format!("{}.hypot({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], ".."))); + } + } + } + + None +} + +fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { + if let Some(message) = detect_hypot(cx, args) { + span_lint_and_sugg( + cx, + IMPRECISE_FLOPS, + expr.span, + "hypotenuse can be computed more accurately", + "consider using", + message, + 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.typeck_results().expr_ty(lhs).is_floating_point(); + if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs); + if F32(1.0) == value || F64(1.0) == value; + if let ExprKind::MethodCall(ref path, _, ref method_args, _) = lhs.kind; + if cx.typeck_results().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.typeck_results().expr_ty(lhs).is_floating_point(); + if cx.typeck_results().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 + { + if let Some(parent) = get_parent_expr(cx, expr) { + if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args, _) = parent.kind { + if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() { + return; + } + } + } + + 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) && eq_expr_value(cx, left, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(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) && eq_expr_value(cx, right, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test), + _ => false, + } + } else { + false + } +} + +/// Returns true iff expr is some zero literal +fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + match constant_simple(cx, cx.typeck_results(), 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::Neg, expr1_negated) = &expr1.kind { + if eq_expr_value(cx, expr1_negated, expr2) { + return Some((false, expr2)); + } + } + if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind { + if eq_expr_value(cx, expr1, expr2_negated) { + return Some((true, expr1)); + } + } + None +} + +fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::If(cond, body, else_body) = expr.kind; + if let ExprKind::Block(block, _) = body.kind; + if block.stmts.is_empty(); + if let Some(if_body_expr) = block.expr; + if let Some(ExprKind::Block(else_block, _)) = else_body.map(|el| &el.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, + ); + } + } +} + +fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool { + if_chain! { + if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, ref args_a, _) = expr_a.kind; + if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, ref args_b, _) = expr_b.kind; + then { + return method_name_a.as_str() == method_name_b.as_str() && + args_a.len() == args_b.len() && + ( + ["ln", "log2", "log10"].contains(&&*method_name_a.as_str()) || + method_name_a.as_str() == "log" && args_a.len() == 2 && eq_expr_value(cx, &args_a[1], &args_b[1]) + ); + } + } + + false +} + +fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { + // check if expression of the form x.logN() / y.logN() + if_chain! { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Div, .. + }, + lhs, + rhs, + ) = &expr.kind; + if are_same_base_logs(cx, lhs, rhs); + if let ExprKind::MethodCall(_, _, ref largs, _) = lhs.kind; + if let ExprKind::MethodCall(_, _, ref rargs, _) = rhs.kind; + then { + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "log base can be expressed more clearly", + "consider using", + format!("{}.log({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], ".."),), + Applicability::MachineApplicable, + ); + } + } +} + +fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Div, .. + }, + div_lhs, + div_rhs, + ) = &expr.kind; + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Mul, .. + }, + mul_lhs, + mul_rhs, + ) = &div_lhs.kind; + if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), div_rhs); + if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), mul_rhs); + then { + // TODO: also check for constant values near PI/180 or 180/PI + if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) && + (F32(180_f32) == lvalue || F64(180_f64) == lvalue) + { + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "conversion to degrees can be done more accurately", + "consider using", + format!("{}.to_degrees()", Sugg::hir(cx, &mul_lhs, "..")), + Applicability::MachineApplicable, + ); + } else if + (F32(180_f32) == rvalue || F64(180_f64) == rvalue) && + (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue) + { + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "conversion to radians can be done more accurately", + "consider using", + format!("{}.to_radians()", Sugg::hir(cx, &mul_lhs, "..")), + Applicability::MachineApplicable, + ); + } + } + } +} + +impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::MethodCall(ref path, _, args, _) = &expr.kind { + let recv_ty = cx.typeck_results().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), + "powi" => check_powi(cx, expr, args), + "sqrt" => check_hypot(cx, expr, args), + _ => {}, + } + } + } else { + check_expm1(cx, expr); + check_mul_add(cx, expr); + check_custom_abs(cx, expr); + check_log_division(cx, expr); + check_radians(cx, expr); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/format.rs b/src/tools/clippy/clippy_lints/src/format.rs new file mode 100644 index 0000000000..fd6bf19db9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/format.rs @@ -0,0 +1,205 @@ +use crate::utils::paths; +use crate::utils::{ + is_expn_of, is_type_diagnostic_item, last_path_segment, match_def_path, match_function_call, snippet, snippet_opt, + span_lint_and_then, +}; +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; +use rustc_span::sym; + +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 + /// + /// // Bad + /// let foo = "foo"; + /// format!("{}", foo); + /// + /// // Good + /// foo.to_owned(); + /// ``` + pub USELESS_FORMAT, + complexity, + "useless use of `format!`" +} + +declare_lint_pass!(UselessFormat => [USELESS_FORMAT]); + +impl<'tcx> LateLintPass<'tcx> for UselessFormat { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let span = match is_expn_of(expr.span, "format") { + Some(s) if !s.from_expansion() => s, + _ => return, + }; + + // Operate on the only argument of `alloc::fmt::format`. + if let Some(sugg) = on_new_v1(cx, expr) { + span_useless_format(cx, span, "consider using `.to_string()`", sugg); + } else if let Some(sugg) = on_new_v1_fmt(cx, expr) { + span_useless_format(cx, span, "consider using `.to_string()`", sugg); + } + } +} + +fn span_useless_format(cx: &T, span: Span, help: &str, mut sugg: String) { + let to_replace = span.source_callsite(); + + // The callsite span contains the statement semicolon for some reason. + let snippet = snippet(cx, to_replace, ".."); + if snippet.ends_with(';') { + sugg.push(';'); + } + + span_lint_and_then(cx, USELESS_FORMAT, span, "useless use of `format!`", |diag| { + diag.span_suggestion( + to_replace, + help, + sugg, + Applicability::MachineApplicable, // snippet + ); + }); +} + +fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) -> Option { + if_chain! { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref format_args) = expr.kind; + if let ExprKind::Array(ref elems) = arms[0].body.kind; + if elems.len() == 1; + if let Some(args) = match_function_call(cx, &elems[0], &paths::FMT_ARGUMENTV1_NEW); + // matches `core::fmt::Display::fmt` + if args.len() == 2; + if let ExprKind::Path(ref qpath) = args[1].kind; + if let Some(did) = cx.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 = cx.typeck_results().pat_ty(&pats[0]).peel_refs(); + if *ty.kind() != rustc_middle::ty::Str && !is_type_diagnostic_item(cx, ty, sym::string_type) { + return None; + } + if let ExprKind::Lit(ref lit) = format_args.kind { + if let LitKind::Str(ref s, _) = lit.node { + return Some(format!("{:?}.to_string()", s.as_str())); + } + } else { + let snip = snippet(cx, format_args.span, ""); + if let ExprKind::MethodCall(ref path, _, _, _) = format_args.kind { + if path.ident.name == sym!(to_string) { + return Some(format!("{}", snip)); + } + } else if let ExprKind::Binary(..) = format_args.kind { + return Some(format!("{}", snip)); + } + return Some(format!("{}.to_string()", snip)); + } + } + } + None +} + +fn on_new_v1<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option { + if_chain! { + if let Some(args) = match_function_call(cx, expr, &paths::FMT_ARGUMENTS_NEW_V1); + if args.len() == 2; + // Argument 1 in `new_v1()` + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arr) = args[0].kind; + if let ExprKind::Array(ref pieces) = arr.kind; + if pieces.len() == 1; + if let ExprKind::Lit(ref lit) = pieces[0].kind; + if let LitKind::Str(ref s, _) = lit.node; + // Argument 2 in `new_v1()` + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg1) = args[1].kind; + if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.kind; + if arms.len() == 1; + if let ExprKind::Tup(ref tup) = matchee.kind; + then { + // `format!("foo")` expansion contains `match () { () => [], }` + if tup.is_empty() { + if let Some(s_src) = snippet_opt(cx, lit.span) { + // Simulate macro expansion, converting {{ and }} to { and }. + let s_expand = s_src.replace("{{", "{").replace("}}", "}"); + return Some(format!("{}.to_string()", s_expand)) + } + } else if s.as_str().is_empty() { + return on_argumentv1_new(cx, &tup[0], arms); + } + } + } + None +} + +fn on_new_v1_fmt<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option { + if_chain! { + if let Some(args) = match_function_call(cx, expr, &paths::FMT_ARGUMENTS_NEW_V1_FORMATTED); + if args.len() == 3; + if check_unformatted(&args[2]); + // Argument 1 in `new_v1_formatted()` + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arr) = args[0].kind; + if let ExprKind::Array(ref pieces) = arr.kind; + if pieces.len() == 1; + if let ExprKind::Lit(ref lit) = pieces[0].kind; + if let LitKind::Str(..) = lit.node; + // Argument 2 in `new_v1_formatted()` + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg1) = args[1].kind; + if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.kind; + if arms.len() == 1; + if let ExprKind::Tup(ref tup) = matchee.kind; + then { + return on_argumentv1_new(cx, &tup[0], arms); + } + } + None +} + +/// Checks if the expression matches +/// ```rust,ignore +/// &[_ { +/// format: _ { +/// width: _::Implied, +/// precision: _::Implied, +/// ... +/// }, +/// ..., +/// }] +/// ``` +fn check_unformatted(expr: &Expr<'_>) -> bool { + if_chain! { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) = expr.kind; + if let ExprKind::Array(ref exprs) = expr.kind; + if exprs.len() == 1; + // struct `core::fmt::rt::v1::Argument` + if let ExprKind::Struct(_, ref fields, _) = exprs[0].kind; + if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format); + // struct `core::fmt::rt::v1::FormatSpec` + if let ExprKind::Struct(_, ref fields, _) = format_field.expr.kind; + if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym::precision); + if let ExprKind::Path(ref precision_path) = precision_field.expr.kind; + if last_path_segment(precision_path).ident.name == sym::Implied; + if let Some(width_field) = fields.iter().find(|f| f.ident.name == sym::width); + if let ExprKind::Path(ref width_qpath) = width_field.expr.kind; + if last_path_segment(width_qpath).ident.name == sym::Implied; + then { + return true; + } + } + + false +} diff --git a/src/tools/clippy/clippy_lints/src/formatting.rs b/src/tools/clippy/clippy_lints/src/formatting.rs new file mode 100644 index 0000000000..1bd16e6cce --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/formatting.rs @@ -0,0 +1,314 @@ +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) { + if let (StmtKind::Expr(first), StmtKind::Expr(second) | StmtKind::Semi(second)) = (&w[0].kind, &w[1].kind) { + check_missing_else(cx, first, second); + } + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + check_assign(cx, expr); + check_unop(cx, expr); + check_else(cx, expr); + check_array(cx, expr); + } +} + +/// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint. +fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind { + if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion() { + let eq_span = lhs.span.between(rhs.span); + if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind { + if let Some(eq_snippet) = snippet_opt(cx, eq_span) { + let op = UnOp::to_string(op); + let eqop_span = lhs.span.between(sub_rhs.span); + if eq_snippet.ends_with('=') { + span_lint_and_note( + cx, + SUSPICIOUS_ASSIGNMENT_FORMATTING, + eqop_span, + &format!( + "this looks like you are trying to use `.. {op}= ..`, but you \ + really are doing `.. = ({op} ..)`", + op = op + ), + None, + &format!("to remove this lint, use either `{op}=` or `= {op}`", op = op), + ); + } + } + } + } + } +} + +/// Implementation of the `SUSPICIOUS_UNARY_OP_FORMATTING` lint. +fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) { + if_chain! { + if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind; + if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion(); + // span between BinOp LHS and RHS + let binop_span = lhs.span.between(rhs.span); + // if RHS is a UnOp + if let ExprKind::Unary(op, ref un_rhs) = rhs.kind; + // from UnOp operator to UnOp operand + let unop_operand_span = rhs.span.until(un_rhs.span); + if let Some(binop_snippet) = snippet_opt(cx, binop_span); + if let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span); + let binop_str = BinOpKind::to_string(&binop.node); + // no space after BinOp operator and space after UnOp operator + if binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' '); + then { + let unop_str = UnOp::to_string(op); + let eqop_span = lhs.span.between(un_rhs.span); + span_lint_and_help( + cx, + SUSPICIOUS_UNARY_OP_FORMATTING, + eqop_span, + &format!( + "by not having a space between `{binop}` and `{unop}` it looks like \ + `{binop}{unop}` is a single operator", + binop = binop_str, + unop = unop_str + ), + None, + &format!( + "put a space between `{binop}` and `{unop}` and remove the space after `{unop}`", + binop = binop_str, + unop = unop_str + ), + ); + } + } +} + +/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else`. +fn check_else(cx: &EarlyContext<'_>, expr: &Expr) { + if_chain! { + if let ExprKind::If(_, then, Some(else_)) = &expr.kind; + if is_block(else_) || is_if(else_); + if !differing_macro_contexts(then.span, else_.span); + if !then.span.from_expansion() && !in_external_macro(cx.sess, expr.span); + + // workaround for rust-lang/rust#43081 + if expr.span.lo().0 != 0 && expr.span.hi().0 != 0; + + // this will be a span from the closing ‘}’ of the “then” block (excluding) to + // the “if” of the “else if” block (excluding) + let else_span = then.span.between(else_.span); + + // the snippet should look like " else \n " with maybe comments anywhere + // it’s bad when there is a ‘\n’ after the “else” + if let Some(else_snippet) = snippet_opt(cx, else_span); + if let Some(else_pos) = else_snippet.find("else"); + if else_snippet[else_pos..].contains('\n'); + let else_desc = if is_if(else_) { "if" } else { "{..}" }; + + then { + span_lint_and_note( + cx, + SUSPICIOUS_ELSE_FORMATTING, + else_span, + &format!("this is an `else {}` but the formatting might hide it", else_desc), + None, + &format!( + "to remove this lint, remove the `else` or remove the new line between \ + `else` and `{}`", + else_desc, + ), + ); + } + } +} + +#[must_use] +fn has_unary_equivalent(bin_op: BinOpKind) -> bool { + // &, *, - + bin_op == BinOpKind::And || bin_op == BinOpKind::Mul || bin_op == BinOpKind::Sub +} + +fn indentation(cx: &EarlyContext<'_>, span: Span) -> usize { + cx.sess.source_map().lookup_char_pos(span.lo()).col.0 +} + +/// Implementation of the `POSSIBLE_MISSING_COMMA` lint for array +fn check_array(cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Array(ref array) = expr.kind { + for element in array { + if_chain! { + if let ExprKind::Binary(ref op, ref lhs, _) = element.kind; + if has_unary_equivalent(op.node) && !differing_macro_contexts(lhs.span, op.span); + let space_span = lhs.span.between(op.span); + if let Some(space_snippet) = snippet_opt(cx, space_span); + let lint_span = lhs.span.with_lo(lhs.span.hi()); + if space_snippet.contains('\n'); + if indentation(cx, op.span) <= indentation(cx, lhs.span); + then { + span_lint_and_note( + cx, + POSSIBLE_MISSING_COMMA, + lint_span, + "possibly missing a comma here", + None, + "to remove this lint, add a comma or write the expr in a single line", + ); + } + } + } + } +} + +fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) { + if !differing_macro_contexts(first.span, second.span) + && !first.span.from_expansion() + && is_if(first) + && (is_block(second) || is_if(second)) + { + // where the else would be + let else_span = first.span.between(second.span); + + if let Some(else_snippet) = snippet_opt(cx, else_span) { + if !else_snippet.contains('\n') { + let (looks_like, next_thing) = if is_if(second) { + ("an `else if`", "the second `if`") + } else { + ("an `else {..}`", "the next block") + }; + + span_lint_and_note( + cx, + SUSPICIOUS_ELSE_FORMATTING, + else_span, + &format!("this looks like {} but the `else` is missing", looks_like), + None, + &format!( + "to remove this lint, add the missing `else` or add a new line before {}", + next_thing, + ), + ); + } + } + } +} + +fn is_block(expr: &Expr) -> bool { + matches!(expr.kind, ExprKind::Block(..)) +} + +/// Check if the expression is an `if` or `if let` +fn is_if(expr: &Expr) -> bool { + matches!(expr.kind, ExprKind::If(..)) +} diff --git a/src/tools/clippy/clippy_lints/src/from_over_into.rs b/src/tools/clippy/clippy_lints/src/from_over_into.rs new file mode 100644 index 0000000000..b644bb0799 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/from_over_into.rs @@ -0,0 +1,82 @@ +use crate::utils::paths::INTO; +use crate::utils::{match_def_path, meets_msrv, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +const FROM_OVER_INTO_MSRV: RustcVersion = RustcVersion::new(1, 41, 0); + +declare_clippy_lint! { + /// **What it does:** Searches for implementations of the `Into<..>` trait and suggests to implement `From<..>` instead. + /// + /// **Why is this bad?** According the std docs implementing `From<..>` is preferred since it gives you `Into<..>` for free where the reverse isn't true. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// struct StringWrapper(String); + /// + /// impl Into for String { + /// fn into(self) -> StringWrapper { + /// StringWrapper(self) + /// } + /// } + /// ``` + /// Use instead: + /// ```rust + /// struct StringWrapper(String); + /// + /// impl From for StringWrapper { + /// fn from(s: String) -> StringWrapper { + /// StringWrapper(s) + /// } + /// } + /// ``` + pub FROM_OVER_INTO, + style, + "Warns on implementations of `Into<..>` to use `From<..>`" +} + +pub struct FromOverInto { + msrv: Option, +} + +impl FromOverInto { + #[must_use] + pub fn new(msrv: Option) -> Self { + FromOverInto { msrv } + } +} + +impl_lint_pass!(FromOverInto => [FROM_OVER_INTO]); + +impl LateLintPass<'_> for FromOverInto { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { + if !meets_msrv(self.msrv.as_ref(), &FROM_OVER_INTO_MSRV) { + return; + } + + if_chain! { + if let hir::ItemKind::Impl{ .. } = &item.kind; + if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.def_id); + if match_def_path(cx, impl_trait_ref.def_id, &INTO); + + then { + span_lint_and_help( + cx, + FROM_OVER_INTO, + cx.tcx.sess.source_map().guess_head_span(item.span), + "an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true", + None, + "consider to implement `From` instead", + ); + } + } + } + + extract_msrv_attr!(LateContext); +} diff --git a/src/tools/clippy/clippy_lints/src/from_str_radix_10.rs b/src/tools/clippy/clippy_lints/src/from_str_radix_10.rs new file mode 100644 index 0000000000..0933f98301 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/from_str_radix_10.rs @@ -0,0 +1,101 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{def, Expr, ExprKind, PrimTy, QPath, TyKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; + +use crate::utils::is_type_diagnostic_item; +use crate::utils::span_lint_and_sugg; +use crate::utils::sugg::Sugg; + +declare_clippy_lint! { + /// **What it does:** + /// Checks for function invocations of the form `primitive::from_str_radix(s, 10)` + /// + /// **Why is this bad?** + /// This specific common use case can be rewritten as `s.parse::()` + /// (and in most cases, the turbofish can be removed), which reduces code length + /// and complexity. + /// + /// **Known problems:** + /// This lint may suggest using (&).parse() instead of .parse() directly + /// in some cases, which is correct but adds unnecessary complexity to the code. + /// + /// **Example:** + /// + /// ```ignore + /// let input: &str = get_input(); + /// let num = u16::from_str_radix(input, 10)?; + /// ``` + /// Use instead: + /// ```ignore + /// let input: &str = get_input(); + /// let num: u16 = input.parse()?; + /// ``` + pub FROM_STR_RADIX_10, + style, + "from_str_radix with radix 10" +} + +declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]); + +impl LateLintPass<'tcx> for FromStrRadix10 { + fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) { + if_chain! { + if let ExprKind::Call(maybe_path, arguments) = &exp.kind; + if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind; + + // check if the first part of the path is some integer primitive + if let TyKind::Path(ty_qpath) = &ty.kind; + let ty_res = cx.qpath_res(ty_qpath, ty.hir_id); + if let def::Res::PrimTy(prim_ty) = ty_res; + if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_)); + + // check if the second part of the path indeed calls the associated + // function `from_str_radix` + if pathseg.ident.name.as_str() == "from_str_radix"; + + // check if the second argument is a primitive `10` + if arguments.len() == 2; + if let ExprKind::Lit(lit) = &arguments[1].kind; + if let rustc_ast::ast::LitKind::Int(10, _) = lit.node; + + then { + let expr = if let ExprKind::AddrOf(_, _, expr) = &arguments[0].kind { + let ty = cx.typeck_results().expr_ty(expr); + if is_ty_stringish(cx, ty) { + expr + } else { + &arguments[0] + } + } else { + &arguments[0] + }; + + let sugg = Sugg::hir_with_applicability( + cx, + expr, + "", + &mut Applicability::MachineApplicable + ).maybe_par(); + + span_lint_and_sugg( + cx, + FROM_STR_RADIX_10, + exp.span, + "this call to `from_str_radix` can be replaced with a call to `str::parse`", + "try", + format!("{}.parse::<{}>()", sugg, prim_ty.name_str()), + Applicability::MaybeIncorrect + ); + } + } + } +} + +/// Checks if a Ty is `String` or `&str` +fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + is_type_diagnostic_item(cx, ty, sym::string_type) || is_type_diagnostic_item(cx, ty, sym::str) +} diff --git a/src/tools/clippy/clippy_lints/src/functions.rs b/src/tools/clippy/clippy_lints/src/functions.rs new file mode 100644 index 0000000000..c474db06fe --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/functions.rs @@ -0,0 +1,736 @@ +use crate::utils::{ + attr_by_name, attrs::is_proc_macro, is_must_use_ty, is_trait_impl_item, is_type_diagnostic_item, iter_input_pats, + match_def_path, must_use_attr, path_to_local, return_ty, snippet, snippet_opt, span_lint, span_lint_and_help, + span_lint_and_then, trait_ref_of_method, type_is_unsafe_function, +}; +use if_chain::if_chain; +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, QPath}; +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_span::sym; +use rustc_target::spec::abi::Abi; +use rustc_typeck::hir_ty_to_ty; + +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,ignore + /// // Bad + /// pub fn foo(x: *const u8) { + /// println!("{}", unsafe { *x }); + /// } + /// + /// // Good + /// pub unsafe fn foo(x: *const u8) { + /// println!("{}", unsafe { *x }); + /// } + /// ``` + pub NOT_UNSAFE_PTR_ARG_DEREF, + correctness, + "public functions dereferencing raw pointer arguments but not marked `unsafe`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for a [`#[must_use]`] attribute on + /// unit-returning functions and methods. + /// + /// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute + /// + /// **Why is this bad?** Unit values are useless. The attribute is likely + /// a remnant of a refactoring that removed the return type. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + /// #[must_use] + /// fn useless() { } + /// ``` + pub MUST_USE_UNIT, + style, + "`#[must_use]` attribute on a unit-returning function / method" +} + +declare_clippy_lint! { + /// **What it does:** Checks for a [`#[must_use]`] attribute without + /// further information on functions and methods that return a type already + /// marked as `#[must_use]`. + /// + /// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute + /// + /// **Why is this bad?** The attribute isn't needed. Not using the result + /// will already be reported. Alternatively, one can add some text to the + /// attribute to improve the lint message. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + /// #[must_use] + /// fn double_must_use() -> Result<(), ()> { + /// unimplemented!(); + /// } + /// ``` + pub DOUBLE_MUST_USE, + style, + "`#[must_use]` attribute on a `#[must_use]`-returning function / method" +} + +declare_clippy_lint! { + /// **What it does:** Checks for public functions that have no + /// [`#[must_use]`] attribute, but return something not already marked + /// must-use, have no mutable arg and mutate no statics. + /// + /// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute + /// + /// **Why is this bad?** Not bad at all, this lint just shows places where + /// you could add the attribute. + /// + /// **Known problems:** The lint only checks the arguments for mutable + /// types without looking if they are actually changed. On the other hand, + /// it also ignores a broad range of potentially interesting side effects, + /// because we cannot decide whether the programmer intends the function to + /// be called for the side effect or the result. Expect many false + /// positives. At least we don't lint if the result type is unit or already + /// `#[must_use]`. + /// + /// **Examples:** + /// ```rust + /// // this could be annotated with `#[must_use]`. + /// fn id(t: T) -> T { t } + /// ``` + pub MUST_USE_CANDIDATE, + pedantic, + "function or method that could take a `#[must_use]` attribute" +} + +declare_clippy_lint! { + /// **What it does:** Checks for public functions that return a `Result` + /// with an `Err` type of `()`. It suggests using a custom type that + /// implements [`std::error::Error`]. + /// + /// **Why is this bad?** Unit does not implement `Error` and carries no + /// further information about what went wrong. + /// + /// **Known problems:** Of course, this lint assumes that `Result` is used + /// for a fallible operation (which is after all the intended use). However + /// code may opt to (mis)use it as a basic two-variant-enum. In that case, + /// the suggestion is misguided, and the code should use a custom enum + /// instead. + /// + /// **Examples:** + /// ```rust + /// pub fn read_u8() -> Result { Err(()) } + /// ``` + /// should become + /// ```rust,should_panic + /// use std::fmt; + /// + /// #[derive(Debug)] + /// pub struct EndOfStream; + /// + /// impl fmt::Display for EndOfStream { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// write!(f, "End of Stream") + /// } + /// } + /// + /// impl std::error::Error for EndOfStream { } + /// + /// pub fn read_u8() -> Result { Err(EndOfStream) } + ///# fn main() { + ///# read_u8().unwrap(); + ///# } + /// ``` + /// + /// Note that there are crates that simplify creating the error type, e.g. + /// [`thiserror`](https://docs.rs/thiserror). + pub RESULT_UNIT_ERR, + style, + "public function returning `Result` with an `Err` type of `()`" +} + +#[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, + RESULT_UNIT_ERR, +]); + +impl<'tcx> LateLintPass<'tcx> for Functions { + fn check_fn( + &mut self, + cx: &LateContext<'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<'tcx>, item: &'tcx hir::Item<'_>) { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let attr = must_use_attr(attrs); + if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind { + let is_public = cx.access_levels.is_exported(item.hir_id()); + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + if is_public { + check_result_unit_err(cx, &sig.decl, item.span, fn_header_span); + } + if let Some(attr) = attr { + check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr); + return; + } + if is_public && !is_proc_macro(cx.sess(), attrs) && attr_by_name(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<'tcx>, item: &'tcx hir::ImplItem<'_>) { + if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind { + let is_public = cx.access_levels.is_exported(item.hir_id()); + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + if is_public && trait_ref_of_method(cx, item.hir_id()).is_none() { + check_result_unit_err(cx, &sig.decl, item.span, fn_header_span); + } + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let attr = must_use_attr(attrs); + if let Some(attr) = attr { + check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr); + } else if is_public && !is_proc_macro(cx.sess(), 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<'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 is_public = cx.access_levels.is_exported(item.hir_id()); + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + if is_public { + check_result_unit_err(cx, &sig.decl, item.span, fn_header_span); + } + + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let attr = must_use_attr(attrs); + if let Some(attr) = attr { + 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() && is_public && !is_proc_macro(cx.sess(), 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<'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 { + if let Some(i) = line.find("*/") { + line = &line[i + 2..]; + in_comment = false; + continue; + } + } 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, + &format!("this function has too many lines ({}/{})", line_count, self.max_lines), + ) + } + } + + fn check_raw_ptr( + cx: &LateContext<'tcx>, + unsafety: hir::Unsafety, + decl: &'tcx hir::FnDecl<'_>, + body: &'tcx hir::Body<'_>, + hir_id: hir::HirId, + ) { + let expr = &body.value; + if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(hir_id) { + let raw_ptrs = iter_input_pats(decl, body) + .zip(decl.inputs.iter()) + .filter_map(|(arg, ty)| raw_ptr_arg(arg, ty)) + .collect::>(); + + if !raw_ptrs.is_empty() { + let typeck_results = cx.tcx.typeck_body(body.id()); + let mut v = DerefVisitor { + cx, + ptrs: raw_ptrs, + typeck_results, + }; + + intravisit::walk_expr(&mut v, expr); + } + } + } +} + +fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span: Span, fn_header_span: Span) { + if_chain! { + if !in_external_macro(cx.sess(), item_span); + if let hir::FnRetTy::Return(ref ty) = decl.output; + let ty = hir_ty_to_ty(cx.tcx, ty); + if is_type_diagnostic_item(cx, ty, sym::result_type); + if let ty::Adt(_, substs) = ty.kind(); + let err_ty = substs.type_at(1); + if err_ty.is_unit(); + then { + span_lint_and_help( + cx, + RESULT_UNIT_ERR, + fn_header_span, + "this returns a `Result<_, ()>", + None, + "use a custom Error type instead", + ); + } + } +} + +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<'tcx>( + cx: &LateContext<'tcx>, + decl: &'tcx hir::FnDecl<'_>, + body: &'tcx hir::Body<'_>, + item_span: Span, + item_id: hir::HirId, + fn_span: Span, + msg: &str, +) { + if has_mutable_arg(cx, body) + || mutates_static(cx, body) + || in_external_macro(cx.sess(), item_span) + || returns_unit(decl) + || !cx.access_levels.is_exported(item_id) + || is_must_use_ty(cx, return_ty(cx, item_id)) + { + return; + } + span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| { + if let Some(snippet) = snippet_opt(cx, fn_span) { + diag.span_suggestion( + fn_span, + "add the attribute", + format!("#[must_use] {}", snippet), + Applicability::MachineApplicable, + ); + } + }); +} + +fn returns_unit(decl: &hir::FnDecl<'_>) -> bool { + match decl.output { + hir::FnRetTy::DefaultReturn(_) => true, + hir::FnRetTy::Return(ref ty) => match ty.kind { + hir::TyKind::Tup(ref tys) => tys.is_empty(), + hir::TyKind::Never => true, + _ => false, + }, + } +} + +fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool { + let mut tys = FxHashSet::default(); + body.params.iter().any(|param| is_mutable_pat(cx, ¶m.pat, &mut tys)) +} + +fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut FxHashSet) -> bool { + if let hir::PatKind::Wild = pat.kind { + return false; // ignore `_` patterns + } + if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) { + is_mutable_ty(cx, &cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys) + } else { + false + } +} + +static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]]; + +fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut FxHashSet) -> bool { + match *ty.kind() { + // primitive types are never mutable + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false, + ty::Adt(ref adt, ref substs) => { + tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env) + || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path)) + && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)) + }, + ty::Tuple(ref substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)), + ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys), + ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => { + mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys) + }, + // calling something constitutes a side effect, so return true on all callables + // also never calls need not be used, so return true for them, too + _ => true, + } +} + +fn raw_ptr_arg(arg: &hir::Param<'_>, ty: &hir::Ty<'_>) -> Option { + if let (&hir::PatKind::Binding(_, id, _, _), &hir::TyKind::Ptr(_)) = (&arg.pat.kind, &ty.kind) { + Some(id) + } else { + None + } +} + +struct DerefVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + ptrs: FxHashSet, + typeck_results: &'a ty::TypeckResults<'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.typeck_results.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.typeck_results.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::Deref, ref ptr) => self.check_arg(ptr), + _ => (), + } + + intravisit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } +} + +impl<'a, 'tcx> DerefVisitor<'a, 'tcx> { + fn check_arg(&self, ptr: &hir::Expr<'_>) { + if let Some(id) = path_to_local(ptr) { + 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<'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 { + if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id()) + && is_mutable_ty( + self.cx, + self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg), + arg.span, + &mut tys, + ) + && is_mutated_static(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(target) + }, + _ => {}, + } + } + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } +} + +fn is_mutated_static(e: &hir::Expr<'_>) -> bool { + use hir::ExprKind::{Field, Index, Path}; + + match e.kind { + Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)), + Path(_) => true, + Field(ref inner, _) | Index(ref inner, _) => is_mutated_static(inner), + _ => false, + } +} + +fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool { + let mut v = StaticMutVisitor { + cx, + mutates_static: false, + }; + intravisit::walk_expr(&mut v, &body.value); + v.mutates_static +} diff --git a/src/tools/clippy/clippy_lints/src/future_not_send.rs b/src/tools/clippy/clippy_lints/src/future_not_send.rs new file mode 100644 index 0000000000..9e1a8864a3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/future_not_send.rs @@ -0,0 +1,111 @@ +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::subst::Subst; +use rustc_middle::ty::{Opaque, PredicateKind::Trait}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{sym, Span}; +use rustc_trait_selection::traits::error_reporting::suggestions::InferCtxtExt; +use rustc_trait_selection::traits::{self, FulfillmentError, TraitEngine}; + +declare_clippy_lint! { + /// **What it does:** This lint requires Future implementations returned from + /// functions and methods to implement the `Send` marker trait. It is mostly + /// used by library authors (public and internal) that target an audience where + /// multithreaded executors are likely to be used for running these Futures. + /// + /// **Why is this bad?** A Future implementation captures some state that it + /// needs to eventually produce its final value. When targeting a multithreaded + /// executor (which is the norm on non-embedded devices) this means that this + /// state may need to be transported to other threads, in other words the + /// whole Future needs to implement the `Send` marker trait. If it does not, + /// then the resulting Future cannot be submitted to a thread pool in the + /// end user’s code. + /// + /// Especially for generic functions it can be confusing to leave the + /// discovery of this problem to the end user: the reported error location + /// will be far from its cause and can in many cases not even be fixed without + /// modifying the library where the offending Future implementation is + /// produced. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// async fn not_send(bytes: std::rc::Rc<[u8]>) {} + /// ``` + /// Use instead: + /// ```rust + /// async fn is_send(bytes: std::sync::Arc<[u8]>) {} + /// ``` + pub FUTURE_NOT_SEND, + nursery, + "public Futures must be Send" +} + +declare_lint_pass!(FutureNotSend => [FUTURE_NOT_SEND]); + +impl<'tcx> LateLintPass<'tcx> for FutureNotSend { + fn check_fn( + &mut self, + cx: &LateContext<'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.explicit_item_bounds(id); + let mut is_future = false; + for &(p, _span) in preds { + let p = p.subst(cx.tcx, subst); + if let Some(trait_ref) = p.to_opt_poly_trait_ref() { + if Some(trait_ref.value.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.kind().skip_binder() { + db.note(&format!( + "`{}` doesn't implement `{}`", + trait_pred.self_ty(), + trait_pred.trait_ref.print_only_trait_path(), + )); + } + } + }) + }, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/get_last_with_len.rs b/src/tools/clippy/clippy_lints/src/get_last_with_len.rs new file mode 100644 index 0000000000..cdd8a42e7c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/get_last_with_len.rs @@ -0,0 +1,104 @@ +//! 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; +use rustc_span::sym; + +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<'tcx> LateLintPass<'tcx> for GetLastWithLen { + fn check_expr(&mut self, cx: &LateContext<'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.typeck_results().expr_ty(struct_calling_on); + if is_type_diagnostic_item(cx, struct_ty, sym::vec_type); + + // Argument to "get" is a subtraction + if let Some(get_index_arg) = args.get(1); + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Sub, + .. + }, + lhs, + rhs, + ) = &get_index_arg.kind; + + // LHS of subtraction is "x.len()" + if let ExprKind::MethodCall(arg_lhs_path, _, lhs_args, _) = &lhs.kind; + if arg_lhs_path.ident.name == sym!(len); + if let Some(arg_lhs_struct) = lhs_args.get(0); + + // The two vectors referenced (x in x.get(...) and in x.len()) + if SpanlessEq::new(cx).eq_expr(struct_calling_on, arg_lhs_struct); + + // RHS of subtraction is 1 + if let ExprKind::Lit(rhs_lit) = &rhs.kind; + if let LitKind::Int(1, ..) = rhs_lit.node; + + then { + let mut applicability = Applicability::MachineApplicable; + let vec_name = snippet_with_applicability( + cx, + struct_calling_on.span, "vec", + &mut applicability, + ); + + span_lint_and_sugg( + cx, + GET_LAST_WITH_LEN, + expr.span, + &format!("accessing last element with `{0}.get({0}.len() - 1)`", vec_name), + "try", + format!("{}.last()", vec_name), + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/identity_op.rs b/src/tools/clippy/clippy_lints/src/identity_op.rs new file mode 100644 index 0000000000..8501d34770 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/identity_op.rs @@ -0,0 +1,100 @@ +use if_chain::if_chain; +use rustc_hir::{BinOp, 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<'tcx> LateLintPass<'tcx> for IdentityOp { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + return; + } + if let ExprKind::Binary(cmp, ref left, ref right) = e.kind { + if is_allowed(cx, cmp, left, right) { + return; + } + 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); + }, + _ => (), + } + } + } +} + +fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> bool { + // `1 << 0` is a common pattern in bit manipulation code + if_chain! { + if let BinOpKind::Shl = cmp.node; + if let Some(Constant::Int(0)) = constant_simple(cx, cx.typeck_results(), right); + if let Some(Constant::Int(1)) = constant_simple(cx, cx.typeck_results(), left); + then { + return true; + } + } + + false +} + +#[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.typeck_results(), e) { + let check = match *cx.typeck_results().expr_ty(e).kind() { + ty::Int(ity) => unsext(cx.tcx, -1_i128, ity), + ty::Uint(uty) => clip(cx.tcx, !0, uty), + _ => return, + }; + if match m { + 0 => v == 0, + -1 => v == check, + 1 => v == 1, + _ => unreachable!(), + } { + span_lint( + cx, + IDENTITY_OP, + span, + &format!( + "the operation is ineffective. Consider reducing it to `{}`", + snippet(cx, arg, "..") + ), + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/if_let_mutex.rs b/src/tools/clippy/clippy_lints/src/if_let_mutex.rs new file mode 100644 index 0000000000..58511c6d57 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/if_let_mutex.rs @@ -0,0 +1,157 @@ +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<'tcx> LateLintPass<'tcx> for IfLetMutex { + fn check_expr(&mut self, cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>) { + 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<'a, 'tcx> { + mutex_lock_called: bool, + found_mutex: Option<&'tcx Expr<'tcx>>, + cx: &'a LateContext<'tcx>, +} + +impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + if let Some(mutex) = is_mutex_lock_call(self.cx, expr); + then { + self.found_mutex = Some(mutex); + self.mutex_lock_called = true; + return; + } + } + visit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Checks if `Mutex::lock` is called in any of the branches. +pub struct ArmVisitor<'a, 'tcx> { + mutex_lock_called: bool, + found_mutex: Option<&'tcx Expr<'tcx>>, + cx: &'a LateContext<'tcx>, +} + +impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + if_chain! { + if let Some(mutex) = is_mutex_lock_call(self.cx, expr); + then { + self.found_mutex = Some(mutex); + self.mutex_lock_called = true; + return; + } + } + visit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +impl<'tcx, 'l> ArmVisitor<'tcx, 'l> { + fn same_mutex(&self, cx: &LateContext<'_>, op_mutex: &Expr<'_>) -> bool { + self.found_mutex + .map_or(false, |arm_mutex| SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex)) + } +} + +fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if_chain! { + if let ExprKind::MethodCall(path, _span, args, _) = &expr.kind; + if path.ident.as_str() == "lock"; + let ty = cx.typeck_results().expr_ty(&args[0]); + if is_type_diagnostic_item(cx, ty, sym!(mutex_type)); + then { + Some(&args[0]) + } else { + None + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/if_let_some_result.rs b/src/tools/clippy/clippy_lints/src/if_let_some_result.rs new file mode 100644 index 0000000000..1194bd7e55 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/if_let_some_result.rs @@ -0,0 +1,72 @@ +use crate::utils::{is_type_diagnostic_item, method_chain_args, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, MatchSource, PatKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +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<'tcx> LateLintPass<'tcx> for OkIfLet { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { //begin checking variables + if let ExprKind::Match(ref op, ref body, MatchSource::IfLetDesugar { .. }) = expr.kind; //test if expr is if let + if let ExprKind::MethodCall(_, ok_span, ref result_types, _) = op.kind; //check is expr.ok() has type Result.ok(, _) + if let PatKind::TupleStruct(QPath::Resolved(_, ref x), ref y, _) = body[0].pat.kind; //get operation + if method_chain_args(op, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized; + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&result_types[0]), sym::result_type); + if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some"; + + then { + let mut applicability = Applicability::MachineApplicable; + let some_expr_string = snippet_with_applicability(cx, y[0].span, "", &mut applicability); + let trimmed_ok = snippet_with_applicability(cx, op.span.until(ok_span), "", &mut applicability); + let sugg = format!( + "if let Ok({}) = {}", + some_expr_string, + trimmed_ok.trim().trim_end_matches('.'), + ); + span_lint_and_sugg( + cx, + IF_LET_SOME_RESULT, + expr.span.with_hi(op.span.hi()), + "matching on `Some` with `ok()` is redundant", + &format!("consider matching on `Ok({})` and removing the call to `ok` instead", some_expr_string), + sugg, + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/if_not_else.rs b/src/tools/clippy/clippy_lints/src/if_not_else.rs new file mode 100644 index 0000000000..b86d2e7665 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/if_not_else.rs @@ -0,0 +1,83 @@ +//! lint on if branches that could be swapped so no `!` operation is necessary +//! on the condition + +use rustc_ast::ast::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::span_lint_and_help; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `!` or `!=` in an if condition with an + /// else branch. + /// + /// **Why is this bad?** Negations reduce the readability of statements. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let v: Vec = vec![]; + /// # fn a() {} + /// # fn b() {} + /// if !v.is_empty() { + /// a() + /// } else { + /// b() + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// # let v: Vec = vec![]; + /// # fn a() {} + /// # fn b() {} + /// if v.is_empty() { + /// b() + /// } else { + /// a() + /// } + /// ``` + pub IF_NOT_ELSE, + pedantic, + "`if` branches that could be swapped so no negation operation is necessary on the condition" +} + +declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]); + +impl EarlyLintPass for IfNotElse { + fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) { + if in_external_macro(cx.sess(), item.span) { + return; + } + if let ExprKind::If(ref cond, _, Some(ref els)) = item.kind { + if let ExprKind::Block(..) = els.kind { + match cond.kind { + ExprKind::Unary(UnOp::Not, _) => { + span_lint_and_help( + cx, + IF_NOT_ELSE, + item.span, + "unnecessary boolean `not` operation", + None, + "remove the `!` and swap the blocks of the `if`/`else`", + ); + }, + ExprKind::Binary(ref kind, _, _) if kind.node == BinOpKind::Ne => { + span_lint_and_help( + cx, + IF_NOT_ELSE, + item.span, + "unnecessary `!=` operation", + None, + "change to `==` and swap the blocks of the `if`/`else`", + ); + }, + _ => (), + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/implicit_return.rs b/src/tools/clippy/clippy_lints/src/implicit_return.rs new file mode 100644 index 0000000000..b4f814e1dc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/implicit_return.rs @@ -0,0 +1,145 @@ +use crate::utils::{match_panic_def_id, 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(.., Some(break_expr)) = &expr.kind; + 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::If(.., if_expr, else_expr) => { + expr_match(cx, if_expr); + + if let Some(else_expr) = else_expr { + expr_match(cx, else_expr); + } + }, + 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.qpath_res(qpath, expr.hir_id).opt_def_id(); + if match_panic_def_id(cx, path_def_id); + then { } + else { + lint(cx, expr.span, expr.span, LINT_RETURN) + } + } + }, + // everything else is missing `return` + _ => lint(cx, expr.span, expr.span, LINT_RETURN), + } +} + +impl<'tcx> LateLintPass<'tcx> for ImplicitReturn { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: FnKind<'tcx>, + _: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + span: Span, + _: HirId, + ) { + if span.from_expansion() { + return; + } + let body = cx.tcx.hir().body(body.id()); + if cx.typeck_results().expr_ty(&body.value).is_unit() { + return; + } + expr_match(cx, &body.value); + } +} diff --git a/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs new file mode 100644 index 0000000000..16e162badb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs @@ -0,0 +1,165 @@ +use crate::utils::{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; + /// } + /// + /// // 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<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if in_macro(expr.span) { + return; + } + if_chain! { + if let ExprKind::If(cond, then, None) = &expr.kind; + + // 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(QPath::Resolved(_, ref ares_path)) = target.kind; + + 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.typeck_results().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.typeck_results().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'), + Applicability::MachineApplicable, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs b/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs new file mode 100644 index 0000000000..48aef74e4d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs @@ -0,0 +1,133 @@ +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::Applicability; +use rustc_hir::{self as hir, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Symbol; + +use if_chain::if_chain; + +use crate::utils::{snippet, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for struct constructors where the order of the field init + /// shorthand in the constructor is inconsistent with the order in the struct definition. + /// + /// **Why is this bad?** Since the order of fields in a constructor doesn't affect the + /// resulted instance as the below example indicates, + /// + /// ```rust + /// #[derive(Debug, PartialEq, Eq)] + /// struct Foo { + /// x: i32, + /// y: i32, + /// } + /// let x = 1; + /// let y = 2; + /// + /// // This assertion never fails. + /// assert_eq!(Foo { x, y }, Foo { y, x }); + /// ``` + /// + /// inconsistent order means nothing and just decreases readability and consistency. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// struct Foo { + /// x: i32, + /// y: i32, + /// } + /// let x = 1; + /// let y = 2; + /// Foo { y, x }; + /// ``` + /// + /// Use instead: + /// ```rust + /// # struct Foo { + /// # x: i32, + /// # y: i32, + /// # } + /// # let x = 1; + /// # let y = 2; + /// Foo { x, y }; + /// ``` + pub INCONSISTENT_STRUCT_CONSTRUCTOR, + style, + "the order of the field init shorthand is inconsistent with the order in the struct definition" +} + +declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]); + +impl LateLintPass<'_> for InconsistentStructConstructor { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if_chain! { + if let ExprKind::Struct(qpath, fields, base) = expr.kind; + let ty = cx.typeck_results().expr_ty(expr); + if let Some(adt_def) = ty.ty_adt_def(); + if adt_def.is_struct(); + if let Some(variant) = adt_def.variants.iter().next(); + if fields.iter().all(|f| f.is_shorthand); + then { + let mut def_order_map = FxHashMap::default(); + for (idx, field) in variant.fields.iter().enumerate() { + def_order_map.insert(field.ident.name, idx); + } + + if is_consistent_order(fields, &def_order_map) { + return; + } + + let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect(); + ordered_fields.sort_unstable_by_key(|id| def_order_map[id]); + + let mut fields_snippet = String::new(); + let (last_ident, idents) = ordered_fields.split_last().unwrap(); + for ident in idents { + fields_snippet.push_str(&format!("{}, ", ident)); + } + fields_snippet.push_str(&last_ident.to_string()); + + let base_snippet = if let Some(base) = base { + format!(", ..{}", snippet(cx, base.span, "..")) + } else { + String::new() + }; + + let sugg = format!("{} {{ {}{} }}", + snippet(cx, qpath.span(), ".."), + fields_snippet, + base_snippet, + ); + + span_lint_and_sugg( + cx, + INCONSISTENT_STRUCT_CONSTRUCTOR, + expr.span, + "inconsistent struct constructor", + "try", + sugg, + Applicability::MachineApplicable, + ) + } + } + } +} + +// Check whether the order of the fields in the constructor is consistent with the order in the +// definition. +fn is_consistent_order<'tcx>(fields: &'tcx [hir::ExprField<'tcx>], def_order_map: &FxHashMap) -> bool { + let mut cur_idx = usize::MIN; + for f in fields { + let next_idx = def_order_map[&f.ident.name]; + if cur_idx > next_idx { + return false; + } + cur_idx = next_idx; + } + + true +} diff --git a/src/tools/clippy/clippy_lints/src/indexing_slicing.rs b/src/tools/clippy/clippy_lints/src/indexing_slicing.rs new file mode 100644 index 0000000000..c919ec097a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/indexing_slicing.rs @@ -0,0 +1,197 @@ +//! 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<'tcx> LateLintPass<'tcx> for IndexingSlicing { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Index(ref array, ref index) = &expr.kind { + let ty = cx.typeck_results().expr_ty(array).peel_refs(); + if let Some(range) = higher::range(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.typeck_results(), 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<'tcx>( + cx: &LateContext<'tcx>, + range: higher::Range<'_>, + array_size: u128, +) -> (Option, Option) { + let s = range + .start + .map(|expr| constant(cx, cx.typeck_results(), 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.typeck_results(), expr).map(|(c, _)| c)); + let end = match e { + Some(Some(Constant::Int(x))) => { + if range.limits == RangeLimits::Closed { + Some(x + 1) + } else { + Some(x) + } + }, + Some(_) => None, + None => Some(array_size), + }; + + (start, end) +} diff --git a/src/tools/clippy/clippy_lints/src/infinite_iter.rs b/src/tools/clippy/clippy_lints/src/infinite_iter.rs new file mode 100644 index 0000000000..7040ac3191 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/infinite_iter.rs @@ -0,0 +1,250 @@ +use rustc_hir::{BorrowKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{get_trait_def_id, higher, implements_trait, match_qpath, match_type, paths, span_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for iteration that is guaranteed to be infinite. + /// + /// **Why is this bad?** While there may be places where this is acceptable + /// (e.g., in event streams), in most cases this is simply an error. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// use std::iter; + /// + /// iter::repeat(1_u8).collect::>(); + /// ``` + pub INFINITE_ITER, + correctness, + "infinite iteration" +} + +declare_clippy_lint! { + /// **What it does:** Checks for iteration that may be infinite. + /// + /// **Why is this bad?** While there may be places where this is acceptable + /// (e.g., in event streams), in most cases this is simply an error. + /// + /// **Known problems:** The code may have a condition to stop iteration, but + /// this lint is not clever enough to analyze it. + /// + /// **Example:** + /// ```rust + /// let infinite_iter = 0..; + /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5)); + /// ``` + pub MAYBE_INFINITE_ITER, + pedantic, + "possible infinite iteration" +} + +declare_lint_pass!(InfiniteIter => [INFINITE_ITER, MAYBE_INFINITE_ITER]); + +impl<'tcx> LateLintPass<'tcx> for InfiniteIter { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let (lint, msg) = match complete_infinite_iter(cx, expr) { + Infinite => (INFINITE_ITER, "infinite iteration detected"), + MaybeInfinite => (MAYBE_INFINITE_ITER, "possible infinite iteration detected"), + Finite => { + return; + }, + }; + span_lint(cx, lint, expr.span, msg) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum Finiteness { + Infinite, + MaybeInfinite, + Finite, +} + +use self::Finiteness::{Finite, Infinite, MaybeInfinite}; + +impl Finiteness { + #[must_use] + fn and(self, b: Self) -> Self { + match (self, b) { + (Finite, _) | (_, Finite) => Finite, + (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite, + _ => Infinite, + } + } + + #[must_use] + fn or(self, b: Self) -> Self { + match (self, b) { + (Infinite, _) | (_, Infinite) => Infinite, + (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite, + _ => Finite, + } + } +} + +impl From for Finiteness { + #[must_use] + fn from(b: bool) -> Self { + if b { Infinite } else { Finite } + } +} + +/// This tells us what to look for to know if the iterator returned by +/// this method is infinite +#[derive(Copy, Clone)] +enum Heuristic { + /// infinite no matter what + Always, + /// infinite if the first argument is + First, + /// infinite if any of the supplied arguments is + Any, + /// infinite if all of the supplied arguments are + All, +} + +use self::Heuristic::{All, Always, Any, First}; + +/// a slice of (method name, number of args, heuristic, bounds) tuples +/// that will be used to determine whether the method in question +/// returns an infinite or possibly infinite iterator. The finiteness +/// is an upper bound, e.g., some methods can return a possibly +/// infinite iterator at worst, e.g., `take_while`. +const HEURISTICS: [(&str, usize, Heuristic, Finiteness); 19] = [ + ("zip", 2, All, Infinite), + ("chain", 2, Any, Infinite), + ("cycle", 1, Always, Infinite), + ("map", 2, First, Infinite), + ("by_ref", 1, First, Infinite), + ("cloned", 1, First, Infinite), + ("rev", 1, First, Infinite), + ("inspect", 1, First, Infinite), + ("enumerate", 1, First, Infinite), + ("peekable", 2, First, Infinite), + ("fuse", 1, First, Infinite), + ("skip", 2, First, Infinite), + ("skip_while", 1, First, Infinite), + ("filter", 2, First, Infinite), + ("filter_map", 2, First, Infinite), + ("flat_map", 2, First, Infinite), + ("unzip", 1, First, Infinite), + ("take_while", 2, First, MaybeInfinite), + ("scan", 3, First, MaybeInfinite), +]; + +fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { + match expr.kind { + ExprKind::MethodCall(ref method, _, ref args, _) => { + for &(name, len, heuristic, cap) in &HEURISTICS { + if method.ident.name.as_str() == name && args.len() == len { + return (match heuristic { + Always => Infinite, + First => is_infinite(cx, &args[0]), + Any => is_infinite(cx, &args[0]).or(is_infinite(cx, &args[1])), + All => is_infinite(cx, &args[0]).and(is_infinite(cx, &args[1])), + }) + .and(cap); + } + } + if method.ident.name == sym!(flat_map) && args.len() == 2 { + if let ExprKind::Closure(_, _, body_id, _, _) = args[1].kind { + let body = cx.tcx.hir().body(body_id); + return is_infinite(cx, &body.value); + } + } + Finite + }, + ExprKind::Block(ref block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)), + ExprKind::Box(ref e) | ExprKind::AddrOf(BorrowKind::Ref, _, ref e) => is_infinite(cx, e), + ExprKind::Call(ref path, _) => { + if let ExprKind::Path(ref qpath) = path.kind { + match_qpath(qpath, &paths::REPEAT).into() + } else { + Finite + } + }, + ExprKind::Struct(..) => higher::range(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.typeck_results().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.typeck_results().expr_ty(expr); + if INFINITE_COLLECTORS.iter().any(|path| match_type(cx, ty, path)) { + return is_infinite(cx, &args[0]); + } + } + }, + ExprKind::Binary(op, ref l, ref r) => { + if op.node.is_comparison() { + return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite); + } + }, // TODO: ExprKind::Loop + Match + _ => (), + } + Finite +} diff --git a/src/tools/clippy/clippy_lints/src/inherent_impl.rs b/src/tools/clippy/clippy_lints/src/inherent_impl.rs new file mode 100644 index 0000000000..005c461f10 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/inherent_impl.rs @@ -0,0 +1,89 @@ +//! 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, Impl, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for multiple inherent implementations of a struct + /// + /// **Why is this bad?** Splitting the implementation of a type makes the code harder to navigate. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// struct X; + /// impl X { + /// fn one() {} + /// } + /// impl X { + /// fn other() {} + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// struct X; + /// impl X { + /// fn one() {} + /// fn other() {} + /// } + /// ``` + pub MULTIPLE_INHERENT_IMPL, + restriction, + "Multiple inherent impl that could be grouped" +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Default)] +pub struct MultipleInherentImpl { + impls: FxHashMap, +} + +impl_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); + +impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { + fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Impl(Impl { + ref generics, + of_trait: None, + .. + }) = item.kind + { + // Remember for each inherent implementation encountered 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.def_id.to_def_id(), item.span); + } + } + } + + fn check_crate_post(&mut self, cx: &LateContext<'tcx>, krate: &'tcx Crate<'_>) { + if !krate.items.is_empty() { + // Retrieve all inherent implementations from the crate, grouped by type + for impls in cx.tcx.crate_inherent_impls(def_id::LOCAL_CRATE).inherent_impls.values() { + // Filter out implementations that have generic params (type or lifetime) + let mut impl_spans = impls.iter().filter_map(|impl_def| self.impls.get(impl_def)); + if let Some(initial_span) = impl_spans.next() { + impl_spans.for_each(|additional_span| { + span_lint_and_then( + cx, + MULTIPLE_INHERENT_IMPL, + *additional_span, + "multiple implementations of this structure", + |diag| { + diag.span_note(*initial_span, "first implementation here"); + }, + ) + }) + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/inherent_to_string.rs b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs new file mode 100644 index 0000000000..c1f3e1d9d6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs @@ -0,0 +1,157 @@ +use if_chain::if_chain; +use rustc_hir::{ImplItem, ImplItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +use crate::utils::{ + get_trait_def_id, implements_trait, is_type_diagnostic_item, paths, return_ty, span_lint_and_help, + trait_ref_of_method, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for the definition of inherent methods with a signature of `to_string(&self) -> String`. + /// + /// **Why is this bad?** This method is also implicitly defined if a type implements the `Display` trait. As the functionality of `Display` is much more versatile, it should be preferred. + /// + /// **Known problems:** None + /// + /// ** Example:** + /// + /// ```rust + /// // Bad + /// pub struct A; + /// + /// impl A { + /// pub fn to_string(&self) -> String { + /// "I am A".to_string() + /// } + /// } + /// ``` + /// + /// ```rust + /// // Good + /// use std::fmt; + /// + /// pub struct A; + /// + /// impl fmt::Display for A { + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// write!(f, "I am A") + /// } + /// } + /// ``` + pub INHERENT_TO_STRING, + style, + "type implements inherent method `to_string()`, but should instead implement the `Display` trait" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the definition of inherent methods with a signature of `to_string(&self) -> String` and if the type implementing this method also implements the `Display` trait. + /// + /// **Why is this bad?** This method is also implicitly defined if a type implements the `Display` trait. The less versatile inherent method will then shadow the implementation introduced by `Display`. + /// + /// **Known problems:** None + /// + /// ** Example:** + /// + /// ```rust + /// // Bad + /// use std::fmt; + /// + /// pub struct A; + /// + /// impl A { + /// pub fn to_string(&self) -> String { + /// "I am A".to_string() + /// } + /// } + /// + /// impl fmt::Display for A { + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// write!(f, "I am A, too") + /// } + /// } + /// ``` + /// + /// ```rust + /// // Good + /// use std::fmt; + /// + /// pub struct A; + /// + /// impl fmt::Display for A { + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// write!(f, "I am A") + /// } + /// } + /// ``` + pub INHERENT_TO_STRING_SHADOW_DISPLAY, + correctness, + "type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait" +} + +declare_lint_pass!(InherentToString => [INHERENT_TO_STRING, INHERENT_TO_STRING_SHADOW_DISPLAY]); + +impl<'tcx> LateLintPass<'tcx> for InherentToString { + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { + if impl_item.span.from_expansion() { + return; + } + + if_chain! { + // Check if item is a method, called to_string and has a parameter 'self' + if let ImplItemKind::Fn(ref signature, _) = impl_item.kind; + if impl_item.ident.name.as_str() == "to_string"; + let decl = &signature.decl; + if decl.implicit_self.has_implicit_self(); + if decl.inputs.len() == 1; + if impl_item.generics.params.is_empty(); + + // Check if return type is String + if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::string_type); + + // Filters instances of to_string which are required by a trait + if trait_ref_of_method(cx, impl_item.hir_id()).is_none(); + + then { + show_lint(cx, impl_item); + } + } + } +} + +fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) { + let display_trait_id = get_trait_def_id(cx, &paths::DISPLAY_TRAIT).expect("Failed to get trait ID of `Display`!"); + + // Get the real type of 'self' + let self_type = cx.tcx.fn_sig(item.def_id).input(0); + let self_type = self_type.skip_binder().peel_refs(); + + // Emit either a warning or an error + if implements_trait(cx, self_type, display_trait_id, &[]) { + span_lint_and_help( + cx, + INHERENT_TO_STRING_SHADOW_DISPLAY, + item.span, + &format!( + "type `{}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`", + self_type.to_string() + ), + None, + &format!("remove the inherent method from type `{}`", self_type.to_string()), + ); + } else { + span_lint_and_help( + cx, + INHERENT_TO_STRING, + item.span, + &format!( + "implementation of inherent method `to_string(&self) -> String` for type `{}`", + self_type.to_string() + ), + None, + &format!("implement trait `Display` for type `{}` instead", self_type.to_string()), + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs b/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs new file mode 100644 index 0000000000..00acbd6cc3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs @@ -0,0 +1,59 @@ +//! checks for `#[inline]` on trait methods without bodies + +use crate::utils::span_lint_and_then; +use crate::utils::sugg::DiagnosticBuilderExt; +use rustc_ast::ast::Attribute; +use rustc_errors::Applicability; +use rustc_hir::{TraitFn, TraitItem, TraitItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{sym, Symbol}; + +declare_clippy_lint! { + /// **What it does:** Checks for `#[inline]` on trait methods without bodies + /// + /// **Why is this bad?** Only implementations of trait methods may be inlined. + /// The inline attribute is ignored for trait methods without bodies. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// trait Animal { + /// #[inline] + /// fn name(&self) -> &'static str; + /// } + /// ``` + pub INLINE_FN_WITHOUT_BODY, + correctness, + "use of `#[inline]` on trait methods without bodies" +} + +declare_lint_pass!(InlineFnWithoutBody => [INLINE_FN_WITHOUT_BODY]); + +impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody { + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + check_attrs(cx, item.ident.name, attrs); + } + } +} + +fn check_attrs(cx: &LateContext<'_>, name: Symbol, attrs: &[Attribute]) { + for attr in attrs { + if !attr.has_name(sym::inline) { + continue; + } + + span_lint_and_then( + cx, + INLINE_FN_WITHOUT_BODY, + attr.span, + &format!("use of `#[inline]` on trait method `{}` which has no body", name), + |diag| { + diag.suggest_remove_item(cx, attr.span, "remove", Applicability::MachineApplicable); + }, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/int_plus_one.rs b/src/tools/clippy/clippy_lints/src/int_plus_one.rs new file mode 100644 index 0000000000..260b8988d3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/int_plus_one.rs @@ -0,0 +1,171 @@ +//! lint on blocks unnecessarily using >= with a + 1 or - 1 + +use rustc_ast::ast::{BinOpKind, Expr, ExprKind, Lit, LitKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{snippet_opt, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `x >= y + 1` or `x - 1 >= y` (and `<=`) in a block + /// + /// **Why is this bad?** Readability -- better to use `> y` instead of `>= y + 1`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// # let y = 1; + /// if x >= y + 1 {} + /// ``` + /// + /// Could be written as: + /// + /// ```rust + /// # let x = 1; + /// # let y = 1; + /// if x > y {} + /// ``` + pub INT_PLUS_ONE, + complexity, + "instead of using `x >= y + 1`, use `x > y`" +} + +declare_lint_pass!(IntPlusOne => [INT_PLUS_ONE]); + +// cases: +// BinOpKind::Ge +// x >= y + 1 +// x - 1 >= y +// +// BinOpKind::Le +// x + 1 <= y +// x <= y - 1 + +#[derive(Copy, Clone)] +enum Side { + Lhs, + Rhs, +} + +impl IntPlusOne { + #[allow(clippy::cast_sign_loss)] + fn check_lit(lit: &Lit, target_value: i128) -> bool { + if let LitKind::Int(value, ..) = lit.kind { + return value == (target_value as u128); + } + false + } + + fn check_binop(cx: &EarlyContext<'_>, binop: BinOpKind, lhs: &Expr, rhs: &Expr) -> Option { + match (binop, &lhs.kind, &rhs.kind) { + // case where `x - 1 >= ...` or `-1 + x >= ...` + (BinOpKind::Ge, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _) => { + match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) { + // `-1 + x` + (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => { + Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs) + }, + // `x - 1` + (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs) + }, + _ => None, + } + }, + // case where `... >= y + 1` or `... >= 1 + y` + (BinOpKind::Ge, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs)) + if rhskind.node == BinOpKind::Add => + { + match (&rhslhs.kind, &rhsrhs.kind) { + // `y + 1` and `1 + y` + (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs) + }, + (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs) + }, + _ => None, + } + } + // case where `x + 1 <= ...` or `1 + x <= ...` + (BinOpKind::Le, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _) + if lhskind.node == BinOpKind::Add => + { + match (&lhslhs.kind, &lhsrhs.kind) { + // `1 + x` and `x + 1` + (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs) + }, + (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs) + }, + _ => None, + } + } + // case where `... >= y - 1` or `... >= -1 + y` + (BinOpKind::Le, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs)) => { + match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) { + // `-1 + y` + (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => { + Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs) + }, + // `y - 1` + (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs) + }, + _ => None, + } + }, + _ => None, + } + } + + fn generate_recommendation( + cx: &EarlyContext<'_>, + binop: BinOpKind, + node: &Expr, + other_side: &Expr, + side: Side, + ) -> Option { + let binop_string = match binop { + BinOpKind::Ge => ">", + BinOpKind::Le => "<", + _ => return None, + }; + if let Some(snippet) = snippet_opt(cx, node.span) { + if let Some(other_side_snippet) = snippet_opt(cx, other_side.span) { + let rec = match side { + Side::Lhs => Some(format!("{} {} {}", snippet, binop_string, other_side_snippet)), + Side::Rhs => Some(format!("{} {} {}", other_side_snippet, binop_string, snippet)), + }; + return rec; + } + } + None + } + + fn emit_warning(cx: &EarlyContext<'_>, block: &Expr, recommendation: String) { + span_lint_and_sugg( + cx, + INT_PLUS_ONE, + block.span, + "unnecessary `>= y + 1` or `x - 1 >=`", + "change it to", + recommendation, + Applicability::MachineApplicable, // snippet + ); + } +} + +impl EarlyLintPass for IntPlusOne { + fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) { + if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind { + if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) { + Self::emit_warning(cx, item, rec); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/integer_division.rs b/src/tools/clippy/clippy_lints/src/integer_division.rs new file mode 100644 index 0000000000..39b4605e72 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/integer_division.rs @@ -0,0 +1,59 @@ +use crate::utils::span_lint_and_help; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for division of integers + /// + /// **Why is this bad?** When outside of some very specific algorithms, + /// integer division is very often a mistake because it discards the + /// remainder. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// let x = 3 / 2; + /// println!("{}", x); + /// + /// // Good + /// let x = 3f32 / 2f32; + /// println!("{}", x); + /// ``` + pub INTEGER_DIVISION, + restriction, + "integer division may cause loss of precision" +} + +declare_lint_pass!(IntegerDivision => [INTEGER_DIVISION]); + +impl<'tcx> LateLintPass<'tcx> for IntegerDivision { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if is_integer_division(cx, expr) { + span_lint_and_help( + cx, + INTEGER_DIVISION, + expr.span, + "integer division", + None, + "division of integers may cause loss of precision. consider using floats", + ); + } + } +} + +fn is_integer_division<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) -> bool { + if_chain! { + if let hir::ExprKind::Binary(binop, left, right) = &expr.kind; + if let hir::BinOpKind::Div = &binop.node; + then { + let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right)); + return left_ty.is_integral() && right_ty.is_integral(); + } + } + + false +} diff --git a/src/tools/clippy/clippy_lints/src/items_after_statements.rs b/src/tools/clippy/clippy_lints/src/items_after_statements.rs new file mode 100644 index 0000000000..0927d21844 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/items_after_statements.rs @@ -0,0 +1,88 @@ +//! lint when items are used after statements + +use crate::utils::span_lint; +use rustc_ast::ast::{Block, ItemKind, StmtKind}; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for items declared after some statement in a block. + /// + /// **Why is this bad?** Items live for the entire scope they are declared + /// in. But statements are processed in order. This might cause confusion as + /// it's hard to figure out which item is meant in a statement. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// fn foo() { + /// println!("cake"); + /// } + /// + /// fn main() { + /// foo(); // prints "foo" + /// fn foo() { + /// println!("foo"); + /// } + /// foo(); // prints "foo" + /// } + /// ``` + /// + /// ```rust + /// // Good + /// fn foo() { + /// println!("cake"); + /// } + /// + /// fn main() { + /// fn foo() { + /// println!("foo"); + /// } + /// foo(); // prints "foo" + /// foo(); // prints "foo" + /// } + /// ``` + pub ITEMS_AFTER_STATEMENTS, + pedantic, + "blocks where an item comes after a statement" +} + +declare_lint_pass!(ItemsAfterStatements => [ITEMS_AFTER_STATEMENTS]); + +impl EarlyLintPass for ItemsAfterStatements { + fn check_block(&mut self, cx: &EarlyContext<'_>, item: &Block) { + if in_external_macro(cx.sess(), item.span) { + return; + } + + // skip initial items and trailing semicolons + let stmts = item + .stmts + .iter() + .map(|stmt| &stmt.kind) + .skip_while(|s| matches!(**s, StmtKind::Item(..) | StmtKind::Empty)); + + // lint on all further items + for stmt in stmts { + if let StmtKind::Item(ref it) = *stmt { + if in_external_macro(cx.sess(), it.span) { + return; + } + if let ItemKind::MacroDef(..) = it.kind { + // do not lint `macro_rules`, but continue processing further statements + continue; + } + span_lint( + cx, + ITEMS_AFTER_STATEMENTS, + it.span, + "adding items after statements is confusing, since items exist from the \ + start of the scope", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/large_const_arrays.rs b/src/tools/clippy/clippy_lints/src/large_const_arrays.rs new file mode 100644 index 0000000000..a76595ed08 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/large_const_arrays.rs @@ -0,0 +1,84 @@ +use crate::rustc_target::abi::LayoutOf; +use crate::utils::span_lint_and_then; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::interpret::ConstValue; +use rustc_middle::ty::{self, ConstKind}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{BytePos, Pos, Span}; +use rustc_typeck::hir_ty_to_ty; + +declare_clippy_lint! { + /// **What it does:** Checks for large `const` arrays that should + /// be defined as `static` instead. + /// + /// **Why is this bad?** Performance: const variables are inlined upon use. + /// Static items result in only one instance and has a fixed location in memory. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// pub const a = [0u32; 1_000_000]; + /// + /// // Good + /// pub static a = [0u32; 1_000_000]; + /// ``` + pub LARGE_CONST_ARRAYS, + perf, + "large non-scalar const array may cause performance overhead" +} + +pub struct LargeConstArrays { + maximum_allowed_size: u64, +} + +impl LargeConstArrays { + #[must_use] + pub fn new(maximum_allowed_size: u64) -> Self { + Self { maximum_allowed_size } + } +} + +impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]); + +impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if_chain! { + if !item.span.from_expansion(); + if let ItemKind::Const(hir_ty, _) = &item.kind; + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + if let ty::Array(element_type, cst) = ty.kind(); + if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val; + if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); + if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes()); + if self.maximum_allowed_size < element_count * element_size; + + then { + let hi_pos = item.ident.span.lo() - BytePos::from_usize(1); + let sugg_span = Span::new( + hi_pos - BytePos::from_usize("const".len()), + hi_pos, + item.span.ctxt(), + ); + span_lint_and_then( + cx, + LARGE_CONST_ARRAYS, + item.span, + "large array defined as const", + |diag| { + diag.span_suggestion( + sugg_span, + "make this a static item", + "static".to_string(), + Applicability::MachineApplicable, + ); + } + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/large_enum_variant.rs b/src/tools/clippy/clippy_lints/src/large_enum_variant.rs new file mode 100644 index 0000000000..ab4cb33612 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/large_enum_variant.rs @@ -0,0 +1,137 @@ +//! lint when there is a large size difference between variants on an enum + +use crate::utils::{snippet_opt, span_lint_and_then}; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind, VariantData}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_target::abi::LayoutOf; + +declare_clippy_lint! { + /// **What it does:** Checks for large size differences between variants on + /// `enum`s. + /// + /// **Why is this bad?** Enum size is bounded by the largest variant. Having a + /// large variant can penalize the memory layout of that enum. + /// + /// **Known problems:** This lint obviously cannot take the distribution of + /// variants in your running program into account. It is possible that the + /// smaller variants make up less than 1% of all instances, in which case + /// the overhead is negligible and the boxing is counter-productive. Always + /// measure the change this lint suggests. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// enum Test { + /// A(i32), + /// B([i32; 8000]), + /// } + /// + /// // Possibly better + /// enum Test2 { + /// A(i32), + /// B(Box<[i32; 8000]>), + /// } + /// ``` + pub LARGE_ENUM_VARIANT, + perf, + "large size difference between variants on an enum" +} + +#[derive(Copy, Clone)] +pub struct LargeEnumVariant { + maximum_size_difference_allowed: u64, +} + +impl LargeEnumVariant { + #[must_use] + pub fn new(maximum_size_difference_allowed: u64) -> Self { + Self { + maximum_size_difference_allowed, + } + } +} + +impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]); + +impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if in_external_macro(cx.tcx.sess, item.span) { + return; + } + if let ItemKind::Enum(ref def, _) = item.kind { + let ty = cx.tcx.type_of(item.def_id); + let adt = ty.ty_adt_def().expect("already checked whether this is an enum"); + + let mut largest_variant: Option<(_, _)> = None; + let mut second_variant: Option<(_, _)> = None; + + for (i, variant) in adt.variants.iter().enumerate() { + let size: u64 = variant + .fields + .iter() + .filter_map(|f| { + let ty = cx.tcx.type_of(f.did); + // don't count generics by filtering out everything + // that does not have a layout + cx.layout_of(ty).ok().map(|l| l.size.bytes()) + }) + .sum(); + + let grouped = (size, (i, variant)); + + if grouped.0 >= largest_variant.map_or(0, |x| x.0) { + second_variant = largest_variant; + largest_variant = Some(grouped); + } + } + + if let (Some(largest), Some(second)) = (largest_variant, second_variant) { + let difference = largest.0 - second.0; + + if difference > self.maximum_size_difference_allowed { + let (i, variant) = largest.1; + + let help_text = "consider boxing the large fields to reduce the total size of the enum"; + span_lint_and_then( + cx, + LARGE_ENUM_VARIANT, + def.variants[i].span, + "large size difference between variants", + |diag| { + diag.span_label( + def.variants[(largest.1).0].span, + &format!("this variant is {} bytes", largest.0), + ); + diag.span_note( + def.variants[(second.1).0].span, + &format!("and the second-largest variant is {} bytes:", second.0), + ); + if variant.fields.len() == 1 { + let span = match def.variants[i].data { + VariantData::Struct(ref fields, ..) | VariantData::Tuple(ref fields, ..) => { + fields[0].ty.span + }, + VariantData::Unit(..) => unreachable!(), + }; + if let Some(snip) = snippet_opt(cx, span) { + diag.span_suggestion( + span, + help_text, + format!("Box<{}>", snip), + Applicability::MaybeIncorrect, + ); + return; + } + } + diag.span_help(def.variants[i].span, help_text); + }, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs b/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs new file mode 100644 index 0000000000..9a448ab125 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs @@ -0,0 +1,68 @@ +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::interpret::ConstValue; +use rustc_middle::ty::{self, ConstKind}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +use if_chain::if_chain; + +use crate::rustc_target::abi::LayoutOf; +use crate::utils::{snippet, span_lint_and_help}; + +declare_clippy_lint! { + /// **What it does:** Checks for local arrays that may be too large. + /// + /// **Why is this bad?** Large local arrays may cause stack overflow. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// let a = [0u32; 1_000_000]; + /// ``` + pub LARGE_STACK_ARRAYS, + pedantic, + "allocating large arrays on stack may cause stack overflow" +} + +pub struct LargeStackArrays { + maximum_allowed_size: u64, +} + +impl LargeStackArrays { + #[must_use] + pub fn new(maximum_allowed_size: u64) -> Self { + Self { maximum_allowed_size } + } +} + +impl_lint_pass!(LargeStackArrays => [LARGE_STACK_ARRAYS]); + +impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::Repeat(_, _) = expr.kind; + if let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind(); + if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val; + if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); + if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes()); + if self.maximum_allowed_size < element_count * element_size; + then { + span_lint_and_help( + cx, + LARGE_STACK_ARRAYS, + expr.span, + &format!( + "allocating a local array larger than {} bytes", + self.maximum_allowed_size + ), + None, + &format!( + "consider allocating on the heap with `vec!{}.into_boxed_slice()`", + snippet(cx, expr.span, "[...]") + ), + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs new file mode 100644 index 0000000000..1e1023b274 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/len_zero.rs @@ -0,0 +1,451 @@ +use crate::utils::{ + get_item_name, get_parent_as_impl, is_allowed, snippet_with_applicability, span_lint, span_lint_and_sugg, + span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_hir::{ + def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item, + ItemKind, Mutability, Node, TraitItemRef, TyKind, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, AssocKind, FnSig}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::{Span, Spanned, Symbol}; + +declare_clippy_lint! { + /// **What it does:** Checks for getting the length of something via `.len()` + /// just to compare to zero, and suggests using `.is_empty()` where applicable. + /// + /// **Why is this bad?** Some structures can answer `.is_empty()` much faster + /// than calculating their length. So it is good to get into the habit of using + /// `.is_empty()`, and having it is cheap. + /// Besides, it makes the intent clearer than a manual comparison in some contexts. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// if x.len() == 0 { + /// .. + /// } + /// if y.len() != 0 { + /// .. + /// } + /// ``` + /// instead use + /// ```ignore + /// if x.is_empty() { + /// .. + /// } + /// if !y.is_empty() { + /// .. + /// } + /// ``` + pub LEN_ZERO, + style, + "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead" +} + +declare_clippy_lint! { + /// **What it does:** Checks for items that implement `.len()` but not + /// `.is_empty()`. + /// + /// **Why is this bad?** It is good custom to have both methods, because for + /// some data structures, asking about the length will be a costly operation, + /// whereas `.is_empty()` can usually answer in constant time. Also it used to + /// lead to false positives on the [`len_zero`](#len_zero) lint – currently that + /// lint will ignore such entities. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// impl X { + /// pub fn len(&self) -> usize { + /// .. + /// } + /// } + /// ``` + pub LEN_WITHOUT_IS_EMPTY, + style, + "traits or impls with a public `len` method but no corresponding `is_empty` method" +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparing to an empty slice such as `""` or `[]`, + /// and suggests using `.is_empty()` where applicable. + /// + /// **Why is this bad?** Some structures can answer `.is_empty()` much faster + /// than checking for equality. So it is good to get into the habit of using + /// `.is_empty()`, and having it is cheap. + /// Besides, it makes the intent clearer than a manual comparison in some contexts. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```ignore + /// if s == "" { + /// .. + /// } + /// + /// if arr == [] { + /// .. + /// } + /// ``` + /// Use instead: + /// ```ignore + /// if s.is_empty() { + /// .. + /// } + /// + /// if arr.is_empty() { + /// .. + /// } + /// ``` + pub COMPARISON_TO_EMPTY, + style, + "checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead" +} + +declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]); + +impl<'tcx> LateLintPass<'tcx> for LenZero { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if item.span.from_expansion() { + return; + } + + if let ItemKind::Trait(_, _, _, _, ref trait_items) = item.kind { + check_trait_items(cx, item, trait_items); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { + if_chain! { + if item.ident.as_str() == "len"; + if let ImplItemKind::Fn(sig, _) = &item.kind; + if sig.decl.implicit_self.has_implicit_self(); + if cx.access_levels.is_exported(item.hir_id()); + if matches!(sig.decl.output, FnRetTy::Return(_)); + if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id()); + if imp.of_trait.is_none(); + if let TyKind::Path(ty_path) = &imp.self_ty.kind; + if let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id(); + if let Some(local_id) = ty_id.as_local(); + let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id); + if !is_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id); + then { + let (name, kind) = match cx.tcx.hir().find(ty_hir_id) { + Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"), + Some(Node::Item(x)) => match x.kind { + ItemKind::Struct(..) => (x.ident.name, "struct"), + ItemKind::Enum(..) => (x.ident.name, "enum"), + ItemKind::Union(..) => (x.ident.name, "union"), + _ => (x.ident.name, "type"), + } + _ => return, + }; + check_for_is_empty(cx, sig.span, sig.decl.implicit_self, ty_id, name, kind) + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + if let ExprKind::Binary(Spanned { node: cmp, .. }, ref left, ref right) = expr.kind { + match cmp { + BinOpKind::Eq => { + check_cmp(cx, expr.span, left, right, "", 0); // len == 0 + check_cmp(cx, expr.span, right, left, "", 0); // 0 == len + }, + BinOpKind::Ne => { + check_cmp(cx, expr.span, left, right, "!", 0); // len != 0 + check_cmp(cx, expr.span, right, left, "!", 0); // 0 != len + }, + BinOpKind::Gt => { + check_cmp(cx, expr.span, left, right, "!", 0); // len > 0 + check_cmp(cx, expr.span, right, left, "", 1); // 1 > len + }, + BinOpKind::Lt => { + check_cmp(cx, expr.span, left, right, "", 1); // len < 1 + check_cmp(cx, expr.span, right, left, "!", 0); // 0 < len + }, + BinOpKind::Ge => check_cmp(cx, expr.span, left, right, "!", 1), // len >= 1 + BinOpKind::Le => check_cmp(cx, expr.span, right, left, "!", 1), // 1 <= len + _ => (), + } + } + } +} + +fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items: &[TraitItemRef]) { + fn is_named_self(cx: &LateContext<'_>, item: &TraitItemRef, name: &str) -> bool { + item.ident.name.as_str() == name + && if let AssocItemKind::Fn { has_self } = item.kind { + has_self && { cx.tcx.fn_sig(item.id.def_id).inputs().skip_binder().len() == 1 } + } else { + false + } + } + + // fill the set with current and super traits + fn fill_trait_set(traitt: DefId, set: &mut FxHashSet, cx: &LateContext<'_>) { + if set.insert(traitt) { + for supertrait in rustc_trait_selection::traits::supertrait_def_ids(cx.tcx, traitt) { + fill_trait_set(supertrait, set, cx); + } + } + } + + if cx.access_levels.is_exported(visited_trait.hir_id()) && trait_items.iter().any(|i| is_named_self(cx, i, "len")) { + let mut current_and_super_traits = FxHashSet::default(); + fill_trait_set(visited_trait.def_id.to_def_id(), &mut current_and_super_traits, cx); + + let is_empty_method_found = current_and_super_traits + .iter() + .flat_map(|&i| cx.tcx.associated_items(i).in_definition_order()) + .any(|i| { + i.kind == ty::AssocKind::Fn + && i.fn_has_self_parameter + && i.ident.name == sym!(is_empty) + && cx.tcx.fn_sig(i.def_id).inputs().skip_binder().len() == 1 + }); + + if !is_empty_method_found { + span_lint( + cx, + LEN_WITHOUT_IS_EMPTY, + visited_trait.span, + &format!( + "trait `{}` has a `len` method but no (possibly inherited) `is_empty` method", + visited_trait.ident.name + ), + ); + } + } +} + +/// Checks if the given signature matches the expectations for `is_empty` +fn check_is_empty_sig(cx: &LateContext<'_>, sig: FnSig<'_>, self_kind: ImplicitSelfKind) -> bool { + match &**sig.inputs_and_output { + [arg, res] if *res == cx.tcx.types.bool => { + matches!( + (arg.kind(), self_kind), + (ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef) + | (ty::Ref(_, _, Mutability::Mut), ImplicitSelfKind::MutRef) + ) || (!arg.is_ref() && matches!(self_kind, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut)) + }, + _ => false, + } +} + +/// Checks if the given type has an `is_empty` method with the appropriate signature. +fn check_for_is_empty( + cx: &LateContext<'_>, + span: Span, + self_kind: ImplicitSelfKind, + impl_ty: DefId, + item_name: Symbol, + item_kind: &str, +) { + let is_empty = Symbol::intern("is_empty"); + let is_empty = cx + .tcx + .inherent_impls(impl_ty) + .iter() + .flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty)) + .find(|item| item.kind == AssocKind::Fn); + + let (msg, is_empty_span, self_kind) = match is_empty { + None => ( + format!( + "{} `{}` has a public `len` method, but no `is_empty` method", + item_kind, + item_name.as_str(), + ), + None, + None, + ), + Some(is_empty) + if !cx + .access_levels + .is_exported(cx.tcx.hir().local_def_id_to_hir_id(is_empty.def_id.expect_local())) => + { + ( + format!( + "{} `{}` has a public `len` method, but a private `is_empty` method", + item_kind, + item_name.as_str(), + ), + Some(cx.tcx.def_span(is_empty.def_id)), + None, + ) + }, + Some(is_empty) + if !(is_empty.fn_has_self_parameter + && check_is_empty_sig(cx, cx.tcx.fn_sig(is_empty.def_id).skip_binder(), self_kind)) => + { + ( + format!( + "{} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature", + item_kind, + item_name.as_str(), + ), + Some(cx.tcx.def_span(is_empty.def_id)), + Some(self_kind), + ) + }, + Some(_) => return, + }; + + span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, &msg, |db| { + if let Some(span) = is_empty_span { + db.span_note(span, "`is_empty` defined here"); + } + if let Some(self_kind) = self_kind { + db.note(&format!( + "expected signature: `({}self) -> bool`", + match self_kind { + ImplicitSelfKind::ImmRef => "&", + ImplicitSelfKind::MutRef => "&mut ", + _ => "", + } + )); + } + }); +} + +fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) { + if let (&ExprKind::MethodCall(ref method_path, _, ref args, _), &ExprKind::Lit(ref lit)) = (&method.kind, &lit.kind) + { + // check if we are in an is_empty() method + if let Some(name) = get_item_name(cx, method) { + if name.as_str() == "is_empty" { + return; + } + } + + check_len(cx, span, method_path.ident.name, args, &lit.node, op, compare_to) + } else { + check_empty_expr(cx, span, method, lit, op) + } +} + +fn check_len( + cx: &LateContext<'_>, + span: Span, + method_name: Symbol, + args: &[Expr<'_>], + lit: &LitKind, + op: &str, + compare_to: u32, +) { + if let LitKind::Int(lit, _) = *lit { + // check if length is compared to the specified number + if lit != u128::from(compare_to) { + return; + } + + if method_name.as_str() == "len" && args.len() == 1 && has_is_empty(cx, &args[0]) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + LEN_ZERO, + span, + &format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }), + &format!("using `{}is_empty` is clearer and more explicit", op), + format!( + "{}{}.is_empty()", + op, + snippet_with_applicability(cx, args[0].span, "_", &mut applicability) + ), + applicability, + ); + } + } +} + +fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) { + if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + COMPARISON_TO_EMPTY, + span, + "comparison to empty slice", + &format!("using `{}is_empty` is clearer and more explicit", op), + format!( + "{}{}.is_empty()", + op, + snippet_with_applicability(cx, lit1.span, "_", &mut applicability) + ), + applicability, + ); + } +} + +fn is_empty_string(expr: &Expr<'_>) -> bool { + if let ExprKind::Lit(ref lit) = expr.kind { + if let LitKind::Str(lit, _) = lit.node { + let lit = lit.as_str(); + return lit == ""; + } + } + false +} + +fn is_empty_array(expr: &Expr<'_>) -> bool { + if let ExprKind::Array(ref arr) = expr.kind { + return arr.is_empty(); + } + false +} + +/// Checks if this type has an `is_empty` method. +fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + /// Gets an `AssocItem` and return true if it matches `is_empty(self)`. + fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool { + if let ty::AssocKind::Fn = item.kind { + if item.ident.name.as_str() == "is_empty" { + let sig = cx.tcx.fn_sig(item.def_id); + let ty = sig.skip_binder(); + ty.inputs().len() == 1 + } else { + false + } + } else { + false + } + } + + /// Checks the inherent impl's items for an `is_empty(self)` method. + fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool { + cx.tcx.inherent_impls(id).iter().any(|imp| { + cx.tcx + .associated_items(*imp) + .in_definition_order() + .any(|item| is_is_empty(cx, &item)) + }) + } + + let ty = &cx.typeck_results().expr_ty(expr).peel_refs(); + match ty.kind() { + ty::Dynamic(ref tt, ..) => tt.principal().map_or(false, |principal| { + cx.tcx + .associated_items(principal.def_id()) + .in_definition_order() + .any(|item| is_is_empty(cx, &item)) + }), + ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id), + ty::Adt(id, _) => has_is_empty_impl(cx, id.did), + ty::Array(..) | ty::Slice(..) | ty::Str => true, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/let_if_seq.rs b/src/tools/clippy/clippy_lints/src/let_if_seq.rs new file mode 100644 index 0000000000..5863eef8a2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/let_if_seq.rs @@ -0,0 +1,160 @@ +use crate::utils::{path_to_local_id, snippet, span_lint_and_then, visitors::LocalUsedVisitor}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::BindingAnnotation; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for variable declarations immediately followed by a + /// conditional affectation. + /// + /// **Why is this bad?** This is not idiomatic Rust. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// let foo; + /// + /// if bar() { + /// foo = 42; + /// } else { + /// foo = 0; + /// } + /// + /// let mut baz = None; + /// + /// if bar() { + /// baz = Some(42); + /// } + /// ``` + /// + /// should be written + /// + /// ```rust,ignore + /// let foo = if bar() { + /// 42 + /// } else { + /// 0 + /// }; + /// + /// let baz = if bar() { + /// Some(42) + /// } else { + /// None + /// }; + /// ``` + pub USELESS_LET_IF_SEQ, + nursery, + "unidiomatic `let mut` declaration followed by initialization in `if`" +} + +declare_lint_pass!(LetIfSeq => [USELESS_LET_IF_SEQ]); + +impl<'tcx> LateLintPass<'tcx> for LetIfSeq { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { + let mut it = block.stmts.iter().peekable(); + while let Some(stmt) = it.next() { + if_chain! { + if let Some(expr) = it.peek(); + if let hir::StmtKind::Local(ref local) = stmt.kind; + if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind; + if let hir::StmtKind::Expr(ref if_) = expr.kind; + if let hir::ExprKind::If(ref cond, ref then, ref else_) = if_.kind; + let mut used_visitor = LocalUsedVisitor::new(cx, canonical_id); + if !used_visitor.check_expr(cond); + if let hir::ExprKind::Block(ref then, _) = then.kind; + if let Some(value) = check_assign(cx, canonical_id, &*then); + if !used_visitor.check_expr(value); + then { + let span = stmt.span.to(if_.span); + + let has_interior_mutability = !cx.typeck_results().node_type(canonical_id).is_freeze( + cx.tcx.at(span), + cx.param_env, + ); + if has_interior_mutability { return; } + + let (default_multi_stmts, default) = if let Some(ref else_) = *else_ { + if let hir::ExprKind::Block(ref else_, _) = else_.kind { + if let Some(default) = check_assign(cx, canonical_id, else_) { + (else_.stmts.len() > 1, default) + } else if let Some(ref default) = local.init { + (true, &**default) + } else { + continue; + } + } else { + continue; + } + } else if let Some(ref default) = local.init { + (false, &**default) + } else { + continue; + }; + + let mutability = match mode { + BindingAnnotation::RefMut | BindingAnnotation::Mutable => " ", + _ => "", + }; + + // FIXME: this should not suggest `mut` if we can detect that the variable is not + // use mutably after the `if` + + let sug = format!( + "let {mut}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};", + mut=mutability, + name=ident.name, + cond=snippet(cx, cond.span, "_"), + then=if then.stmts.len() > 1 { " ..;" } else { "" }, + else=if default_multi_stmts { " ..;" } else { "" }, + value=snippet(cx, value.span, ""), + default=snippet(cx, default.span, ""), + ); + span_lint_and_then(cx, + USELESS_LET_IF_SEQ, + span, + "`if _ { .. } else { .. }` is an expression", + |diag| { + diag.span_suggestion( + span, + "it is more idiomatic to write", + sug, + Applicability::HasPlaceholders, + ); + if !mutability.is_empty() { + diag.note("you might not need `mut` at all"); + } + }); + } + } + } + } +} + +fn check_assign<'tcx>( + cx: &LateContext<'tcx>, + decl: hir::HirId, + block: &'tcx hir::Block<'_>, +) -> Option<&'tcx hir::Expr<'tcx>> { + if_chain! { + if block.expr.is_none(); + if let Some(expr) = block.stmts.iter().last(); + if let hir::StmtKind::Semi(ref expr) = expr.kind; + if let hir::ExprKind::Assign(ref var, ref value, _) = expr.kind; + if path_to_local_id(var, decl); + then { + let mut v = LocalUsedVisitor::new(cx, decl); + + if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| v.check_stmt(stmt)) { + return None; + } + + return Some(value); + } + } + + None +} diff --git a/src/tools/clippy/clippy_lints/src/let_underscore.rs b/src/tools/clippy/clippy_lints/src/let_underscore.rs new file mode 100644 index 0000000000..7e96dfcc7d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/let_underscore.rs @@ -0,0 +1,170 @@ +use if_chain::if_chain; +use rustc_hir::{Local, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{is_must_use_func_call, is_must_use_ty, match_type, paths, span_lint_and_help}; + +declare_clippy_lint! { + /// **What it does:** Checks for `let _ = ` + /// where expr is #[must_use] + /// + /// **Why is this bad?** It's better to explicitly + /// handle the value of a #[must_use] expr + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn f() -> Result { + /// Ok(0) + /// } + /// + /// let _ = f(); + /// // is_ok() is marked #[must_use] + /// let _ = f().is_ok(); + /// ``` + pub LET_UNDERSCORE_MUST_USE, + restriction, + "non-binding let on a `#[must_use]` expression" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `let _ = sync_lock` + /// + /// **Why is this bad?** This statement immediately drops the lock instead of + /// extending its lifetime to the end of the scope, which is often not intended. + /// To extend lock lifetime to the end of the scope, use an underscore-prefixed + /// name instead (i.e. _lock). If you want to explicitly drop the lock, + /// `std::mem::drop` conveys your intention better and is less error-prone. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// Bad: + /// ```rust,ignore + /// let _ = mutex.lock(); + /// ``` + /// + /// Good: + /// ```rust,ignore + /// let _lock = mutex.lock(); + /// ``` + pub LET_UNDERSCORE_LOCK, + correctness, + "non-binding let on a synchronization lock" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `let _ = ` + /// where expr has a type that implements `Drop` + /// + /// **Why is this bad?** This statement immediately drops the initializer + /// expression instead of extending its lifetime to the end of the scope, which + /// is often not intended. To extend the expression's lifetime to the end of the + /// scope, use an underscore-prefixed name instead (i.e. _var). If you want to + /// explicitly drop the expression, `std::mem::drop` conveys your intention + /// better and is less error-prone. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// Bad: + /// ```rust,ignore + /// struct Droppable; + /// impl Drop for Droppable { + /// fn drop(&mut self) {} + /// } + /// { + /// let _ = Droppable; + /// // ^ dropped here + /// /* more code */ + /// } + /// ``` + /// + /// Good: + /// ```rust,ignore + /// { + /// let _droppable = Droppable; + /// /* more code */ + /// // dropped at end of scope + /// } + /// ``` + pub LET_UNDERSCORE_DROP, + pedantic, + "non-binding let on a type that implements `Drop`" +} + +declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP]); + +const SYNC_GUARD_PATHS: [&[&str]; 3] = [ + &paths::MUTEX_GUARD, + &paths::RWLOCK_READ_GUARD, + &paths::RWLOCK_WRITE_GUARD, +]; + +impl<'tcx> LateLintPass<'tcx> for LetUnderscore { + fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) { + if in_external_macro(cx.tcx.sess, local.span) { + return; + } + + if_chain! { + if let PatKind::Wild = local.pat.kind; + if let Some(ref init) = local.init; + then { + let init_ty = cx.typeck_results().expr_ty(init); + let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() { + GenericArgKind::Type(inner_ty) => { + SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path)) + }, + + GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, + }); + if contains_sync_guard { + span_lint_and_help( + cx, + LET_UNDERSCORE_LOCK, + local.span, + "non-binding let on a synchronization lock", + None, + "consider using an underscore-prefixed named \ + binding or dropping explicitly with `std::mem::drop`" + ) + } else if init_ty.needs_drop(cx.tcx, cx.param_env) { + span_lint_and_help( + cx, + LET_UNDERSCORE_DROP, + local.span, + "non-binding `let` on a type that implements `Drop`", + None, + "consider using an underscore-prefixed named \ + binding or dropping explicitly with `std::mem::drop`" + ) + } else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) { + span_lint_and_help( + cx, + LET_UNDERSCORE_MUST_USE, + local.span, + "non-binding let on an expression with `#[must_use]` type", + None, + "consider explicitly using expression value" + ) + } else if is_must_use_func_call(cx, init) { + span_lint_and_help( + cx, + LET_UNDERSCORE_MUST_USE, + local.span, + "non-binding let on a result of a `#[must_use]` function", + None, + "consider explicitly using function result" + ) + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs new file mode 100644 index 0000000000..04e151df8e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -0,0 +1,2139 @@ +// error-pattern:cargo-clippy + +#![feature(box_patterns)] +#![feature(box_syntax)] +#![feature(drain_filter)] +#![feature(in_band_lifetimes)] +#![feature(once_cell)] +#![feature(or_patterns)] +#![feature(rustc_private)] +#![feature(stmt_expr_attributes)] +#![feature(control_flow_enum)] +#![recursion_limit = "512"] +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![allow(clippy::missing_docs_in_private_items, clippy::must_use_candidate)] +#![warn(trivial_casts, trivial_numeric_casts)] +// warn on lints, that are included in `rust-lang/rust`s bootstrap +#![warn(rust_2018_idioms, unused_lifetimes)] +// warn on rustc internal lints +#![deny(rustc::internal)] + +// FIXME: switch to something more ergonomic here, once available. +// (Currently there is no way to opt into sysroot crates without `extern crate`.) +extern crate rustc_ast; +extern crate rustc_ast_pretty; +extern crate rustc_attr; +extern crate rustc_data_structures; +extern crate rustc_driver; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_hir_pretty; +extern crate rustc_index; +extern crate rustc_infer; +extern crate rustc_lexer; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_mir; +extern crate rustc_parse; +extern crate rustc_parse_format; +extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_target; +extern crate rustc_trait_selection; +extern crate rustc_typeck; + +use crate::utils::parse_msrv; +use rustc_data_structures::fx::FxHashSet; +use rustc_lint::LintId; +use rustc_session::Session; + +/// Macro used to declare a Clippy lint. +/// +/// Every lint declaration consists of 4 parts: +/// +/// 1. The documentation, which is used for the website +/// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions. +/// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or +/// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of. +/// 4. The `description` that contains a short explanation on what's wrong with code where the +/// lint is triggered. +/// +/// Currently the categories `style`, `correctness`, `complexity` and `perf` are enabled by default. +/// As said in the README.md of this repository, if the lint level mapping changes, please update +/// README.md. +/// +/// # Example +/// +/// ``` +/// #![feature(rustc_private)] +/// extern crate rustc_session; +/// use rustc_session::declare_tool_lint; +/// use clippy_lints::declare_clippy_lint; +/// +/// declare_clippy_lint! { +/// /// **What it does:** Checks for ... (describe what the lint matches). +/// /// +/// /// **Why is this bad?** Supply the reason for linting the code. +/// /// +/// /// **Known problems:** None. (Or describe where it could go wrong.) +/// /// +/// /// **Example:** +/// /// +/// /// ```rust +/// /// // Bad +/// /// Insert a short example of code that triggers the lint +/// /// +/// /// // Good +/// /// Insert a short example of improved code that doesn't trigger the lint +/// /// ``` +/// pub LINT_NAME, +/// pedantic, +/// "description" +/// } +/// ``` +/// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints +#[macro_export] +macro_rules! declare_clippy_lint { + { $(#[$attr:meta])* pub $name:tt, style, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, correctness, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Deny, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, complexity, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, perf, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, pedantic, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, restriction, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, cargo, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, nursery, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, internal, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, internal_warn, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; +} + +#[macro_export] +macro_rules! sym { + ( $($x:tt)* ) => { clippy_utils::sym!($($x)*) } +} + +#[macro_export] +macro_rules! unwrap_cargo_metadata { + ( $($x:tt)* ) => { clippy_utils::unwrap_cargo_metadata!($($x)*) } +} + +macro_rules! extract_msrv_attr { + ( $($x:tt)* ) => { clippy_utils::extract_msrv_attr!($($x)*); } +} + +mod consts; +#[macro_use] +mod utils; + +// begin lints modules, do not remove this comment, it’s used in `update_lints` +mod approx_const; +mod arithmetic; +mod as_conversions; +mod asm_syntax; +mod assertions_on_constants; +mod assign_ops; +mod async_yields_async; +mod atomic_ordering; +mod attrs; +mod await_holding_invalid; +mod bit_mask; +mod blacklisted_name; +mod blocks_in_if_conditions; +mod booleans; +mod bytecount; +mod cargo_common_metadata; +mod case_sensitive_file_extension_comparisons; +mod casts; +mod checked_conversions; +mod cognitive_complexity; +mod collapsible_if; +mod collapsible_match; +mod comparison_chain; +mod copies; +mod copy_iterator; +mod create_dir; +mod dbg_macro; +mod default; +mod default_numeric_fallback; +mod dereference; +mod derive; +mod disallowed_method; +mod doc; +mod double_comparison; +mod double_parens; +mod drop_forget_ref; +mod duration_subsec; +mod else_if_without_else; +mod empty_enum; +mod entry; +mod enum_clike; +mod enum_variants; +mod eq_op; +mod erasing_op; +mod escape; +mod eta_reduction; +mod eval_order_dependence; +mod excessive_bools; +mod exhaustive_items; +mod exit; +mod explicit_write; +mod fallible_impl_from; +mod float_equality_without_abs; +mod float_literal; +mod floating_point_arithmetic; +mod format; +mod formatting; +mod from_over_into; +mod from_str_radix_10; +mod functions; +mod future_not_send; +mod get_last_with_len; +mod identity_op; +mod if_let_mutex; +mod if_let_some_result; +mod if_not_else; +mod implicit_return; +mod implicit_saturating_sub; +mod inconsistent_struct_constructor; +mod indexing_slicing; +mod infinite_iter; +mod inherent_impl; +mod inherent_to_string; +mod inline_fn_without_body; +mod int_plus_one; +mod integer_division; +mod items_after_statements; +mod large_const_arrays; +mod large_enum_variant; +mod large_stack_arrays; +mod len_zero; +mod let_if_seq; +mod let_underscore; +mod lifetimes; +mod literal_representation; +mod loops; +mod macro_use; +mod main_recursion; +mod manual_async_fn; +mod manual_map; +mod manual_non_exhaustive; +mod manual_ok_or; +mod manual_strip; +mod manual_unwrap_or; +mod map_clone; +mod map_err_ignore; +mod map_identity; +mod map_unit_fn; +mod match_on_vec_items; +mod matches; +mod mem_discriminant; +mod mem_forget; +mod mem_replace; +mod methods; +mod minmax; +mod misc; +mod misc_early; +mod missing_const_for_fn; +mod missing_doc; +mod missing_inline; +mod modulo_arithmetic; +mod multiple_crate_versions; +mod mut_key; +mod mut_mut; +mod mut_mutex_lock; +mod mut_reference; +mod mutable_debug_assertion; +mod mutex_atomic; +mod needless_arbitrary_self_type; +mod needless_bool; +mod needless_borrow; +mod needless_borrowed_ref; +mod needless_continue; +mod needless_pass_by_value; +mod needless_question_mark; +mod needless_update; +mod neg_cmp_op_on_partial_ord; +mod neg_multiply; +mod new_without_default; +mod no_effect; +mod non_copy_const; +mod non_expressive_names; +mod open_options; +mod option_env_unwrap; +mod option_if_let_else; +mod overflow_check_conditional; +mod panic_in_result_fn; +mod panic_unimplemented; +mod partialeq_ne_impl; +mod pass_by_ref_or_value; +mod path_buf_push_overwrite; +mod pattern_type_mismatch; +mod precedence; +mod ptr; +mod ptr_eq; +mod ptr_offset_with_cast; +mod question_mark; +mod ranges; +mod redundant_clone; +mod redundant_closure_call; +mod redundant_else; +mod redundant_field_names; +mod redundant_pub_crate; +mod redundant_slicing; +mod redundant_static_lifetimes; +mod ref_option_ref; +mod reference; +mod regex; +mod repeat_once; +mod returns; +mod self_assignment; +mod semicolon_if_nothing_returned; +mod serde_api; +mod shadow; +mod single_component_path_imports; +mod size_of_in_element_count; +mod slow_vector_initialization; +mod stable_sort_primitive; +mod strings; +mod suspicious_operation_groupings; +mod suspicious_trait_impl; +mod swap; +mod tabs_in_doc_comments; +mod temporary_assignment; +mod to_digit_is_some; +mod to_string_in_display; +mod trait_bounds; +mod transmute; +mod transmuting_null; +mod try_err; +mod types; +mod undropped_manually_drops; +mod unicode; +mod unit_return_expecting_ord; +mod unnamed_address; +mod unnecessary_sort_by; +mod unnecessary_wraps; +mod unnested_or_patterns; +mod unsafe_removed_from_name; +mod unused_io_amount; +mod unused_self; +mod unused_unit; +mod unwrap; +mod unwrap_in_result; +mod upper_case_acronyms; +mod use_self; +mod useless_conversion; +mod vec; +mod vec_init_then_push; +mod vec_resize_to_zero; +mod verbose_file_reads; +mod wildcard_dependencies; +mod wildcard_imports; +mod write; +mod zero_div_zero; +mod zero_sized_map_values; +// end lints modules, do not remove this comment, it’s used in `update_lints` + +pub use crate::utils::conf::Conf; + +/// Register all pre expansion lints +/// +/// Pre-expansion lints run before any macro expansion has happened. +/// +/// Note that due to the architecture of the compiler, currently `cfg_attr` attributes on crate +/// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. +/// +/// Used in `./src/driver.rs`. +pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) { + store.register_pre_expansion_pass(|| box write::Write::default()); + store.register_pre_expansion_pass(|| box attrs::EarlyAttributes); + store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro); +} + +#[doc(hidden)] +pub fn read_conf(args: &[rustc_ast::NestedMetaItem], sess: &Session) -> Conf { + use std::path::Path; + match utils::conf::file_from_args(args) { + Ok(file_name) => { + // if the user specified a file, it must exist, otherwise default to `clippy.toml` but + // do not require the file to exist + let file_name = match file_name { + Some(file_name) => file_name, + None => match utils::conf::lookup_conf_file() { + Ok(Some(path)) => path, + Ok(None) => return Conf::default(), + Err(error) => { + sess.struct_err(&format!("error finding Clippy's configuration file: {}", error)) + .emit(); + return Conf::default(); + }, + }, + }; + + let file_name = if file_name.is_relative() { + sess.local_crate_source_file + .as_deref() + .and_then(Path::parent) + .unwrap_or_else(|| Path::new("")) + .join(file_name) + } else { + file_name + }; + + let (conf, errors) = utils::conf::read(&file_name); + + // all conf errors are non-fatal, we just use the default conf in case of error + for error in errors { + sess.struct_err(&format!( + "error reading Clippy's configuration file `{}`: {}", + file_name.display(), + error + )) + .emit(); + } + + conf + }, + Err((err, span)) => { + sess.struct_span_err(span, err) + .span_note(span, "Clippy will use default configuration") + .emit(); + Conf::default() + }, + } +} + +/// Register all lints and lint groups with the rustc plugin registry +/// +/// Used in `./src/driver.rs`. +#[allow(clippy::too_many_lines)] +#[rustfmt::skip] +pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { + register_removed_non_tool_lints(store); + + // begin deprecated lints, do not remove this comment, it’s used in `update_lints` + store.register_removed( + "clippy::should_assert_eq", + "`assert!()` will be more flexible with RFC 2011", + ); + store.register_removed( + "clippy::extend_from_slice", + "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice", + ); + store.register_removed( + "clippy::range_step_by_zero", + "`iterator.step_by(0)` panics nowadays", + ); + store.register_removed( + "clippy::unstable_as_slice", + "`Vec::as_slice` has been stabilized in 1.7", + ); + store.register_removed( + "clippy::unstable_as_mut_slice", + "`Vec::as_mut_slice` has been stabilized in 1.7", + ); + store.register_removed( + "clippy::misaligned_transmute", + "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr", + ); + store.register_removed( + "clippy::assign_ops", + "using compound assignment operators (e.g., `+=`) is harmless", + ); + store.register_removed( + "clippy::if_let_redundant_pattern_matching", + "this lint has been changed to redundant_pattern_matching", + ); + store.register_removed( + "clippy::unsafe_vector_initialization", + "the replacement suggested by this lint had substantially different behavior", + ); + store.register_removed( + "clippy::invalid_ref", + "superseded by rustc lint `invalid_value`", + ); + store.register_removed( + "clippy::unused_collect", + "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint", + ); + store.register_removed( + "clippy::into_iter_on_array", + "this lint has been uplifted to rustc and is now called `array_into_iter`", + ); + store.register_removed( + "clippy::unused_label", + "this lint has been uplifted to rustc and is now called `unused_labels`", + ); + store.register_removed( + "clippy::replace_consts", + "associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants", + ); + store.register_removed( + "clippy::regex_macro", + "the regex! macro has been removed from the regex crate in 2018", + ); + store.register_removed( + "clippy::drop_bounds", + "this lint has been uplifted to rustc and is now called `drop_bounds`", + ); + store.register_removed( + "clippy::temporary_cstring_as_ptr", + "this lint has been uplifted to rustc and is now called `temporary_cstring_as_ptr`", + ); + store.register_removed( + "clippy::panic_params", + "this lint has been uplifted to rustc and is now called `panic_fmt`", + ); + store.register_removed( + "clippy::unknown_clippy_lints", + "this lint has been integrated into the `unknown_lints` rustc lint", + ); + store.register_removed( + "clippy::find_map", + "this lint has been replaced by `manual_find_map`, a more specific lint", + ); + // end deprecated lints, do not remove this comment, it’s used in `update_lints` + + // begin register lints, do not remove this comment, it’s used in `update_lints` + store.register_lints(&[ + #[cfg(feature = "internal-lints")] + &utils::internal_lints::CLIPPY_LINTS_INTERNAL, + #[cfg(feature = "internal-lints")] + &utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS, + #[cfg(feature = "internal-lints")] + &utils::internal_lints::COMPILER_LINT_FUNCTIONS, + #[cfg(feature = "internal-lints")] + &utils::internal_lints::DEFAULT_LINT, + #[cfg(feature = "internal-lints")] + &utils::internal_lints::INTERNING_DEFINED_SYMBOL, + #[cfg(feature = "internal-lints")] + &utils::internal_lints::INVALID_PATHS, + #[cfg(feature = "internal-lints")] + &utils::internal_lints::LINT_WITHOUT_LINT_PASS, + #[cfg(feature = "internal-lints")] + &utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM, + #[cfg(feature = "internal-lints")] + &utils::internal_lints::OUTER_EXPN_EXPN_DATA, + #[cfg(feature = "internal-lints")] + &utils::internal_lints::PRODUCE_ICE, + #[cfg(feature = "internal-lints")] + &utils::internal_lints::UNNECESSARY_SYMBOL_STR, + &approx_const::APPROX_CONSTANT, + &arithmetic::FLOAT_ARITHMETIC, + &arithmetic::INTEGER_ARITHMETIC, + &as_conversions::AS_CONVERSIONS, + &asm_syntax::INLINE_ASM_X86_ATT_SYNTAX, + &asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX, + &assertions_on_constants::ASSERTIONS_ON_CONSTANTS, + &assign_ops::ASSIGN_OP_PATTERN, + &assign_ops::MISREFACTORED_ASSIGN_OP, + &async_yields_async::ASYNC_YIELDS_ASYNC, + &atomic_ordering::INVALID_ATOMIC_ORDERING, + &attrs::BLANKET_CLIPPY_RESTRICTION_LINTS, + &attrs::DEPRECATED_CFG_ATTR, + &attrs::DEPRECATED_SEMVER, + &attrs::EMPTY_LINE_AFTER_OUTER_ATTR, + &attrs::INLINE_ALWAYS, + &attrs::MISMATCHED_TARGET_OS, + &attrs::USELESS_ATTRIBUTE, + &await_holding_invalid::AWAIT_HOLDING_LOCK, + &await_holding_invalid::AWAIT_HOLDING_REFCELL_REF, + &bit_mask::BAD_BIT_MASK, + &bit_mask::INEFFECTIVE_BIT_MASK, + &bit_mask::VERBOSE_BIT_MASK, + &blacklisted_name::BLACKLISTED_NAME, + &blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS, + &booleans::LOGIC_BUG, + &booleans::NONMINIMAL_BOOL, + &bytecount::NAIVE_BYTECOUNT, + &cargo_common_metadata::CARGO_COMMON_METADATA, + &case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + &casts::CAST_LOSSLESS, + &casts::CAST_POSSIBLE_TRUNCATION, + &casts::CAST_POSSIBLE_WRAP, + &casts::CAST_PRECISION_LOSS, + &casts::CAST_PTR_ALIGNMENT, + &casts::CAST_REF_TO_MUT, + &casts::CAST_SIGN_LOSS, + &casts::CHAR_LIT_AS_U8, + &casts::FN_TO_NUMERIC_CAST, + &casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + &casts::PTR_AS_PTR, + &casts::UNNECESSARY_CAST, + &checked_conversions::CHECKED_CONVERSIONS, + &cognitive_complexity::COGNITIVE_COMPLEXITY, + &collapsible_if::COLLAPSIBLE_ELSE_IF, + &collapsible_if::COLLAPSIBLE_IF, + &collapsible_match::COLLAPSIBLE_MATCH, + &comparison_chain::COMPARISON_CHAIN, + &copies::IFS_SAME_COND, + &copies::IF_SAME_THEN_ELSE, + &copies::SAME_FUNCTIONS_IN_IF_CONDITION, + ©_iterator::COPY_ITERATOR, + &create_dir::CREATE_DIR, + &dbg_macro::DBG_MACRO, + &default::DEFAULT_TRAIT_ACCESS, + &default::FIELD_REASSIGN_WITH_DEFAULT, + &default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK, + &dereference::EXPLICIT_DEREF_METHODS, + &derive::DERIVE_HASH_XOR_EQ, + &derive::DERIVE_ORD_XOR_PARTIAL_ORD, + &derive::EXPL_IMPL_CLONE_ON_COPY, + &derive::UNSAFE_DERIVE_DESERIALIZE, + &disallowed_method::DISALLOWED_METHOD, + &doc::DOC_MARKDOWN, + &doc::MISSING_ERRORS_DOC, + &doc::MISSING_PANICS_DOC, + &doc::MISSING_SAFETY_DOC, + &doc::NEEDLESS_DOCTEST_MAIN, + &double_comparison::DOUBLE_COMPARISONS, + &double_parens::DOUBLE_PARENS, + &drop_forget_ref::DROP_COPY, + &drop_forget_ref::DROP_REF, + &drop_forget_ref::FORGET_COPY, + &drop_forget_ref::FORGET_REF, + &duration_subsec::DURATION_SUBSEC, + &else_if_without_else::ELSE_IF_WITHOUT_ELSE, + &empty_enum::EMPTY_ENUM, + &entry::MAP_ENTRY, + &enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT, + &enum_variants::ENUM_VARIANT_NAMES, + &enum_variants::MODULE_INCEPTION, + &enum_variants::MODULE_NAME_REPETITIONS, + &enum_variants::PUB_ENUM_VARIANT_NAMES, + &eq_op::EQ_OP, + &eq_op::OP_REF, + &erasing_op::ERASING_OP, + &escape::BOXED_LOCAL, + &eta_reduction::REDUNDANT_CLOSURE, + &eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS, + &eval_order_dependence::DIVERGING_SUB_EXPRESSION, + &eval_order_dependence::EVAL_ORDER_DEPENDENCE, + &excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS, + &excessive_bools::STRUCT_EXCESSIVE_BOOLS, + &exhaustive_items::EXHAUSTIVE_ENUMS, + &exhaustive_items::EXHAUSTIVE_STRUCTS, + &exit::EXIT, + &explicit_write::EXPLICIT_WRITE, + &fallible_impl_from::FALLIBLE_IMPL_FROM, + &float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS, + &float_literal::EXCESSIVE_PRECISION, + &float_literal::LOSSY_FLOAT_LITERAL, + &floating_point_arithmetic::IMPRECISE_FLOPS, + &floating_point_arithmetic::SUBOPTIMAL_FLOPS, + &format::USELESS_FORMAT, + &formatting::POSSIBLE_MISSING_COMMA, + &formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING, + &formatting::SUSPICIOUS_ELSE_FORMATTING, + &formatting::SUSPICIOUS_UNARY_OP_FORMATTING, + &from_over_into::FROM_OVER_INTO, + &from_str_radix_10::FROM_STR_RADIX_10, + &functions::DOUBLE_MUST_USE, + &functions::MUST_USE_CANDIDATE, + &functions::MUST_USE_UNIT, + &functions::NOT_UNSAFE_PTR_ARG_DEREF, + &functions::RESULT_UNIT_ERR, + &functions::TOO_MANY_ARGUMENTS, + &functions::TOO_MANY_LINES, + &future_not_send::FUTURE_NOT_SEND, + &get_last_with_len::GET_LAST_WITH_LEN, + &identity_op::IDENTITY_OP, + &if_let_mutex::IF_LET_MUTEX, + &if_let_some_result::IF_LET_SOME_RESULT, + &if_not_else::IF_NOT_ELSE, + &implicit_return::IMPLICIT_RETURN, + &implicit_saturating_sub::IMPLICIT_SATURATING_SUB, + &inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR, + &indexing_slicing::INDEXING_SLICING, + &indexing_slicing::OUT_OF_BOUNDS_INDEXING, + &infinite_iter::INFINITE_ITER, + &infinite_iter::MAYBE_INFINITE_ITER, + &inherent_impl::MULTIPLE_INHERENT_IMPL, + &inherent_to_string::INHERENT_TO_STRING, + &inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY, + &inline_fn_without_body::INLINE_FN_WITHOUT_BODY, + &int_plus_one::INT_PLUS_ONE, + &integer_division::INTEGER_DIVISION, + &items_after_statements::ITEMS_AFTER_STATEMENTS, + &large_const_arrays::LARGE_CONST_ARRAYS, + &large_enum_variant::LARGE_ENUM_VARIANT, + &large_stack_arrays::LARGE_STACK_ARRAYS, + &len_zero::COMPARISON_TO_EMPTY, + &len_zero::LEN_WITHOUT_IS_EMPTY, + &len_zero::LEN_ZERO, + &let_if_seq::USELESS_LET_IF_SEQ, + &let_underscore::LET_UNDERSCORE_DROP, + &let_underscore::LET_UNDERSCORE_LOCK, + &let_underscore::LET_UNDERSCORE_MUST_USE, + &lifetimes::EXTRA_UNUSED_LIFETIMES, + &lifetimes::NEEDLESS_LIFETIMES, + &literal_representation::DECIMAL_LITERAL_REPRESENTATION, + &literal_representation::INCONSISTENT_DIGIT_GROUPING, + &literal_representation::LARGE_DIGIT_GROUPS, + &literal_representation::MISTYPED_LITERAL_SUFFIXES, + &literal_representation::UNREADABLE_LITERAL, + &literal_representation::UNUSUAL_BYTE_GROUPINGS, + &loops::EMPTY_LOOP, + &loops::EXPLICIT_COUNTER_LOOP, + &loops::EXPLICIT_INTO_ITER_LOOP, + &loops::EXPLICIT_ITER_LOOP, + &loops::FOR_KV_MAP, + &loops::FOR_LOOPS_OVER_FALLIBLES, + &loops::ITER_NEXT_LOOP, + &loops::MANUAL_FLATTEN, + &loops::MANUAL_MEMCPY, + &loops::MUT_RANGE_BOUND, + &loops::NEEDLESS_COLLECT, + &loops::NEEDLESS_RANGE_LOOP, + &loops::NEVER_LOOP, + &loops::SAME_ITEM_PUSH, + &loops::SINGLE_ELEMENT_LOOP, + &loops::WHILE_IMMUTABLE_CONDITION, + &loops::WHILE_LET_LOOP, + &loops::WHILE_LET_ON_ITERATOR, + ¯o_use::MACRO_USE_IMPORTS, + &main_recursion::MAIN_RECURSION, + &manual_async_fn::MANUAL_ASYNC_FN, + &manual_map::MANUAL_MAP, + &manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE, + &manual_ok_or::MANUAL_OK_OR, + &manual_strip::MANUAL_STRIP, + &manual_unwrap_or::MANUAL_UNWRAP_OR, + &map_clone::MAP_CLONE, + &map_err_ignore::MAP_ERR_IGNORE, + &map_identity::MAP_IDENTITY, + &map_unit_fn::OPTION_MAP_UNIT_FN, + &map_unit_fn::RESULT_MAP_UNIT_FN, + &match_on_vec_items::MATCH_ON_VEC_ITEMS, + &matches::INFALLIBLE_DESTRUCTURING_MATCH, + &matches::MATCH_AS_REF, + &matches::MATCH_BOOL, + &matches::MATCH_LIKE_MATCHES_MACRO, + &matches::MATCH_OVERLAPPING_ARM, + &matches::MATCH_REF_PATS, + &matches::MATCH_SAME_ARMS, + &matches::MATCH_SINGLE_BINDING, + &matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + &matches::MATCH_WILD_ERR_ARM, + &matches::REDUNDANT_PATTERN_MATCHING, + &matches::REST_PAT_IN_FULLY_BOUND_STRUCTS, + &matches::SINGLE_MATCH, + &matches::SINGLE_MATCH_ELSE, + &matches::WILDCARD_ENUM_MATCH_ARM, + &matches::WILDCARD_IN_OR_PATTERNS, + &mem_discriminant::MEM_DISCRIMINANT_NON_ENUM, + &mem_forget::MEM_FORGET, + &mem_replace::MEM_REPLACE_OPTION_WITH_NONE, + &mem_replace::MEM_REPLACE_WITH_DEFAULT, + &mem_replace::MEM_REPLACE_WITH_UNINIT, + &methods::BIND_INSTEAD_OF_MAP, + &methods::BYTES_NTH, + &methods::CHARS_LAST_CMP, + &methods::CHARS_NEXT_CMP, + &methods::CLONE_DOUBLE_REF, + &methods::CLONE_ON_COPY, + &methods::CLONE_ON_REF_PTR, + &methods::EXPECT_FUN_CALL, + &methods::EXPECT_USED, + &methods::FILETYPE_IS_FILE, + &methods::FILTER_MAP, + &methods::FILTER_MAP_IDENTITY, + &methods::FILTER_MAP_NEXT, + &methods::FILTER_NEXT, + &methods::FLAT_MAP_IDENTITY, + &methods::FROM_ITER_INSTEAD_OF_COLLECT, + &methods::GET_UNWRAP, + &methods::IMPLICIT_CLONE, + &methods::INEFFICIENT_TO_STRING, + &methods::INSPECT_FOR_EACH, + &methods::INTO_ITER_ON_REF, + &methods::ITERATOR_STEP_BY_ZERO, + &methods::ITER_CLONED_COLLECT, + &methods::ITER_COUNT, + &methods::ITER_NEXT_SLICE, + &methods::ITER_NTH, + &methods::ITER_NTH_ZERO, + &methods::ITER_SKIP_NEXT, + &methods::MANUAL_FILTER_MAP, + &methods::MANUAL_FIND_MAP, + &methods::MANUAL_SATURATING_ARITHMETIC, + &methods::MAP_COLLECT_RESULT_UNIT, + &methods::MAP_FLATTEN, + &methods::MAP_UNWRAP_OR, + &methods::NEW_RET_NO_SELF, + &methods::OK_EXPECT, + &methods::OPTION_AS_REF_DEREF, + &methods::OPTION_MAP_OR_NONE, + &methods::OR_FUN_CALL, + &methods::RESULT_MAP_OR_INTO_OPTION, + &methods::SEARCH_IS_SOME, + &methods::SHOULD_IMPLEMENT_TRAIT, + &methods::SINGLE_CHAR_ADD_STR, + &methods::SINGLE_CHAR_PATTERN, + &methods::SKIP_WHILE_NEXT, + &methods::STRING_EXTEND_CHARS, + &methods::SUSPICIOUS_MAP, + &methods::UNINIT_ASSUMED_INIT, + &methods::UNNECESSARY_FILTER_MAP, + &methods::UNNECESSARY_FOLD, + &methods::UNNECESSARY_LAZY_EVALUATIONS, + &methods::UNWRAP_USED, + &methods::USELESS_ASREF, + &methods::WRONG_PUB_SELF_CONVENTION, + &methods::WRONG_SELF_CONVENTION, + &methods::ZST_OFFSET, + &minmax::MIN_MAX, + &misc::CMP_NAN, + &misc::CMP_OWNED, + &misc::FLOAT_CMP, + &misc::FLOAT_CMP_CONST, + &misc::MODULO_ONE, + &misc::SHORT_CIRCUIT_STATEMENT, + &misc::TOPLEVEL_REF_ARG, + &misc::USED_UNDERSCORE_BINDING, + &misc::ZERO_PTR, + &misc_early::BUILTIN_TYPE_SHADOW, + &misc_early::DOUBLE_NEG, + &misc_early::DUPLICATE_UNDERSCORE_ARGUMENT, + &misc_early::MIXED_CASE_HEX_LITERALS, + &misc_early::REDUNDANT_PATTERN, + &misc_early::UNNEEDED_FIELD_PATTERN, + &misc_early::UNNEEDED_WILDCARD_PATTERN, + &misc_early::UNSEPARATED_LITERAL_SUFFIX, + &misc_early::ZERO_PREFIXED_LITERAL, + &missing_const_for_fn::MISSING_CONST_FOR_FN, + &missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS, + &missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS, + &modulo_arithmetic::MODULO_ARITHMETIC, + &multiple_crate_versions::MULTIPLE_CRATE_VERSIONS, + &mut_key::MUTABLE_KEY_TYPE, + &mut_mut::MUT_MUT, + &mut_mutex_lock::MUT_MUTEX_LOCK, + &mut_reference::UNNECESSARY_MUT_PASSED, + &mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL, + &mutex_atomic::MUTEX_ATOMIC, + &mutex_atomic::MUTEX_INTEGER, + &needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE, + &needless_bool::BOOL_COMPARISON, + &needless_bool::NEEDLESS_BOOL, + &needless_borrow::NEEDLESS_BORROW, + &needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE, + &needless_continue::NEEDLESS_CONTINUE, + &needless_pass_by_value::NEEDLESS_PASS_BY_VALUE, + &needless_question_mark::NEEDLESS_QUESTION_MARK, + &needless_update::NEEDLESS_UPDATE, + &neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD, + &neg_multiply::NEG_MULTIPLY, + &new_without_default::NEW_WITHOUT_DEFAULT, + &no_effect::NO_EFFECT, + &no_effect::UNNECESSARY_OPERATION, + &non_copy_const::BORROW_INTERIOR_MUTABLE_CONST, + &non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST, + &non_expressive_names::JUST_UNDERSCORES_AND_DIGITS, + &non_expressive_names::MANY_SINGLE_CHAR_NAMES, + &non_expressive_names::SIMILAR_NAMES, + &open_options::NONSENSICAL_OPEN_OPTIONS, + &option_env_unwrap::OPTION_ENV_UNWRAP, + &option_if_let_else::OPTION_IF_LET_ELSE, + &overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL, + &panic_in_result_fn::PANIC_IN_RESULT_FN, + &panic_unimplemented::PANIC, + &panic_unimplemented::TODO, + &panic_unimplemented::UNIMPLEMENTED, + &panic_unimplemented::UNREACHABLE, + &partialeq_ne_impl::PARTIALEQ_NE_IMPL, + &pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE, + &pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF, + &path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE, + &pattern_type_mismatch::PATTERN_TYPE_MISMATCH, + &precedence::PRECEDENCE, + &ptr::CMP_NULL, + &ptr::MUT_FROM_REF, + &ptr::PTR_ARG, + &ptr_eq::PTR_EQ, + &ptr_offset_with_cast::PTR_OFFSET_WITH_CAST, + &question_mark::QUESTION_MARK, + &ranges::MANUAL_RANGE_CONTAINS, + &ranges::RANGE_MINUS_ONE, + &ranges::RANGE_PLUS_ONE, + &ranges::RANGE_ZIP_WITH_LEN, + &ranges::REVERSED_EMPTY_RANGES, + &redundant_clone::REDUNDANT_CLONE, + &redundant_closure_call::REDUNDANT_CLOSURE_CALL, + &redundant_else::REDUNDANT_ELSE, + &redundant_field_names::REDUNDANT_FIELD_NAMES, + &redundant_pub_crate::REDUNDANT_PUB_CRATE, + &redundant_slicing::REDUNDANT_SLICING, + &redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES, + &ref_option_ref::REF_OPTION_REF, + &reference::DEREF_ADDROF, + &reference::REF_IN_DEREF, + ®ex::INVALID_REGEX, + ®ex::TRIVIAL_REGEX, + &repeat_once::REPEAT_ONCE, + &returns::LET_AND_RETURN, + &returns::NEEDLESS_RETURN, + &self_assignment::SELF_ASSIGNMENT, + &semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED, + &serde_api::SERDE_API_MISUSE, + &shadow::SHADOW_REUSE, + &shadow::SHADOW_SAME, + &shadow::SHADOW_UNRELATED, + &single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, + &size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT, + &slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, + &stable_sort_primitive::STABLE_SORT_PRIMITIVE, + &strings::STRING_ADD, + &strings::STRING_ADD_ASSIGN, + &strings::STRING_FROM_UTF8_AS_BYTES, + &strings::STRING_LIT_AS_BYTES, + &strings::STRING_TO_STRING, + &strings::STR_TO_STRING, + &suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS, + &suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL, + &suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL, + &swap::ALMOST_SWAPPED, + &swap::MANUAL_SWAP, + &tabs_in_doc_comments::TABS_IN_DOC_COMMENTS, + &temporary_assignment::TEMPORARY_ASSIGNMENT, + &to_digit_is_some::TO_DIGIT_IS_SOME, + &to_string_in_display::TO_STRING_IN_DISPLAY, + &trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS, + &trait_bounds::TYPE_REPETITION_IN_BOUNDS, + &transmute::CROSSPOINTER_TRANSMUTE, + &transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, + &transmute::TRANSMUTE_BYTES_TO_STR, + &transmute::TRANSMUTE_FLOAT_TO_INT, + &transmute::TRANSMUTE_INT_TO_BOOL, + &transmute::TRANSMUTE_INT_TO_CHAR, + &transmute::TRANSMUTE_INT_TO_FLOAT, + &transmute::TRANSMUTE_PTR_TO_PTR, + &transmute::TRANSMUTE_PTR_TO_REF, + &transmute::UNSOUND_COLLECTION_TRANSMUTE, + &transmute::USELESS_TRANSMUTE, + &transmute::WRONG_TRANSMUTE, + &transmuting_null::TRANSMUTING_NULL, + &try_err::TRY_ERR, + &types::ABSURD_EXTREME_COMPARISONS, + &types::BORROWED_BOX, + &types::BOX_VEC, + &types::IMPLICIT_HASHER, + &types::INVALID_UPCAST_COMPARISONS, + &types::LET_UNIT_VALUE, + &types::LINKEDLIST, + &types::OPTION_OPTION, + &types::RC_BUFFER, + &types::REDUNDANT_ALLOCATION, + &types::TYPE_COMPLEXITY, + &types::UNIT_ARG, + &types::UNIT_CMP, + &types::VEC_BOX, + &undropped_manually_drops::UNDROPPED_MANUALLY_DROPS, + &unicode::INVISIBLE_CHARACTERS, + &unicode::NON_ASCII_LITERAL, + &unicode::UNICODE_NOT_NFC, + &unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD, + &unnamed_address::FN_ADDRESS_COMPARISONS, + &unnamed_address::VTABLE_ADDRESS_COMPARISONS, + &unnecessary_sort_by::UNNECESSARY_SORT_BY, + &unnecessary_wraps::UNNECESSARY_WRAPS, + &unnested_or_patterns::UNNESTED_OR_PATTERNS, + &unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, + &unused_io_amount::UNUSED_IO_AMOUNT, + &unused_self::UNUSED_SELF, + &unused_unit::UNUSED_UNIT, + &unwrap::PANICKING_UNWRAP, + &unwrap::UNNECESSARY_UNWRAP, + &unwrap_in_result::UNWRAP_IN_RESULT, + &upper_case_acronyms::UPPER_CASE_ACRONYMS, + &use_self::USE_SELF, + &useless_conversion::USELESS_CONVERSION, + &vec::USELESS_VEC, + &vec_init_then_push::VEC_INIT_THEN_PUSH, + &vec_resize_to_zero::VEC_RESIZE_TO_ZERO, + &verbose_file_reads::VERBOSE_FILE_READS, + &wildcard_dependencies::WILDCARD_DEPENDENCIES, + &wildcard_imports::ENUM_GLOB_USE, + &wildcard_imports::WILDCARD_IMPORTS, + &write::PRINTLN_EMPTY_STRING, + &write::PRINT_LITERAL, + &write::PRINT_STDERR, + &write::PRINT_STDOUT, + &write::PRINT_WITH_NEWLINE, + &write::USE_DEBUG, + &write::WRITELN_EMPTY_STRING, + &write::WRITE_LITERAL, + &write::WRITE_WITH_NEWLINE, + &zero_div_zero::ZERO_DIVIDED_BY_ZERO, + &zero_sized_map_values::ZERO_SIZED_MAP_VALUES, + ]); + // end register lints, do not remove this comment, it’s used in `update_lints` + + // all the internal lints + #[cfg(feature = "internal-lints")] + { + store.register_early_pass(|| box utils::internal_lints::ClippyLintsInternal); + store.register_early_pass(|| box utils::internal_lints::ProduceIce); + store.register_late_pass(|| box utils::inspector::DeepCodeInspector); + store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls); + store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new()); + store.register_late_pass(|| box utils::internal_lints::InvalidPaths); + store.register_late_pass(|| box utils::internal_lints::InterningDefinedSymbol::default()); + store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default()); + store.register_late_pass(|| box utils::internal_lints::MatchTypeOnDiagItem); + store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass); + } + store.register_late_pass(|| box utils::author::Author); + store.register_late_pass(|| box await_holding_invalid::AwaitHolding); + store.register_late_pass(|| box serde_api::SerdeApi); + let vec_box_size_threshold = conf.vec_box_size_threshold; + store.register_late_pass(move || box types::Types::new(vec_box_size_threshold)); + store.register_late_pass(|| box booleans::NonminimalBool); + store.register_late_pass(|| box eq_op::EqOp); + store.register_late_pass(|| box enum_clike::UnportableVariant); + store.register_late_pass(|| box float_literal::FloatLiteral); + let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold; + store.register_late_pass(move || box bit_mask::BitMask::new(verbose_bit_mask_threshold)); + store.register_late_pass(|| box ptr::Ptr); + store.register_late_pass(|| box ptr_eq::PtrEq); + store.register_late_pass(|| box needless_bool::NeedlessBool); + store.register_late_pass(|| box needless_bool::BoolComparison); + store.register_late_pass(|| box approx_const::ApproxConstant); + store.register_late_pass(|| box misc::MiscLints); + store.register_late_pass(|| box eta_reduction::EtaReduction); + store.register_late_pass(|| box identity_op::IdentityOp); + store.register_late_pass(|| box erasing_op::ErasingOp); + store.register_late_pass(|| box mut_mut::MutMut); + store.register_late_pass(|| box mut_reference::UnnecessaryMutPassed); + store.register_late_pass(|| box len_zero::LenZero); + store.register_late_pass(|| box attrs::Attributes); + store.register_late_pass(|| box blocks_in_if_conditions::BlocksInIfConditions); + store.register_late_pass(|| box collapsible_match::CollapsibleMatch); + store.register_late_pass(|| box unicode::Unicode); + store.register_late_pass(|| box unit_return_expecting_ord::UnitReturnExpectingOrd); + store.register_late_pass(|| box strings::StringAdd); + store.register_late_pass(|| box implicit_return::ImplicitReturn); + store.register_late_pass(|| box implicit_saturating_sub::ImplicitSaturatingSub); + store.register_late_pass(|| box default_numeric_fallback::DefaultNumericFallback); + store.register_late_pass(|| box inconsistent_struct_constructor::InconsistentStructConstructor); + + let msrv = conf.msrv.as_ref().and_then(|s| { + parse_msrv(s, None, None).or_else(|| { + sess.err(&format!("error reading Clippy's configuration file. `{}` is not a valid Rust version", s)); + None + }) + }); + + store.register_late_pass(move || box methods::Methods::new(msrv)); + store.register_late_pass(move || box matches::Matches::new(msrv)); + store.register_early_pass(move || box manual_non_exhaustive::ManualNonExhaustive::new(msrv)); + store.register_late_pass(move || box manual_strip::ManualStrip::new(msrv)); + store.register_early_pass(move || box redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv)); + store.register_early_pass(move || box redundant_field_names::RedundantFieldNames::new(msrv)); + store.register_late_pass(move || box checked_conversions::CheckedConversions::new(msrv)); + store.register_late_pass(move || box mem_replace::MemReplace::new(msrv)); + store.register_late_pass(move || box ranges::Ranges::new(msrv)); + store.register_late_pass(move || box from_over_into::FromOverInto::new(msrv)); + store.register_late_pass(move || box use_self::UseSelf::new(msrv)); + store.register_late_pass(move || box missing_const_for_fn::MissingConstForFn::new(msrv)); + store.register_late_pass(move || box needless_question_mark::NeedlessQuestionMark::new(msrv)); + store.register_late_pass(move || box casts::Casts::new(msrv)); + + store.register_late_pass(|| box size_of_in_element_count::SizeOfInElementCount); + store.register_late_pass(|| box map_clone::MapClone); + store.register_late_pass(|| box map_err_ignore::MapErrIgnore); + store.register_late_pass(|| box shadow::Shadow); + store.register_late_pass(|| box types::LetUnitValue); + store.register_late_pass(|| box types::UnitCmp); + store.register_late_pass(|| box loops::Loops); + store.register_late_pass(|| box main_recursion::MainRecursion::default()); + store.register_late_pass(|| box lifetimes::Lifetimes); + store.register_late_pass(|| box entry::HashMapPass); + let type_complexity_threshold = conf.type_complexity_threshold; + store.register_late_pass(move || box types::TypeComplexity::new(type_complexity_threshold)); + store.register_late_pass(|| box minmax::MinMaxPass); + store.register_late_pass(|| box open_options::OpenOptions); + store.register_late_pass(|| box zero_div_zero::ZeroDiv); + store.register_late_pass(|| box mutex_atomic::Mutex); + store.register_late_pass(|| box needless_update::NeedlessUpdate); + store.register_late_pass(|| box needless_borrow::NeedlessBorrow::default()); + store.register_late_pass(|| box needless_borrowed_ref::NeedlessBorrowedRef); + store.register_late_pass(|| box no_effect::NoEffect); + store.register_late_pass(|| box temporary_assignment::TemporaryAssignment); + store.register_late_pass(|| box transmute::Transmute); + let cognitive_complexity_threshold = conf.cognitive_complexity_threshold; + store.register_late_pass(move || box cognitive_complexity::CognitiveComplexity::new(cognitive_complexity_threshold)); + let too_large_for_stack = conf.too_large_for_stack; + store.register_late_pass(move || box escape::BoxedLocal{too_large_for_stack}); + store.register_late_pass(move || box vec::UselessVec{too_large_for_stack}); + store.register_late_pass(|| box panic_unimplemented::PanicUnimplemented); + store.register_late_pass(|| box strings::StringLitAsBytes); + store.register_late_pass(|| box derive::Derive); + store.register_late_pass(|| box get_last_with_len::GetLastWithLen); + store.register_late_pass(|| box drop_forget_ref::DropForgetRef); + store.register_late_pass(|| box empty_enum::EmptyEnum); + store.register_late_pass(|| box types::AbsurdExtremeComparisons); + store.register_late_pass(|| box types::InvalidUpcastComparisons); + store.register_late_pass(|| box regex::Regex::default()); + store.register_late_pass(|| box copies::CopyAndPaste); + store.register_late_pass(|| box copy_iterator::CopyIterator); + store.register_late_pass(|| box format::UselessFormat); + store.register_late_pass(|| box swap::Swap); + store.register_late_pass(|| box overflow_check_conditional::OverflowCheckConditional); + store.register_late_pass(|| box new_without_default::NewWithoutDefault::default()); + let blacklisted_names = conf.blacklisted_names.iter().cloned().collect::>(); + store.register_late_pass(move || box blacklisted_name::BlacklistedName::new(blacklisted_names.clone())); + let too_many_arguments_threshold1 = conf.too_many_arguments_threshold; + let too_many_lines_threshold2 = conf.too_many_lines_threshold; + store.register_late_pass(move || box functions::Functions::new(too_many_arguments_threshold1, too_many_lines_threshold2)); + let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::>(); + store.register_late_pass(move || box doc::DocMarkdown::new(doc_valid_idents.clone())); + store.register_late_pass(|| box neg_multiply::NegMultiply); + store.register_late_pass(|| box mem_discriminant::MemDiscriminant); + store.register_late_pass(|| box mem_forget::MemForget); + store.register_late_pass(|| box arithmetic::Arithmetic::default()); + store.register_late_pass(|| box assign_ops::AssignOps); + store.register_late_pass(|| box let_if_seq::LetIfSeq); + store.register_late_pass(|| box eval_order_dependence::EvalOrderDependence); + store.register_late_pass(|| box missing_doc::MissingDoc::new()); + store.register_late_pass(|| box missing_inline::MissingInline); + store.register_late_pass(move || box exhaustive_items::ExhaustiveItems); + store.register_late_pass(|| box if_let_some_result::OkIfLet); + store.register_late_pass(|| box partialeq_ne_impl::PartialEqNeImpl); + store.register_late_pass(|| box unused_io_amount::UnusedIoAmount); + let enum_variant_size_threshold = conf.enum_variant_size_threshold; + store.register_late_pass(move || box large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold)); + store.register_late_pass(|| box explicit_write::ExplicitWrite); + store.register_late_pass(|| box needless_pass_by_value::NeedlessPassByValue); + let pass_by_ref_or_value = pass_by_ref_or_value::PassByRefOrValue::new( + conf.trivial_copy_size_limit, + conf.pass_by_value_size_limit, + &sess.target, + ); + store.register_late_pass(move || box pass_by_ref_or_value); + store.register_late_pass(|| box ref_option_ref::RefOptionRef); + store.register_late_pass(|| box try_err::TryErr); + store.register_late_pass(|| box bytecount::ByteCount); + store.register_late_pass(|| box infinite_iter::InfiniteIter); + store.register_late_pass(|| box inline_fn_without_body::InlineFnWithoutBody); + store.register_late_pass(|| box useless_conversion::UselessConversion::default()); + store.register_late_pass(|| box types::ImplicitHasher); + store.register_late_pass(|| box fallible_impl_from::FallibleImplFrom); + store.register_late_pass(|| box types::UnitArg); + store.register_late_pass(|| box double_comparison::DoubleComparisons); + store.register_late_pass(|| box question_mark::QuestionMark); + store.register_early_pass(|| box suspicious_operation_groupings::SuspiciousOperationGroupings); + store.register_late_pass(|| box suspicious_trait_impl::SuspiciousImpl); + store.register_late_pass(|| box map_unit_fn::MapUnit); + store.register_late_pass(|| box inherent_impl::MultipleInherentImpl::default()); + store.register_late_pass(|| box neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd); + store.register_late_pass(|| box unwrap::Unwrap); + store.register_late_pass(|| box duration_subsec::DurationSubsec); + store.register_late_pass(|| box indexing_slicing::IndexingSlicing); + store.register_late_pass(|| box non_copy_const::NonCopyConst); + store.register_late_pass(|| box ptr_offset_with_cast::PtrOffsetWithCast); + store.register_late_pass(|| box redundant_clone::RedundantClone); + store.register_late_pass(|| box slow_vector_initialization::SlowVectorInit); + store.register_late_pass(|| box unnecessary_sort_by::UnnecessarySortBy); + store.register_late_pass(|| box unnecessary_wraps::UnnecessaryWraps); + store.register_late_pass(|| box assertions_on_constants::AssertionsOnConstants); + store.register_late_pass(|| box transmuting_null::TransmutingNull); + store.register_late_pass(|| box path_buf_push_overwrite::PathBufPushOverwrite); + store.register_late_pass(|| box integer_division::IntegerDivision); + store.register_late_pass(|| box inherent_to_string::InherentToString); + let max_trait_bounds = conf.max_trait_bounds; + store.register_late_pass(move || box trait_bounds::TraitBounds::new(max_trait_bounds)); + store.register_late_pass(|| box comparison_chain::ComparisonChain); + store.register_late_pass(|| box mut_key::MutableKeyType); + store.register_late_pass(|| box modulo_arithmetic::ModuloArithmetic); + store.register_early_pass(|| box reference::DerefAddrOf); + store.register_early_pass(|| box reference::RefInDeref); + store.register_early_pass(|| box double_parens::DoubleParens); + store.register_late_pass(|| box to_string_in_display::ToStringInDisplay::new()); + store.register_early_pass(|| box unsafe_removed_from_name::UnsafeNameRemoval); + store.register_early_pass(|| box if_not_else::IfNotElse); + store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse); + store.register_early_pass(|| box int_plus_one::IntPlusOne); + store.register_early_pass(|| box formatting::Formatting); + store.register_early_pass(|| box misc_early::MiscEarlyLints); + store.register_early_pass(|| box redundant_closure_call::RedundantClosureCall); + store.register_late_pass(|| box redundant_closure_call::RedundantClosureCall); + store.register_early_pass(|| box unused_unit::UnusedUnit); + store.register_late_pass(|| box returns::Return); + store.register_early_pass(|| box collapsible_if::CollapsibleIf); + store.register_early_pass(|| box items_after_statements::ItemsAfterStatements); + store.register_early_pass(|| box precedence::Precedence); + store.register_early_pass(|| box needless_continue::NeedlessContinue); + store.register_early_pass(|| box redundant_else::RedundantElse); + store.register_late_pass(|| box create_dir::CreateDir); + store.register_early_pass(|| box needless_arbitrary_self_type::NeedlessArbitrarySelfType); + let cargo_ignore_publish = conf.cargo_ignore_publish; + store.register_late_pass(move || box cargo_common_metadata::CargoCommonMetadata::new(cargo_ignore_publish)); + store.register_late_pass(|| box multiple_crate_versions::MultipleCrateVersions); + store.register_late_pass(|| box wildcard_dependencies::WildcardDependencies); + let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions; + store.register_early_pass(move || box literal_representation::LiteralDigitGrouping::new(literal_representation_lint_fraction_readability)); + let literal_representation_threshold = conf.literal_representation_threshold; + store.register_early_pass(move || box literal_representation::DecimalLiteralRepresentation::new(literal_representation_threshold)); + let enum_variant_name_threshold = conf.enum_variant_name_threshold; + store.register_early_pass(move || box enum_variants::EnumVariantNames::new(enum_variant_name_threshold)); + store.register_early_pass(|| box tabs_in_doc_comments::TabsInDocComments); + let upper_case_acronyms_aggressive = conf.upper_case_acronyms_aggressive; + store.register_early_pass(move || box upper_case_acronyms::UpperCaseAcronyms::new(upper_case_acronyms_aggressive)); + store.register_late_pass(|| box default::Default::default()); + store.register_late_pass(|| box unused_self::UnusedSelf); + store.register_late_pass(|| box mutable_debug_assertion::DebugAssertWithMutCall); + store.register_late_pass(|| box exit::Exit); + store.register_late_pass(|| box to_digit_is_some::ToDigitIsSome); + let array_size_threshold = conf.array_size_threshold; + store.register_late_pass(move || box large_stack_arrays::LargeStackArrays::new(array_size_threshold)); + store.register_late_pass(move || box large_const_arrays::LargeConstArrays::new(array_size_threshold)); + store.register_late_pass(|| box floating_point_arithmetic::FloatingPointArithmetic); + store.register_early_pass(|| box as_conversions::AsConversions); + store.register_late_pass(|| box let_underscore::LetUnderscore); + store.register_late_pass(|| box atomic_ordering::AtomicOrdering); + store.register_early_pass(|| box single_component_path_imports::SingleComponentPathImports); + let max_fn_params_bools = conf.max_fn_params_bools; + let max_struct_bools = conf.max_struct_bools; + store.register_early_pass(move || box excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools)); + store.register_early_pass(|| box option_env_unwrap::OptionEnvUnwrap); + let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports; + store.register_late_pass(move || box wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)); + store.register_late_pass(|| box verbose_file_reads::VerboseFileReads); + store.register_late_pass(|| box redundant_pub_crate::RedundantPubCrate::default()); + store.register_late_pass(|| box unnamed_address::UnnamedAddress); + store.register_late_pass(|| box dereference::Dereferencing); + store.register_late_pass(|| box option_if_let_else::OptionIfLetElse); + store.register_late_pass(|| box future_not_send::FutureNotSend); + store.register_late_pass(|| box if_let_mutex::IfLetMutex); + store.register_late_pass(|| box mut_mutex_lock::MutMutexLock); + store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems); + store.register_late_pass(|| box manual_async_fn::ManualAsyncFn); + store.register_late_pass(|| box vec_resize_to_zero::VecResizeToZero); + store.register_late_pass(|| box panic_in_result_fn::PanicInResultFn); + let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; + store.register_early_pass(move || box non_expressive_names::NonExpressiveNames { + single_char_binding_names_threshold, + }); + store.register_early_pass(|| box unnested_or_patterns::UnnestedOrPatterns); + store.register_late_pass(|| box macro_use::MacroUseImports::default()); + store.register_late_pass(|| box map_identity::MapIdentity); + store.register_late_pass(|| box pattern_type_mismatch::PatternTypeMismatch); + store.register_late_pass(|| box stable_sort_primitive::StableSortPrimitive); + store.register_late_pass(|| box repeat_once::RepeatOnce); + store.register_late_pass(|| box unwrap_in_result::UnwrapInResult); + store.register_late_pass(|| box self_assignment::SelfAssignment); + store.register_late_pass(|| box manual_unwrap_or::ManualUnwrapOr); + store.register_late_pass(|| box manual_ok_or::ManualOkOr); + store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs); + store.register_late_pass(|| box semicolon_if_nothing_returned::SemicolonIfNothingReturned); + store.register_late_pass(|| box async_yields_async::AsyncYieldsAsync); + let disallowed_methods = conf.disallowed_methods.iter().cloned().collect::>(); + store.register_late_pass(move || box disallowed_method::DisallowedMethod::new(&disallowed_methods)); + store.register_early_pass(|| box asm_syntax::InlineAsmX86AttSyntax); + store.register_early_pass(|| box asm_syntax::InlineAsmX86IntelSyntax); + store.register_late_pass(|| box undropped_manually_drops::UndroppedManuallyDrops); + store.register_late_pass(|| box strings::StrToString); + store.register_late_pass(|| box strings::StringToString); + store.register_late_pass(|| box zero_sized_map_values::ZeroSizedMapValues); + store.register_late_pass(|| box vec_init_then_push::VecInitThenPush::default()); + store.register_late_pass(|| box case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons); + store.register_late_pass(|| box redundant_slicing::RedundantSlicing); + store.register_late_pass(|| box from_str_radix_10::FromStrRadix10); + store.register_late_pass(|| box manual_map::ManualMap); + + store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ + LintId::of(&arithmetic::FLOAT_ARITHMETIC), + LintId::of(&arithmetic::INTEGER_ARITHMETIC), + LintId::of(&as_conversions::AS_CONVERSIONS), + LintId::of(&asm_syntax::INLINE_ASM_X86_ATT_SYNTAX), + LintId::of(&asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX), + LintId::of(&create_dir::CREATE_DIR), + LintId::of(&dbg_macro::DBG_MACRO), + LintId::of(&default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK), + LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE), + LintId::of(&exhaustive_items::EXHAUSTIVE_ENUMS), + LintId::of(&exhaustive_items::EXHAUSTIVE_STRUCTS), + LintId::of(&exit::EXIT), + LintId::of(&float_literal::LOSSY_FLOAT_LITERAL), + LintId::of(&implicit_return::IMPLICIT_RETURN), + LintId::of(&indexing_slicing::INDEXING_SLICING), + LintId::of(&inherent_impl::MULTIPLE_INHERENT_IMPL), + LintId::of(&integer_division::INTEGER_DIVISION), + LintId::of(&let_underscore::LET_UNDERSCORE_MUST_USE), + LintId::of(&literal_representation::DECIMAL_LITERAL_REPRESENTATION), + LintId::of(&map_err_ignore::MAP_ERR_IGNORE), + LintId::of(&matches::REST_PAT_IN_FULLY_BOUND_STRUCTS), + LintId::of(&matches::WILDCARD_ENUM_MATCH_ARM), + LintId::of(&mem_forget::MEM_FORGET), + LintId::of(&methods::CLONE_ON_REF_PTR), + LintId::of(&methods::EXPECT_USED), + LintId::of(&methods::FILETYPE_IS_FILE), + LintId::of(&methods::GET_UNWRAP), + LintId::of(&methods::UNWRAP_USED), + LintId::of(&methods::WRONG_PUB_SELF_CONVENTION), + LintId::of(&misc::FLOAT_CMP_CONST), + LintId::of(&misc_early::UNNEEDED_FIELD_PATTERN), + LintId::of(&missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS), + LintId::of(&missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS), + LintId::of(&modulo_arithmetic::MODULO_ARITHMETIC), + LintId::of(&panic_in_result_fn::PANIC_IN_RESULT_FN), + LintId::of(&panic_unimplemented::PANIC), + LintId::of(&panic_unimplemented::TODO), + LintId::of(&panic_unimplemented::UNIMPLEMENTED), + LintId::of(&panic_unimplemented::UNREACHABLE), + LintId::of(&pattern_type_mismatch::PATTERN_TYPE_MISMATCH), + LintId::of(&semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED), + LintId::of(&shadow::SHADOW_REUSE), + LintId::of(&shadow::SHADOW_SAME), + LintId::of(&strings::STRING_ADD), + LintId::of(&strings::STRING_TO_STRING), + LintId::of(&strings::STR_TO_STRING), + LintId::of(&types::RC_BUFFER), + LintId::of(&unwrap_in_result::UNWRAP_IN_RESULT), + LintId::of(&verbose_file_reads::VERBOSE_FILE_READS), + LintId::of(&write::PRINT_STDERR), + LintId::of(&write::PRINT_STDOUT), + LintId::of(&write::USE_DEBUG), + ]); + + store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ + LintId::of(&attrs::INLINE_ALWAYS), + LintId::of(&await_holding_invalid::AWAIT_HOLDING_LOCK), + LintId::of(&await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), + LintId::of(&bit_mask::VERBOSE_BIT_MASK), + LintId::of(&bytecount::NAIVE_BYTECOUNT), + LintId::of(&case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS), + LintId::of(&casts::CAST_LOSSLESS), + LintId::of(&casts::CAST_POSSIBLE_TRUNCATION), + LintId::of(&casts::CAST_POSSIBLE_WRAP), + LintId::of(&casts::CAST_PRECISION_LOSS), + LintId::of(&casts::CAST_PTR_ALIGNMENT), + LintId::of(&casts::CAST_SIGN_LOSS), + LintId::of(&casts::PTR_AS_PTR), + LintId::of(&checked_conversions::CHECKED_CONVERSIONS), + LintId::of(&copies::SAME_FUNCTIONS_IN_IF_CONDITION), + LintId::of(©_iterator::COPY_ITERATOR), + LintId::of(&default::DEFAULT_TRAIT_ACCESS), + LintId::of(&dereference::EXPLICIT_DEREF_METHODS), + LintId::of(&derive::EXPL_IMPL_CLONE_ON_COPY), + LintId::of(&derive::UNSAFE_DERIVE_DESERIALIZE), + LintId::of(&doc::DOC_MARKDOWN), + LintId::of(&doc::MISSING_ERRORS_DOC), + LintId::of(&doc::MISSING_PANICS_DOC), + LintId::of(&empty_enum::EMPTY_ENUM), + LintId::of(&enum_variants::MODULE_NAME_REPETITIONS), + LintId::of(&enum_variants::PUB_ENUM_VARIANT_NAMES), + LintId::of(&eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS), + LintId::of(&excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS), + LintId::of(&excessive_bools::STRUCT_EXCESSIVE_BOOLS), + LintId::of(&functions::MUST_USE_CANDIDATE), + LintId::of(&functions::TOO_MANY_LINES), + LintId::of(&if_not_else::IF_NOT_ELSE), + LintId::of(&implicit_saturating_sub::IMPLICIT_SATURATING_SUB), + LintId::of(&infinite_iter::MAYBE_INFINITE_ITER), + LintId::of(&items_after_statements::ITEMS_AFTER_STATEMENTS), + LintId::of(&large_stack_arrays::LARGE_STACK_ARRAYS), + LintId::of(&let_underscore::LET_UNDERSCORE_DROP), + LintId::of(&literal_representation::LARGE_DIGIT_GROUPS), + LintId::of(&literal_representation::UNREADABLE_LITERAL), + LintId::of(&loops::EXPLICIT_INTO_ITER_LOOP), + LintId::of(&loops::EXPLICIT_ITER_LOOP), + LintId::of(¯o_use::MACRO_USE_IMPORTS), + LintId::of(&manual_ok_or::MANUAL_OK_OR), + LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), + LintId::of(&matches::MATCH_BOOL), + LintId::of(&matches::MATCH_SAME_ARMS), + LintId::of(&matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS), + LintId::of(&matches::MATCH_WILD_ERR_ARM), + LintId::of(&matches::SINGLE_MATCH_ELSE), + LintId::of(&methods::FILTER_MAP), + LintId::of(&methods::FILTER_MAP_NEXT), + LintId::of(&methods::IMPLICIT_CLONE), + LintId::of(&methods::INEFFICIENT_TO_STRING), + LintId::of(&methods::MAP_FLATTEN), + LintId::of(&methods::MAP_UNWRAP_OR), + LintId::of(&misc::USED_UNDERSCORE_BINDING), + LintId::of(&misc_early::UNSEPARATED_LITERAL_SUFFIX), + LintId::of(&mut_mut::MUT_MUT), + LintId::of(&needless_continue::NEEDLESS_CONTINUE), + LintId::of(&needless_pass_by_value::NEEDLESS_PASS_BY_VALUE), + LintId::of(&non_expressive_names::SIMILAR_NAMES), + LintId::of(&option_if_let_else::OPTION_IF_LET_ELSE), + LintId::of(&pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE), + LintId::of(&pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF), + LintId::of(&ranges::RANGE_MINUS_ONE), + LintId::of(&ranges::RANGE_PLUS_ONE), + LintId::of(&redundant_else::REDUNDANT_ELSE), + LintId::of(&ref_option_ref::REF_OPTION_REF), + LintId::of(&shadow::SHADOW_UNRELATED), + LintId::of(&strings::STRING_ADD_ASSIGN), + LintId::of(&trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS), + LintId::of(&trait_bounds::TYPE_REPETITION_IN_BOUNDS), + LintId::of(&types::IMPLICIT_HASHER), + LintId::of(&types::INVALID_UPCAST_COMPARISONS), + LintId::of(&types::LET_UNIT_VALUE), + LintId::of(&types::LINKEDLIST), + LintId::of(&types::OPTION_OPTION), + LintId::of(&unicode::NON_ASCII_LITERAL), + LintId::of(&unicode::UNICODE_NOT_NFC), + LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS), + LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS), + LintId::of(&unused_self::UNUSED_SELF), + LintId::of(&wildcard_imports::ENUM_GLOB_USE), + LintId::of(&wildcard_imports::WILDCARD_IMPORTS), + LintId::of(&zero_sized_map_values::ZERO_SIZED_MAP_VALUES), + ]); + + #[cfg(feature = "internal-lints")] + store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![ + LintId::of(&utils::internal_lints::CLIPPY_LINTS_INTERNAL), + LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS), + LintId::of(&utils::internal_lints::COMPILER_LINT_FUNCTIONS), + LintId::of(&utils::internal_lints::DEFAULT_LINT), + LintId::of(&utils::internal_lints::INTERNING_DEFINED_SYMBOL), + LintId::of(&utils::internal_lints::INVALID_PATHS), + LintId::of(&utils::internal_lints::LINT_WITHOUT_LINT_PASS), + LintId::of(&utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM), + LintId::of(&utils::internal_lints::OUTER_EXPN_EXPN_DATA), + LintId::of(&utils::internal_lints::PRODUCE_ICE), + LintId::of(&utils::internal_lints::UNNECESSARY_SYMBOL_STR), + ]); + + store.register_group(true, "clippy::all", Some("clippy"), vec![ + LintId::of(&approx_const::APPROX_CONSTANT), + LintId::of(&assertions_on_constants::ASSERTIONS_ON_CONSTANTS), + LintId::of(&assign_ops::ASSIGN_OP_PATTERN), + LintId::of(&assign_ops::MISREFACTORED_ASSIGN_OP), + LintId::of(&async_yields_async::ASYNC_YIELDS_ASYNC), + LintId::of(&atomic_ordering::INVALID_ATOMIC_ORDERING), + LintId::of(&attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), + LintId::of(&attrs::DEPRECATED_CFG_ATTR), + LintId::of(&attrs::DEPRECATED_SEMVER), + LintId::of(&attrs::MISMATCHED_TARGET_OS), + LintId::of(&attrs::USELESS_ATTRIBUTE), + LintId::of(&bit_mask::BAD_BIT_MASK), + LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK), + LintId::of(&blacklisted_name::BLACKLISTED_NAME), + LintId::of(&blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), + LintId::of(&booleans::LOGIC_BUG), + LintId::of(&booleans::NONMINIMAL_BOOL), + LintId::of(&casts::CAST_REF_TO_MUT), + LintId::of(&casts::CHAR_LIT_AS_U8), + LintId::of(&casts::FN_TO_NUMERIC_CAST), + LintId::of(&casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), + LintId::of(&casts::UNNECESSARY_CAST), + LintId::of(&collapsible_if::COLLAPSIBLE_ELSE_IF), + LintId::of(&collapsible_if::COLLAPSIBLE_IF), + LintId::of(&collapsible_match::COLLAPSIBLE_MATCH), + LintId::of(&comparison_chain::COMPARISON_CHAIN), + LintId::of(&copies::IFS_SAME_COND), + LintId::of(&copies::IF_SAME_THEN_ELSE), + LintId::of(&default::FIELD_REASSIGN_WITH_DEFAULT), + LintId::of(&derive::DERIVE_HASH_XOR_EQ), + LintId::of(&derive::DERIVE_ORD_XOR_PARTIAL_ORD), + LintId::of(&doc::MISSING_SAFETY_DOC), + LintId::of(&doc::NEEDLESS_DOCTEST_MAIN), + LintId::of(&double_comparison::DOUBLE_COMPARISONS), + LintId::of(&double_parens::DOUBLE_PARENS), + LintId::of(&drop_forget_ref::DROP_COPY), + LintId::of(&drop_forget_ref::DROP_REF), + LintId::of(&drop_forget_ref::FORGET_COPY), + LintId::of(&drop_forget_ref::FORGET_REF), + LintId::of(&duration_subsec::DURATION_SUBSEC), + LintId::of(&entry::MAP_ENTRY), + LintId::of(&enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), + LintId::of(&enum_variants::ENUM_VARIANT_NAMES), + LintId::of(&enum_variants::MODULE_INCEPTION), + LintId::of(&eq_op::EQ_OP), + LintId::of(&eq_op::OP_REF), + LintId::of(&erasing_op::ERASING_OP), + LintId::of(&escape::BOXED_LOCAL), + LintId::of(&eta_reduction::REDUNDANT_CLOSURE), + LintId::of(&eval_order_dependence::DIVERGING_SUB_EXPRESSION), + LintId::of(&eval_order_dependence::EVAL_ORDER_DEPENDENCE), + LintId::of(&explicit_write::EXPLICIT_WRITE), + LintId::of(&float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), + LintId::of(&float_literal::EXCESSIVE_PRECISION), + LintId::of(&format::USELESS_FORMAT), + LintId::of(&formatting::POSSIBLE_MISSING_COMMA), + LintId::of(&formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), + LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING), + LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING), + LintId::of(&from_over_into::FROM_OVER_INTO), + LintId::of(&from_str_radix_10::FROM_STR_RADIX_10), + LintId::of(&functions::DOUBLE_MUST_USE), + LintId::of(&functions::MUST_USE_UNIT), + LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF), + LintId::of(&functions::RESULT_UNIT_ERR), + LintId::of(&functions::TOO_MANY_ARGUMENTS), + LintId::of(&get_last_with_len::GET_LAST_WITH_LEN), + LintId::of(&identity_op::IDENTITY_OP), + LintId::of(&if_let_mutex::IF_LET_MUTEX), + LintId::of(&if_let_some_result::IF_LET_SOME_RESULT), + LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR), + LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING), + LintId::of(&infinite_iter::INFINITE_ITER), + LintId::of(&inherent_to_string::INHERENT_TO_STRING), + LintId::of(&inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY), + LintId::of(&inline_fn_without_body::INLINE_FN_WITHOUT_BODY), + LintId::of(&int_plus_one::INT_PLUS_ONE), + LintId::of(&large_const_arrays::LARGE_CONST_ARRAYS), + LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT), + LintId::of(&len_zero::COMPARISON_TO_EMPTY), + LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), + LintId::of(&len_zero::LEN_ZERO), + LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), + LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES), + LintId::of(&lifetimes::NEEDLESS_LIFETIMES), + LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), + LintId::of(&literal_representation::MISTYPED_LITERAL_SUFFIXES), + LintId::of(&literal_representation::UNUSUAL_BYTE_GROUPINGS), + LintId::of(&loops::EMPTY_LOOP), + LintId::of(&loops::EXPLICIT_COUNTER_LOOP), + LintId::of(&loops::FOR_KV_MAP), + LintId::of(&loops::FOR_LOOPS_OVER_FALLIBLES), + LintId::of(&loops::ITER_NEXT_LOOP), + LintId::of(&loops::MANUAL_FLATTEN), + LintId::of(&loops::MANUAL_MEMCPY), + LintId::of(&loops::MUT_RANGE_BOUND), + LintId::of(&loops::NEEDLESS_COLLECT), + LintId::of(&loops::NEEDLESS_RANGE_LOOP), + LintId::of(&loops::NEVER_LOOP), + LintId::of(&loops::SAME_ITEM_PUSH), + LintId::of(&loops::SINGLE_ELEMENT_LOOP), + LintId::of(&loops::WHILE_IMMUTABLE_CONDITION), + LintId::of(&loops::WHILE_LET_LOOP), + LintId::of(&loops::WHILE_LET_ON_ITERATOR), + LintId::of(&main_recursion::MAIN_RECURSION), + LintId::of(&manual_async_fn::MANUAL_ASYNC_FN), + LintId::of(&manual_map::MANUAL_MAP), + LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE), + LintId::of(&manual_strip::MANUAL_STRIP), + LintId::of(&manual_unwrap_or::MANUAL_UNWRAP_OR), + LintId::of(&map_clone::MAP_CLONE), + LintId::of(&map_identity::MAP_IDENTITY), + LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN), + LintId::of(&map_unit_fn::RESULT_MAP_UNIT_FN), + LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), + LintId::of(&matches::MATCH_AS_REF), + LintId::of(&matches::MATCH_LIKE_MATCHES_MACRO), + LintId::of(&matches::MATCH_OVERLAPPING_ARM), + LintId::of(&matches::MATCH_REF_PATS), + LintId::of(&matches::MATCH_SINGLE_BINDING), + LintId::of(&matches::REDUNDANT_PATTERN_MATCHING), + LintId::of(&matches::SINGLE_MATCH), + LintId::of(&matches::WILDCARD_IN_OR_PATTERNS), + LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM), + LintId::of(&mem_replace::MEM_REPLACE_OPTION_WITH_NONE), + LintId::of(&mem_replace::MEM_REPLACE_WITH_DEFAULT), + LintId::of(&mem_replace::MEM_REPLACE_WITH_UNINIT), + LintId::of(&methods::BIND_INSTEAD_OF_MAP), + LintId::of(&methods::BYTES_NTH), + LintId::of(&methods::CHARS_LAST_CMP), + LintId::of(&methods::CHARS_NEXT_CMP), + LintId::of(&methods::CLONE_DOUBLE_REF), + LintId::of(&methods::CLONE_ON_COPY), + LintId::of(&methods::EXPECT_FUN_CALL), + LintId::of(&methods::FILTER_MAP_IDENTITY), + LintId::of(&methods::FILTER_NEXT), + LintId::of(&methods::FLAT_MAP_IDENTITY), + LintId::of(&methods::FROM_ITER_INSTEAD_OF_COLLECT), + LintId::of(&methods::INSPECT_FOR_EACH), + LintId::of(&methods::INTO_ITER_ON_REF), + LintId::of(&methods::ITERATOR_STEP_BY_ZERO), + LintId::of(&methods::ITER_CLONED_COLLECT), + LintId::of(&methods::ITER_COUNT), + LintId::of(&methods::ITER_NEXT_SLICE), + LintId::of(&methods::ITER_NTH), + LintId::of(&methods::ITER_NTH_ZERO), + LintId::of(&methods::ITER_SKIP_NEXT), + LintId::of(&methods::MANUAL_FILTER_MAP), + LintId::of(&methods::MANUAL_FIND_MAP), + LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC), + LintId::of(&methods::MAP_COLLECT_RESULT_UNIT), + LintId::of(&methods::NEW_RET_NO_SELF), + LintId::of(&methods::OK_EXPECT), + LintId::of(&methods::OPTION_AS_REF_DEREF), + LintId::of(&methods::OPTION_MAP_OR_NONE), + LintId::of(&methods::OR_FUN_CALL), + LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION), + LintId::of(&methods::SEARCH_IS_SOME), + LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT), + LintId::of(&methods::SINGLE_CHAR_ADD_STR), + LintId::of(&methods::SINGLE_CHAR_PATTERN), + LintId::of(&methods::SKIP_WHILE_NEXT), + LintId::of(&methods::STRING_EXTEND_CHARS), + LintId::of(&methods::SUSPICIOUS_MAP), + LintId::of(&methods::UNINIT_ASSUMED_INIT), + LintId::of(&methods::UNNECESSARY_FILTER_MAP), + LintId::of(&methods::UNNECESSARY_FOLD), + LintId::of(&methods::UNNECESSARY_LAZY_EVALUATIONS), + LintId::of(&methods::USELESS_ASREF), + LintId::of(&methods::WRONG_SELF_CONVENTION), + LintId::of(&methods::ZST_OFFSET), + LintId::of(&minmax::MIN_MAX), + LintId::of(&misc::CMP_NAN), + LintId::of(&misc::CMP_OWNED), + LintId::of(&misc::FLOAT_CMP), + LintId::of(&misc::MODULO_ONE), + LintId::of(&misc::SHORT_CIRCUIT_STATEMENT), + LintId::of(&misc::TOPLEVEL_REF_ARG), + LintId::of(&misc::ZERO_PTR), + LintId::of(&misc_early::BUILTIN_TYPE_SHADOW), + LintId::of(&misc_early::DOUBLE_NEG), + LintId::of(&misc_early::DUPLICATE_UNDERSCORE_ARGUMENT), + LintId::of(&misc_early::MIXED_CASE_HEX_LITERALS), + LintId::of(&misc_early::REDUNDANT_PATTERN), + LintId::of(&misc_early::UNNEEDED_WILDCARD_PATTERN), + LintId::of(&misc_early::ZERO_PREFIXED_LITERAL), + LintId::of(&mut_key::MUTABLE_KEY_TYPE), + LintId::of(&mut_mutex_lock::MUT_MUTEX_LOCK), + LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED), + LintId::of(&mutex_atomic::MUTEX_ATOMIC), + LintId::of(&needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE), + LintId::of(&needless_bool::BOOL_COMPARISON), + LintId::of(&needless_bool::NEEDLESS_BOOL), + LintId::of(&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), + LintId::of(&needless_question_mark::NEEDLESS_QUESTION_MARK), + LintId::of(&needless_update::NEEDLESS_UPDATE), + LintId::of(&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), + LintId::of(&neg_multiply::NEG_MULTIPLY), + LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT), + LintId::of(&no_effect::NO_EFFECT), + LintId::of(&no_effect::UNNECESSARY_OPERATION), + LintId::of(&non_copy_const::BORROW_INTERIOR_MUTABLE_CONST), + LintId::of(&non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST), + LintId::of(&non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), + LintId::of(&non_expressive_names::MANY_SINGLE_CHAR_NAMES), + LintId::of(&open_options::NONSENSICAL_OPEN_OPTIONS), + LintId::of(&option_env_unwrap::OPTION_ENV_UNWRAP), + LintId::of(&overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), + LintId::of(&partialeq_ne_impl::PARTIALEQ_NE_IMPL), + LintId::of(&precedence::PRECEDENCE), + LintId::of(&ptr::CMP_NULL), + LintId::of(&ptr::MUT_FROM_REF), + LintId::of(&ptr::PTR_ARG), + LintId::of(&ptr_eq::PTR_EQ), + LintId::of(&ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), + LintId::of(&question_mark::QUESTION_MARK), + LintId::of(&ranges::MANUAL_RANGE_CONTAINS), + LintId::of(&ranges::RANGE_ZIP_WITH_LEN), + LintId::of(&ranges::REVERSED_EMPTY_RANGES), + LintId::of(&redundant_clone::REDUNDANT_CLONE), + LintId::of(&redundant_closure_call::REDUNDANT_CLOSURE_CALL), + LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES), + LintId::of(&redundant_slicing::REDUNDANT_SLICING), + LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), + LintId::of(&reference::DEREF_ADDROF), + LintId::of(&reference::REF_IN_DEREF), + LintId::of(®ex::INVALID_REGEX), + LintId::of(&repeat_once::REPEAT_ONCE), + LintId::of(&returns::LET_AND_RETURN), + LintId::of(&returns::NEEDLESS_RETURN), + LintId::of(&self_assignment::SELF_ASSIGNMENT), + LintId::of(&serde_api::SERDE_API_MISUSE), + LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), + LintId::of(&size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT), + LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), + LintId::of(&stable_sort_primitive::STABLE_SORT_PRIMITIVE), + LintId::of(&strings::STRING_FROM_UTF8_AS_BYTES), + LintId::of(&suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS), + LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), + LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), + LintId::of(&swap::ALMOST_SWAPPED), + LintId::of(&swap::MANUAL_SWAP), + LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), + LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), + LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME), + LintId::of(&to_string_in_display::TO_STRING_IN_DISPLAY), + LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), + LintId::of(&transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS), + LintId::of(&transmute::TRANSMUTE_BYTES_TO_STR), + LintId::of(&transmute::TRANSMUTE_FLOAT_TO_INT), + LintId::of(&transmute::TRANSMUTE_INT_TO_BOOL), + LintId::of(&transmute::TRANSMUTE_INT_TO_CHAR), + LintId::of(&transmute::TRANSMUTE_INT_TO_FLOAT), + LintId::of(&transmute::TRANSMUTE_PTR_TO_PTR), + LintId::of(&transmute::TRANSMUTE_PTR_TO_REF), + LintId::of(&transmute::UNSOUND_COLLECTION_TRANSMUTE), + LintId::of(&transmute::WRONG_TRANSMUTE), + LintId::of(&transmuting_null::TRANSMUTING_NULL), + LintId::of(&try_err::TRY_ERR), + LintId::of(&types::ABSURD_EXTREME_COMPARISONS), + LintId::of(&types::BORROWED_BOX), + LintId::of(&types::BOX_VEC), + LintId::of(&types::REDUNDANT_ALLOCATION), + LintId::of(&types::TYPE_COMPLEXITY), + LintId::of(&types::UNIT_ARG), + LintId::of(&types::UNIT_CMP), + LintId::of(&types::VEC_BOX), + LintId::of(&undropped_manually_drops::UNDROPPED_MANUALLY_DROPS), + LintId::of(&unicode::INVISIBLE_CHARACTERS), + LintId::of(&unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD), + LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), + LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), + LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), + LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), + LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), + LintId::of(&unused_unit::UNUSED_UNIT), + LintId::of(&unwrap::PANICKING_UNWRAP), + LintId::of(&unwrap::UNNECESSARY_UNWRAP), + LintId::of(&upper_case_acronyms::UPPER_CASE_ACRONYMS), + LintId::of(&useless_conversion::USELESS_CONVERSION), + LintId::of(&vec::USELESS_VEC), + LintId::of(&vec_init_then_push::VEC_INIT_THEN_PUSH), + LintId::of(&vec_resize_to_zero::VEC_RESIZE_TO_ZERO), + LintId::of(&write::PRINTLN_EMPTY_STRING), + LintId::of(&write::PRINT_LITERAL), + LintId::of(&write::PRINT_WITH_NEWLINE), + LintId::of(&write::WRITELN_EMPTY_STRING), + LintId::of(&write::WRITE_LITERAL), + LintId::of(&write::WRITE_WITH_NEWLINE), + LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), + ]); + + store.register_group(true, "clippy::style", Some("clippy_style"), vec![ + LintId::of(&assertions_on_constants::ASSERTIONS_ON_CONSTANTS), + LintId::of(&assign_ops::ASSIGN_OP_PATTERN), + LintId::of(&attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), + LintId::of(&blacklisted_name::BLACKLISTED_NAME), + LintId::of(&blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), + LintId::of(&casts::FN_TO_NUMERIC_CAST), + LintId::of(&casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), + LintId::of(&collapsible_if::COLLAPSIBLE_ELSE_IF), + LintId::of(&collapsible_if::COLLAPSIBLE_IF), + LintId::of(&collapsible_match::COLLAPSIBLE_MATCH), + LintId::of(&comparison_chain::COMPARISON_CHAIN), + LintId::of(&default::FIELD_REASSIGN_WITH_DEFAULT), + LintId::of(&doc::MISSING_SAFETY_DOC), + LintId::of(&doc::NEEDLESS_DOCTEST_MAIN), + LintId::of(&enum_variants::ENUM_VARIANT_NAMES), + LintId::of(&enum_variants::MODULE_INCEPTION), + LintId::of(&eq_op::OP_REF), + LintId::of(&eta_reduction::REDUNDANT_CLOSURE), + LintId::of(&float_literal::EXCESSIVE_PRECISION), + LintId::of(&formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), + LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING), + LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING), + LintId::of(&from_over_into::FROM_OVER_INTO), + LintId::of(&from_str_radix_10::FROM_STR_RADIX_10), + LintId::of(&functions::DOUBLE_MUST_USE), + LintId::of(&functions::MUST_USE_UNIT), + LintId::of(&functions::RESULT_UNIT_ERR), + LintId::of(&if_let_some_result::IF_LET_SOME_RESULT), + LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR), + LintId::of(&inherent_to_string::INHERENT_TO_STRING), + LintId::of(&len_zero::COMPARISON_TO_EMPTY), + LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), + LintId::of(&len_zero::LEN_ZERO), + LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), + LintId::of(&literal_representation::UNUSUAL_BYTE_GROUPINGS), + LintId::of(&loops::EMPTY_LOOP), + LintId::of(&loops::FOR_KV_MAP), + LintId::of(&loops::NEEDLESS_RANGE_LOOP), + LintId::of(&loops::SAME_ITEM_PUSH), + LintId::of(&loops::WHILE_LET_ON_ITERATOR), + LintId::of(&main_recursion::MAIN_RECURSION), + LintId::of(&manual_async_fn::MANUAL_ASYNC_FN), + LintId::of(&manual_map::MANUAL_MAP), + LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE), + LintId::of(&map_clone::MAP_CLONE), + LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), + LintId::of(&matches::MATCH_LIKE_MATCHES_MACRO), + LintId::of(&matches::MATCH_OVERLAPPING_ARM), + LintId::of(&matches::MATCH_REF_PATS), + LintId::of(&matches::REDUNDANT_PATTERN_MATCHING), + LintId::of(&matches::SINGLE_MATCH), + LintId::of(&mem_replace::MEM_REPLACE_OPTION_WITH_NONE), + LintId::of(&mem_replace::MEM_REPLACE_WITH_DEFAULT), + LintId::of(&methods::BYTES_NTH), + LintId::of(&methods::CHARS_LAST_CMP), + LintId::of(&methods::CHARS_NEXT_CMP), + LintId::of(&methods::FROM_ITER_INSTEAD_OF_COLLECT), + LintId::of(&methods::INTO_ITER_ON_REF), + LintId::of(&methods::ITER_CLONED_COLLECT), + LintId::of(&methods::ITER_NEXT_SLICE), + LintId::of(&methods::ITER_NTH_ZERO), + LintId::of(&methods::ITER_SKIP_NEXT), + LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC), + LintId::of(&methods::MAP_COLLECT_RESULT_UNIT), + LintId::of(&methods::NEW_RET_NO_SELF), + LintId::of(&methods::OK_EXPECT), + LintId::of(&methods::OPTION_MAP_OR_NONE), + LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION), + LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT), + LintId::of(&methods::SINGLE_CHAR_ADD_STR), + LintId::of(&methods::STRING_EXTEND_CHARS), + LintId::of(&methods::UNNECESSARY_FOLD), + LintId::of(&methods::UNNECESSARY_LAZY_EVALUATIONS), + LintId::of(&methods::WRONG_SELF_CONVENTION), + LintId::of(&misc::TOPLEVEL_REF_ARG), + LintId::of(&misc::ZERO_PTR), + LintId::of(&misc_early::BUILTIN_TYPE_SHADOW), + LintId::of(&misc_early::DOUBLE_NEG), + LintId::of(&misc_early::DUPLICATE_UNDERSCORE_ARGUMENT), + LintId::of(&misc_early::MIXED_CASE_HEX_LITERALS), + LintId::of(&misc_early::REDUNDANT_PATTERN), + LintId::of(&mut_mutex_lock::MUT_MUTEX_LOCK), + LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED), + LintId::of(&neg_multiply::NEG_MULTIPLY), + LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT), + LintId::of(&non_copy_const::BORROW_INTERIOR_MUTABLE_CONST), + LintId::of(&non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST), + LintId::of(&non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), + LintId::of(&non_expressive_names::MANY_SINGLE_CHAR_NAMES), + LintId::of(&ptr::CMP_NULL), + LintId::of(&ptr::PTR_ARG), + LintId::of(&ptr_eq::PTR_EQ), + LintId::of(&question_mark::QUESTION_MARK), + LintId::of(&ranges::MANUAL_RANGE_CONTAINS), + LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES), + LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), + LintId::of(&returns::LET_AND_RETURN), + LintId::of(&returns::NEEDLESS_RETURN), + LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), + LintId::of(&suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS), + LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), + LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME), + LintId::of(&try_err::TRY_ERR), + LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), + LintId::of(&unused_unit::UNUSED_UNIT), + LintId::of(&upper_case_acronyms::UPPER_CASE_ACRONYMS), + LintId::of(&write::PRINTLN_EMPTY_STRING), + LintId::of(&write::PRINT_LITERAL), + LintId::of(&write::PRINT_WITH_NEWLINE), + LintId::of(&write::WRITELN_EMPTY_STRING), + LintId::of(&write::WRITE_LITERAL), + LintId::of(&write::WRITE_WITH_NEWLINE), + ]); + + store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec![ + LintId::of(&assign_ops::MISREFACTORED_ASSIGN_OP), + LintId::of(&attrs::DEPRECATED_CFG_ATTR), + LintId::of(&booleans::NONMINIMAL_BOOL), + LintId::of(&casts::CHAR_LIT_AS_U8), + LintId::of(&casts::UNNECESSARY_CAST), + LintId::of(&double_comparison::DOUBLE_COMPARISONS), + LintId::of(&double_parens::DOUBLE_PARENS), + LintId::of(&duration_subsec::DURATION_SUBSEC), + LintId::of(&eval_order_dependence::DIVERGING_SUB_EXPRESSION), + LintId::of(&eval_order_dependence::EVAL_ORDER_DEPENDENCE), + LintId::of(&explicit_write::EXPLICIT_WRITE), + LintId::of(&format::USELESS_FORMAT), + LintId::of(&functions::TOO_MANY_ARGUMENTS), + LintId::of(&get_last_with_len::GET_LAST_WITH_LEN), + LintId::of(&identity_op::IDENTITY_OP), + LintId::of(&int_plus_one::INT_PLUS_ONE), + LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES), + LintId::of(&lifetimes::NEEDLESS_LIFETIMES), + LintId::of(&loops::EXPLICIT_COUNTER_LOOP), + LintId::of(&loops::MANUAL_FLATTEN), + LintId::of(&loops::MUT_RANGE_BOUND), + LintId::of(&loops::SINGLE_ELEMENT_LOOP), + LintId::of(&loops::WHILE_LET_LOOP), + LintId::of(&manual_strip::MANUAL_STRIP), + LintId::of(&manual_unwrap_or::MANUAL_UNWRAP_OR), + LintId::of(&map_identity::MAP_IDENTITY), + LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN), + LintId::of(&map_unit_fn::RESULT_MAP_UNIT_FN), + LintId::of(&matches::MATCH_AS_REF), + LintId::of(&matches::MATCH_SINGLE_BINDING), + LintId::of(&matches::WILDCARD_IN_OR_PATTERNS), + LintId::of(&methods::BIND_INSTEAD_OF_MAP), + LintId::of(&methods::CLONE_ON_COPY), + LintId::of(&methods::FILTER_MAP_IDENTITY), + LintId::of(&methods::FILTER_NEXT), + LintId::of(&methods::FLAT_MAP_IDENTITY), + LintId::of(&methods::INSPECT_FOR_EACH), + LintId::of(&methods::ITER_COUNT), + LintId::of(&methods::MANUAL_FILTER_MAP), + LintId::of(&methods::MANUAL_FIND_MAP), + LintId::of(&methods::OPTION_AS_REF_DEREF), + LintId::of(&methods::SEARCH_IS_SOME), + LintId::of(&methods::SKIP_WHILE_NEXT), + LintId::of(&methods::SUSPICIOUS_MAP), + LintId::of(&methods::UNNECESSARY_FILTER_MAP), + LintId::of(&methods::USELESS_ASREF), + LintId::of(&misc::SHORT_CIRCUIT_STATEMENT), + LintId::of(&misc_early::UNNEEDED_WILDCARD_PATTERN), + LintId::of(&misc_early::ZERO_PREFIXED_LITERAL), + LintId::of(&needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE), + LintId::of(&needless_bool::BOOL_COMPARISON), + LintId::of(&needless_bool::NEEDLESS_BOOL), + LintId::of(&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), + LintId::of(&needless_question_mark::NEEDLESS_QUESTION_MARK), + LintId::of(&needless_update::NEEDLESS_UPDATE), + LintId::of(&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), + LintId::of(&no_effect::NO_EFFECT), + LintId::of(&no_effect::UNNECESSARY_OPERATION), + LintId::of(&overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), + LintId::of(&partialeq_ne_impl::PARTIALEQ_NE_IMPL), + LintId::of(&precedence::PRECEDENCE), + LintId::of(&ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), + LintId::of(&ranges::RANGE_ZIP_WITH_LEN), + LintId::of(&redundant_closure_call::REDUNDANT_CLOSURE_CALL), + LintId::of(&redundant_slicing::REDUNDANT_SLICING), + LintId::of(&reference::DEREF_ADDROF), + LintId::of(&reference::REF_IN_DEREF), + LintId::of(&repeat_once::REPEAT_ONCE), + LintId::of(&strings::STRING_FROM_UTF8_AS_BYTES), + LintId::of(&swap::MANUAL_SWAP), + LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), + LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), + LintId::of(&transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS), + LintId::of(&transmute::TRANSMUTE_BYTES_TO_STR), + LintId::of(&transmute::TRANSMUTE_FLOAT_TO_INT), + LintId::of(&transmute::TRANSMUTE_INT_TO_BOOL), + LintId::of(&transmute::TRANSMUTE_INT_TO_CHAR), + LintId::of(&transmute::TRANSMUTE_INT_TO_FLOAT), + LintId::of(&transmute::TRANSMUTE_PTR_TO_PTR), + LintId::of(&transmute::TRANSMUTE_PTR_TO_REF), + LintId::of(&types::BORROWED_BOX), + LintId::of(&types::TYPE_COMPLEXITY), + LintId::of(&types::UNIT_ARG), + LintId::of(&types::VEC_BOX), + LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), + LintId::of(&unwrap::UNNECESSARY_UNWRAP), + LintId::of(&useless_conversion::USELESS_CONVERSION), + LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), + ]); + + store.register_group(true, "clippy::correctness", Some("clippy_correctness"), vec![ + LintId::of(&approx_const::APPROX_CONSTANT), + LintId::of(&async_yields_async::ASYNC_YIELDS_ASYNC), + LintId::of(&atomic_ordering::INVALID_ATOMIC_ORDERING), + LintId::of(&attrs::DEPRECATED_SEMVER), + LintId::of(&attrs::MISMATCHED_TARGET_OS), + LintId::of(&attrs::USELESS_ATTRIBUTE), + LintId::of(&bit_mask::BAD_BIT_MASK), + LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK), + LintId::of(&booleans::LOGIC_BUG), + LintId::of(&casts::CAST_REF_TO_MUT), + LintId::of(&copies::IFS_SAME_COND), + LintId::of(&copies::IF_SAME_THEN_ELSE), + LintId::of(&derive::DERIVE_HASH_XOR_EQ), + LintId::of(&derive::DERIVE_ORD_XOR_PARTIAL_ORD), + LintId::of(&drop_forget_ref::DROP_COPY), + LintId::of(&drop_forget_ref::DROP_REF), + LintId::of(&drop_forget_ref::FORGET_COPY), + LintId::of(&drop_forget_ref::FORGET_REF), + LintId::of(&enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), + LintId::of(&eq_op::EQ_OP), + LintId::of(&erasing_op::ERASING_OP), + LintId::of(&float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), + LintId::of(&formatting::POSSIBLE_MISSING_COMMA), + LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF), + LintId::of(&if_let_mutex::IF_LET_MUTEX), + LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING), + LintId::of(&infinite_iter::INFINITE_ITER), + LintId::of(&inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY), + LintId::of(&inline_fn_without_body::INLINE_FN_WITHOUT_BODY), + LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), + LintId::of(&literal_representation::MISTYPED_LITERAL_SUFFIXES), + LintId::of(&loops::FOR_LOOPS_OVER_FALLIBLES), + LintId::of(&loops::ITER_NEXT_LOOP), + LintId::of(&loops::NEVER_LOOP), + LintId::of(&loops::WHILE_IMMUTABLE_CONDITION), + LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM), + LintId::of(&mem_replace::MEM_REPLACE_WITH_UNINIT), + LintId::of(&methods::CLONE_DOUBLE_REF), + LintId::of(&methods::ITERATOR_STEP_BY_ZERO), + LintId::of(&methods::UNINIT_ASSUMED_INIT), + LintId::of(&methods::ZST_OFFSET), + LintId::of(&minmax::MIN_MAX), + LintId::of(&misc::CMP_NAN), + LintId::of(&misc::FLOAT_CMP), + LintId::of(&misc::MODULO_ONE), + LintId::of(&mut_key::MUTABLE_KEY_TYPE), + LintId::of(&open_options::NONSENSICAL_OPEN_OPTIONS), + LintId::of(&option_env_unwrap::OPTION_ENV_UNWRAP), + LintId::of(&ptr::MUT_FROM_REF), + LintId::of(&ranges::REVERSED_EMPTY_RANGES), + LintId::of(®ex::INVALID_REGEX), + LintId::of(&self_assignment::SELF_ASSIGNMENT), + LintId::of(&serde_api::SERDE_API_MISUSE), + LintId::of(&size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT), + LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), + LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), + LintId::of(&swap::ALMOST_SWAPPED), + LintId::of(&to_string_in_display::TO_STRING_IN_DISPLAY), + LintId::of(&transmute::UNSOUND_COLLECTION_TRANSMUTE), + LintId::of(&transmute::WRONG_TRANSMUTE), + LintId::of(&transmuting_null::TRANSMUTING_NULL), + LintId::of(&types::ABSURD_EXTREME_COMPARISONS), + LintId::of(&types::UNIT_CMP), + LintId::of(&undropped_manually_drops::UNDROPPED_MANUALLY_DROPS), + LintId::of(&unicode::INVISIBLE_CHARACTERS), + LintId::of(&unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD), + LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), + LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), + LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), + LintId::of(&unwrap::PANICKING_UNWRAP), + LintId::of(&vec_resize_to_zero::VEC_RESIZE_TO_ZERO), + ]); + + store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ + LintId::of(&entry::MAP_ENTRY), + LintId::of(&escape::BOXED_LOCAL), + LintId::of(&large_const_arrays::LARGE_CONST_ARRAYS), + LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT), + LintId::of(&loops::MANUAL_MEMCPY), + LintId::of(&loops::NEEDLESS_COLLECT), + LintId::of(&methods::EXPECT_FUN_CALL), + LintId::of(&methods::ITER_NTH), + LintId::of(&methods::OR_FUN_CALL), + LintId::of(&methods::SINGLE_CHAR_PATTERN), + LintId::of(&misc::CMP_OWNED), + LintId::of(&mutex_atomic::MUTEX_ATOMIC), + LintId::of(&redundant_clone::REDUNDANT_CLONE), + LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), + LintId::of(&stable_sort_primitive::STABLE_SORT_PRIMITIVE), + LintId::of(&types::BOX_VEC), + LintId::of(&types::REDUNDANT_ALLOCATION), + LintId::of(&vec::USELESS_VEC), + LintId::of(&vec_init_then_push::VEC_INIT_THEN_PUSH), + ]); + + store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![ + LintId::of(&cargo_common_metadata::CARGO_COMMON_METADATA), + LintId::of(&multiple_crate_versions::MULTIPLE_CRATE_VERSIONS), + LintId::of(&wildcard_dependencies::WILDCARD_DEPENDENCIES), + ]); + + store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ + LintId::of(&attrs::EMPTY_LINE_AFTER_OUTER_ATTR), + LintId::of(&cognitive_complexity::COGNITIVE_COMPLEXITY), + LintId::of(&disallowed_method::DISALLOWED_METHOD), + LintId::of(&fallible_impl_from::FALLIBLE_IMPL_FROM), + LintId::of(&floating_point_arithmetic::IMPRECISE_FLOPS), + LintId::of(&floating_point_arithmetic::SUBOPTIMAL_FLOPS), + LintId::of(&future_not_send::FUTURE_NOT_SEND), + LintId::of(&let_if_seq::USELESS_LET_IF_SEQ), + LintId::of(&missing_const_for_fn::MISSING_CONST_FOR_FN), + LintId::of(&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), + LintId::of(&mutex_atomic::MUTEX_INTEGER), + LintId::of(&needless_borrow::NEEDLESS_BORROW), + LintId::of(&path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE), + LintId::of(&redundant_pub_crate::REDUNDANT_PUB_CRATE), + LintId::of(®ex::TRIVIAL_REGEX), + LintId::of(&strings::STRING_LIT_AS_BYTES), + LintId::of(&transmute::USELESS_TRANSMUTE), + LintId::of(&use_self::USE_SELF), + ]); +} + +#[rustfmt::skip] +fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) { + store.register_removed( + "should_assert_eq", + "`assert!()` will be more flexible with RFC 2011", + ); + store.register_removed( + "extend_from_slice", + "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice", + ); + store.register_removed( + "range_step_by_zero", + "`iterator.step_by(0)` panics nowadays", + ); + store.register_removed( + "unstable_as_slice", + "`Vec::as_slice` has been stabilized in 1.7", + ); + store.register_removed( + "unstable_as_mut_slice", + "`Vec::as_mut_slice` has been stabilized in 1.7", + ); + store.register_removed( + "misaligned_transmute", + "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr", + ); + store.register_removed( + "assign_ops", + "using compound assignment operators (e.g., `+=`) is harmless", + ); + store.register_removed( + "if_let_redundant_pattern_matching", + "this lint has been changed to redundant_pattern_matching", + ); + store.register_removed( + "unsafe_vector_initialization", + "the replacement suggested by this lint had substantially different behavior", + ); + store.register_removed( + "reverse_range_loop", + "this lint is now included in reversed_empty_ranges", + ); +} + +/// Register renamed lints. +/// +/// Used in `./src/driver.rs`. +pub fn register_renamed(ls: &mut rustc_lint::LintStore) { + ls.register_renamed("clippy::stutter", "clippy::module_name_repetitions"); + ls.register_renamed("clippy::new_without_default_derive", "clippy::new_without_default"); + ls.register_renamed("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"); + ls.register_renamed("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"); + ls.register_renamed("clippy::option_and_then_some", "clippy::bind_instead_of_map"); + ls.register_renamed("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions"); + ls.register_renamed("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions"); + ls.register_renamed("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"); + ls.register_renamed("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"); + ls.register_renamed("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"); + ls.register_renamed("clippy::option_unwrap_used", "clippy::unwrap_used"); + ls.register_renamed("clippy::result_unwrap_used", "clippy::unwrap_used"); + ls.register_renamed("clippy::option_expect_used", "clippy::expect_used"); + ls.register_renamed("clippy::result_expect_used", "clippy::expect_used"); + ls.register_renamed("clippy::for_loop_over_option", "clippy::for_loops_over_fallibles"); + ls.register_renamed("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles"); + ls.register_renamed("clippy::identity_conversion", "clippy::useless_conversion"); + ls.register_renamed("clippy::zero_width_space", "clippy::invisible_characters"); + ls.register_renamed("clippy::single_char_push_str", "clippy::single_char_add_str"); +} + +// only exists to let the dogfood integration test works. +// Don't run clippy as an executable directly +#[allow(dead_code)] +fn main() { + panic!("Please use the cargo-clippy executable"); +} diff --git a/src/tools/clippy/clippy_lints/src/lifetimes.rs b/src/tools/clippy/clippy_lints/src/lifetimes.rs new file mode 100644 index 0000000000..3ac6e6cbbe --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/lifetimes.rs @@ -0,0 +1,514 @@ +use crate::utils::{in_macro, span_lint, trait_ref_of_method}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::intravisit::{ + walk_fn_decl, walk_generic_param, walk_generics, walk_item, walk_param_bound, walk_poly_trait_ref, walk_ty, + NestedVisitorMap, Visitor, +}; +use rustc_hir::FnRetTy::Return; +use rustc_hir::{ + BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, + ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, TraitBoundModifier, + TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WhereClause, WherePredicate, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::symbol::{kw, Symbol}; + +declare_clippy_lint! { + /// **What it does:** Checks for lifetime annotations which can be removed by + /// relying on lifetime elision. + /// + /// **Why is this bad?** The additional lifetimes make the code look more + /// complicated, while there is nothing out of the ordinary going on. Removing + /// them leads to more readable code. + /// + /// **Known problems:** + /// - We bail out if the function has a `where` clause where lifetimes + /// are mentioned due to potenial false positives. + /// - Lifetime bounds such as `impl Foo + 'a` and `T: 'a` must be elided with the + /// placeholder notation `'_` because the fully elided notation leaves the type bound to `'static`. + /// + /// **Example:** + /// ```rust + /// // Bad: unnecessary lifetime annotations + /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 { + /// x + /// } + /// + /// // Good + /// fn elided(x: &u8, y: u8) -> &u8 { + /// x + /// } + /// ``` + pub NEEDLESS_LIFETIMES, + complexity, + "using explicit lifetimes for references in function arguments when elision rules \ + would allow omitting them" +} + +declare_clippy_lint! { + /// **What it does:** Checks for lifetimes in generics that are never used + /// anywhere else. + /// + /// **Why is this bad?** The additional lifetimes make the code look more + /// complicated, while there is nothing out of the ordinary going on. Removing + /// them leads to more readable code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad: unnecessary lifetimes + /// fn unused_lifetime<'a>(x: u8) { + /// // .. + /// } + /// + /// // Good + /// fn no_lifetime(x: u8) { + /// // ... + /// } + /// ``` + pub EXTRA_UNUSED_LIFETIMES, + complexity, + "unused lifetimes in function definitions" +} + +declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]); + +impl<'tcx> LateLintPass<'tcx> for Lifetimes { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Fn(ref sig, ref generics, id) = item.kind { + check_fn_inner(cx, &sig.decl, Some(id), generics, item.span, true); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { + if let ImplItemKind::Fn(ref sig, id) = item.kind { + let report_extra_lifetimes = trait_ref_of_method(cx, item.hir_id()).is_none(); + check_fn_inner( + cx, + &sig.decl, + Some(id), + &item.generics, + item.span, + report_extra_lifetimes, + ); + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Fn(ref sig, ref body) = item.kind { + let body = match *body { + TraitFn::Required(_) => None, + TraitFn::Provided(id) => Some(id), + }; + check_fn_inner(cx, &sig.decl, body, &item.generics, item.span, true); + } + } +} + +/// The lifetime of a &-reference. +#[derive(PartialEq, Eq, Hash, Debug, Clone)] +enum RefLt { + Unnamed, + Static, + Named(Symbol), +} + +fn check_fn_inner<'tcx>( + cx: &LateContext<'tcx>, + decl: &'tcx FnDecl<'_>, + body: Option, + generics: &'tcx Generics<'_>, + span: Span, + report_extra_lifetimes: bool, +) { + if in_macro(span) || has_where_lifetimes(cx, &generics.where_clause) { + return; + } + + let types = generics + .params + .iter() + .filter(|param| matches!(param.kind, GenericParamKind::Type { .. })); + for typ in types { + for bound in typ.bounds { + let mut visitor = RefVisitor::new(cx); + walk_param_bound(&mut visitor, bound); + if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) { + return; + } + if let GenericBound::Trait(ref trait_ref, _) = *bound { + let params = &trait_ref + .trait_ref + .path + .segments + .last() + .expect("a path must have at least one segment") + .args; + if let Some(ref params) = *params { + let lifetimes = params.args.iter().filter_map(|arg| match arg { + GenericArg::Lifetime(lt) => Some(lt), + _ => None, + }); + for bound in lifetimes { + if bound.name != LifetimeName::Static && !bound.is_elided() { + return; + } + } + } + } + } + } + if could_use_elision(cx, decl, body, &generics.params) { + span_lint( + cx, + NEEDLESS_LIFETIMES, + span.with_hi(decl.output.span().hi()), + "explicit lifetimes given in parameter types where they could be elided \ + (or replaced with `'_` if needed by type declaration)", + ); + } + if report_extra_lifetimes { + self::report_extra_lifetimes(cx, decl, generics); + } +} + +fn could_use_elision<'tcx>( + cx: &LateContext<'tcx>, + func: &'tcx FnDecl<'_>, + body: Option, + named_generics: &'tcx [GenericParam<'_>], +) -> bool { + // There are two scenarios where elision works: + // * no output references, all input references have different LT + // * output references, exactly one input reference with same LT + // All lifetimes must be unnamed, 'static or defined without bounds on the + // level of the current item. + + // check named LTs + let allowed_lts = allowed_lts_from(named_generics); + + // these will collect all the lifetimes for references in arg/return types + let mut input_visitor = RefVisitor::new(cx); + let mut output_visitor = RefVisitor::new(cx); + + // extract lifetimes in input argument types + for arg in func.inputs { + input_visitor.visit_ty(arg); + } + // extract lifetimes in output type + if let Return(ref ty) = func.output { + output_visitor.visit_ty(ty); + } + for lt in named_generics { + input_visitor.visit_generic_param(lt) + } + + if input_visitor.abort() || output_visitor.abort() { + return false; + } + + if allowed_lts + .intersection( + &input_visitor + .nested_elision_site_lts + .iter() + .chain(output_visitor.nested_elision_site_lts.iter()) + .cloned() + .filter(|v| matches!(v, RefLt::Named(_))) + .collect(), + ) + .next() + .is_some() + { + return false; + } + + let input_lts = input_visitor.lts; + let output_lts = output_visitor.lts; + + if let Some(body_id) = body { + let mut checker = BodyLifetimeChecker { + lifetimes_used_in_body: false, + }; + checker.visit_expr(&cx.tcx.hir().body(body_id).value); + if checker.lifetimes_used_in_body { + return false; + } + } + + // check for lifetimes from higher scopes + for lt in input_lts.iter().chain(output_lts.iter()) { + if !allowed_lts.contains(lt) { + return false; + } + } + + // no input lifetimes? easy case! + if input_lts.is_empty() { + false + } else if output_lts.is_empty() { + // no output lifetimes, check distinctness of input lifetimes + + // only unnamed and static, ok + let unnamed_and_static = input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static); + if unnamed_and_static { + return false; + } + // we have no output reference, so we only need all distinct lifetimes + input_lts.len() == unique_lifetimes(&input_lts) + } else { + // we have output references, so we need one input reference, + // and all output lifetimes must be the same + if unique_lifetimes(&output_lts) > 1 { + return false; + } + if input_lts.len() == 1 { + match (&input_lts[0], &output_lts[0]) { + (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true, + (&RefLt::Named(_), &RefLt::Unnamed) => true, + _ => false, /* already elided, different named lifetimes + * or something static going on */ + } + } else { + false + } + } +} + +fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet { + let mut allowed_lts = FxHashSet::default(); + for par in named_generics.iter() { + if let GenericParamKind::Lifetime { .. } = par.kind { + if par.bounds.is_empty() { + allowed_lts.insert(RefLt::Named(par.name.ident().name)); + } + } + } + allowed_lts.insert(RefLt::Unnamed); + allowed_lts.insert(RefLt::Static); + allowed_lts +} + +/// Number of unique lifetimes in the given vector. +#[must_use] +fn unique_lifetimes(lts: &[RefLt]) -> usize { + lts.iter().collect::>().len() +} + +const CLOSURE_TRAIT_BOUNDS: [LangItem; 3] = [LangItem::Fn, LangItem::FnMut, LangItem::FnOnce]; + +/// A visitor usable for `rustc_front::visit::walk_ty()`. +struct RefVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + lts: Vec, + nested_elision_site_lts: Vec, + unelided_trait_object_lifetime: bool, +} + +impl<'a, 'tcx> RefVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { + cx, + lts: Vec::new(), + nested_elision_site_lts: Vec::new(), + unelided_trait_object_lifetime: false, + } + } + + fn record(&mut self, lifetime: &Option) { + if let Some(ref lt) = *lifetime { + if lt.name == LifetimeName::Static { + self.lts.push(RefLt::Static); + } else if let LifetimeName::Param(ParamName::Fresh(_)) = lt.name { + // Fresh lifetimes generated should be ignored. + } else if lt.is_elided() { + self.lts.push(RefLt::Unnamed); + } else { + self.lts.push(RefLt::Named(lt.name.ident().name)); + } + } else { + self.lts.push(RefLt::Unnamed); + } + } + + fn all_lts(&self) -> Vec { + self.lts + .iter() + .chain(self.nested_elision_site_lts.iter()) + .cloned() + .collect::>() + } + + fn abort(&self) -> bool { + self.unelided_trait_object_lifetime + } +} + +impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + // for lifetimes as parameters of generics + fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { + self.record(&Some(*lifetime)); + } + + fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>, tbm: TraitBoundModifier) { + let trait_ref = &poly_tref.trait_ref; + if CLOSURE_TRAIT_BOUNDS.iter().any(|&item| { + self.cx + .tcx + .lang_items() + .require(item) + .map_or(false, |id| Some(id) == trait_ref.trait_def_id()) + }) { + let mut sub_visitor = RefVisitor::new(self.cx); + sub_visitor.visit_trait_ref(trait_ref); + self.nested_elision_site_lts.append(&mut sub_visitor.all_lts()); + } else { + walk_poly_trait_ref(self, poly_tref, tbm); + } + } + + fn visit_ty(&mut self, ty: &'tcx Ty<'_>) { + match ty.kind { + TyKind::OpaqueDef(item, _) => { + let map = self.cx.tcx.hir(); + let item = map.item(item); + walk_item(self, item); + walk_ty(self, ty); + }, + TyKind::BareFn(&BareFnTy { decl, .. }) => { + let mut sub_visitor = RefVisitor::new(self.cx); + sub_visitor.visit_fn_decl(decl); + self.nested_elision_site_lts.append(&mut sub_visitor.all_lts()); + return; + }, + TyKind::TraitObject(bounds, ref lt, _) => { + if !lt.is_elided() { + self.unelided_trait_object_lifetime = true; + } + for bound in bounds { + self.visit_poly_trait_ref(bound, TraitBoundModifier::None); + } + return; + }, + _ => (), + } + walk_ty(self, ty); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Are any lifetimes mentioned in the `where` clause? If so, we don't try to +/// reason about elision. +fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, where_clause: &'tcx WhereClause<'_>) -> bool { + for predicate in where_clause.predicates { + match *predicate { + WherePredicate::RegionPredicate(..) => return true, + WherePredicate::BoundPredicate(ref pred) => { + // a predicate like F: Trait or F: for<'a> Trait<'a> + let mut visitor = RefVisitor::new(cx); + // walk the type F, it may not contain LT refs + walk_ty(&mut visitor, &pred.bounded_ty); + if !visitor.all_lts().is_empty() { + return true; + } + // if the bounds define new lifetimes, they are fine to occur + let allowed_lts = allowed_lts_from(&pred.bound_generic_params); + // now walk the bounds + for bound in pred.bounds.iter() { + walk_param_bound(&mut visitor, bound); + } + // and check that all lifetimes are allowed + if visitor.all_lts().iter().any(|it| !allowed_lts.contains(it)) { + return true; + } + }, + WherePredicate::EqPredicate(ref pred) => { + let mut visitor = RefVisitor::new(cx); + walk_ty(&mut visitor, &pred.lhs_ty); + walk_ty(&mut visitor, &pred.rhs_ty); + if !visitor.lts.is_empty() { + return true; + } + }, + } + } + false +} + +struct LifetimeChecker { + map: FxHashMap, +} + +impl<'tcx> Visitor<'tcx> for LifetimeChecker { + type Map = Map<'tcx>; + + // for lifetimes as parameters of generics + fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { + self.map.remove(&lifetime.name.ident().name); + } + + fn visit_generic_param(&mut self, param: &'tcx GenericParam<'_>) { + // don't actually visit `<'a>` or `<'a: 'b>` + // we've already visited the `'a` declarations and + // don't want to spuriously remove them + // `'b` in `'a: 'b` is useless unless used elsewhere in + // a non-lifetime bound + if let GenericParamKind::Type { .. } = param.kind { + walk_generic_param(self, param) + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) { + let hs = generics + .params + .iter() + .filter_map(|par| match par.kind { + GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)), + _ => None, + }) + .collect(); + let mut checker = LifetimeChecker { map: hs }; + + walk_generics(&mut checker, generics); + walk_fn_decl(&mut checker, func); + + for &v in checker.map.values() { + span_lint( + cx, + EXTRA_UNUSED_LIFETIMES, + v, + "this lifetime isn't used in the function definition", + ); + } +} + +struct BodyLifetimeChecker { + lifetimes_used_in_body: bool, +} + +impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker { + type Map = Map<'tcx>; + + // for lifetimes as parameters of generics + fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { + if lifetime.name.ident().name != kw::Empty && lifetime.name.ident().name != kw::StaticLifetime { + self.lifetimes_used_in_body = true; + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/literal_representation.rs b/src/tools/clippy/clippy_lints/src/literal_representation.rs new file mode 100644 index 0000000000..87a957a9bd --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/literal_representation.rs @@ -0,0 +1,497 @@ +//! Lints concerned with the grouping of digits with underscores in integral or +//! floating-point literal expressions. + +use crate::utils::{ + in_macro, + numeric_literal::{NumericLiteral, Radix}, + snippet_opt, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +declare_clippy_lint! { + /// **What it does:** Warns if a long integral or floating-point constant does + /// not contain underscores. + /// + /// **Why is this bad?** Reading long numbers is difficult without separators. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// let x: u64 = 61864918973511; + /// + /// // Good + /// let x: u64 = 61_864_918_973_511; + /// ``` + pub UNREADABLE_LITERAL, + pedantic, + "long literal without underscores" +} + +declare_clippy_lint! { + /// **What it does:** Warns for mistyped suffix in literals + /// + /// **Why is this bad?** This is most probably a typo + /// + /// **Known problems:** + /// - Recommends a signed suffix, even though the number might be too big and an unsigned + /// suffix is required + /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers + /// + /// **Example:** + /// + /// ```rust + /// // Probably mistyped + /// 2_32; + /// + /// // Good + /// 2_i32; + /// ``` + pub MISTYPED_LITERAL_SUFFIXES, + correctness, + "mistyped literal suffix" +} + +declare_clippy_lint! { + /// **What it does:** Warns if an integral or floating-point constant is + /// grouped inconsistently with underscores. + /// + /// **Why is this bad?** Readers may incorrectly interpret inconsistently + /// grouped digits. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// let x: u64 = 618_64_9189_73_511; + /// + /// // Good + /// let x: u64 = 61_864_918_973_511; + /// ``` + pub INCONSISTENT_DIGIT_GROUPING, + style, + "integer literals with digits grouped inconsistently" +} + +declare_clippy_lint! { + /// **What it does:** Warns if hexadecimal or binary literals are not grouped + /// by nibble or byte. + /// + /// **Why is this bad?** Negatively impacts readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x: u32 = 0xFFF_FFF; + /// let y: u8 = 0b01_011_101; + /// ``` + pub UNUSUAL_BYTE_GROUPINGS, + style, + "binary or hex literals that aren't grouped by four" +} + +declare_clippy_lint! { + /// **What it does:** Warns if the digits of an integral or floating-point + /// constant are grouped into groups that + /// are too large. + /// + /// **Why is this bad?** Negatively impacts readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x: u64 = 6186491_8973511; + /// ``` + pub LARGE_DIGIT_GROUPS, + pedantic, + "grouping digits into groups that are too large" +} + +declare_clippy_lint! { + /// **What it does:** Warns if there is a better representation for a numeric literal. + /// + /// **Why is this bad?** Especially for big powers of 2 a hexadecimal representation is more + /// readable than a decimal representation. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// `255` => `0xFF` + /// `65_535` => `0xFFFF` + /// `4_042_322_160` => `0xF0F0_F0F0` + pub DECIMAL_LITERAL_REPRESENTATION, + restriction, + "using decimal representation when hexadecimal would be better" +} + +enum WarningType { + UnreadableLiteral, + InconsistentDigitGrouping, + LargeDigitGroups, + DecimalRepresentation, + MistypedLiteralSuffix, + UnusualByteGroupings, +} + +impl WarningType { + fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: rustc_span::Span) { + match self { + Self::MistypedLiteralSuffix => span_lint_and_sugg( + cx, + MISTYPED_LITERAL_SUFFIXES, + span, + "mistyped literal suffix", + "did you mean to write", + suggested_format, + Applicability::MaybeIncorrect, + ), + Self::UnreadableLiteral => span_lint_and_sugg( + cx, + UNREADABLE_LITERAL, + span, + "long literal lacking separators", + "consider", + suggested_format, + Applicability::MachineApplicable, + ), + Self::LargeDigitGroups => span_lint_and_sugg( + cx, + LARGE_DIGIT_GROUPS, + span, + "digit groups should be smaller", + "consider", + suggested_format, + Applicability::MachineApplicable, + ), + Self::InconsistentDigitGrouping => span_lint_and_sugg( + cx, + INCONSISTENT_DIGIT_GROUPING, + span, + "digits grouped inconsistently by underscores", + "consider", + suggested_format, + Applicability::MachineApplicable, + ), + Self::DecimalRepresentation => span_lint_and_sugg( + cx, + DECIMAL_LITERAL_REPRESENTATION, + span, + "integer literal has a better hexadecimal representation", + "consider", + suggested_format, + Applicability::MachineApplicable, + ), + Self::UnusualByteGroupings => span_lint_and_sugg( + cx, + UNUSUAL_BYTE_GROUPINGS, + span, + "digits of hex or binary literal not grouped by four", + "consider", + suggested_format, + Applicability::MachineApplicable, + ), + }; + } +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Copy, Clone)] +pub struct LiteralDigitGrouping { + lint_fraction_readability: bool, +} + +impl_lint_pass!(LiteralDigitGrouping => [ + UNREADABLE_LITERAL, + INCONSISTENT_DIGIT_GROUPING, + LARGE_DIGIT_GROUPS, + MISTYPED_LITERAL_SUFFIXES, + UNUSUAL_BYTE_GROUPINGS, +]); + +impl EarlyLintPass for LiteralDigitGrouping { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if let ExprKind::Lit(ref lit) = expr.kind { + self.check_lit(cx, lit) + } + } +} + +// Length of each UUID hyphenated group in hex digits. +const UUID_GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; + +impl LiteralDigitGrouping { + pub fn new(lint_fraction_readability: bool) -> Self { + Self { + lint_fraction_readability, + } + } + + fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) { + if_chain! { + if let Some(src) = snippet_opt(cx, lit.span); + if let Some(mut num_lit) = NumericLiteral::from_lit(&src, &lit); + then { + if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) { + return; + } + + if Self::is_literal_uuid_formatted(&mut num_lit) { + return; + } + + let result = (|| { + + let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?; + if let Some(fraction) = num_lit.fraction { + let fractional_group_size = Self::get_group_size( + fraction.rsplit('_'), + num_lit.radix, + self.lint_fraction_readability)?; + + let consistent = Self::parts_consistent(integral_group_size, + fractional_group_size, + num_lit.integer.len(), + fraction.len()); + if !consistent { + return Err(WarningType::InconsistentDigitGrouping); + }; + } + + Ok(()) + })(); + + + if let Err(warning_type) = result { + let should_warn = match warning_type { + | WarningType::UnreadableLiteral + | WarningType::InconsistentDigitGrouping + | WarningType::UnusualByteGroupings + | WarningType::LargeDigitGroups => { + !in_macro(lit.span) + } + WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => { + true + } + }; + if should_warn { + warning_type.display(num_lit.format(), cx, lit.span) + } + } + } + } + } + + // Returns `false` if the check fails + fn check_for_mistyped_suffix( + cx: &EarlyContext<'_>, + span: rustc_span::Span, + num_lit: &mut NumericLiteral<'_>, + ) -> bool { + if num_lit.suffix.is_some() { + return true; + } + + let (part, mistyped_suffixes, missing_char) = if let Some((_, exponent)) = &mut num_lit.exponent { + (exponent, &["32", "64"][..], 'f') + } else if num_lit.fraction.is_some() { + (&mut num_lit.integer, &["32", "64"][..], 'f') + } else { + (&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i') + }; + + let mut split = part.rsplit('_'); + let last_group = split.next().expect("At least one group"); + if split.next().is_some() && mistyped_suffixes.contains(&last_group) { + *part = &part[..part.len() - last_group.len()]; + let mut sugg = num_lit.format(); + sugg.push('_'); + sugg.push(missing_char); + sugg.push_str(last_group); + WarningType::MistypedLiteralSuffix.display(sugg, cx, span); + false + } else { + true + } + } + + /// Checks whether the numeric literal matches the formatting of a UUID. + /// + /// Returns `true` if the radix is hexadecimal, and the groups match the + /// UUID format of 8-4-4-4-12. + fn is_literal_uuid_formatted(num_lit: &mut NumericLiteral<'_>) -> bool { + if num_lit.radix != Radix::Hexadecimal { + return false; + } + + // UUIDs should not have a fraction + if num_lit.fraction.is_some() { + return false; + } + + let group_sizes: Vec = num_lit.integer.split('_').map(str::len).collect(); + if UUID_GROUP_LENS.len() == group_sizes.len() { + UUID_GROUP_LENS.iter().zip(&group_sizes).all(|(&a, &b)| a == b) + } else { + false + } + } + + /// Given the sizes of the digit groups of both integral and fractional + /// parts, and the length + /// of both parts, determine if the digits have been grouped consistently. + #[must_use] + fn parts_consistent( + int_group_size: Option, + frac_group_size: Option, + int_size: usize, + frac_size: usize, + ) -> bool { + match (int_group_size, frac_group_size) { + // No groups on either side of decimal point - trivially consistent. + (None, None) => true, + // Integral part has grouped digits, fractional part does not. + (Some(int_group_size), None) => frac_size <= int_group_size, + // Fractional part has grouped digits, integral part does not. + (None, Some(frac_group_size)) => int_size <= frac_group_size, + // Both parts have grouped digits. Groups should be the same size. + (Some(int_group_size), Some(frac_group_size)) => int_group_size == frac_group_size, + } + } + + /// Returns the size of the digit groups (or None if ungrouped) if successful, + /// otherwise returns a `WarningType` for linting. + fn get_group_size<'a>( + groups: impl Iterator, + radix: Radix, + lint_unreadable: bool, + ) -> Result, WarningType> { + let mut groups = groups.map(str::len); + + let first = groups.next().expect("At least one group"); + + if (radix == Radix::Binary || radix == Radix::Hexadecimal) && groups.any(|i| i != 4 && i != 2) { + return Err(WarningType::UnusualByteGroupings); + } + + if let Some(second) = groups.next() { + if !groups.all(|x| x == second) || first > second { + Err(WarningType::InconsistentDigitGrouping) + } else if second > 4 { + Err(WarningType::LargeDigitGroups) + } else { + Ok(Some(second)) + } + } else if first > 5 && lint_unreadable { + Err(WarningType::UnreadableLiteral) + } else { + Ok(None) + } + } +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Copy, Clone)] +pub struct DecimalLiteralRepresentation { + threshold: u64, +} + +impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]); + +impl EarlyLintPass for DecimalLiteralRepresentation { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if let ExprKind::Lit(ref lit) = expr.kind { + self.check_lit(cx, lit) + } + } +} + +impl DecimalLiteralRepresentation { + #[must_use] + pub fn new(threshold: u64) -> Self { + Self { threshold } + } + fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) { + // Lint integral literals. + if_chain! { + if let LitKind::Int(val, _) = lit.kind; + if let Some(src) = snippet_opt(cx, lit.span); + if let Some(num_lit) = NumericLiteral::from_lit(&src, &lit); + if num_lit.radix == Radix::Decimal; + if val >= u128::from(self.threshold); + then { + let hex = format!("{:#X}", val); + let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false); + let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| { + warning_type.display(num_lit.format(), cx, lit.span) + }); + } + } + } + + fn do_lint(digits: &str) -> Result<(), WarningType> { + if digits.len() == 1 { + // Lint for 1 digit literals, if someone really sets the threshold that low + if digits == "1" + || digits == "2" + || digits == "4" + || digits == "8" + || digits == "3" + || digits == "7" + || digits == "F" + { + return Err(WarningType::DecimalRepresentation); + } + } else if digits.len() < 4 { + // Lint for Literals with a hex-representation of 2 or 3 digits + let f = &digits[0..1]; // first digit + let s = &digits[1..]; // suffix + + // Powers of 2 + if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0')) + // Powers of 2 minus 1 + || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F')) + { + return Err(WarningType::DecimalRepresentation); + } + } else { + // Lint for Literals with a hex-representation of 4 digits or more + let f = &digits[0..1]; // first digit + let m = &digits[1..digits.len() - 1]; // middle digits, except last + let s = &digits[1..]; // suffix + + // Powers of 2 with a margin of +15/-16 + if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0')) + || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F')) + // Lint for representations with only 0s and Fs, while allowing 7 as the first + // digit + || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F')) + { + return Err(WarningType::DecimalRepresentation); + } + } + + Ok(()) + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/empty_loop.rs b/src/tools/clippy/clippy_lints/src/loops/empty_loop.rs new file mode 100644 index 0000000000..43e85538f2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/empty_loop.rs @@ -0,0 +1,17 @@ +use super::EMPTY_LOOP; +use crate::utils::{is_in_panic_handler, is_no_std_crate, span_lint_and_help}; + +use rustc_hir::{Block, Expr}; +use rustc_lint::LateContext; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { + if loop_block.stmts.is_empty() && loop_block.expr.is_none() && !is_in_panic_handler(cx, expr) { + let msg = "empty `loop {}` wastes CPU cycles"; + let help = if is_no_std_crate(cx) { + "you should either use `panic!()` or add a call pausing or sleeping the thread to the loop body" + } else { + "you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body" + }; + span_lint_and_help(cx, EMPTY_LOOP, expr.span, msg, None, help); + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/explicit_counter_loop.rs b/src/tools/clippy/clippy_lints/src/loops/explicit_counter_loop.rs new file mode 100644 index 0000000000..8d98b940c6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/explicit_counter_loop.rs @@ -0,0 +1,58 @@ +use super::{ + get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP, +}; +use crate::utils::{get_enclosing_block, is_integer_const, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr}; +use rustc_hir::{Expr, Pat}; +use rustc_lint::LateContext; + +// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be +// incremented exactly once in the loop body, and initialized to zero +// at the start of the loop. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + // Look for variables that are incremented once per loop iteration. + let mut increment_visitor = IncrementVisitor::new(cx); + walk_expr(&mut increment_visitor, body); + + // For each candidate, check the parent block to see if + // it's initialized to zero at the start of the loop. + if let Some(block) = get_enclosing_block(&cx, expr.hir_id) { + for id in increment_visitor.into_results() { + let mut initialize_visitor = InitializeVisitor::new(cx, expr, id); + walk_block(&mut initialize_visitor, block); + + if_chain! { + if let Some((name, initializer)) = initialize_visitor.get_result(); + if is_integer_const(cx, initializer, 0); + then { + let mut applicability = Applicability::MachineApplicable; + + let for_span = get_span_of_entire_for_loop(expr); + + span_lint_and_sugg( + cx, + EXPLICIT_COUNTER_LOOP, + for_span.with_hi(arg.span.hi()), + &format!("the variable `{}` is used as a loop counter", name), + "consider using", + format!( + "for ({}, {}) in {}.enumerate()", + name, + snippet_with_applicability(cx, pat.span, "item", &mut applicability), + make_iterator_snippet(cx, arg, &mut applicability), + ), + applicability, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/explicit_into_iter_loop.rs b/src/tools/clippy/clippy_lints/src/loops/explicit_into_iter_loop.rs new file mode 100644 index 0000000000..1d778205a2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/explicit_into_iter_loop.rs @@ -0,0 +1,27 @@ +use super::EXPLICIT_INTO_ITER_LOOP; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::TyS; + +pub(super) fn check(cx: &LateContext<'_>, args: &'hir [Expr<'hir>], arg: &Expr<'_>) { + let receiver_ty = cx.typeck_results().expr_ty(&args[0]); + let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]); + if !TyS::same_type(receiver_ty, receiver_ty_adjusted) { + return; + } + + let mut applicability = Applicability::MachineApplicable; + let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); + span_lint_and_sugg( + cx, + EXPLICIT_INTO_ITER_LOOP, + arg.span, + "it is more concise to loop over containers instead of using explicit \ + iteration methods", + "to write this more concisely, try", + object.to_string(), + applicability, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs b/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs new file mode 100644 index 0000000000..9683e59a39 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs @@ -0,0 +1,74 @@ +use super::EXPLICIT_ITER_LOOP; +use crate::utils::{match_trait_method, snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, Mutability}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty, TyS}; +use rustc_span::sym; + +use crate::utils::{is_type_diagnostic_item, match_type, paths}; + +pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { + let should_lint = match method_name { + "iter" | "iter_mut" => is_ref_iterable_type(cx, &args[0]), + "into_iter" if match_trait_method(cx, arg, &paths::INTO_ITERATOR) => { + let receiver_ty = cx.typeck_results().expr_ty(&args[0]); + let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]); + let ref_receiver_ty = cx.tcx.mk_ref( + cx.tcx.lifetimes.re_erased, + ty::TypeAndMut { + ty: receiver_ty, + mutbl: Mutability::Not, + }, + ); + TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) + }, + _ => false, + }; + + if !should_lint { + return; + } + + let mut applicability = Applicability::MachineApplicable; + let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); + let muta = if method_name == "iter_mut" { "mut " } else { "" }; + span_lint_and_sugg( + cx, + EXPLICIT_ITER_LOOP, + arg.span, + "it is more concise to loop over references to containers instead of using explicit \ + iteration methods", + "to write this more concisely, try", + format!("&{}{}", muta, object), + applicability, + ) +} + +/// Returns `true` if the type of expr is one that provides `IntoIterator` impls +/// for `&T` and `&mut T`, such as `Vec`. +#[rustfmt::skip] +fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + // no walk_ptrs_ty: calling iter() on a reference can make sense because it + // will allow further borrows afterwards + let ty = cx.typeck_results().expr_ty(e); + is_iterable_array(ty, cx) || + is_type_diagnostic_item(cx, ty, sym::vec_type) || + match_type(cx, ty, &paths::LINKED_LIST) || + is_type_diagnostic_item(cx, ty, sym::hashmap_type) || + is_type_diagnostic_item(cx, ty, sym::hashset_type) || + is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || + match_type(cx, ty, &paths::BINARY_HEAP) || + match_type(cx, ty, &paths::BTREEMAP) || + match_type(cx, ty, &paths::BTREESET) +} + +fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { + // IntoIterator is currently only implemented for array sizes <= 32 in rustc + match ty.kind() { + ty::Array(_, n) => n + .try_eval_usize(cx.tcx, cx.param_env) + .map_or(false, |val| (0..=32).contains(&val)), + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/for_kv_map.rs b/src/tools/clippy/clippy_lints/src/loops/for_kv_map.rs new file mode 100644 index 0000000000..6ee9b95a3b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/for_kv_map.rs @@ -0,0 +1,71 @@ +use super::FOR_KV_MAP; +use crate::utils::visitors::LocalUsedVisitor; +use crate::utils::{is_type_diagnostic_item, match_type, multispan_sugg, paths, snippet, span_lint_and_then, sugg}; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::sym; + +/// Checks for the `FOR_KV_MAP` lint. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + let pat_span = pat.span; + + if let PatKind::Tuple(ref pat, _) = pat.kind { + if pat.len() == 2 { + let arg_span = arg.span; + let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { + ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { + (key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl), + (_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not), + _ => return, + }, + _ => return, + }; + let mutbl = match mutbl { + Mutability::Not => "", + Mutability::Mut => "_mut", + }; + let arg = match arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) => &**expr, + _ => arg, + }; + + if is_type_diagnostic_item(cx, ty, sym::hashmap_type) || match_type(cx, ty, &paths::BTREEMAP) { + span_lint_and_then( + cx, + FOR_KV_MAP, + expr.span, + &format!("you seem to want to iterate on a map's {}s", kind), + |diag| { + let map = sugg::Sugg::hir(cx, arg, "map"); + multispan_sugg( + diag, + "use the corresponding method", + vec![ + (pat_span, snippet(cx, new_pat_span, kind).into_owned()), + (arg_span, format!("{}.{}s{}()", map.maybe_par(), kind, mutbl)), + ], + ); + }, + ); + } + } + } +} + +/// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`. +fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { + match *pat { + PatKind::Wild => true, + PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => { + !LocalUsedVisitor::new(cx, id).check_expr(body) + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/for_loops_over_fallibles.rs b/src/tools/clippy/clippy_lints/src/loops/for_loops_over_fallibles.rs new file mode 100644 index 0000000000..db22d90a30 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/for_loops_over_fallibles.rs @@ -0,0 +1,45 @@ +use super::FOR_LOOPS_OVER_FALLIBLES; +use crate::utils::{is_type_diagnostic_item, snippet, span_lint_and_help}; +use rustc_hir::{Expr, Pat}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +/// Checks for `for` loops over `Option`s and `Result`s. +pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { + let ty = cx.typeck_results().expr_ty(arg); + if is_type_diagnostic_item(cx, ty, sym::option_type) { + span_lint_and_help( + cx, + FOR_LOOPS_OVER_FALLIBLES, + arg.span, + &format!( + "for loop over `{0}`, which is an `Option`. This is more readably written as an \ + `if let` statement", + snippet(cx, arg.span, "_") + ), + None, + &format!( + "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ), + ); + } else if is_type_diagnostic_item(cx, ty, sym::result_type) { + span_lint_and_help( + cx, + FOR_LOOPS_OVER_FALLIBLES, + arg.span, + &format!( + "for loop over `{0}`, which is a `Result`. This is more readably written as an \ + `if let` statement", + snippet(cx, arg.span, "_") + ), + None, + &format!( + "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ), + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/iter_next_loop.rs b/src/tools/clippy/clippy_lints/src/loops/iter_next_loop.rs new file mode 100644 index 0000000000..cf78bbc49a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/iter_next_loop.rs @@ -0,0 +1,19 @@ +use super::ITER_NEXT_LOOP; +use crate::utils::{match_trait_method, paths, span_lint}; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, expr: &Expr<'_>) -> bool { + if match_trait_method(cx, arg, &paths::ITERATOR) { + span_lint( + cx, + ITER_NEXT_LOOP, + expr.span, + "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ + probably not what you want", + ); + true + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs b/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs new file mode 100644 index 0000000000..3d3ae6f315 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs @@ -0,0 +1,79 @@ +use super::utils::make_iterator_snippet; +use super::MANUAL_FLATTEN; +use crate::utils::{is_ok_ctor, is_some_ctor, path_to_local_id, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, StmtKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the +/// iterator element is used. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + span: Span, +) { + if let ExprKind::Block(ref block, _) = body.kind { + // Ensure the `if let` statement is the only expression or statement in the for-loop + let inner_expr = if block.stmts.len() == 1 && block.expr.is_none() { + let match_stmt = &block.stmts[0]; + if let StmtKind::Semi(inner_expr) = match_stmt.kind { + Some(inner_expr) + } else { + None + } + } else if block.stmts.is_empty() { + block.expr + } else { + None + }; + + if_chain! { + if let Some(inner_expr) = inner_expr; + if let ExprKind::Match( + ref match_expr, ref match_arms, MatchSource::IfLetDesugar{ contains_else_clause: false } + ) = inner_expr.kind; + // Ensure match_expr in `if let` statement is the same as the pat from the for-loop + if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind; + if path_to_local_id(match_expr, pat_hir_id); + // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result` + if let PatKind::TupleStruct(QPath::Resolved(None, path), _, _) = match_arms[0].pat.kind; + let some_ctor = is_some_ctor(cx, path.res); + let ok_ctor = is_ok_ctor(cx, path.res); + if some_ctor || ok_ctor; + let if_let_type = if some_ctor { "Some" } else { "Ok" }; + + then { + // Prepare the error message + let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type); + + // Prepare the help message + let mut applicability = Applicability::MaybeIncorrect; + let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability); + + span_lint_and_then( + cx, + MANUAL_FLATTEN, + span, + &msg, + |diag| { + let sugg = format!("{}.flatten()", arg_snippet); + diag.span_suggestion( + arg.span, + "try", + sugg, + Applicability::MaybeIncorrect, + ); + diag.span_help( + inner_expr.span, + "...and remove the `if let` statement in the for loop", + ); + } + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/manual_memcpy.rs b/src/tools/clippy/clippy_lints/src/loops/manual_memcpy.rs new file mode 100644 index 0000000000..11660a8fe0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/manual_memcpy.rs @@ -0,0 +1,454 @@ +use super::{get_span_of_entire_for_loop, IncrementVisitor, InitializeVisitor, MANUAL_MEMCPY}; +use crate::utils::sugg::Sugg; +use crate::utils::{ + get_enclosing_block, higher, is_type_diagnostic_item, path_to_local, snippet, span_lint_and_sugg, sugg, +}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir::intravisit::walk_block; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Pat, PatKind, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::symbol::sym; +use std::iter::Iterator; + +/// Checks for for loops that sequentially copy items from one slice-like +/// object to another. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) -> bool { + if let Some(higher::Range { + start: Some(start), + end: Some(end), + limits, + }) = higher::range(arg) + { + // the var must be a single name + if let PatKind::Binding(_, canonical_id, _, _) = pat.kind { + let mut starts = vec![Start { + id: canonical_id, + kind: StartKind::Range, + }]; + + // This is one of few ways to return different iterators + // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434 + let mut iter_a = None; + let mut iter_b = None; + + if let ExprKind::Block(block, _) = body.kind { + if let Some(loop_counters) = get_loop_counters(cx, block, expr) { + starts.extend(loop_counters); + } + iter_a = Some(get_assignments(block, &starts)); + } else { + iter_b = Some(get_assignment(body)); + } + + let assignments = iter_a.into_iter().flatten().chain(iter_b.into_iter()); + + let big_sugg = assignments + // The only statements in the for loops can be indexed assignments from + // indexed retrievals (except increments of loop counters). + .map(|o| { + o.and_then(|(lhs, rhs)| { + let rhs = fetch_cloned_expr(rhs); + if_chain! { + if let ExprKind::Index(base_left, idx_left) = lhs.kind; + if let ExprKind::Index(base_right, idx_right) = rhs.kind; + if is_slice_like(cx, cx.typeck_results().expr_ty(base_left)) + && is_slice_like(cx, cx.typeck_results().expr_ty(base_right)); + if let Some((start_left, offset_left)) = get_details_from_idx(cx, &idx_left, &starts); + if let Some((start_right, offset_right)) = get_details_from_idx(cx, &idx_right, &starts); + + // Source and destination must be different + if path_to_local(base_left) != path_to_local(base_right); + then { + Some((IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left }, + IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right })) + } else { + None + } + } + }) + }) + .map(|o| o.map(|(dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, &dst, &src))) + .collect::>>() + .filter(|v| !v.is_empty()) + .map(|v| v.join("\n ")); + + if let Some(big_sugg) = big_sugg { + span_lint_and_sugg( + cx, + MANUAL_MEMCPY, + get_span_of_entire_for_loop(expr), + "it looks like you're manually copying between slices", + "try replacing the loop by", + big_sugg, + Applicability::Unspecified, + ); + return true; + } + } + } + false +} + +fn build_manual_memcpy_suggestion<'tcx>( + cx: &LateContext<'tcx>, + start: &Expr<'_>, + end: &Expr<'_>, + limits: ast::RangeLimits, + dst: &IndexExpr<'_>, + src: &IndexExpr<'_>, +) -> String { + fn print_offset(offset: MinifyingSugg<'static>) -> MinifyingSugg<'static> { + if offset.as_str() == "0" { + sugg::EMPTY.into() + } else { + offset + } + } + + let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| { + if_chain! { + if let ExprKind::MethodCall(method, _, len_args, _) = end.kind; + if method.ident.name == sym!(len); + if len_args.len() == 1; + if let Some(arg) = len_args.get(0); + if path_to_local(arg) == path_to_local(base); + then { + if sugg.as_str() == end_str { + sugg::EMPTY.into() + } else { + sugg + } + } else { + match limits { + ast::RangeLimits::Closed => { + sugg + &sugg::ONE.into() + }, + ast::RangeLimits::HalfOpen => sugg, + } + } + } + }; + + let start_str = Sugg::hir(cx, start, "").into(); + let end_str: MinifyingSugg<'_> = Sugg::hir(cx, end, "").into(); + + let print_offset_and_limit = |idx_expr: &IndexExpr<'_>| match idx_expr.idx { + StartKind::Range => ( + print_offset(apply_offset(&start_str, &idx_expr.idx_offset)).into_sugg(), + print_limit( + end, + end_str.as_str(), + idx_expr.base, + apply_offset(&end_str, &idx_expr.idx_offset), + ) + .into_sugg(), + ), + StartKind::Counter { initializer } => { + let counter_start = Sugg::hir(cx, initializer, "").into(); + ( + print_offset(apply_offset(&counter_start, &idx_expr.idx_offset)).into_sugg(), + print_limit( + end, + end_str.as_str(), + idx_expr.base, + apply_offset(&end_str, &idx_expr.idx_offset) + &counter_start - &start_str, + ) + .into_sugg(), + ) + }, + }; + + let (dst_offset, dst_limit) = print_offset_and_limit(&dst); + let (src_offset, src_limit) = print_offset_and_limit(&src); + + let dst_base_str = snippet(cx, dst.base.span, "???"); + let src_base_str = snippet(cx, src.base.span, "???"); + + let dst = if dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY { + dst_base_str + } else { + format!( + "{}[{}..{}]", + dst_base_str, + dst_offset.maybe_par(), + dst_limit.maybe_par() + ) + .into() + }; + + format!( + "{}.clone_from_slice(&{}[{}..{}]);", + dst, + src_base_str, + src_offset.maybe_par(), + src_limit.maybe_par() + ) +} + +/// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`; +/// and also, it avoids subtracting a variable from the same one by replacing it with `0`. +/// it exists for the convenience of the overloaded operators while normal functions can do the +/// same. +#[derive(Clone)] +struct MinifyingSugg<'a>(Sugg<'a>); + +impl<'a> MinifyingSugg<'a> { + fn as_str(&self) -> &str { + // HACK: Don't sync to Clippy! Required because something with the `or_patterns` feature + // changed and this would now require parentheses. + match &self.0 { + Sugg::NonParen(s) | Sugg::MaybeParen(s) | Sugg::BinOp(_, s) => s.as_ref(), + } + } + + fn into_sugg(self) -> Sugg<'a> { + self.0 + } +} + +impl<'a> From> for MinifyingSugg<'a> { + fn from(sugg: Sugg<'a>) -> Self { + Self(sugg) + } +} + +impl std::ops::Add for &MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + ("0", _) => rhs.clone(), + (_, "0") => self.clone(), + (_, _) => (&self.0 + &rhs.0).into(), + } + } +} + +impl std::ops::Sub for &MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + (_, "0") => self.clone(), + ("0", _) => (-rhs.0.clone()).into(), + (x, y) if x == y => sugg::ZERO.into(), + (_, _) => (&self.0 - &rhs.0).into(), + } + } +} + +impl std::ops::Add<&MinifyingSugg<'static>> for MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + ("0", _) => rhs.clone(), + (_, "0") => self, + (_, _) => (self.0 + &rhs.0).into(), + } + } +} + +impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + (_, "0") => self, + ("0", _) => (-rhs.0.clone()).into(), + (x, y) if x == y => sugg::ZERO.into(), + (_, _) => (self.0 - &rhs.0).into(), + } + } +} + +/// a wrapper around `MinifyingSugg`, which carries a operator like currying +/// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`). +struct Offset { + value: MinifyingSugg<'static>, + sign: OffsetSign, +} + +#[derive(Clone, Copy)] +enum OffsetSign { + Positive, + Negative, +} + +impl Offset { + fn negative(value: Sugg<'static>) -> Self { + Self { + value: value.into(), + sign: OffsetSign::Negative, + } + } + + fn positive(value: Sugg<'static>) -> Self { + Self { + value: value.into(), + sign: OffsetSign::Positive, + } + } + + fn empty() -> Self { + Self::positive(sugg::ZERO) + } +} + +fn apply_offset(lhs: &MinifyingSugg<'static>, rhs: &Offset) -> MinifyingSugg<'static> { + match rhs.sign { + OffsetSign::Positive => lhs + &rhs.value, + OffsetSign::Negative => lhs - &rhs.value, + } +} + +#[derive(Debug, Clone, Copy)] +enum StartKind<'hir> { + Range, + Counter { initializer: &'hir Expr<'hir> }, +} + +struct IndexExpr<'hir> { + base: &'hir Expr<'hir>, + idx: StartKind<'hir>, + idx_offset: Offset, +} + +struct Start<'hir> { + id: HirId, + kind: StartKind<'hir>, +} + +fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'_>) -> bool { + let is_slice = match ty.kind() { + ty::Ref(_, subty, _) => is_slice_like(cx, subty), + ty::Slice(..) | ty::Array(..) => true, + _ => false, + }; + + is_slice || is_type_diagnostic_item(cx, ty, sym::vec_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) +} + +fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { + if_chain! { + if let ExprKind::MethodCall(method, _, args, _) = expr.kind; + if method.ident.name == sym::clone; + if args.len() == 1; + if let Some(arg) = args.get(0); + then { arg } else { expr } + } +} + +fn get_details_from_idx<'tcx>( + cx: &LateContext<'tcx>, + idx: &Expr<'_>, + starts: &[Start<'tcx>], +) -> Option<(StartKind<'tcx>, Offset)> { + fn get_start<'tcx>(e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option> { + let id = path_to_local(e)?; + starts.iter().find(|start| start.id == id).map(|start| start.kind) + } + + fn get_offset<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option> { + match &e.kind { + ExprKind::Lit(l) => match l.node { + ast::LitKind::Int(x, _ty) => Some(Sugg::NonParen(x.to_string().into())), + _ => None, + }, + ExprKind::Path(..) if get_start(e, starts).is_none() => Some(Sugg::hir(cx, e, "???")), + _ => None, + } + } + + match idx.kind { + ExprKind::Binary(op, lhs, rhs) => match op.node { + BinOpKind::Add => { + let offset_opt = get_start(lhs, starts) + .and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, o))) + .or_else(|| get_start(rhs, starts).and_then(|s| get_offset(cx, lhs, starts).map(|o| (s, o)))); + + offset_opt.map(|(s, o)| (s, Offset::positive(o))) + }, + BinOpKind::Sub => { + get_start(lhs, starts).and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, Offset::negative(o)))) + }, + _ => None, + }, + ExprKind::Path(..) => get_start(idx, starts).map(|s| (s, Offset::empty())), + _ => None, + } +} + +fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + if let ExprKind::Assign(lhs, rhs, _) = e.kind { + Some((lhs, rhs)) + } else { + None + } +} + +/// Get assignments from the given block. +/// The returned iterator yields `None` if no assignment expressions are there, +/// filtering out the increments of the given whitelisted loop counters; +/// because its job is to make sure there's nothing other than assignments and the increments. +fn get_assignments<'a, 'tcx>( + Block { stmts, expr, .. }: &'tcx Block<'tcx>, + loop_counters: &'a [Start<'tcx>], +) -> impl Iterator, &'tcx Expr<'tcx>)>> + 'a { + // As the `filter` and `map` below do different things, I think putting together + // just increases complexity. (cc #3188 and #4193) + stmts + .iter() + .filter_map(move |stmt| match stmt.kind { + StmtKind::Local(..) | StmtKind::Item(..) => None, + StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e), + }) + .chain((*expr).into_iter()) + .filter(move |e| { + if let ExprKind::AssignOp(_, place, _) = e.kind { + path_to_local(place).map_or(false, |id| { + !loop_counters + .iter() + // skip the first item which should be `StartKind::Range` + // this makes it possible to use the slice with `StartKind::Range` in the same iterator loop. + .skip(1) + .any(|counter| counter.id == id) + }) + } else { + true + } + }) + .map(get_assignment) +} + +fn get_loop_counters<'a, 'tcx>( + cx: &'a LateContext<'tcx>, + body: &'tcx Block<'tcx>, + expr: &'tcx Expr<'_>, +) -> Option> + 'a> { + // Look for variables that are incremented once per loop iteration. + let mut increment_visitor = IncrementVisitor::new(cx); + walk_block(&mut increment_visitor, body); + + // For each candidate, check the parent block to see if + // it's initialized to zero at the start of the loop. + get_enclosing_block(&cx, expr.hir_id).and_then(|block| { + increment_visitor + .into_results() + .filter_map(move |var_id| { + let mut initialize_visitor = InitializeVisitor::new(cx, expr, var_id); + walk_block(&mut initialize_visitor, block); + + initialize_visitor.get_result().map(|(_, initializer)| Start { + id: var_id, + kind: StartKind::Counter { initializer }, + }) + }) + .into() + }) +} diff --git a/src/tools/clippy/clippy_lints/src/loops/mod.rs b/src/tools/clippy/clippy_lints/src/loops/mod.rs new file mode 100644 index 0000000000..2a372c6307 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/mod.rs @@ -0,0 +1,627 @@ +mod empty_loop; +mod explicit_counter_loop; +mod explicit_into_iter_loop; +mod explicit_iter_loop; +mod for_kv_map; +mod for_loops_over_fallibles; +mod iter_next_loop; +mod manual_flatten; +mod manual_memcpy; +mod mut_range_bound; +mod needless_collect; +mod needless_range_loop; +mod never_loop; +mod same_item_push; +mod single_element_loop; +mod utils; +mod while_immutable_condition; +mod while_let_loop; +mod while_let_on_iterator; + +use crate::utils::higher; +use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use utils::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor}; + +declare_clippy_lint! { + /// **What it does:** Checks for for-loops that manually copy items between + /// slices that could be optimized by having a memcpy. + /// + /// **Why is this bad?** It is not as fast as a memcpy. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let src = vec![1]; + /// # let mut dst = vec![0; 65]; + /// for i in 0..src.len() { + /// dst[i + 64] = src[i]; + /// } + /// ``` + /// Could be written as: + /// ```rust + /// # let src = vec![1]; + /// # let mut dst = vec![0; 65]; + /// dst[64..(src.len() + 64)].clone_from_slice(&src[..]); + /// ``` + pub MANUAL_MEMCPY, + perf, + "manually copying items between slices" +} + +declare_clippy_lint! { + /// **What it does:** Checks for looping over the range of `0..len` of some + /// collection just to get the values by index. + /// + /// **Why is this bad?** Just iterating the collection itself makes the intent + /// more clear and is probably faster. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let vec = vec!['a', 'b', 'c']; + /// for i in 0..vec.len() { + /// println!("{}", vec[i]); + /// } + /// ``` + /// Could be written as: + /// ```rust + /// let vec = vec!['a', 'b', 'c']; + /// for i in vec { + /// println!("{}", i); + /// } + /// ``` + pub NEEDLESS_RANGE_LOOP, + style, + "for-looping over a range of indices where an iterator over items would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops on `x.iter()` where `&x` will do, and + /// suggests the latter. + /// + /// **Why is this bad?** Readability. + /// + /// **Known problems:** False negatives. We currently only warn on some known + /// types. + /// + /// **Example:** + /// ```rust + /// // with `y` a `Vec` or slice: + /// # let y = vec![1]; + /// for x in y.iter() { + /// // .. + /// } + /// ``` + /// can be rewritten to + /// ```rust + /// # let y = vec![1]; + /// for x in &y { + /// // .. + /// } + /// ``` + pub EXPLICIT_ITER_LOOP, + pedantic, + "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops on `y.into_iter()` where `y` will do, and + /// suggests the latter. + /// + /// **Why is this bad?** Readability. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// # let y = vec![1]; + /// // with `y` a `Vec` or slice: + /// for x in y.into_iter() { + /// // .. + /// } + /// ``` + /// can be rewritten to + /// ```rust + /// # let y = vec![1]; + /// for x in y { + /// // .. + /// } + /// ``` + pub EXPLICIT_INTO_ITER_LOOP, + pedantic, + "for-looping over `_.into_iter()` when `_` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops on `x.next()`. + /// + /// **Why is this bad?** `next()` returns either `Some(value)` if there was a + /// value, or `None` otherwise. The insidious thing is that `Option<_>` + /// implements `IntoIterator`, so that possibly one value will be iterated, + /// leading to some hard to find bugs. No one will want to write such code + /// [except to win an Underhanded Rust + /// Contest](https://www.reddit.com/r/rust/comments/3hb0wm/underhanded_rust_contest/cu5yuhr). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// for x in y.next() { + /// .. + /// } + /// ``` + pub ITER_NEXT_LOOP, + correctness, + "for-looping over `_.next()` which is probably not intended" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `for` loops over `Option` or `Result` values. + /// + /// **Why is this bad?** Readability. This is more clearly expressed as an `if + /// let`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let opt = Some(1); + /// + /// // Bad + /// for x in opt { + /// // .. + /// } + /// + /// // Good + /// if let Some(x) = opt { + /// // .. + /// } + /// ``` + /// + /// // or + /// + /// ```rust + /// # let res: Result = Ok(1); + /// + /// // Bad + /// for x in &res { + /// // .. + /// } + /// + /// // Good + /// if let Ok(x) = res { + /// // .. + /// } + /// ``` + pub FOR_LOOPS_OVER_FALLIBLES, + correctness, + "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`" +} + +declare_clippy_lint! { + /// **What it does:** Detects `loop + match` combinations that are easier + /// written as a `while let` loop. + /// + /// **Why is this bad?** The `while let` loop is usually shorter and more + /// readable. + /// + /// **Known problems:** Sometimes the wrong binding is displayed ([#383](https://github.com/rust-lang/rust-clippy/issues/383)). + /// + /// **Example:** + /// ```rust,no_run + /// # let y = Some(1); + /// loop { + /// let x = match y { + /// Some(x) => x, + /// None => break, + /// }; + /// // .. do something with x + /// } + /// // is easier written as + /// while let Some(x) = y { + /// // .. do something with x + /// }; + /// ``` + pub WHILE_LET_LOOP, + complexity, + "`loop { if let { ... } else break }`, which can be written as a `while let` loop" +} + +declare_clippy_lint! { + /// **What it does:** Checks for functions collecting an iterator when collect + /// is not needed. + /// + /// **Why is this bad?** `collect` causes the allocation of a new data structure, + /// when this allocation may not be needed. + /// + /// **Known problems:** + /// None + /// + /// **Example:** + /// ```rust + /// # let iterator = vec![1].into_iter(); + /// let len = iterator.clone().collect::>().len(); + /// // should be + /// let len = iterator.count(); + /// ``` + pub NEEDLESS_COLLECT, + perf, + "collecting an iterator when collect is not needed" +} + +declare_clippy_lint! { + /// **What it does:** Checks `for` loops over slices with an explicit counter + /// and suggests the use of `.enumerate()`. + /// + /// **Why is it bad?** Using `.enumerate()` makes the intent more clear, + /// declutters the code and may be faster in some instances. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let v = vec![1]; + /// # fn bar(bar: usize, baz: usize) {} + /// let mut i = 0; + /// for item in &v { + /// bar(i, *item); + /// i += 1; + /// } + /// ``` + /// Could be written as + /// ```rust + /// # let v = vec![1]; + /// # fn bar(bar: usize, baz: usize) {} + /// for (i, item) in v.iter().enumerate() { bar(i, *item); } + /// ``` + pub EXPLICIT_COUNTER_LOOP, + complexity, + "for-looping with an explicit counter when `_.enumerate()` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for empty `loop` expressions. + /// + /// **Why is this bad?** These busy loops burn CPU cycles without doing + /// anything. It is _almost always_ a better idea to `panic!` than to have + /// a busy loop. + /// + /// If panicking isn't possible, think of the environment and either: + /// - block on something + /// - sleep the thread for some microseconds + /// - yield or pause the thread + /// + /// For `std` targets, this can be done with + /// [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html) + /// or [`std::thread::yield_now`](https://doc.rust-lang.org/std/thread/fn.yield_now.html). + /// + /// For `no_std` targets, doing this is more complicated, especially because + /// `#[panic_handler]`s can't panic. To stop/pause the thread, you will + /// probably need to invoke some target-specific intrinsic. Examples include: + /// - [`x86_64::instructions::hlt`](https://docs.rs/x86_64/0.12.2/x86_64/instructions/fn.hlt.html) + /// - [`cortex_m::asm::wfi`](https://docs.rs/cortex-m/0.6.3/cortex_m/asm/fn.wfi.html) + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// loop {} + /// ``` + pub EMPTY_LOOP, + style, + "empty `loop {}`, which should block or sleep" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `while let` expressions on iterators. + /// + /// **Why is this bad?** Readability. A simple `for` loop is shorter and conveys + /// the intent better. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// while let Some(val) = iter() { + /// .. + /// } + /// ``` + pub WHILE_LET_ON_ITERATOR, + style, + "using a `while let` loop instead of a for loop on an iterator" +} + +declare_clippy_lint! { + /// **What it does:** Checks for iterating a map (`HashMap` or `BTreeMap`) and + /// ignoring either the keys or values. + /// + /// **Why is this bad?** Readability. There are `keys` and `values` methods that + /// can be used to express that don't need the values or keys. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// for (k, _) in &map { + /// .. + /// } + /// ``` + /// + /// could be replaced by + /// + /// ```ignore + /// for k in map.keys() { + /// .. + /// } + /// ``` + pub FOR_KV_MAP, + style, + "looping on a map using `iter` when `keys` or `values` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops that will always `break`, `return` or + /// `continue` an outer loop. + /// + /// **Why is this bad?** This loop never loops, all it does is obfuscating the + /// code. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// loop { + /// ..; + /// break; + /// } + /// ``` + pub NEVER_LOOP, + correctness, + "any loop that will always `break` or `return`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops which have a range bound that is a mutable variable + /// + /// **Why is this bad?** One might think that modifying the mutable variable changes the loop bounds + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let mut foo = 42; + /// for i in 0..foo { + /// foo -= 1; + /// println!("{}", i); // prints numbers from 0 to 42, not 0 to 21 + /// } + /// ``` + pub MUT_RANGE_BOUND, + complexity, + "for loop over a range where one of the bounds is a mutable variable" +} + +declare_clippy_lint! { + /// **What it does:** Checks whether variables used within while loop condition + /// can be (and are) mutated in the body. + /// + /// **Why is this bad?** If the condition is unchanged, entering the body of the loop + /// will lead to an infinite loop. + /// + /// **Known problems:** If the `while`-loop is in a closure, the check for mutation of the + /// condition variables in the body can cause false negatives. For example when only `Upvar` `a` is + /// in the condition and only `Upvar` `b` gets mutated in the body, the lint will not trigger. + /// + /// **Example:** + /// ```rust + /// let i = 0; + /// while i > 10 { + /// println!("let me loop forever!"); + /// } + /// ``` + pub WHILE_IMMUTABLE_CONDITION, + correctness, + "variables used within while expression are not mutated in the body" +} + +declare_clippy_lint! { + /// **What it does:** Checks whether a for loop is being used to push a constant + /// value into a Vec. + /// + /// **Why is this bad?** This kind of operation can be expressed more succinctly with + /// `vec![item;SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also + /// have better performance. + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let item1 = 2; + /// let item2 = 3; + /// let mut vec: Vec = Vec::new(); + /// for _ in 0..20 { + /// vec.push(item1); + /// } + /// for _ in 0..30 { + /// vec.push(item2); + /// } + /// ``` + /// could be written as + /// ```rust + /// let item1 = 2; + /// let item2 = 3; + /// let mut vec: Vec = vec![item1; 20]; + /// vec.resize(20 + 30, item2); + /// ``` + pub SAME_ITEM_PUSH, + style, + "the same item is pushed inside of a for loop" +} + +declare_clippy_lint! { + /// **What it does:** Checks whether a for loop has a single element. + /// + /// **Why is this bad?** There is no reason to have a loop of a + /// single element. + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let item1 = 2; + /// for item in &[item1] { + /// println!("{}", item); + /// } + /// ``` + /// could be written as + /// ```rust + /// let item1 = 2; + /// let item = &item1; + /// println!("{}", item); + /// ``` + pub SINGLE_ELEMENT_LOOP, + complexity, + "there is no reason to have a single element loop" +} + +declare_clippy_lint! { + /// **What it does:** Check for unnecessary `if let` usage in a for loop + /// where only the `Some` or `Ok` variant of the iterator element is used. + /// + /// **Why is this bad?** It is verbose and can be simplified + /// by first calling the `flatten` method on the `Iterator`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x = vec![Some(1), Some(2), Some(3)]; + /// for n in x { + /// if let Some(n) = n { + /// println!("{}", n); + /// } + /// } + /// ``` + /// Use instead: + /// ```rust + /// let x = vec![Some(1), Some(2), Some(3)]; + /// for n in x.into_iter().flatten() { + /// println!("{}", n); + /// } + /// ``` + pub MANUAL_FLATTEN, + complexity, + "for loops over `Option`s or `Result`s with a single expression can be simplified" +} + +declare_lint_pass!(Loops => [ + MANUAL_MEMCPY, + MANUAL_FLATTEN, + NEEDLESS_RANGE_LOOP, + EXPLICIT_ITER_LOOP, + EXPLICIT_INTO_ITER_LOOP, + ITER_NEXT_LOOP, + FOR_LOOPS_OVER_FALLIBLES, + WHILE_LET_LOOP, + NEEDLESS_COLLECT, + EXPLICIT_COUNTER_LOOP, + EMPTY_LOOP, + WHILE_LET_ON_ITERATOR, + FOR_KV_MAP, + NEVER_LOOP, + MUT_RANGE_BOUND, + WHILE_IMMUTABLE_CONDITION, + SAME_ITEM_PUSH, + SINGLE_ELEMENT_LOOP, +]); + +impl<'tcx> LateLintPass<'tcx> for Loops { + #[allow(clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some((pat, arg, body, span)) = higher::for_loop(expr) { + // we don't want to check expanded macros + // this check is not at the top of the function + // since higher::for_loop expressions are marked as expansions + if body.span.from_expansion() { + return; + } + check_for_loop(cx, pat, arg, body, expr, span); + } + + // we don't want to check expanded macros + if expr.span.from_expansion() { + return; + } + + // check for never_loop + never_loop::check(cx, expr); + + // check for `loop { if let {} else break }` that could be `while let` + // (also matches an explicit "match" instead of "if let") + // (even if the "match" or "if let" is used for declaration) + if let ExprKind::Loop(ref block, _, LoopSource::Loop, _) = expr.kind { + // also check for empty `loop {}` statements, skipping those in #[panic_handler] + empty_loop::check(cx, expr, block); + while_let_loop::check(cx, expr, block); + } + + while_let_on_iterator::check(cx, expr); + + if let Some((cond, body)) = higher::while_loop(&expr) { + while_immutable_condition::check(cx, cond, body); + } + + needless_collect::check(expr, cx); + } +} + +fn check_for_loop<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, + span: Span, +) { + let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr); + if !is_manual_memcpy_triggered { + needless_range_loop::check(cx, pat, arg, body, expr); + explicit_counter_loop::check(cx, pat, arg, body, expr); + } + check_for_loop_arg(cx, pat, arg, expr); + for_kv_map::check(cx, pat, arg, body, expr); + mut_range_bound::check(cx, arg, body); + single_element_loop::check(cx, pat, arg, body, expr); + same_item_push::check(cx, pat, arg, body, expr); + manual_flatten::check(cx, pat, arg, body, span); +} + +fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) { + let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used + + if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind { + // just the receiver, no arguments + if args.len() == 1 { + let method_name = &*method.ident.as_str(); + // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x + match method_name { + "iter" | "iter_mut" => explicit_iter_loop::check(cx, args, arg, method_name), + "into_iter" => { + explicit_iter_loop::check(cx, args, arg, method_name); + explicit_into_iter_loop::check(cx, args, arg); + }, + "next" => { + next_loop_linted = iter_next_loop::check(cx, arg, expr); + }, + _ => {}, + } + } + } + + if !next_loop_linted { + for_loops_over_fallibles::check(cx, pat, arg); + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/mut_range_bound.rs b/src/tools/clippy/clippy_lints/src/loops/mut_range_bound.rs new file mode 100644 index 0000000000..cb56512db6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/mut_range_bound.rs @@ -0,0 +1,118 @@ +use super::MUT_RANGE_BOUND; +use crate::utils::{higher, path_to_local, span_lint}; +use if_chain::if_chain; +use rustc_hir::{BindingAnnotation, Expr, HirId, Node, PatKind}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::LateContext; +use rustc_middle::mir::FakeReadCause; +use rustc_middle::ty; +use rustc_span::source_map::Span; +use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; + +pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) { + if let Some(higher::Range { + start: Some(start), + end: Some(end), + .. + }) = higher::range(arg) + { + let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)]; + if mut_ids[0].is_some() || mut_ids[1].is_some() { + let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids); + mut_warn_with_span(cx, span_low); + mut_warn_with_span(cx, span_high); + } + } +} + +fn mut_warn_with_span(cx: &LateContext<'_>, span: Option) { + if let Some(sp) = span { + span_lint( + cx, + MUT_RANGE_BOUND, + sp, + "attempt to mutate range bound within loop; note that the range of the loop is unchanged", + ); + } +} + +fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option { + if_chain! { + if let Some(hir_id) = path_to_local(bound); + if let Node::Binding(pat) = cx.tcx.hir().get(hir_id); + if let PatKind::Binding(BindingAnnotation::Mutable, ..) = pat.kind; + then { + return Some(hir_id); + } + } + None +} + +fn check_for_mutation<'tcx>( + cx: &LateContext<'tcx>, + body: &Expr<'_>, + bound_ids: &[Option], +) -> (Option, Option) { + let mut delegate = MutatePairDelegate { + cx, + hir_id_low: bound_ids[0], + hir_id_high: bound_ids[1], + span_low: None, + span_high: None, + }; + cx.tcx.infer_ctxt().enter(|infcx| { + ExprUseVisitor::new( + &mut delegate, + &infcx, + body.hir_id.owner, + cx.param_env, + cx.typeck_results(), + ) + .walk_expr(body); + }); + delegate.mutation_span() +} + +struct MutatePairDelegate<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + hir_id_low: Option, + hir_id_high: Option, + span_low: Option, + span_high: Option, +} + +impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> { + fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {} + + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { + if let ty::BorrowKind::MutBorrow = bk { + if let PlaceBase::Local(id) = cmt.place.base { + if Some(id) == self.hir_id_low { + self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + if Some(id) == self.hir_id_high { + self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + } + } + } + + fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { + if let PlaceBase::Local(id) = cmt.place.base { + if Some(id) == self.hir_id_low { + self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + if Some(id) == self.hir_id_high { + self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + } + } + + fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _:HirId) { } +} + +impl MutatePairDelegate<'_, '_> { + fn mutation_span(&self) -> (Option, Option) { + (self.span_low, self.span_high) + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_collect.rs b/src/tools/clippy/clippy_lints/src/loops/needless_collect.rs new file mode 100644 index 0000000000..92560c8062 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/needless_collect.rs @@ -0,0 +1,283 @@ +use super::NEEDLESS_COLLECT; +use crate::utils::sugg::Sugg; +use crate::utils::{ + is_type_diagnostic_item, match_trait_method, match_type, path_to_local_id, paths, snippet, span_lint_and_sugg, + span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{Block, Expr, ExprKind, GenericArg, HirId, Local, Pat, PatKind, QPath, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::source_map::Span; +use rustc_span::symbol::{sym, Ident}; + +const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; + +pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { + check_needless_collect_direct_usage(expr, cx); + check_needless_collect_indirect_usage(expr, cx); +} +fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind; + if let ExprKind::MethodCall(ref chain_method, _, _, _) = args[0].kind; + if chain_method.ident.name == sym!(collect) && match_trait_method(cx, &args[0], &paths::ITERATOR); + if let Some(ref generic_args) = chain_method.args; + if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); + then { + let ty = cx.typeck_results().node_type(ty.hir_id); + if is_type_diagnostic_item(cx, ty, sym::vec_type) || + is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || + match_type(cx, ty, &paths::BTREEMAP) || + is_type_diagnostic_item(cx, ty, sym::hashmap_type) { + if method.ident.name == sym!(len) { + let span = shorten_needless_collect_span(expr); + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + "replace with", + "count()".to_string(), + Applicability::MachineApplicable, + ); + } + if method.ident.name == sym!(is_empty) { + let span = shorten_needless_collect_span(expr); + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + "replace with", + "next().is_none()".to_string(), + Applicability::MachineApplicable, + ); + } + if method.ident.name == sym!(contains) { + let contains_arg = snippet(cx, args[1].span, "??"); + let span = shorten_needless_collect_span(expr); + span_lint_and_then( + cx, + NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + |diag| { + let (arg, pred) = contains_arg + .strip_prefix('&') + .map_or(("&x", &*contains_arg), |s| ("x", s)); + diag.span_suggestion( + span, + "replace with", + format!( + "any(|{}| x == {})", + arg, pred + ), + Applicability::MachineApplicable, + ); + } + ); + } + } + } + } +} + +fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { + if let ExprKind::Block(ref block, _) = expr.kind { + for ref stmt in block.stmts { + if_chain! { + if let StmtKind::Local( + Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }, + init: Some(ref init_expr), .. } + ) = stmt.kind; + if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind; + if method_name.ident.name == sym!(collect) && match_trait_method(cx, &init_expr, &paths::ITERATOR); + if let Some(ref generic_args) = method_name.args; + if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); + if let ty = cx.typeck_results().node_type(ty.hir_id); + if is_type_diagnostic_item(cx, ty, sym::vec_type) || + is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || + match_type(cx, ty, &paths::LINKED_LIST); + if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident); + if iter_calls.len() == 1; + then { + let mut used_count_visitor = UsedCountVisitor { + cx, + id: *pat_id, + count: 0, + }; + walk_block(&mut used_count_visitor, block); + if used_count_visitor.count > 1 { + return; + } + + // Suggest replacing iter_call with iter_replacement, and removing stmt + let iter_call = &iter_calls[0]; + span_lint_and_then( + cx, + super::NEEDLESS_COLLECT, + stmt.span.until(iter_call.span), + NEEDLESS_COLLECT_MSG, + |diag| { + let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx)); + diag.multipart_suggestion( + iter_call.get_suggestion_text(), + vec![ + (stmt.span, String::new()), + (iter_call.span, iter_replacement) + ], + Applicability::MachineApplicable,// MaybeIncorrect, + ).emit(); + }, + ); + } + } + } + } +} + +struct IterFunction { + func: IterFunctionKind, + span: Span, +} +impl IterFunction { + fn get_iter_method(&self, cx: &LateContext<'_>) -> String { + match &self.func { + IterFunctionKind::IntoIter => String::new(), + IterFunctionKind::Len => String::from(".count()"), + IterFunctionKind::IsEmpty => String::from(".next().is_none()"), + IterFunctionKind::Contains(span) => { + let s = snippet(cx, *span, ".."); + if let Some(stripped) = s.strip_prefix('&') { + format!(".any(|x| x == {})", stripped) + } else { + format!(".any(|x| x == *{})", s) + } + }, + } + } + fn get_suggestion_text(&self) -> &'static str { + match &self.func { + IterFunctionKind::IntoIter => { + "use the original Iterator instead of collecting it and then producing a new one" + }, + IterFunctionKind::Len => { + "take the original Iterator's count instead of collecting it and finding the length" + }, + IterFunctionKind::IsEmpty => { + "check if the original Iterator has anything instead of collecting it and seeing if it's empty" + }, + IterFunctionKind::Contains(_) => { + "check if the original Iterator contains an element instead of collecting then checking" + }, + } + } +} +enum IterFunctionKind { + IntoIter, + Len, + IsEmpty, + Contains(Span), +} + +struct IterFunctionVisitor { + uses: Vec, + seen_other: bool, + target: Ident, +} +impl<'tcx> Visitor<'tcx> for IterFunctionVisitor { + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + // Check function calls on our collection + if_chain! { + if let ExprKind::MethodCall(method_name, _, ref args, _) = &expr.kind; + if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. }) = args.get(0); + if let &[name] = &path.segments; + if name.ident == self.target; + then { + let len = sym!(len); + let is_empty = sym!(is_empty); + let contains = sym!(contains); + match method_name.ident.name { + sym::into_iter => self.uses.push( + IterFunction { func: IterFunctionKind::IntoIter, span: expr.span } + ), + name if name == len => self.uses.push( + IterFunction { func: IterFunctionKind::Len, span: expr.span } + ), + name if name == is_empty => self.uses.push( + IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span } + ), + name if name == contains => self.uses.push( + IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span } + ), + _ => self.seen_other = true, + } + return + } + } + // Check if the collection is used for anything else + if_chain! { + if let Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. } = expr; + if let &[name] = &path.segments; + if name.ident == self.target; + then { + self.seen_other = true; + } else { + walk_expr(self, expr); + } + } + } + + type Map = Map<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +struct UsedCountVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + id: HirId, + count: usize, +} + +impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if path_to_local_id(expr, self.id) { + self.count += 1; + } else { + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +/// Detect the occurrences of calls to `iter` or `into_iter` for the +/// given identifier +fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option> { + let mut visitor = IterFunctionVisitor { + uses: Vec::new(), + target: identifier, + seen_other: false, + }; + visitor.visit_block(block); + if visitor.seen_other { None } else { Some(visitor.uses) } +} + +fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span { + if_chain! { + if let ExprKind::MethodCall(.., args, _) = &expr.kind; + if let ExprKind::MethodCall(_, span, ..) = &args[0].kind; + then { + return expr.span.with_lo(span.lo()); + } + } + unreachable!(); +} diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs new file mode 100644 index 0000000000..5f02e4b9d8 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs @@ -0,0 +1,391 @@ +use super::NEEDLESS_RANGE_LOOP; +use crate::utils::visitors::LocalUsedVisitor; +use crate::utils::{ + contains_name, has_iter_method, higher, is_integer_const, match_trait_method, multispan_sugg, path_to_local_id, + paths, snippet, span_lint_and_then, sugg, SpanlessEq, +}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_middle::middle::region; +use rustc_middle::ty::{self, Ty}; +use rustc_span::symbol::{sym, Symbol}; +use std::iter::Iterator; +use std::mem; + +/// Checks for looping over a range and then indexing a sequence with it. +/// The iteratee must be a range literal. +#[allow(clippy::too_many_lines)] +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + if let Some(higher::Range { + start: Some(start), + ref end, + limits, + }) = higher::range(arg) + { + // the var must be a single name + if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind { + let mut visitor = VarVisitor { + cx, + var: canonical_id, + indexed_mut: FxHashSet::default(), + indexed_indirectly: FxHashMap::default(), + indexed_directly: FxHashMap::default(), + referenced: FxHashSet::default(), + nonindex: false, + prefer_mutable: false, + }; + walk_expr(&mut visitor, body); + + // linting condition: we only indexed one variable, and indexed it directly + if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 { + let (indexed, (indexed_extent, indexed_ty)) = visitor + .indexed_directly + .into_iter() + .next() + .expect("already checked that we have exactly 1 element"); + + // ensure that the indexed variable was declared before the loop, see #601 + if let Some(indexed_extent) = indexed_extent { + let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); + let parent_def_id = cx.tcx.hir().local_def_id(parent_id); + let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); + let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id); + if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { + return; + } + } + + // don't lint if the container that is indexed does not have .iter() method + let has_iter = has_iter_method(cx, indexed_ty); + if has_iter.is_none() { + return; + } + + // don't lint if the container that is indexed into is also used without + // indexing + if visitor.referenced.contains(&indexed) { + return; + } + + let starts_at_zero = is_integer_const(cx, start, 0); + + let skip = if starts_at_zero { + String::new() + } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start) { + return; + } else { + format!(".skip({})", snippet(cx, start.span, "..")) + }; + + let mut end_is_start_plus_val = false; + + let take = if let Some(end) = *end { + let mut take_expr = end; + + if let ExprKind::Binary(ref op, ref left, ref right) = end.kind { + if let BinOpKind::Add = op.node { + let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left); + let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right); + + if start_equal_left { + take_expr = right; + } else if start_equal_right { + take_expr = left; + } + + end_is_start_plus_val = start_equal_left | start_equal_right; + } + } + + if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) { + String::new() + } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) { + return; + } else { + match limits { + ast::RangeLimits::Closed => { + let take_expr = sugg::Sugg::hir(cx, take_expr, ""); + format!(".take({})", take_expr + sugg::ONE) + }, + ast::RangeLimits::HalfOpen => format!(".take({})", snippet(cx, take_expr.span, "..")), + } + } + } else { + String::new() + }; + + let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) { + ("mut ", "iter_mut") + } else { + ("", "iter") + }; + + let take_is_empty = take.is_empty(); + let mut method_1 = take; + let mut method_2 = skip; + + if end_is_start_plus_val { + mem::swap(&mut method_1, &mut method_2); + } + + if visitor.nonindex { + span_lint_and_then( + cx, + NEEDLESS_RANGE_LOOP, + expr.span, + &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed), + |diag| { + multispan_sugg( + diag, + "consider using an iterator", + vec![ + (pat.span, format!("({}, )", ident.name)), + ( + arg.span, + format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2), + ), + ], + ); + }, + ); + } else { + let repl = if starts_at_zero && take_is_empty { + format!("&{}{}", ref_mut, indexed) + } else { + format!("{}.{}(){}{}", indexed, method, method_1, method_2) + }; + + span_lint_and_then( + cx, + NEEDLESS_RANGE_LOOP, + expr.span, + &format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed), + |diag| { + multispan_sugg( + diag, + "consider using an iterator", + vec![(pat.span, "".to_string()), (arg.span, repl)], + ); + }, + ); + } + } + } + } +} + +fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref len_args, _) = expr.kind; + if len_args.len() == 1; + if method.ident.name == sym!(len); + if let ExprKind::Path(QPath::Resolved(_, ref path)) = len_args[0].kind; + if path.segments.len() == 1; + if path.segments[0].ident.name == var; + then { + return true; + } + } + + false +} + +fn is_end_eq_array_len<'tcx>( + cx: &LateContext<'tcx>, + end: &Expr<'_>, + limits: ast::RangeLimits, + indexed_ty: Ty<'tcx>, +) -> bool { + if_chain! { + if let ExprKind::Lit(ref lit) = end.kind; + if let ast::LitKind::Int(end_int, _) = lit.node; + if let ty::Array(_, arr_len_const) = indexed_ty.kind(); + if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env); + then { + return match limits { + ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(), + ast::RangeLimits::HalfOpen => end_int >= arr_len.into(), + }; + } + } + + false +} + +struct VarVisitor<'a, 'tcx> { + /// context reference + cx: &'a LateContext<'tcx>, + /// var name to look for as index + var: HirId, + /// indexed variables that are used mutably + indexed_mut: FxHashSet, + /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global + indexed_indirectly: FxHashMap>, + /// subset of `indexed` of vars that are indexed directly: `v[i]` + /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]` + indexed_directly: FxHashMap, Ty<'tcx>)>, + /// Any names that are used outside an index operation. + /// Used to detect things like `&mut vec` used together with `vec[i]` + referenced: FxHashSet, + /// has the loop variable been used in expressions other than the index of + /// an index op? + nonindex: bool, + /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar + /// takes `&mut self` + prefer_mutable: bool, +} + +impl<'a, 'tcx> VarVisitor<'a, 'tcx> { + fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { + if_chain! { + // the indexed container is referenced by a name + if let ExprKind::Path(ref seqpath) = seqexpr.kind; + if let QPath::Resolved(None, ref seqvar) = *seqpath; + if seqvar.segments.len() == 1; + then { + let index_used_directly = path_to_local_id(idx, self.var); + let indexed_indirectly = { + let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var); + walk_expr(&mut used_visitor, idx); + used_visitor.used + }; + + if indexed_indirectly || index_used_directly { + if self.prefer_mutable { + self.indexed_mut.insert(seqvar.segments[0].ident.name); + } + let res = self.cx.qpath_res(seqpath, seqexpr.hir_id); + match res { + Res::Local(hir_id) => { + let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); + let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id); + let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id); + if indexed_indirectly { + self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent)); + } + if index_used_directly { + self.indexed_directly.insert( + seqvar.segments[0].ident.name, + (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)), + ); + } + return false; // no need to walk further *on the variable* + } + Res::Def(DefKind::Static | DefKind::Const, ..) => { + if indexed_indirectly { + self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None); + } + if index_used_directly { + self.indexed_directly.insert( + seqvar.segments[0].ident.name, + (None, self.cx.typeck_results().node_type(seqexpr.hir_id)), + ); + } + return false; // no need to walk further *on the variable* + } + _ => (), + } + } + } + } + true + } +} + +impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + // a range index op + if let ExprKind::MethodCall(ref meth, _, ref args, _) = expr.kind; + if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX)) + || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT)); + if !self.check(&args[1], &args[0], expr); + then { return } + } + + if_chain! { + // an index op + if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind; + if !self.check(idx, seqexpr, expr); + then { return } + } + + if_chain! { + // directly using a variable + if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind; + if let Res::Local(local_id) = path.res; + then { + if local_id == self.var { + self.nonindex = true; + } else { + // not the correct variable, but still a variable + self.referenced.insert(path.segments[0].ident.name); + } + } + } + + let old = self.prefer_mutable; + match expr.kind { + ExprKind::AssignOp(_, ref lhs, ref rhs) | ExprKind::Assign(ref lhs, ref rhs, _) => { + self.prefer_mutable = true; + self.visit_expr(lhs); + self.prefer_mutable = false; + self.visit_expr(rhs); + }, + ExprKind::AddrOf(BorrowKind::Ref, mutbl, ref expr) => { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + self.visit_expr(expr); + }, + ExprKind::Call(ref f, args) => { + self.visit_expr(f); + for expr in args { + let ty = self.cx.typeck_results().expr_ty_adjusted(expr); + self.prefer_mutable = false; + if let ty::Ref(_, _, mutbl) = *ty.kind() { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + } + self.visit_expr(expr); + } + }, + ExprKind::MethodCall(_, _, args, _) => { + let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); + for (ty, expr) in self.cx.tcx.fn_sig(def_id).inputs().skip_binder().iter().zip(args) { + self.prefer_mutable = false; + if let ty::Ref(_, _, mutbl) = *ty.kind() { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + } + self.visit_expr(expr); + } + }, + ExprKind::Closure(_, _, body_id, ..) => { + let body = self.cx.tcx.hir().body(body_id); + self.visit_expr(&body.value); + }, + _ => walk_expr(self, expr), + } + self.prefer_mutable = old; + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs new file mode 100644 index 0000000000..45e1001d75 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs @@ -0,0 +1,172 @@ +use super::NEVER_LOOP; +use crate::utils::span_lint; +use rustc_hir::{Block, Expr, ExprKind, HirId, InlineAsmOperand, Stmt, StmtKind}; +use rustc_lint::LateContext; +use std::iter::{once, Iterator}; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Loop(ref block, _, _, _) = expr.kind { + match never_loop_block(block, expr.hir_id) { + NeverLoopResult::AlwaysBreak => span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops"), + NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (), + } + } +} + +enum NeverLoopResult { + // A break/return always get triggered but not necessarily for the main loop. + AlwaysBreak, + // A continue may occur for the main loop. + MayContinueMainLoop, + Otherwise, +} + +#[must_use] +fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult { + match *arg { + NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise, + NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop, + } +} + +// Combine two results for parts that are called in order. +#[must_use] +fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult { + match first { + NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first, + NeverLoopResult::Otherwise => second, + } +} + +// Combine two results where both parts are called but not necessarily in order. +#[must_use] +fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult { + match (left, right) { + (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { + NeverLoopResult::MayContinueMainLoop + }, + (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, + (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, + } +} + +// Combine two results where only one of the part may have been executed. +#[must_use] +fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult { + match (b1, b2) { + (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, + (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { + NeverLoopResult::MayContinueMainLoop + }, + (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, + } +} + +fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult { + let stmts = block.stmts.iter().map(stmt_to_expr); + let expr = once(block.expr.as_deref()); + let mut iter = stmts.chain(expr).flatten(); + never_loop_expr_seq(&mut iter, main_loop_id) +} + +fn never_loop_expr_seq<'a, T: Iterator>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { + es.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::Otherwise, combine_seq) +} + +fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match stmt.kind { + StmtKind::Semi(ref e, ..) | StmtKind::Expr(ref e, ..) => Some(e), + StmtKind::Local(ref local) => local.init.as_deref(), + _ => None, + } +} + +fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { + match expr.kind { + ExprKind::Box(ref e) + | ExprKind::Unary(_, ref e) + | ExprKind::Cast(ref e, _) + | ExprKind::Type(ref e, _) + | ExprKind::Field(ref e, _) + | ExprKind::AddrOf(_, _, ref e) + | ExprKind::Struct(_, _, Some(ref e)) + | ExprKind::Repeat(ref e, _) + | ExprKind::DropTemps(ref e) => never_loop_expr(e, main_loop_id), + ExprKind::Array(ref es) | ExprKind::MethodCall(_, _, ref es, _) | ExprKind::Tup(ref es) => { + never_loop_expr_all(&mut es.iter(), main_loop_id) + }, + ExprKind::Call(ref e, ref es) => never_loop_expr_all(&mut once(&**e).chain(es.iter()), main_loop_id), + ExprKind::Binary(_, ref e1, ref e2) + | ExprKind::Assign(ref e1, ref e2, _) + | ExprKind::AssignOp(_, ref e1, ref e2) + | ExprKind::Index(ref e1, ref e2) => never_loop_expr_all(&mut [&**e1, &**e2].iter().cloned(), main_loop_id), + ExprKind::Loop(ref b, _, _, _) => { + // Break can come from the inner loop so remove them. + absorb_break(&never_loop_block(b, main_loop_id)) + }, + ExprKind::If(ref e, ref e2, ref e3) => { + let e1 = never_loop_expr(e, main_loop_id); + let e2 = never_loop_expr(e2, main_loop_id); + let e3 = e3 + .as_ref() + .map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id)); + combine_seq(e1, combine_branches(e2, e3)) + }, + ExprKind::Match(ref e, ref arms, _) => { + let e = never_loop_expr(e, main_loop_id); + if arms.is_empty() { + e + } else { + let arms = never_loop_expr_branch(&mut arms.iter().map(|a| &*a.body), main_loop_id); + combine_seq(e, arms) + } + }, + ExprKind::Block(ref b, _) => never_loop_block(b, main_loop_id), + ExprKind::Continue(d) => { + let id = d + .target_id + .expect("target ID can only be missing in the presence of compilation errors"); + if id == main_loop_id { + NeverLoopResult::MayContinueMainLoop + } else { + NeverLoopResult::AlwaysBreak + } + }, + ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| { + combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak) + }), + ExprKind::InlineAsm(ref asm) => asm + .operands + .iter() + .map(|(o, _)| match o { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr } + | InlineAsmOperand::Sym { expr } => never_loop_expr(expr, main_loop_id), + InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter(), main_loop_id), + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + never_loop_expr_all(&mut once(in_expr).chain(out_expr.iter()), main_loop_id) + }, + }) + .fold(NeverLoopResult::Otherwise, combine_both), + ExprKind::Struct(_, _, None) + | ExprKind::Yield(_, _) + | ExprKind::Closure(_, _, _, _, _) + | ExprKind::LlvmInlineAsm(_) + | ExprKind::Path(_) + | ExprKind::ConstBlock(_) + | ExprKind::Lit(_) + | ExprKind::Err => NeverLoopResult::Otherwise, + } +} + +fn never_loop_expr_all<'a, T: Iterator>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { + es.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::Otherwise, combine_both) +} + +fn never_loop_expr_branch<'a, T: Iterator>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult { + e.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::AlwaysBreak, combine_branches) +} diff --git a/src/tools/clippy/clippy_lints/src/loops/same_item_push.rs b/src/tools/clippy/clippy_lints/src/loops/same_item_push.rs new file mode 100644 index 0000000000..f3585830e4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/same_item_push.rs @@ -0,0 +1,169 @@ +use super::SAME_ITEM_PUSH; +use crate::utils::{implements_trait, is_type_diagnostic_item, snippet_with_macro_callsite, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Node, Pat, PatKind, Stmt, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::symbol::sym; +use std::iter::Iterator; + +/// Detects for loop pushing the same item into a Vec +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + _: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + _: &'tcx Expr<'_>, +) { + fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) { + let vec_str = snippet_with_macro_callsite(cx, vec.span, ""); + let item_str = snippet_with_macro_callsite(cx, pushed_item.span, ""); + + span_lint_and_help( + cx, + SAME_ITEM_PUSH, + vec.span, + "it looks like the same item is being pushed into this Vec", + None, + &format!( + "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})", + item_str, vec_str, item_str + ), + ) + } + + if !matches!(pat.kind, PatKind::Wild) { + return; + } + + // Determine whether it is safe to lint the body + let mut same_item_push_visitor = SameItemPushVisitor { + should_lint: true, + vec_push: None, + cx, + }; + walk_expr(&mut same_item_push_visitor, body); + if same_item_push_visitor.should_lint { + if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push { + let vec_ty = cx.typeck_results().expr_ty(vec); + let ty = vec_ty.walk().nth(1).unwrap().expect_ty(); + if cx + .tcx + .lang_items() + .clone_trait() + .map_or(false, |id| implements_trait(cx, ty, id, &[])) + { + // Make sure that the push does not involve possibly mutating values + match pushed_item.kind { + ExprKind::Path(ref qpath) => { + match cx.qpath_res(qpath, pushed_item.hir_id) { + // immutable bindings that are initialized with literal or constant + Res::Local(hir_id) => { + if_chain! { + let node = cx.tcx.hir().get(hir_id); + if let Node::Binding(pat) = node; + if let PatKind::Binding(bind_ann, ..) = pat.kind; + if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable); + let parent_node = cx.tcx.hir().get_parent_node(hir_id); + if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node); + if let Some(init) = parent_let_expr.init; + then { + match init.kind { + // immutable bindings that are initialized with literal + ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), + // immutable bindings that are initialized with constant + ExprKind::Path(ref path) => { + if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) { + emit_lint(cx, vec, pushed_item); + } + } + _ => {}, + } + } + } + }, + // constant + Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item), + _ => {}, + } + }, + ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), + _ => {}, + } + } + } + } +} + +// Scans the body of the for loop and determines whether lint should be given +struct SameItemPushVisitor<'a, 'tcx> { + should_lint: bool, + // this field holds the last vec push operation visited, which should be the only push seen + vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>, + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + match &expr.kind { + // Non-determinism may occur ... don't give a lint + ExprKind::Loop(..) | ExprKind::Match(..) => self.should_lint = false, + ExprKind::Block(block, _) => self.visit_block(block), + _ => {}, + } + } + + fn visit_block(&mut self, b: &'tcx Block<'_>) { + for stmt in b.stmts.iter() { + self.visit_stmt(stmt); + } + } + + fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) { + let vec_push_option = get_vec_push(self.cx, s); + if vec_push_option.is_none() { + // Current statement is not a push so visit inside + match &s.kind { + StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(&expr), + _ => {}, + } + } else { + // Current statement is a push ...check whether another + // push had been previously done + if self.vec_push.is_none() { + self.vec_push = vec_push_option; + } else { + // There are multiple pushes ... don't lint + self.should_lint = false; + } + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +// Given some statement, determine if that statement is a push on a Vec. If it is, return +// the Vec being pushed into and the item being pushed +fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + if_chain! { + // Extract method being called + if let StmtKind::Semi(semi_stmt) = &stmt.kind; + if let ExprKind::MethodCall(path, _, args, _) = &semi_stmt.kind; + // Figure out the parameters for the method call + if let Some(self_expr) = args.get(0); + if let Some(pushed_item) = args.get(1); + // Check that the method being called is push() on a Vec + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::vec_type); + if path.ident.name.as_str() == "push"; + then { + return Some((self_expr, pushed_item)) + } + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs b/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs new file mode 100644 index 0000000000..38400c93c9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs @@ -0,0 +1,42 @@ +use super::{get_span_of_entire_for_loop, SINGLE_ELEMENT_LOOP}; +use crate::utils::{indent_of, single_segment_path, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind, Pat, PatKind}; +use rustc_lint::LateContext; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + if_chain! { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg_expr) = arg.kind; + if let PatKind::Binding(.., target, _) = pat.kind; + if let ExprKind::Array([arg_expression]) = arg_expr.kind; + if let ExprKind::Path(ref list_item) = arg_expression.kind; + if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name); + if let ExprKind::Block(ref block, _) = body.kind; + if !block.stmts.is_empty(); + + then { + let for_span = get_span_of_entire_for_loop(expr); + let mut block_str = snippet(cx, block.span, "..").into_owned(); + block_str.remove(0); + block_str.pop(); + + + span_lint_and_sugg( + cx, + SINGLE_ELEMENT_LOOP, + for_span, + "for loop over a single element", + "try", + format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str), + Applicability::MachineApplicable + ) + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/utils.rs b/src/tools/clippy/clippy_lints/src/loops/utils.rs new file mode 100644 index 0000000000..9e38e17719 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/utils.rs @@ -0,0 +1,350 @@ +use crate::utils::{ + get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, is_integer_const, path_to_local, + path_to_local_id, paths, sugg, +}; +use if_chain::if_chain; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Stmt, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::source_map::Span; +use rustc_span::symbol::Symbol; +use std::iter::Iterator; + +#[derive(Debug, PartialEq)] +enum IncrementVisitorVarState { + Initial, // Not examined yet + IncrOnce, // Incremented exactly once, may be a loop counter + DontWarn, +} + +/// Scan a for loop for variables that are incremented exactly once and not used after that. +pub(super) struct IncrementVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, // context reference + states: FxHashMap, // incremented variables + depth: u32, // depth of conditional expressions + done: bool, +} + +impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> { + pub(super) fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { + cx, + states: FxHashMap::default(), + depth: 0, + done: false, + } + } + + pub(super) fn into_results(self) -> impl Iterator { + self.states.into_iter().filter_map(|(id, state)| { + if state == IncrementVisitorVarState::IncrOnce { + Some(id) + } else { + None + } + }) + } +} + +impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.done { + return; + } + + // If node is a variable + if let Some(def_id) = path_to_local(expr) { + if let Some(parent) = get_parent_expr(self.cx, expr) { + let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial); + if *state == IncrementVisitorVarState::IncrOnce { + *state = IncrementVisitorVarState::DontWarn; + return; + } + + match parent.kind { + ExprKind::AssignOp(op, ref lhs, ref rhs) => { + if lhs.hir_id == expr.hir_id { + *state = if op.node == BinOpKind::Add + && is_integer_const(self.cx, rhs, 1) + && *state == IncrementVisitorVarState::Initial + && self.depth == 0 + { + IncrementVisitorVarState::IncrOnce + } else { + // Assigned some other value or assigned multiple times + IncrementVisitorVarState::DontWarn + }; + } + }, + ExprKind::Assign(ref lhs, _, _) if lhs.hir_id == expr.hir_id => { + *state = IncrementVisitorVarState::DontWarn + }, + ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + *state = IncrementVisitorVarState::DontWarn + }, + _ => (), + } + } + + walk_expr(self, expr); + } else if is_loop(expr) || is_conditional(expr) { + self.depth += 1; + walk_expr(self, expr); + self.depth -= 1; + } else if let ExprKind::Continue(_) = expr.kind { + self.done = true; + } else { + walk_expr(self, expr); + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +enum InitializeVisitorState<'hir> { + Initial, // Not examined yet + Declared(Symbol), // Declared but not (yet) initialized + Initialized { + name: Symbol, + initializer: &'hir Expr<'hir>, + }, + DontWarn, +} + +/// Checks whether a variable is initialized at the start of a loop and not modified +/// and used after the loop. +pub(super) struct InitializeVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, // context reference + end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here. + var_id: HirId, + state: InitializeVisitorState<'tcx>, + depth: u32, // depth of conditional expressions + past_loop: bool, +} + +impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> { + pub(super) fn new(cx: &'a LateContext<'tcx>, end_expr: &'tcx Expr<'tcx>, var_id: HirId) -> Self { + Self { + cx, + end_expr, + var_id, + state: InitializeVisitorState::Initial, + depth: 0, + past_loop: false, + } + } + + pub(super) fn get_result(&self) -> Option<(Symbol, &'tcx Expr<'tcx>)> { + if let InitializeVisitorState::Initialized { name, initializer } = self.state { + Some((name, initializer)) + } else { + None + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + // Look for declarations of the variable + if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if local.pat.hir_id == self.var_id; + if let PatKind::Binding(.., ident, _) = local.pat.kind; + then { + self.state = local.init.map_or(InitializeVisitorState::Declared(ident.name), |init| { + InitializeVisitorState::Initialized { + initializer: init, + name: ident.name, + } + }) + } + } + walk_stmt(self, stmt); + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if matches!(self.state, InitializeVisitorState::DontWarn) { + return; + } + if expr.hir_id == self.end_expr.hir_id { + self.past_loop = true; + return; + } + // No need to visit expressions before the variable is + // declared + if matches!(self.state, InitializeVisitorState::Initial) { + return; + } + + // If node is the desired variable, see how it's used + if path_to_local_id(expr, self.var_id) { + if self.past_loop { + self.state = InitializeVisitorState::DontWarn; + return; + } + + if let Some(parent) = get_parent_expr(self.cx, expr) { + match parent.kind { + ExprKind::AssignOp(_, ref lhs, _) if lhs.hir_id == expr.hir_id => { + self.state = InitializeVisitorState::DontWarn; + }, + ExprKind::Assign(ref lhs, ref rhs, _) if lhs.hir_id == expr.hir_id => { + self.state = if_chain! { + if self.depth == 0; + if let InitializeVisitorState::Declared(name) + | InitializeVisitorState::Initialized { name, ..} = self.state; + then { + InitializeVisitorState::Initialized { initializer: rhs, name } + } else { + InitializeVisitorState::DontWarn + } + } + }, + ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + self.state = InitializeVisitorState::DontWarn + }, + _ => (), + } + } + + walk_expr(self, expr); + } else if !self.past_loop && is_loop(expr) { + self.state = InitializeVisitorState::DontWarn; + } else if is_conditional(expr) { + self.depth += 1; + walk_expr(self, expr); + self.depth -= 1; + } else { + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +fn is_loop(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Loop(..)) +} + +fn is_conditional(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..)) +} + +#[derive(PartialEq, Eq)] +pub(super) enum Nesting { + Unknown, // no nesting detected yet + RuledOut, // the iterator is initialized or assigned within scope + LookFurther, // no nesting detected, no further walk required +} + +use self::Nesting::{LookFurther, RuledOut, Unknown}; + +pub(super) struct LoopNestVisitor { + pub(super) hir_id: HirId, + pub(super) iterator: HirId, + pub(super) nesting: Nesting, +} + +impl<'tcx> Visitor<'tcx> for LoopNestVisitor { + type Map = Map<'tcx>; + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + if stmt.hir_id == self.hir_id { + self.nesting = LookFurther; + } else if self.nesting == Unknown { + walk_stmt(self, stmt); + } + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.nesting != Unknown { + return; + } + if expr.hir_id == self.hir_id { + self.nesting = LookFurther; + return; + } + match expr.kind { + ExprKind::Assign(ref path, _, _) | ExprKind::AssignOp(_, ref path, _) => { + if path_to_local_id(path, self.iterator) { + self.nesting = RuledOut; + } + }, + _ => walk_expr(self, expr), + } + } + + fn visit_pat(&mut self, pat: &'tcx Pat<'_>) { + if self.nesting != Unknown { + return; + } + if let PatKind::Binding(_, id, ..) = pat.kind { + if id == self.iterator { + self.nesting = RuledOut; + return; + } + } + walk_pat(self, pat) + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +// this function assumes the given expression is a `for` loop. +pub(super) fn get_span_of_entire_for_loop(expr: &Expr<'_>) -> Span { + // for some reason this is the only way to get the `Span` + // of the entire `for` loop + if let ExprKind::Match(_, arms, _) = &expr.kind { + arms[0].body.span + } else { + unreachable!() + } +} + +/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the +/// actual `Iterator` that the loop uses. +pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String { + let impls_iterator = get_trait_def_id(cx, &paths::ITERATOR).map_or(false, |id| { + implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[]) + }); + if impls_iterator { + format!( + "{}", + sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() + ) + } else { + // (&x).into_iter() ==> x.iter() + // (&mut x).into_iter() ==> x.iter_mut() + match &arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner) + if has_iter_method(cx, cx.typeck_results().expr_ty(&arg_inner)).is_some() => + { + let meth_name = match mutability { + Mutability::Mut => "iter_mut", + Mutability::Not => "iter", + }; + format!( + "{}.{}()", + sugg::Sugg::hir_with_applicability(cx, &arg_inner, "_", applic_ref).maybe_par(), + meth_name, + ) + } + _ => format!( + "{}.into_iter()", + sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() + ), + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/while_immutable_condition.rs b/src/tools/clippy/clippy_lints/src/loops/while_immutable_condition.rs new file mode 100644 index 0000000000..05e0a72256 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/while_immutable_condition.rs @@ -0,0 +1,139 @@ +use super::WHILE_IMMUTABLE_CONDITION; +use crate::consts::constant; +use crate::utils::span_lint_and_then; +use crate::utils::usage::mutated_variables; +use if_chain::if_chain; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{def_id, Expr, ExprKind, HirId, QPath}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use std::iter::Iterator; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { + if constant(cx, cx.typeck_results(), cond).is_some() { + // A pure constant condition (e.g., `while false`) is not linted. + return; + } + + let mut var_visitor = VarCollectorVisitor { + cx, + ids: FxHashSet::default(), + def_ids: FxHashMap::default(), + skip: false, + }; + var_visitor.visit_expr(cond); + if var_visitor.skip { + return; + } + let used_in_condition = &var_visitor.ids; + let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) { + used_in_condition.is_disjoint(&used_mutably) + } else { + return; + }; + let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v); + + let mut has_break_or_return_visitor = HasBreakOrReturnVisitor { + has_break_or_return: false, + }; + has_break_or_return_visitor.visit_expr(expr); + let has_break_or_return = has_break_or_return_visitor.has_break_or_return; + + if no_cond_variable_mutated && !mutable_static_in_cond { + span_lint_and_then( + cx, + WHILE_IMMUTABLE_CONDITION, + cond.span, + "variables in the condition are not mutated in the loop body", + |diag| { + diag.note("this may lead to an infinite or to a never running loop"); + + if has_break_or_return { + diag.note("this loop contains `return`s or `break`s"); + diag.help("rewrite it as `if cond { loop { } }`"); + } + }, + ); + } +} + +struct HasBreakOrReturnVisitor { + has_break_or_return: bool, +} + +impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.has_break_or_return { + return; + } + + match expr.kind { + ExprKind::Ret(_) | ExprKind::Break(_, _) => { + self.has_break_or_return = true; + return; + }, + _ => {}, + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Collects the set of variables in an expression +/// Stops analysis if a function call is found +/// Note: In some cases such as `self`, there are no mutable annotation, +/// All variables definition IDs are collected +struct VarCollectorVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + ids: FxHashSet, + def_ids: FxHashMap, + skip: bool, +} + +impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { + fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Path(ref qpath) = ex.kind; + if let QPath::Resolved(None, _) = *qpath; + let res = self.cx.qpath_res(qpath, ex.hir_id); + then { + match res { + Res::Local(hir_id) => { + self.ids.insert(hir_id); + }, + Res::Def(DefKind::Static, def_id) => { + let mutable = self.cx.tcx.is_mutable_static(def_id); + self.def_ids.insert(def_id, mutable); + }, + _ => {}, + } + } + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { + match ex.kind { + ExprKind::Path(_) => self.insert_def_id(ex), + // If there is any function/method call… we just stop analysis + ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true, + + _ => walk_expr(self, ex), + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/while_let_loop.rs b/src/tools/clippy/clippy_lints/src/loops/while_let_loop.rs new file mode 100644 index 0000000000..65d8f2f111 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/while_let_loop.rs @@ -0,0 +1,87 @@ +use super::WHILE_LET_LOOP; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind, MatchSource, StmtKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { + // extract the expression from the first statement (if any) in a block + let inner_stmt_expr = extract_expr_from_first_stmt(loop_block); + // or extract the first expression (if any) from the block + if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(loop_block)) { + if let ExprKind::Match(ref matchexpr, ref arms, ref source) = inner.kind { + // ensure "if let" compatible match structure + match *source { + MatchSource::Normal | MatchSource::IfLetDesugar { .. } => { + if arms.len() == 2 + && arms[0].guard.is_none() + && arms[1].guard.is_none() + && is_simple_break_expr(&arms[1].body) + { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + // NOTE: we used to build a body here instead of using + // ellipsis, this was removed because: + // 1) it was ugly with big bodies; + // 2) it was not indented properly; + // 3) it wasn’t very smart (see #675). + let mut applicability = Applicability::HasPlaceholders; + span_lint_and_sugg( + cx, + WHILE_LET_LOOP, + expr.span, + "this loop could be written as a `while let` loop", + "try", + format!( + "while let {} = {} {{ .. }}", + snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability), + snippet_with_applicability(cx, matchexpr.span, "..", &mut applicability), + ), + applicability, + ); + } + }, + _ => (), + } + } + } +} + +/// If a block begins with a statement (possibly a `let` binding) and has an +/// expression, return it. +fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if block.stmts.is_empty() { + return None; + } + if let StmtKind::Local(ref local) = block.stmts[0].kind { + local.init //.map(|expr| expr) + } else { + None + } +} + +/// If a block begins with an expression (with or without semicolon), return it. +fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match block.expr { + Some(ref expr) if block.stmts.is_empty() => Some(expr), + None if !block.stmts.is_empty() => match block.stmts[0].kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => Some(expr), + StmtKind::Local(..) | StmtKind::Item(..) => None, + }, + _ => None, + } +} + +/// Returns `true` if expr contains a single break expr without destination label +/// and +/// passed expression. The expression may be within a block. +fn is_simple_break_expr(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true, + ExprKind::Block(ref b, _) => extract_first_expr(b).map_or(false, |subexpr| is_simple_break_expr(subexpr)), + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs b/src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs new file mode 100644 index 0000000000..e5a47694fa --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs @@ -0,0 +1,171 @@ +use super::utils::{LoopNestVisitor, Nesting}; +use super::WHILE_LET_ON_ITERATOR; +use crate::utils::usage::mutated_variables; +use crate::utils::{ + get_enclosing_block, get_trait_def_id, implements_trait, is_refutable, last_path_segment, match_trait_method, + path_to_local, path_to_local_id, paths, snippet_with_applicability, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; + +use rustc_span::symbol::sym; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind { + let pat = &arms[0].pat.kind; + if let ( + &PatKind::TupleStruct(ref qpath, ref pat_args, _), + &ExprKind::MethodCall(ref method_path, _, ref method_args, _), + ) = (pat, &match_expr.kind) + { + let iter_expr = &method_args[0]; + + // Don't lint when the iterator is recreated on every iteration + if_chain! { + if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind; + if let Some(iter_def_id) = get_trait_def_id(cx, &paths::ITERATOR); + if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]); + then { + return; + } + } + + let lhs_constructor = last_path_segment(qpath); + if method_path.ident.name == sym::next + && match_trait_method(cx, match_expr, &paths::ITERATOR) + && lhs_constructor.ident.name == sym::Some + && (pat_args.is_empty() + || !is_refutable(cx, &pat_args[0]) + && !is_used_inside(cx, iter_expr, &arms[0].body) + && !is_iterator_used_after_while_let(cx, iter_expr) + && !is_nested(cx, expr, &method_args[0])) + { + let mut applicability = Applicability::MachineApplicable; + let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability); + let loop_var = if pat_args.is_empty() { + "_".to_string() + } else { + snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned() + }; + span_lint_and_sugg( + cx, + WHILE_LET_ON_ITERATOR, + expr.span.with_hi(match_expr.span.hi()), + "this loop could be written as a `for` loop", + "try", + format!("for {} in {}", loop_var, iterator), + applicability, + ); + } + } + } +} + +fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { + let def_id = match path_to_local(expr) { + Some(id) => id, + None => return false, + }; + if let Some(used_mutably) = mutated_variables(container, cx) { + if used_mutably.contains(&def_id) { + return true; + } + } + false +} + +fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool { + let def_id = match path_to_local(iter_expr) { + Some(id) => id, + None => return false, + }; + let mut visitor = VarUsedAfterLoopVisitor { + def_id, + iter_expr_id: iter_expr.hir_id, + past_while_let: false, + var_used_after_while_let: false, + }; + if let Some(enclosing_block) = get_enclosing_block(cx, def_id) { + walk_block(&mut visitor, enclosing_block); + } + visitor.var_used_after_while_let +} + +fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { + if_chain! { + if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id); + let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id); + if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node); + then { + return is_loop_nested(cx, loop_expr, iter_expr) + } + } + false +} + +fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { + let mut id = loop_expr.hir_id; + let iter_id = if let Some(id) = path_to_local(iter_expr) { + id + } else { + return true; + }; + loop { + let parent = cx.tcx.hir().get_parent_node(id); + if parent == id { + return false; + } + match cx.tcx.hir().find(parent) { + Some(Node::Expr(expr)) => { + if let ExprKind::Loop(..) = expr.kind { + return true; + }; + }, + Some(Node::Block(block)) => { + let mut block_visitor = LoopNestVisitor { + hir_id: id, + iterator: iter_id, + nesting: Nesting::Unknown, + }; + walk_block(&mut block_visitor, block); + if block_visitor.nesting == Nesting::RuledOut { + return false; + } + }, + Some(Node::Stmt(_)) => (), + _ => { + return false; + }, + } + id = parent; + } +} + +struct VarUsedAfterLoopVisitor { + def_id: HirId, + iter_expr_id: HirId, + past_while_let: bool, + var_used_after_while_let: bool, +} + +impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.past_while_let { + if path_to_local_id(expr, self.def_id) { + self.var_used_after_while_let = true; + } + } else if self.iter_expr_id == expr.hir_id { + self.past_while_let = true; + } + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/macro_use.rs b/src/tools/clippy/clippy_lints/src/macro_use.rs new file mode 100644 index 0000000000..6d9c78393c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/macro_use.rs @@ -0,0 +1,232 @@ +use crate::utils::{in_macro, snippet, span_lint_and_sugg}; +use hir::def::{DefKind, Res}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{edition::Edition, Span}; + +declare_clippy_lint! { + /// **What it does:** Checks for `#[macro_use] use...`. + /// + /// **Why is this bad?** Since the Rust 2018 edition you can import + /// macro's directly, this is considered idiomatic. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// #[macro_use] + /// use some_macro; + /// ``` + pub MACRO_USE_IMPORTS, + pedantic, + "#[macro_use] is no longer needed" +} + +const BRACKETS: &[char] = &['<', '>']; + +#[derive(Clone, Debug, PartialEq, Eq)] +struct PathAndSpan { + path: String, + span: Span, +} + +/// `MacroRefData` includes the name of the macro +/// and the path from `SourceMap::span_to_filename`. +#[derive(Debug, Clone)] +pub struct MacroRefData { + name: String, + path: String, +} + +impl MacroRefData { + pub fn new(name: String, callee: Span, cx: &LateContext<'_>) -> Self { + let mut path = cx.sess().source_map().span_to_filename(callee).to_string(); + + // std lib paths are <::std::module::file type> + // so remove brackets, space and type. + if path.contains('<') { + path = path.replace(BRACKETS, ""); + } + if path.contains(' ') { + path = path.split(' ').next().unwrap().to_string(); + } + Self { name, path } + } +} + +#[derive(Default)] +#[allow(clippy::module_name_repetitions)] +pub struct MacroUseImports { + /// the actual import path used and the span of the attribute above it. + imports: Vec<(String, Span)>, + /// the span of the macro reference, kept to ensure only one reference is used per macro call. + collected: FxHashSet, + mac_refs: Vec, +} + +impl_lint_pass!(MacroUseImports => [MACRO_USE_IMPORTS]); + +impl MacroUseImports { + fn push_unique_macro(&mut self, cx: &LateContext<'_>, span: Span) { + let call_site = span.source_callsite(); + let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_"); + if let Some(callee) = span.source_callee() { + if !self.collected.contains(&call_site) { + let name = if name.contains("::") { + name.split("::").last().unwrap().to_string() + } else { + name.to_string() + }; + + self.mac_refs.push(MacroRefData::new(name, callee.def_site, cx)); + self.collected.insert(call_site); + } + } + } + + fn push_unique_macro_pat_ty(&mut self, cx: &LateContext<'_>, span: Span) { + let call_site = span.source_callsite(); + let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_"); + if let Some(callee) = span.source_callee() { + if !self.collected.contains(&call_site) { + self.mac_refs + .push(MacroRefData::new(name.to_string(), callee.def_site, cx)); + self.collected.insert(call_site); + } + } + } +} + +impl<'tcx> LateLintPass<'tcx> for MacroUseImports { + fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + if_chain! { + if cx.sess().opts.edition >= Edition::Edition2018; + if let hir::ItemKind::Use(path, _kind) = &item.kind; + let attrs = cx.tcx.hir().attrs(item.hir_id()); + if let Some(mac_attr) = attrs + .iter() + .find(|attr| attr.ident().map(|s| s.to_string()) == Some("macro_use".to_string())); + if let Res::Def(DefKind::Mod, id) = path.res; + then { + for kid in cx.tcx.item_children(id).iter() { + if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res { + let span = mac_attr.span; + let def_path = cx.tcx.def_path_str(mac_id); + self.imports.push((def_path, span)); + } + } + } else { + if in_macro(item.span) { + self.push_unique_macro_pat_ty(cx, item.span); + } + } + } + } + fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) { + if in_macro(attr.span) { + self.push_unique_macro(cx, attr.span); + } + } + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { + if in_macro(expr.span) { + self.push_unique_macro(cx, expr.span); + } + } + fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) { + if in_macro(stmt.span) { + self.push_unique_macro(cx, stmt.span); + } + } + fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) { + if in_macro(pat.span) { + self.push_unique_macro_pat_ty(cx, pat.span); + } + } + fn check_ty(&mut self, cx: &LateContext<'_>, ty: &hir::Ty<'_>) { + if in_macro(ty.span) { + self.push_unique_macro_pat_ty(cx, ty.span); + } + } + #[allow(clippy::too_many_lines)] + fn check_crate_post(&mut self, cx: &LateContext<'_>, _krate: &hir::Crate<'_>) { + let mut used = FxHashMap::default(); + let mut check_dup = vec![]; + for (import, span) in &self.imports { + let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name)); + + if let Some(idx) = found_idx { + self.mac_refs.remove(idx); + let seg = import.split("::").collect::>(); + + match seg.as_slice() { + // an empty path is impossible + // a path should always consist of 2 or more segments + [] | [_] => return, + [root, item] => { + if !check_dup.contains(&(*item).to_string()) { + used.entry(((*root).to_string(), span)) + .or_insert_with(Vec::new) + .push((*item).to_string()); + check_dup.push((*item).to_string()); + } + }, + [root, rest @ ..] => { + if rest.iter().all(|item| !check_dup.contains(&(*item).to_string())) { + let filtered = rest + .iter() + .filter_map(|item| { + if check_dup.contains(&(*item).to_string()) { + None + } else { + Some((*item).to_string()) + } + }) + .collect::>(); + used.entry(((*root).to_string(), span)) + .or_insert_with(Vec::new) + .push(filtered.join("::")); + check_dup.extend(filtered); + } else { + let rest = rest.to_vec(); + used.entry(((*root).to_string(), span)) + .or_insert_with(Vec::new) + .push(rest.join("::")); + check_dup.extend(rest.iter().map(ToString::to_string)); + } + }, + } + } + } + + let mut suggestions = vec![]; + for ((root, span), path) in used { + if path.len() == 1 { + suggestions.push((span, format!("{}::{}", root, path[0]))) + } else { + suggestions.push((span, format!("{}::{{{}}}", root, path.join(", ")))) + } + } + + // If mac_refs is not empty we have encountered an import we could not handle + // such as `std::prelude::v1::foo` or some other macro that expands to an import. + if self.mac_refs.is_empty() { + for (span, import) in suggestions { + let help = format!("use {};", import); + span_lint_and_sugg( + cx, + MACRO_USE_IMPORTS, + *span, + "`macro_use` attributes are no longer needed in the Rust 2018 edition", + "remove the attribute and import the macro directly, try", + help, + Applicability::MaybeIncorrect, + ) + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/main_recursion.rs b/src/tools/clippy/clippy_lints/src/main_recursion.rs new file mode 100644 index 0000000000..1b274c79d3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/main_recursion.rs @@ -0,0 +1,61 @@ +use rustc_hir::{Crate, Expr, ExprKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +use crate::utils::{is_entrypoint_fn, is_no_std_crate, snippet, span_lint_and_help}; +use if_chain::if_chain; + +declare_clippy_lint! { + /// **What it does:** Checks for recursion using the entrypoint. + /// + /// **Why is this bad?** Apart from special setups (which we could detect following attributes like #![no_std]), + /// recursing into main() seems like an unintuitive antipattern we should be able to detect. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// fn main() { + /// main(); + /// } + /// ``` + pub MAIN_RECURSION, + style, + "recursion using the entrypoint" +} + +#[derive(Default)] +pub struct MainRecursion { + has_no_std_attr: bool, +} + +impl_lint_pass!(MainRecursion => [MAIN_RECURSION]); + +impl LateLintPass<'_> for MainRecursion { + fn check_crate(&mut self, cx: &LateContext<'_>, _: &Crate<'_>) { + self.has_no_std_attr = is_no_std_crate(cx); + } + + fn check_expr_post(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if self.has_no_std_attr { + return; + } + + if_chain! { + if let ExprKind::Call(func, _) = &expr.kind; + if let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind; + if let Some(def_id) = path.res.opt_def_id(); + if is_entrypoint_fn(cx, def_id); + then { + span_lint_and_help( + cx, + MAIN_RECURSION, + func.span, + &format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")), + None, + "consider using another function for this recursion" + ) + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/manual_async_fn.rs b/src/tools/clippy/clippy_lints/src/manual_async_fn.rs new file mode 100644 index 0000000000..2e2e693592 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_async_fn.rs @@ -0,0 +1,200 @@ +use crate::utils::paths::FUTURE_FROM_GENERATOR; +use crate::utils::{match_function_call, position_before_rarrow, snippet_block, snippet_opt, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{ + AsyncGeneratorKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, HirId, + IsAsync, ItemKind, LifetimeName, TraitRef, Ty, TyKind, TypeBindingKind, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{sym, Span}; + +declare_clippy_lint! { + /// **What it does:** It checks for manual implementations of `async` functions. + /// + /// **Why is this bad?** It's more idiomatic to use the dedicated syntax. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// use std::future::Future; + /// + /// fn foo() -> impl Future { async { 42 } } + /// ``` + /// Use instead: + /// ```rust + /// async fn foo() -> i32 { 42 } + /// ``` + pub MANUAL_ASYNC_FN, + style, + "manual implementations of `async` functions can be simplified using the dedicated syntax" +} + +declare_lint_pass!(ManualAsyncFn => [MANUAL_ASYNC_FN]); + +impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + span: Span, + _: HirId, + ) { + if_chain! { + if let Some(header) = kind.header(); + if let IsAsync::NotAsync = header.asyncness; + // Check that this function returns `impl Future` + if let FnRetTy::Return(ret_ty) = decl.output; + if let Some((trait_ref, output_lifetimes)) = future_trait_ref(cx, ret_ty); + if let Some(output) = future_output_ty(trait_ref); + if captures_all_lifetimes(decl.inputs, &output_lifetimes); + // Check that the body of the function consists of one async block + if let ExprKind::Block(block, _) = body.value.kind; + if block.stmts.is_empty(); + if let Some(closure_body) = desugared_async_block(cx, block); + then { + let header_span = span.with_hi(ret_ty.span.hi()); + + span_lint_and_then( + cx, + MANUAL_ASYNC_FN, + header_span, + "this function can be simplified using the `async fn` syntax", + |diag| { + if_chain! { + if let Some(header_snip) = snippet_opt(cx, header_span); + if let Some(ret_pos) = position_before_rarrow(&header_snip); + if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output); + then { + let help = format!("make the function `async` and {}", ret_sugg); + diag.span_suggestion( + header_span, + &help, + format!("async {}{}", &header_snip[..ret_pos], ret_snip), + Applicability::MachineApplicable + ); + + let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span)); + diag.span_suggestion( + block.span, + "move the body of the async block to the enclosing function", + body_snip.to_string(), + Applicability::MachineApplicable + ); + } + } + }, + ); + } + } + } +} + +fn future_trait_ref<'tcx>( + cx: &LateContext<'tcx>, + ty: &'tcx Ty<'tcx>, +) -> Option<(&'tcx TraitRef<'tcx>, Vec)> { + if_chain! { + if let TyKind::OpaqueDef(item_id, bounds) = ty.kind; + let item = cx.tcx.hir().item(item_id); + if let ItemKind::OpaqueTy(opaque) = &item.kind; + if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| { + if let GenericBound::Trait(poly, _) = bound { + Some(&poly.trait_ref) + } else { + None + } + }); + if trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait(); + then { + let output_lifetimes = bounds + .iter() + .filter_map(|bound| { + if let GenericArg::Lifetime(lt) = bound { + Some(lt.name) + } else { + None + } + }) + .collect(); + + return Some((trait_ref, output_lifetimes)); + } + } + + None +} + +fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> { + if_chain! { + if let Some(segment) = trait_ref.path.segments.last(); + if let Some(args) = segment.args; + if args.bindings.len() == 1; + let binding = &args.bindings[0]; + if binding.ident.name == sym::Output; + if let TypeBindingKind::Equality{ty: output} = binding.kind; + then { + return Some(output) + } + } + + None +} + +fn captures_all_lifetimes(inputs: &[Ty<'_>], output_lifetimes: &[LifetimeName]) -> bool { + let input_lifetimes: Vec = inputs + .iter() + .filter_map(|ty| { + if let TyKind::Rptr(lt, _) = ty.kind { + Some(lt.name) + } else { + None + } + }) + .collect(); + + // The lint should trigger in one of these cases: + // - There are no input lifetimes + // - There's only one output lifetime bound using `+ '_` + // - All input lifetimes are explicitly bound to the output + input_lifetimes.is_empty() + || (output_lifetimes.len() == 1 && matches!(output_lifetimes[0], LifetimeName::Underscore)) + || input_lifetimes + .iter() + .all(|in_lt| output_lifetimes.iter().any(|out_lt| in_lt == out_lt)) +} + +fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> { + if_chain! { + if let Some(block_expr) = block.expr; + if let Some(args) = match_function_call(cx, block_expr, &FUTURE_FROM_GENERATOR); + if args.len() == 1; + if let Expr{kind: ExprKind::Closure(_, _, body_id, ..), ..} = args[0]; + let closure_body = cx.tcx.hir().body(body_id); + if let Some(GeneratorKind::Async(AsyncGeneratorKind::Block)) = closure_body.generator_kind; + then { + return Some(closure_body); + } + } + + None +} + +fn suggested_ret(cx: &LateContext<'_>, output: &Ty<'_>) -> Option<(&'static str, String)> { + match output.kind { + TyKind::Tup(tys) if tys.is_empty() => { + let sugg = "remove the return type"; + Some((sugg, "".into())) + }, + _ => { + let sugg = "return the output of the future directly"; + snippet_opt(cx, output.span).map(|snip| (sugg, format!(" -> {}", snip))) + }, + } +} diff --git a/src/tools/clippy/clippy_lints/src/manual_map.rs b/src/tools/clippy/clippy_lints/src/manual_map.rs new file mode 100644 index 0000000000..ac1d51e199 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_map.rs @@ -0,0 +1,338 @@ +use crate::{ + map_unit_fn::OPTION_MAP_UNIT_FN, + matches::MATCH_AS_REF, + utils::{ + can_partially_move_ty, is_allowed, is_type_diagnostic_item, match_def_path, match_var, paths, + peel_hir_expr_refs, peel_mid_ty_refs_is_mutable, snippet_with_applicability, snippet_with_context, + span_lint_and_sugg, + }, +}; +use rustc_ast::util::parser::PREC_POSTFIX; +use rustc_errors::Applicability; +use rustc_hir::{ + def::Res, + intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor}, + Arm, BindingAnnotation, Block, Expr, ExprKind, Mutability, Pat, PatKind, Path, QPath, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{ + symbol::{sym, Ident}, + SyntaxContext, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for usages of `match` which could be implemented using `map` + /// + /// **Why is this bad?** Using the `map` method is clearer and more concise. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// match Some(0) { + /// Some(x) => Some(x + 1), + /// None => None, + /// }; + /// ``` + /// Use instead: + /// ```rust + /// Some(0).map(|x| x + 1); + /// ``` + pub MANUAL_MAP, + style, + "reimplementation of `map`" +} + +declare_lint_pass!(ManualMap => [MANUAL_MAP]); + +impl LateLintPass<'_> for ManualMap { + #[allow(clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if let ExprKind::Match(scrutinee, [arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }], _) = + expr.kind + { + let (scrutinee_ty, ty_ref_count, ty_mutability) = + peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee)); + if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type) + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type)) + { + return; + } + + let expr_ctxt = expr.span.ctxt(); + let (some_expr, some_pat, pat_ref_count, is_wild_none) = match ( + try_parse_pattern(cx, arm1.pat, expr_ctxt), + try_parse_pattern(cx, arm2.pat, expr_ctxt), + ) { + (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) + if is_none_expr(cx, arm1.body) => + { + (arm2.body, pattern, ref_count, true) + }, + (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) + if is_none_expr(cx, arm1.body) => + { + (arm2.body, pattern, ref_count, false) + }, + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) + if is_none_expr(cx, arm2.body) => + { + (arm1.body, pattern, ref_count, true) + }, + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) + if is_none_expr(cx, arm2.body) => + { + (arm1.body, pattern, ref_count, false) + }, + _ => return, + }; + + // Top level or patterns aren't allowed in closures. + if matches!(some_pat.kind, PatKind::Or(_)) { + return; + } + + let some_expr = match get_some_expr(cx, some_expr, expr_ctxt) { + Some(expr) => expr, + None => return, + }; + + if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit + && !is_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id) + { + return; + } + + if !can_move_expr_to_closure(cx, some_expr) { + return; + } + + // Determine which binding mode to use. + let explicit_ref = some_pat.contains_explicit_ref_binding(); + let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability)); + + let as_ref_str = match binding_ref { + Some(Mutability::Mut) => ".as_mut()", + Some(Mutability::Not) => ".as_ref()", + None => "", + }; + + let mut app = Applicability::MachineApplicable; + + // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or + // it's being passed by value. + let scrutinee = peel_hir_expr_refs(scrutinee).0; + let scrutinee_str = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app); + let scrutinee_str = + if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX { + format!("({})", scrutinee_str) + } else { + scrutinee_str.into() + }; + + let body_str = if let PatKind::Binding(annotation, _, some_binding, None) = some_pat.kind { + match can_pass_as_func(cx, some_binding, some_expr) { + Some(func) if func.span.ctxt() == some_expr.span.ctxt() => { + snippet_with_applicability(cx, func.span, "..", &mut app).into_owned() + }, + _ => { + if match_var(some_expr, some_binding.name) + && !is_allowed(cx, MATCH_AS_REF, expr.hir_id) + && binding_ref.is_some() + { + return; + } + + // `ref` and `ref mut` annotations were handled earlier. + let annotation = if matches!(annotation, BindingAnnotation::Mutable) { + "mut " + } else { + "" + }; + format!( + "|{}{}| {}", + annotation, + some_binding, + snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app) + ) + }, + } + } else if !is_wild_none && explicit_ref.is_none() { + // TODO: handle explicit reference annotations. + format!( + "|{}| {}", + snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app), + snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app) + ) + } else { + // Refutable bindings and mixed reference annotations can't be handled by `map`. + return; + }; + + span_lint_and_sugg( + cx, + MANUAL_MAP, + expr.span, + "manual implementation of `Option::map`", + "try this", + format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str), + app, + ); + } + } +} + +// Checks if the expression can be moved into a closure as is. +fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + make_closure: bool, + } + impl Visitor<'tcx> for V<'_, 'tcx> { + type Map = ErasedMap<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + match e.kind { + ExprKind::Break(..) + | ExprKind::Continue(_) + | ExprKind::Ret(_) + | ExprKind::Yield(..) + | ExprKind::InlineAsm(_) + | ExprKind::LlvmInlineAsm(_) => { + self.make_closure = false; + }, + // Accessing a field of a local value can only be done if the type isn't + // partially moved. + ExprKind::Field(base_expr, _) + if matches!( + base_expr.kind, + ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. })) + ) && can_partially_move_ty(self.cx, self.cx.typeck_results().expr_ty(base_expr)) => + { + // TODO: check if the local has been partially moved. Assume it has for now. + self.make_closure = false; + return; + } + _ => (), + }; + walk_expr(self, e); + } + } + + let mut v = V { cx, make_closure: true }; + v.visit_expr(expr); + v.make_closure +} + +// Checks whether the expression could be passed as a function, or whether a closure is needed. +// Returns the function to be passed to `map` if it exists. +fn can_pass_as_func(cx: &LateContext<'tcx>, binding: Ident, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + match expr.kind { + ExprKind::Call(func, [arg]) + if match_var(arg, binding.name) && cx.typeck_results().expr_adjustments(arg).is_empty() => + { + Some(func) + }, + _ => None, + } +} + +enum OptionPat<'a> { + Wild, + None, + Some { + // The pattern contained in the `Some` tuple. + pattern: &'a Pat<'a>, + // The number of references before the `Some` tuple. + // e.g. `&&Some(_)` has a ref count of 2. + ref_count: usize, + }, +} + +// Try to parse into a recognized `Option` pattern. +// i.e. `_`, `None`, `Some(..)`, or a reference to any of those. +fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option> { + fn f(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ref_count: usize, ctxt: SyntaxContext) -> Option> { + match pat.kind { + PatKind::Wild => Some(OptionPat::Wild), + PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt), + PatKind::Path(QPath::Resolved(None, path)) + if path + .res + .opt_def_id() + .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)) => + { + Some(OptionPat::None) + }, + PatKind::TupleStruct(QPath::Resolved(None, path), [pattern], _) + if path + .res + .opt_def_id() + .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_SOME)) + && pat.span.ctxt() == ctxt => + { + Some(OptionPat::Some { pattern, ref_count }) + }, + _ => None, + } + } + f(cx, pat, 0, ctxt) +} + +// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression. +fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ctxt: SyntaxContext) -> Option<&'tcx Expr<'tcx>> { + // TODO: Allow more complex expressions. + match expr.kind { + ExprKind::Call( + Expr { + kind: ExprKind::Path(QPath::Resolved(None, path)), + .. + }, + [arg], + ) if ctxt == expr.span.ctxt() => { + if match_def_path(cx, path.res.opt_def_id()?, &paths::OPTION_SOME) { + Some(arg) + } else { + None + } + }, + ExprKind::Block( + Block { + stmts: [], + expr: Some(expr), + .. + }, + _, + ) => get_some_expr(cx, expr, ctxt), + _ => None, + } +} + +// Checks for the `None` value. +fn is_none_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + match expr.kind { + ExprKind::Path(QPath::Resolved(None, path)) => path + .res + .opt_def_id() + .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)), + ExprKind::Block( + Block { + stmts: [], + expr: Some(expr), + .. + }, + _, + ) => is_none_expr(cx, expr), + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs new file mode 100644 index 0000000000..7e6d4d3a21 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs @@ -0,0 +1,194 @@ +use crate::utils::{meets_msrv, snippet_opt, span_lint_and_then}; +use if_chain::if_chain; +use rustc_ast::ast::{Attribute, Item, ItemKind, FieldDef, Variant, VariantData, VisibilityKind}; +use rustc_attr as attr; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{sym, Span}; + +const MANUAL_NON_EXHAUSTIVE_MSRV: RustcVersion = RustcVersion::new(1, 40, 0); + +declare_clippy_lint! { + /// **What it does:** Checks for manual implementations of the non-exhaustive pattern. + /// + /// **Why is this bad?** Using the #[non_exhaustive] attribute expresses better the intent + /// and allows possible optimizations when applied to enums. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// struct S { + /// pub a: i32, + /// pub b: i32, + /// _c: (), + /// } + /// + /// enum E { + /// A, + /// B, + /// #[doc(hidden)] + /// _C, + /// } + /// + /// struct T(pub i32, pub i32, ()); + /// ``` + /// Use instead: + /// ```rust + /// #[non_exhaustive] + /// struct S { + /// pub a: i32, + /// pub b: i32, + /// } + /// + /// #[non_exhaustive] + /// enum E { + /// A, + /// B, + /// } + /// + /// #[non_exhaustive] + /// struct T(pub i32, pub i32); + /// ``` + pub MANUAL_NON_EXHAUSTIVE, + style, + "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]" +} + +#[derive(Clone)] +pub struct ManualNonExhaustive { + msrv: Option, +} + +impl ManualNonExhaustive { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]); + +impl EarlyLintPass for ManualNonExhaustive { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if !meets_msrv(self.msrv.as_ref(), &MANUAL_NON_EXHAUSTIVE_MSRV) { + return; + } + + match &item.kind { + ItemKind::Enum(def, _) => { + check_manual_non_exhaustive_enum(cx, item, &def.variants); + }, + ItemKind::Struct(variant_data, _) => { + if let VariantData::Unit(..) = variant_data { + return; + } + + check_manual_non_exhaustive_struct(cx, item, variant_data); + }, + _ => {}, + } + } + + extract_msrv_attr!(EarlyContext); +} + +fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants: &[Variant]) { + fn is_non_exhaustive_marker(variant: &Variant) -> bool { + matches!(variant.data, VariantData::Unit(_)) + && variant.ident.as_str().starts_with('_') + && variant.attrs.iter().any(|a| is_doc_hidden(a)) + } + + fn is_doc_hidden(attr: &Attribute) -> bool { + attr.has_name(sym::doc) + && match attr.meta_item_list() { + Some(l) => attr::list_contains_name(&l, sym::hidden), + None => false, + } + } + + if_chain! { + let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v)); + if let Some(marker) = markers.next(); + if markers.count() == 0 && variants.len() > 1; + then { + span_lint_and_then( + cx, + MANUAL_NON_EXHAUSTIVE, + item.span, + "this seems like a manual implementation of the non-exhaustive pattern", + |diag| { + if_chain! { + if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive)); + let header_span = cx.sess.source_map().span_until_char(item.span, '{'); + if let Some(snippet) = snippet_opt(cx, header_span); + then { + diag.span_suggestion( + header_span, + "add the attribute", + format!("#[non_exhaustive] {}", snippet), + Applicability::Unspecified, + ); + } + } + diag.span_help(marker.span, "remove this variant"); + }); + } + } +} + +fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) { + fn is_private(field: &FieldDef) -> bool { + matches!(field.vis.kind, VisibilityKind::Inherited) + } + + fn is_non_exhaustive_marker(field: &FieldDef) -> bool { + is_private(field) && field.ty.kind.is_unit() && field.ident.map_or(true, |n| n.as_str().starts_with('_')) + } + + fn find_header_span(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) -> Span { + let delimiter = match data { + VariantData::Struct(..) => '{', + VariantData::Tuple(..) => '(', + VariantData::Unit(_) => unreachable!("`VariantData::Unit` is already handled above"), + }; + + cx.sess.source_map().span_until_char(item.span, delimiter) + } + + let fields = data.fields(); + let private_fields = fields.iter().filter(|f| is_private(f)).count(); + let public_fields = fields.iter().filter(|f| f.vis.kind.is_pub()).count(); + + if_chain! { + if private_fields == 1 && public_fields >= 1 && public_fields == fields.len() - 1; + if let Some(marker) = fields.iter().find(|f| is_non_exhaustive_marker(f)); + then { + span_lint_and_then( + cx, + MANUAL_NON_EXHAUSTIVE, + item.span, + "this seems like a manual implementation of the non-exhaustive pattern", + |diag| { + if_chain! { + if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive)); + let header_span = find_header_span(cx, item, data); + if let Some(snippet) = snippet_opt(cx, header_span); + then { + diag.span_suggestion( + header_span, + "add the attribute", + format!("#[non_exhaustive] {}", snippet), + Applicability::Unspecified, + ); + } + } + diag.span_help(marker.span, "remove this field"); + }); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/manual_ok_or.rs b/src/tools/clippy/clippy_lints/src/manual_ok_or.rs new file mode 100644 index 0000000000..efb05b8ffd --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_ok_or.rs @@ -0,0 +1,96 @@ +use crate::utils::{ + indent_of, is_type_diagnostic_item, match_qpath, path_to_local_id, paths, reindent_multiline, snippet_opt, + span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, PatKind}; +use rustc_lint::LintContext; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; + +declare_clippy_lint! { + /// **What it does:** + /// Finds patterns that reimplement `Option::ok_or`. + /// + /// **Why is this bad?** + /// Concise code helps focusing on behavior instead of boilerplate. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + /// let foo: Option = None; + /// foo.map_or(Err("error"), |v| Ok(v)); + /// ``` + /// + /// Use instead: + /// ```rust + /// let foo: Option = None; + /// foo.ok_or("error"); + /// ``` + pub MANUAL_OK_OR, + pedantic, + "finds patterns that can be encoded more concisely with `Option::ok_or`" +} + +declare_lint_pass!(ManualOkOr => [MANUAL_OK_OR]); + +impl LateLintPass<'_> for ManualOkOr { + fn check_expr(&mut self, cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>) { + if in_external_macro(cx.sess(), scrutinee.span) { + return; + } + + if_chain! { + if let ExprKind::MethodCall(method_segment, _, args, _) = scrutinee.kind; + if method_segment.ident.name == sym!(map_or); + if args.len() == 3; + let method_receiver = &args[0]; + let ty = cx.typeck_results().expr_ty(method_receiver); + if is_type_diagnostic_item(cx, ty, sym::option_type); + let or_expr = &args[1]; + if is_ok_wrapping(cx, &args[2]); + if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind; + if match_qpath(err_path, &paths::RESULT_ERR); + if let Some(method_receiver_snippet) = snippet_opt(cx, method_receiver.span); + if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span); + if let Some(indent) = indent_of(cx, scrutinee.span); + then { + let reindented_err_arg_snippet = + reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4)); + span_lint_and_sugg( + cx, + MANUAL_OK_OR, + scrutinee.span, + "this pattern reimplements `Option::ok_or`", + "replace with", + format!( + "{}.ok_or({})", + method_receiver_snippet, + reindented_err_arg_snippet + ), + Applicability::MachineApplicable, + ); + } + } + } +} + +fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool { + if let ExprKind::Path(ref qpath) = map_expr.kind { + if match_qpath(qpath, &paths::RESULT_OK) { + return true; + } + } + if_chain! { + if let ExprKind::Closure(_, _, body_id, ..) = map_expr.kind; + let body = cx.tcx.hir().body(body_id); + if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind; + if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind; + if match_qpath(ok_path, &paths::RESULT_OK); + then { path_to_local_id(ok_arg, param_id) } else { false } + } +} diff --git a/src/tools/clippy/clippy_lints/src/manual_strip.rs b/src/tools/clippy/clippy_lints/src/manual_strip.rs new file mode 100644 index 0000000000..42a92104a4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_strip.rs @@ -0,0 +1,264 @@ +use crate::consts::{constant, Constant}; +use crate::utils::usage::mutated_variables; +use crate::utils::{ + eq_expr_value, higher, match_def_path, meets_msrv, multispan_sugg, paths, snippet, span_lint_and_then, +}; + +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_hir::def::Res; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::BinOpKind; +use rustc_hir::{BorrowKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::ty; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Spanned; +use rustc_span::Span; + +const MANUAL_STRIP_MSRV: RustcVersion = RustcVersion::new(1, 45, 0); + +declare_clippy_lint! { + /// **What it does:** + /// Suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing using + /// the pattern's length. + /// + /// **Why is this bad?** + /// Using `str:strip_{prefix,suffix}` is safer and may have better performance as there is no + /// slicing which may panic and the compiler does not need to insert this panic code. It is + /// also sometimes more readable as it removes the need for duplicating or storing the pattern + /// used by `str::{starts,ends}_with` and in the slicing. + /// + /// **Known problems:** + /// None. + /// + /// **Example:** + /// + /// ```rust + /// let s = "hello, world!"; + /// if s.starts_with("hello, ") { + /// assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); + /// } + /// ``` + /// Use instead: + /// ```rust + /// let s = "hello, world!"; + /// if let Some(end) = s.strip_prefix("hello, ") { + /// assert_eq!(end.to_uppercase(), "WORLD!"); + /// } + /// ``` + pub MANUAL_STRIP, + complexity, + "suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing" +} + +pub struct ManualStrip { + msrv: Option, +} + +impl ManualStrip { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(ManualStrip => [MANUAL_STRIP]); + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum StripKind { + Prefix, + Suffix, +} + +impl<'tcx> LateLintPass<'tcx> for ManualStrip { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if !meets_msrv(self.msrv.as_ref(), &MANUAL_STRIP_MSRV) { + return; + } + + if_chain! { + if let ExprKind::If(cond, then, _) = &expr.kind; + if let ExprKind::MethodCall(_, _, [target_arg, pattern], _) = cond.kind; + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id); + if let ExprKind::Path(target_path) = &target_arg.kind; + then { + let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) { + StripKind::Prefix + } else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) { + StripKind::Suffix + } else { + return; + }; + let target_res = cx.qpath_res(&target_path, target_arg.hir_id); + if target_res == Res::Err { + return; + }; + + if_chain! { + if let Res::Local(hir_id) = target_res; + if let Some(used_mutably) = mutated_variables(then, cx); + if used_mutably.contains(&hir_id); + then { + return; + } + } + + let strippings = find_stripping(cx, strip_kind, target_res, pattern, then); + if !strippings.is_empty() { + + let kind_word = match strip_kind { + StripKind::Prefix => "prefix", + StripKind::Suffix => "suffix", + }; + + let test_span = expr.span.until(then.span); + span_lint_and_then(cx, MANUAL_STRIP, strippings[0], &format!("stripping a {} manually", kind_word), |diag| { + diag.span_note(test_span, &format!("the {} was tested here", kind_word)); + multispan_sugg( + diag, + &format!("try using the `strip_{}` method", kind_word), + vec![(test_span, + format!("if let Some() = {}.strip_{}({}) ", + snippet(cx, target_arg.span, ".."), + kind_word, + snippet(cx, pattern.span, "..")))] + .into_iter().chain(strippings.into_iter().map(|span| (span, "".into()))), + ) + }); + } + } + } + } + + extract_msrv_attr!(LateContext); +} + +// Returns `Some(arg)` if `expr` matches `arg.len()` and `None` otherwise. +fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if_chain! { + if let ExprKind::MethodCall(_, _, [arg], _) = expr.kind; + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if match_def_path(cx, method_def_id, &paths::STR_LEN); + then { + Some(arg) + } else { + None + } + } +} + +// Returns the length of the `expr` if it's a constant string or char. +fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { + let (value, _) = constant(cx, cx.typeck_results(), expr)?; + match value { + Constant::Str(value) => Some(value.len() as u128), + Constant::Char(value) => Some(value.len_utf8() as u128), + _ => None, + } +} + +// Tests if `expr` equals the length of the pattern. +fn eq_pattern_length<'tcx>(cx: &LateContext<'tcx>, pattern: &Expr<'_>, expr: &'tcx Expr<'_>) -> bool { + if let ExprKind::Lit(Spanned { + node: LitKind::Int(n, _), + .. + }) = expr.kind + { + constant_length(cx, pattern).map_or(false, |length| length == n) + } else { + len_arg(cx, expr).map_or(false, |arg| eq_expr_value(cx, pattern, arg)) + } +} + +// Tests if `expr` is a `&str`. +fn is_ref_str(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + match cx.typeck_results().expr_ty_adjusted(&expr).kind() { + ty::Ref(_, ty, _) => ty.is_str(), + _ => false, + } +} + +// Removes the outer `AddrOf` expression if needed. +fn peel_ref<'a>(expr: &'a Expr<'_>) -> &'a Expr<'a> { + if let ExprKind::AddrOf(BorrowKind::Ref, _, unref) = &expr.kind { + unref + } else { + expr + } +} + +// Find expressions where `target` is stripped using the length of `pattern`. +// We'll suggest replacing these expressions with the result of the `strip_{prefix,suffix}` +// method. +fn find_stripping<'tcx>( + cx: &LateContext<'tcx>, + strip_kind: StripKind, + target: Res, + pattern: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) -> Vec { + struct StrippingFinder<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + strip_kind: StripKind, + target: Res, + pattern: &'tcx Expr<'tcx>, + results: Vec, + } + + impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> { + type Map = Map<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { + if_chain! { + if is_ref_str(self.cx, ex); + let unref = peel_ref(ex); + if let ExprKind::Index(indexed, index) = &unref.kind; + if let Some(higher::Range { start, end, .. }) = higher::range(index); + if let ExprKind::Path(path) = &indexed.kind; + if self.cx.qpath_res(path, ex.hir_id) == self.target; + then { + match (self.strip_kind, start, end) { + (StripKind::Prefix, Some(start), None) => { + if eq_pattern_length(self.cx, self.pattern, start) { + self.results.push(ex.span); + return; + } + }, + (StripKind::Suffix, None, Some(end)) => { + if_chain! { + if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, left, right) = end.kind; + if let Some(left_arg) = len_arg(self.cx, left); + if let ExprKind::Path(left_path) = &left_arg.kind; + if self.cx.qpath_res(left_path, left_arg.hir_id) == self.target; + if eq_pattern_length(self.cx, self.pattern, right); + then { + self.results.push(ex.span); + return; + } + } + }, + _ => {} + } + } + } + + walk_expr(self, ex); + } + } + + let mut finder = StrippingFinder { + cx, + strip_kind, + target, + pattern, + results: vec![], + }; + walk_expr(&mut finder, expr); + finder.results +} diff --git a/src/tools/clippy/clippy_lints/src/manual_unwrap_or.rs b/src/tools/clippy/clippy_lints/src/manual_unwrap_or.rs new file mode 100644 index 0000000000..b452225b5d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_unwrap_or.rs @@ -0,0 +1,127 @@ +use crate::consts::constant_simple; +use crate::utils; +use crate::utils::{path_to_local_id, sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Arm, Expr, ExprKind, Pat, PatKind}; +use rustc_lint::LintContext; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** + /// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`. + /// + /// **Why is this bad?** + /// Concise code helps focusing on behavior instead of boilerplate. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let foo: Option = None; + /// match foo { + /// Some(v) => v, + /// None => 1, + /// }; + /// ``` + /// + /// Use instead: + /// ```rust + /// let foo: Option = None; + /// foo.unwrap_or(1); + /// ``` + pub MANUAL_UNWRAP_OR, + complexity, + "finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`" +} + +declare_lint_pass!(ManualUnwrapOr => [MANUAL_UNWRAP_OR]); + +impl LateLintPass<'_> for ManualUnwrapOr { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + lint_manual_unwrap_or(cx, expr); + } +} + +#[derive(Copy, Clone)] +enum Case { + Option, + Result, +} + +impl Case { + fn unwrap_fn_path(&self) -> &str { + match self { + Case::Option => "Option::unwrap_or", + Case::Result => "Result::unwrap_or", + } + } +} + +fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + fn applicable_or_arm<'a>(arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> { + if_chain! { + if arms.len() == 2; + if arms.iter().all(|arm| arm.guard.is_none()); + if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| + match arm.pat.kind { + PatKind::Path(ref some_qpath) => + utils::match_qpath(some_qpath, &utils::paths::OPTION_NONE), + PatKind::TupleStruct(ref err_qpath, &[Pat { kind: PatKind::Wild, .. }], _) => + utils::match_qpath(err_qpath, &utils::paths::RESULT_ERR), + _ => false, + } + ); + let unwrap_arm = &arms[1 - idx]; + if let PatKind::TupleStruct(ref unwrap_qpath, &[unwrap_pat], _) = unwrap_arm.pat.kind; + if utils::match_qpath(unwrap_qpath, &utils::paths::OPTION_SOME) + || utils::match_qpath(unwrap_qpath, &utils::paths::RESULT_OK); + if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind; + if path_to_local_id(unwrap_arm.body, binding_hir_id); + if !utils::usage::contains_return_break_continue_macro(or_arm.body); + then { + Some(or_arm) + } else { + None + } + } + } + + if_chain! { + if let ExprKind::Match(scrutinee, match_arms, _) = expr.kind; + let ty = cx.typeck_results().expr_ty(scrutinee); + if let Some(case) = if utils::is_type_diagnostic_item(cx, ty, sym::option_type) { + Some(Case::Option) + } else if utils::is_type_diagnostic_item(cx, ty, sym::result_type) { + Some(Case::Result) + } else { + None + }; + if let Some(or_arm) = applicable_or_arm(match_arms); + if let Some(or_body_snippet) = utils::snippet_opt(cx, or_arm.body.span); + if let Some(indent) = utils::indent_of(cx, expr.span); + if constant_simple(cx, cx.typeck_results(), or_arm.body).is_some(); + then { + let reindented_or_body = + utils::reindent_multiline(or_body_snippet.into(), true, Some(indent)); + utils::span_lint_and_sugg( + cx, + MANUAL_UNWRAP_OR, expr.span, + &format!("this pattern reimplements `{}`", case.unwrap_fn_path()), + "replace with", + format!( + "{}.unwrap_or({})", + sugg::Sugg::hir(cx, scrutinee, "..").maybe_par(), + reindented_or_body, + ), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/map_clone.rs b/src/tools/clippy/clippy_lints/src/map_clone.rs new file mode 100644 index 0000000000..4b685c09a0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/map_clone.rs @@ -0,0 +1,159 @@ +use crate::utils::paths; +use crate::utils::{ + is_copy, is_type_diagnostic_item, match_trait_method, remove_blocks, snippet_with_applicability, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::Mutability; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::Adjust; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Ident; +use rustc_span::{sym, Span}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `map(|x| x.clone())` or + /// dereferencing closures for `Copy` types, on `Iterator` or `Option`, + /// and suggests `cloned()` or `copied()` instead + /// + /// **Why is this bad?** Readability, this can be written more concisely + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.map(|i| *i); + /// ``` + /// + /// The correct use would be: + /// + /// ```rust + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.cloned(); + /// ``` + pub MAP_CLONE, + style, + "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" +} + +declare_lint_pass!(MapClone => [MAP_CLONE]); + +impl<'tcx> LateLintPass<'tcx> for MapClone { + fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { + if e.span.from_expansion() { + return; + } + + if_chain! { + if let hir::ExprKind::MethodCall(ref method, _, ref args, _) = e.kind; + if args.len() == 2; + if method.ident.name == sym::map; + let ty = cx.typeck_results().expr_ty(&args[0]); + if is_type_diagnostic_item(cx, ty, sym::option_type) || match_trait_method(cx, e, &paths::ITERATOR); + if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].kind; + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + then { + match closure_body.params[0].pat.kind { + hir::PatKind::Ref(ref inner, hir::Mutability::Not) => if let hir::PatKind::Binding( + hir::BindingAnnotation::Unannotated, .., name, None + ) = inner.kind { + if ident_eq(name, closure_expr) { + lint(cx, e.span, args[0].span, true); + } + }, + hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => { + match closure_expr.kind { + hir::ExprKind::Unary(hir::UnOp::Deref, ref inner) => { + if ident_eq(name, inner) { + if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { + lint(cx, e.span, args[0].span, true); + } + } + }, + hir::ExprKind::MethodCall(ref method, _, [obj], _) => if_chain! { + if ident_eq(name, obj) && method.ident.name == sym::clone; + if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id); + if let Some(trait_id) = cx.tcx.trait_of_item(fn_id); + if cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id); + // no autoderefs + if !cx.typeck_results().expr_adjustments(obj).iter() + .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))); + then { + let obj_ty = cx.typeck_results().expr_ty(obj); + if let ty::Ref(_, ty, mutability) = obj_ty.kind() { + if matches!(mutability, Mutability::Not) { + let copy = is_copy(cx, ty); + lint(cx, e.span, args[0].span, copy); + } + } else { + lint_needless_cloning(cx, e.span, args[0].span); + } + } + }, + _ => {}, + } + }, + _ => {}, + } + } + } + } +} + +fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool { + if let hir::ExprKind::Path(hir::QPath::Resolved(None, ref path)) = path.kind { + path.segments.len() == 1 && path.segments[0].ident == name + } else { + false + } +} + +fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) { + span_lint_and_sugg( + cx, + MAP_CLONE, + root.trim_start(receiver).unwrap(), + "you are needlessly cloning iterator elements", + "remove the `map` call", + String::new(), + Applicability::MachineApplicable, + ) +} + +fn lint(cx: &LateContext<'_>, replace: Span, root: Span, copied: bool) { + let mut applicability = Applicability::MachineApplicable; + if copied { + span_lint_and_sugg( + cx, + MAP_CLONE, + replace, + "you are using an explicit closure for copying elements", + "consider calling the dedicated `copied` method", + format!( + "{}.copied()", + snippet_with_applicability(cx, root, "..", &mut applicability) + ), + applicability, + ) + } else { + span_lint_and_sugg( + cx, + MAP_CLONE, + replace, + "you are using an explicit closure for cloning elements", + "consider calling the dedicated `cloned` method", + format!( + "{}.cloned()", + snippet_with_applicability(cx, root, "..", &mut applicability) + ), + applicability, + ) + } +} diff --git a/src/tools/clippy/clippy_lints/src/map_err_ignore.rs b/src/tools/clippy/clippy_lints/src/map_err_ignore.rs new file mode 100644 index 0000000000..76fe8e776e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/map_err_ignore.rs @@ -0,0 +1,147 @@ +use crate::utils::span_lint_and_help; + +use rustc_hir::{CaptureBy, Expr, ExprKind, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for instances of `map_err(|_| Some::Enum)` + /// + /// **Why is this bad?** This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Before: + /// ```rust + /// use std::fmt; + /// + /// #[derive(Debug)] + /// enum Error { + /// Indivisible, + /// Remainder(u8), + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// match self { + /// Error::Indivisible => write!(f, "could not divide input by three"), + /// Error::Remainder(remainder) => write!( + /// f, + /// "input is not divisible by three, remainder = {}", + /// remainder + /// ), + /// } + /// } + /// } + /// + /// impl std::error::Error for Error {} + /// + /// fn divisible_by_3(input: &str) -> Result<(), Error> { + /// input + /// .parse::() + /// .map_err(|_| Error::Indivisible) + /// .map(|v| v % 3) + /// .and_then(|remainder| { + /// if remainder == 0 { + /// Ok(()) + /// } else { + /// Err(Error::Remainder(remainder as u8)) + /// } + /// }) + /// } + /// ``` + /// + /// After: + /// ```rust + /// use std::{fmt, num::ParseIntError}; + /// + /// #[derive(Debug)] + /// enum Error { + /// Indivisible(ParseIntError), + /// Remainder(u8), + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// match self { + /// Error::Indivisible(_) => write!(f, "could not divide input by three"), + /// Error::Remainder(remainder) => write!( + /// f, + /// "input is not divisible by three, remainder = {}", + /// remainder + /// ), + /// } + /// } + /// } + /// + /// impl std::error::Error for Error { + /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + /// match self { + /// Error::Indivisible(source) => Some(source), + /// _ => None, + /// } + /// } + /// } + /// + /// fn divisible_by_3(input: &str) -> Result<(), Error> { + /// input + /// .parse::() + /// .map_err(Error::Indivisible) + /// .map(|v| v % 3) + /// .and_then(|remainder| { + /// if remainder == 0 { + /// Ok(()) + /// } else { + /// Err(Error::Remainder(remainder as u8)) + /// } + /// }) + /// } + /// ``` + pub MAP_ERR_IGNORE, + restriction, + "`map_err` should not ignore the original error" +} + +declare_lint_pass!(MapErrIgnore => [MAP_ERR_IGNORE]); + +impl<'tcx> LateLintPass<'tcx> for MapErrIgnore { + // do not try to lint if this is from a macro or desugaring + fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) { + if e.span.from_expansion() { + return; + } + + // check if this is a method call (e.g. x.foo()) + if let ExprKind::MethodCall(ref method, _t_span, ref args, _) = e.kind { + // only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1] + // Enum::Variant[2])) + if method.ident.as_str() == "map_err" && args.len() == 2 { + // make sure the first argument is a closure, and grab the CaptureRef, body_id, and body_span fields + if let ExprKind::Closure(capture, _, body_id, body_span, _) = args[1].kind { + // check if this is by Reference (meaning there's no move statement) + if capture == CaptureBy::Ref { + // Get the closure body to check the parameters and values + let closure_body = cx.tcx.hir().body(body_id); + // make sure there's only one parameter (`|_|`) + if closure_body.params.len() == 1 { + // make sure that parameter is the wild token (`_`) + if let PatKind::Wild = closure_body.params[0].pat.kind { + // span the area of the closure capture and warn that the + // original error will be thrown away + span_lint_and_help( + cx, + MAP_ERR_IGNORE, + body_span, + "`map_err(|_|...` wildcard pattern discards the original error", + None, + "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)", + ); + } + } + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/map_identity.rs b/src/tools/clippy/clippy_lints/src/map_identity.rs new file mode 100644 index 0000000000..9f9c108a85 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/map_identity.rs @@ -0,0 +1,127 @@ +use crate::utils::{ + is_adjusted, is_type_diagnostic_item, match_path, match_trait_method, match_var, paths, remove_blocks, + span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Body, Expr, ExprKind, Pat, PatKind, QPath, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for instances of `map(f)` where `f` is the identity function. + /// + /// **Why is this bad?** It can be written more concisely without the call to `map`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x = [1, 2, 3]; + /// let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect(); + /// ``` + /// Use instead: + /// ```rust + /// let x = [1, 2, 3]; + /// let y: Vec<_> = x.iter().map(|x| 2*x).collect(); + /// ``` + pub MAP_IDENTITY, + complexity, + "using iterator.map(|x| x)" +} + +declare_lint_pass!(MapIdentity => [MAP_IDENTITY]); + +impl<'tcx> LateLintPass<'tcx> for MapIdentity { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + if_chain! { + if let Some([caller, func]) = get_map_argument(cx, expr); + if is_expr_identity_function(cx, func); + then { + span_lint_and_sugg( + cx, + MAP_IDENTITY, + expr.span.trim_start(caller.span).unwrap(), + "unnecessary map of the identity function", + "remove the call to `map`", + String::new(), + Applicability::MachineApplicable + ) + } + } + } +} + +/// Returns the arguments passed into map() if the expression is a method call to +/// map(). Otherwise, returns None. +fn get_map_argument<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a [Expr<'a>]> { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind; + if args.len() == 2 && method.ident.name == sym::map; + let caller_ty = cx.typeck_results().expr_ty(&args[0]); + if match_trait_method(cx, expr, &paths::ITERATOR) + || is_type_diagnostic_item(cx, caller_ty, sym::result_type) + || is_type_diagnostic_item(cx, caller_ty, sym::option_type); + then { + Some(args) + } else { + None + } + } +} + +/// Checks if an expression represents the identity function +/// Only examines closures and `std::convert::identity` +fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)), + ExprKind::Path(QPath::Resolved(_, ref path)) => match_path(path, &paths::STD_CONVERT_IDENTITY), + _ => false, + } +} + +/// Checks if a function's body represents the identity function +/// Looks for bodies of the form `|x| x`, `|x| return x`, `|x| { return x }` or `|x| { +/// return x; }` +fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { + let params = func.params; + let body = remove_blocks(&func.value); + + // if there's less/more than one parameter, then it is not the identity function + if params.len() != 1 { + return false; + } + + match body.kind { + ExprKind::Path(QPath::Resolved(None, _)) => match_expr_param(cx, body, params[0].pat), + ExprKind::Ret(Some(ref ret_val)) => match_expr_param(cx, ret_val, params[0].pat), + ExprKind::Block(ref block, _) => { + if_chain! { + if block.stmts.len() == 1; + if let StmtKind::Semi(ref expr) | StmtKind::Expr(ref expr) = block.stmts[0].kind; + if let ExprKind::Ret(Some(ref ret_val)) = expr.kind; + then { + match_expr_param(cx, ret_val, params[0].pat) + } else { + false + } + } + }, + _ => false, + } +} + +/// Returns true iff an expression returns the same thing as a parameter's pattern +fn match_expr_param(cx: &LateContext<'_>, expr: &Expr<'_>, pat: &Pat<'_>) -> bool { + if let PatKind::Binding(_, _, ident, _) = pat.kind { + match_var(expr, ident.name) && !(cx.typeck_results().hir_owner == expr.hir_id.owner && is_adjusted(cx, expr)) + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/map_unit_fn.rs b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs new file mode 100644 index 0000000000..01126e8619 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs @@ -0,0 +1,275 @@ +use crate::utils::{is_type_diagnostic_item, iter_input_pats, method_chain_args, snippet, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `option.map(f)` where f is a function + /// or closure that returns the unit type `()`. + /// + /// **Why is this bad?** Readability, this can be written more clearly with + /// an if let statement + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// # fn do_stuff() -> Option { Some(String::new()) } + /// # fn log_err_msg(foo: String) -> Option { Some(foo) } + /// # fn format_msg(foo: String) -> String { String::new() } + /// let x: Option = do_stuff(); + /// x.map(log_err_msg); + /// # let x: Option = do_stuff(); + /// x.map(|msg| log_err_msg(format_msg(msg))); + /// ``` + /// + /// The correct use would be: + /// + /// ```rust + /// # fn do_stuff() -> Option { Some(String::new()) } + /// # fn log_err_msg(foo: String) -> Option { Some(foo) } + /// # fn format_msg(foo: String) -> String { String::new() } + /// let x: Option = do_stuff(); + /// if let Some(msg) = x { + /// log_err_msg(msg); + /// } + /// + /// # let x: Option = do_stuff(); + /// if let Some(msg) = x { + /// log_err_msg(format_msg(msg)); + /// } + /// ``` + pub OPTION_MAP_UNIT_FN, + complexity, + "using `option.map(f)`, where `f` is a function or closure that returns `()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `result.map(f)` where f is a function + /// or closure that returns the unit type `()`. + /// + /// **Why is this bad?** Readability, this can be written more clearly with + /// an if let statement + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// # fn do_stuff() -> Result { Ok(String::new()) } + /// # fn log_err_msg(foo: String) -> Result { Ok(foo) } + /// # fn format_msg(foo: String) -> String { String::new() } + /// let x: Result = do_stuff(); + /// x.map(log_err_msg); + /// # let x: Result = do_stuff(); + /// x.map(|msg| log_err_msg(format_msg(msg))); + /// ``` + /// + /// The correct use would be: + /// + /// ```rust + /// # fn do_stuff() -> Result { Ok(String::new()) } + /// # fn log_err_msg(foo: String) -> Result { Ok(foo) } + /// # fn format_msg(foo: String) -> String { String::new() } + /// let x: Result = do_stuff(); + /// if let Ok(msg) = x { + /// log_err_msg(msg); + /// }; + /// # let x: Result = do_stuff(); + /// if let Ok(msg) = x { + /// log_err_msg(format_msg(msg)); + /// }; + /// ``` + pub RESULT_MAP_UNIT_FN, + complexity, + "using `result.map(f)`, where `f` is a function or closure that returns `()`" +} + +declare_lint_pass!(MapUnit => [OPTION_MAP_UNIT_FN, RESULT_MAP_UNIT_FN]); + +fn is_unit_type(ty: Ty<'_>) -> bool { + match ty.kind() { + ty::Tuple(slice) => slice.is_empty(), + ty::Never => true, + _ => false, + } +} + +fn is_unit_function(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + let ty = cx.typeck_results().expr_ty(expr); + + if let ty::FnDef(id, _) = *ty.kind() { + if let Some(fn_type) = cx.tcx.fn_sig(id).no_bound_vars() { + return is_unit_type(fn_type.output()); + } + } + false +} + +fn is_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + is_unit_type(cx.typeck_results().expr_ty(expr)) +} + +/// The expression inside a closure may or may not have surrounding braces and +/// semicolons, which causes problems when generating a suggestion. Given an +/// expression that evaluates to '()' or '!', recursively remove useless braces +/// and semi-colons until is suitable for including in the suggestion template +fn reduce_unit_expression<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option { + if !is_unit_expression(cx, expr) { + return None; + } + + match expr.kind { + hir::ExprKind::Call(_, _) | hir::ExprKind::MethodCall(_, _, _, _) => { + // Calls can't be reduced any more + Some(expr.span) + }, + hir::ExprKind::Block(ref block, _) => { + match (block.stmts, block.expr.as_ref()) { + (&[], Some(inner_expr)) => { + // If block only contains an expression, + // reduce `{ X }` to `X` + reduce_unit_expression(cx, inner_expr) + }, + (&[ref inner_stmt], None) => { + // If block only contains statements, + // reduce `{ X; }` to `X` or `X;` + match inner_stmt.kind { + hir::StmtKind::Local(ref local) => Some(local.span), + hir::StmtKind::Expr(ref e) => Some(e.span), + hir::StmtKind::Semi(..) => Some(inner_stmt.span), + hir::StmtKind::Item(..) => None, + } + }, + _ => { + // For closures that contain multiple statements + // it's difficult to get a correct suggestion span + // for all cases (multi-line closures specifically) + // + // We do not attempt to build a suggestion for those right now. + None + }, + } + }, + _ => None, + } +} + +fn unit_closure<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, +) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> { + if let hir::ExprKind::Closure(_, ref decl, inner_expr_id, _, _) = expr.kind { + let body = cx.tcx.hir().body(inner_expr_id); + let body_expr = &body.value; + + if_chain! { + if decl.inputs.len() == 1; + if is_unit_expression(cx, body_expr); + if let Some(binding) = iter_input_pats(&decl, body).next(); + then { + return Some((binding, body_expr)); + } + } + } + None +} + +/// Builds a name for the let binding variable (`var_arg`) +/// +/// `x.field` => `x_field` +/// `y` => `_y` +/// +/// Anything else will return `a`. +fn let_binding_name(cx: &LateContext<'_>, var_arg: &hir::Expr<'_>) -> String { + match &var_arg.kind { + hir::ExprKind::Field(_, _) => snippet(cx, var_arg.span, "_").replace(".", "_"), + hir::ExprKind::Path(_) => format!("_{}", snippet(cx, var_arg.span, "")), + _ => "a".to_string(), + } +} + +#[must_use] +fn suggestion_msg(function_type: &str, map_type: &str) -> String { + format!( + "called `map(f)` on an `{0}` value where `f` is a {1} that returns the unit type `()`", + map_type, function_type + ) +} + +fn lint_map_unit_fn(cx: &LateContext<'_>, stmt: &hir::Stmt<'_>, expr: &hir::Expr<'_>, map_args: &[hir::Expr<'_>]) { + let var_arg = &map_args[0]; + + let (map_type, variant, lint) = + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(var_arg), sym::option_type) { + ("Option", "Some", OPTION_MAP_UNIT_FN) + } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(var_arg), sym::result_type) { + ("Result", "Ok", RESULT_MAP_UNIT_FN) + } else { + return; + }; + let fn_arg = &map_args[1]; + + if is_unit_function(cx, fn_arg) { + let msg = suggestion_msg("function", map_type); + let suggestion = format!( + "if let {0}({binding}) = {1} {{ {2}({binding}) }}", + variant, + snippet(cx, var_arg.span, "_"), + snippet(cx, fn_arg.span, "_"), + binding = let_binding_name(cx, var_arg) + ); + + span_lint_and_then(cx, lint, expr.span, &msg, |diag| { + diag.span_suggestion(stmt.span, "try this", suggestion, Applicability::MachineApplicable); + }); + } else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) { + let msg = suggestion_msg("closure", map_type); + + span_lint_and_then(cx, lint, expr.span, &msg, |diag| { + if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) { + let suggestion = format!( + "if let {0}({1}) = {2} {{ {3} }}", + variant, + snippet(cx, binding.pat.span, "_"), + snippet(cx, var_arg.span, "_"), + snippet(cx, reduced_expr_span, "_") + ); + diag.span_suggestion( + stmt.span, + "try this", + suggestion, + Applicability::MachineApplicable, // snippet + ); + } else { + let suggestion = format!( + "if let {0}({1}) = {2} {{ ... }}", + variant, + snippet(cx, binding.pat.span, "_"), + snippet(cx, var_arg.span, "_"), + ); + diag.span_suggestion(stmt.span, "try this", suggestion, Applicability::HasPlaceholders); + } + }); + } +} + +impl<'tcx> LateLintPass<'tcx> for MapUnit { + fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) { + if stmt.span.from_expansion() { + return; + } + + if let hir::StmtKind::Semi(ref expr) = stmt.kind { + if let Some(arglists) = method_chain_args(expr, &["map"]) { + lint_map_unit_fn(cx, stmt, expr, arglists[0]); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/match_on_vec_items.rs b/src/tools/clippy/clippy_lints/src/match_on_vec_items.rs new file mode 100644 index 0000000000..086dae9422 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/match_on_vec_items.rs @@ -0,0 +1,101 @@ +use crate::utils::{is_type_diagnostic_item, is_type_lang_item, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, LangItem, MatchSource}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for `match vec[idx]` or `match vec[n..m]`. + /// + /// **Why is this bad?** This can panic at runtime. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust, no_run + /// let arr = vec![0, 1, 2, 3]; + /// let idx = 1; + /// + /// // Bad + /// match arr[idx] { + /// 0 => println!("{}", 0), + /// 1 => println!("{}", 3), + /// _ => {}, + /// } + /// ``` + /// Use instead: + /// ```rust, no_run + /// let arr = vec![0, 1, 2, 3]; + /// let idx = 1; + /// + /// // Good + /// match arr.get(idx) { + /// Some(0) => println!("{}", 0), + /// Some(1) => println!("{}", 3), + /// _ => {}, + /// } + /// ``` + pub MATCH_ON_VEC_ITEMS, + pedantic, + "matching on vector elements can panic" +} + +declare_lint_pass!(MatchOnVecItems => [MATCH_ON_VEC_ITEMS]); + +impl<'tcx> LateLintPass<'tcx> for MatchOnVecItems { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if_chain! { + if !in_external_macro(cx.sess(), expr.span); + if let ExprKind::Match(ref match_expr, _, MatchSource::Normal) = expr.kind; + if let Some(idx_expr) = is_vec_indexing(cx, match_expr); + if let ExprKind::Index(vec, idx) = idx_expr.kind; + + then { + // FIXME: could be improved to suggest surrounding every pattern with Some(_), + // but only when `or_patterns` are stabilized. + span_lint_and_sugg( + cx, + MATCH_ON_VEC_ITEMS, + match_expr.span, + "indexing into a vector may panic", + "try this", + format!( + "{}.get({})", + snippet(cx, vec.span, ".."), + snippet(cx, idx.span, "..") + ), + Applicability::MaybeIncorrect + ); + } + } + } +} + +fn is_vec_indexing<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if_chain! { + if let ExprKind::Index(ref array, ref index) = expr.kind; + if is_vector(cx, array); + if !is_full_range(cx, index); + + then { + return Some(expr); + } + } + + None +} + +fn is_vector(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let ty = cx.typeck_results().expr_ty(expr); + let ty = ty.peel_refs(); + is_type_diagnostic_item(cx, ty, sym::vec_type) +} + +fn is_full_range(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let ty = cx.typeck_results().expr_ty(expr); + let ty = ty.peel_refs(); + is_type_lang_item(cx, ty, LangItem::RangeFull) +} diff --git a/src/tools/clippy/clippy_lints/src/matches.rs b/src/tools/clippy/clippy_lints/src/matches.rs new file mode 100644 index 0000000000..8570cd724b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/matches.rs @@ -0,0 +1,1975 @@ +use crate::consts::{constant, miri_to_const, Constant}; +use crate::utils::sugg::Sugg; +use crate::utils::visitors::LocalUsedVisitor; +use crate::utils::{ + expr_block, get_parent_expr, implements_trait, in_macro, indent_of, is_allowed, is_expn_of, is_refutable, + is_type_diagnostic_item, is_wild, match_qpath, match_type, meets_msrv, multispan_sugg, path_to_local, + path_to_local_id, peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block, + snippet_opt, snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, + span_lint_and_then, strip_pat_refs, +}; +use crate::utils::{paths, search_same, SpanlessEq, SpanlessHash}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::Applicability; +use rustc_hir::def::CtorKind; +use rustc_hir::{ + Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource, Mutability, Node, Pat, + PatKind, QPath, RangeEnd, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, Ty, TyS}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::{Span, Spanned}; +use rustc_span::sym; +use std::cmp::Ordering; +use std::collections::hash_map::Entry; +use std::ops::Bound; + +declare_clippy_lint! { + /// **What it does:** Checks for matches with a single arm where an `if let` + /// will usually suffice. + /// + /// **Why is this bad?** Just readability – `if let` nests less than a `match`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn bar(stool: &str) {} + /// # let x = Some("abc"); + /// // Bad + /// match x { + /// Some(ref foo) => bar(foo), + /// _ => (), + /// } + /// + /// // Good + /// if let Some(ref foo) = x { + /// bar(foo); + /// } + /// ``` + pub SINGLE_MATCH, + style, + "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for matches with two arms where an `if let else` will + /// usually suffice. + /// + /// **Why is this bad?** Just readability – `if let` nests less than a `match`. + /// + /// **Known problems:** Personal style preferences may differ. + /// + /// **Example:** + /// + /// Using `match`: + /// + /// ```rust + /// # fn bar(foo: &usize) {} + /// # let other_ref: usize = 1; + /// # let x: Option<&usize> = Some(&1); + /// match x { + /// Some(ref foo) => bar(foo), + /// _ => bar(&other_ref), + /// } + /// ``` + /// + /// Using `if let` with `else`: + /// + /// ```rust + /// # fn bar(foo: &usize) {} + /// # let other_ref: usize = 1; + /// # let x: Option<&usize> = Some(&1); + /// if let Some(ref foo) = x { + /// bar(foo); + /// } else { + /// bar(&other_ref); + /// } + /// ``` + pub SINGLE_MATCH_ELSE, + pedantic, + "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern" +} + +declare_clippy_lint! { + /// **What it does:** Checks for matches where all arms match a reference, + /// suggesting to remove the reference and deref the matched expression + /// instead. It also checks for `if let &foo = bar` blocks. + /// + /// **Why is this bad?** It just makes the code less readable. That reference + /// destructuring adds nothing to the code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// match x { + /// &A(ref y) => foo(y), + /// &B => bar(), + /// _ => frob(&x), + /// } + /// + /// // Good + /// match *x { + /// A(ref y) => foo(y), + /// B => bar(), + /// _ => frob(x), + /// } + /// ``` + pub MATCH_REF_PATS, + style, + "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression" +} + +declare_clippy_lint! { + /// **What it does:** Checks for matches where match expression is a `bool`. It + /// suggests to replace the expression with an `if...else` block. + /// + /// **Why is this bad?** It makes the code less readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn foo() {} + /// # fn bar() {} + /// let condition: bool = true; + /// match condition { + /// true => foo(), + /// false => bar(), + /// } + /// ``` + /// Use if/else instead: + /// ```rust + /// # fn foo() {} + /// # fn bar() {} + /// let condition: bool = true; + /// if condition { + /// foo(); + /// } else { + /// bar(); + /// } + /// ``` + pub MATCH_BOOL, + pedantic, + "a `match` on a boolean expression instead of an `if..else` block" +} + +declare_clippy_lint! { + /// **What it does:** Checks for overlapping match arms. + /// + /// **Why is this bad?** It is likely to be an error and if not, makes the code + /// less obvious. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 5; + /// match x { + /// 1...10 => println!("1 ... 10"), + /// 5...15 => println!("5 ... 15"), + /// _ => (), + /// } + /// ``` + pub MATCH_OVERLAPPING_ARM, + style, + "a `match` with overlapping arms" +} + +declare_clippy_lint! { + /// **What it does:** Checks for arm which matches all errors with `Err(_)` + /// and take drastic actions like `panic!`. + /// + /// **Why is this bad?** It is generally a bad practice, similar to + /// catching all exceptions in java with `catch(Exception)` + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x: Result = Ok(3); + /// match x { + /// Ok(_) => println!("ok"), + /// Err(_) => panic!("err"), + /// } + /// ``` + pub MATCH_WILD_ERR_ARM, + pedantic, + "a `match` with `Err(_)` arm and take drastic actions" +} + +declare_clippy_lint! { + /// **What it does:** Checks for match which is used to add a reference to an + /// `Option` value. + /// + /// **Why is this bad?** Using `as_ref()` or `as_mut()` instead is shorter. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x: Option<()> = None; + /// + /// // Bad + /// let r: Option<&()> = match x { + /// None => None, + /// Some(ref v) => Some(v), + /// }; + /// + /// // Good + /// let r: Option<&()> = x.as_ref(); + /// ``` + pub MATCH_AS_REF, + complexity, + "a `match` on an Option value instead of using `as_ref()` or `as_mut`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for wildcard enum matches using `_`. + /// + /// **Why is this bad?** New enum variants added by library updates can be missed. + /// + /// **Known problems:** Suggested replacements may be incorrect if guards exhaustively cover some + /// variants, and also may not use correct path to enum if it's not present in the current scope. + /// + /// **Example:** + /// ```rust + /// # enum Foo { A(usize), B(usize) } + /// # let x = Foo::B(1); + /// // Bad + /// match x { + /// Foo::A(_) => {}, + /// _ => {}, + /// } + /// + /// // Good + /// match x { + /// Foo::A(_) => {}, + /// Foo::B(_) => {}, + /// } + /// ``` + pub WILDCARD_ENUM_MATCH_ARM, + restriction, + "a wildcard enum match arm using `_`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for wildcard enum matches for a single variant. + /// + /// **Why is this bad?** New enum variants added by library updates can be missed. + /// + /// **Known problems:** Suggested replacements may not use correct path to enum + /// if it's not present in the current scope. + /// + /// **Example:** + /// + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; + /// // Bad + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// _ => {}, + /// } + /// + /// // Good + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// Foo::C => {}, + /// } + /// ``` + pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + pedantic, + "a wildcard enum match for a single variant" +} + +declare_clippy_lint! { + /// **What it does:** Checks for wildcard pattern used with others patterns in same match arm. + /// + /// **Why is this bad?** Wildcard pattern already covers any other pattern as it will match anyway. + /// It makes the code less readable, especially to spot wildcard pattern use in match arm. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// match "foo" { + /// "a" => {}, + /// "bar" | _ => {}, + /// } + /// + /// // Good + /// match "foo" { + /// "a" => {}, + /// _ => {}, + /// } + /// ``` + pub WILDCARD_IN_OR_PATTERNS, + complexity, + "a wildcard pattern used with others patterns in same match arm" +} + +declare_clippy_lint! { + /// **What it does:** Checks for matches being used to destructure a single-variant enum + /// or tuple struct where a `let` will suffice. + /// + /// **Why is this bad?** Just readability – `let` doesn't nest, whereas a `match` does. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// enum Wrapper { + /// Data(i32), + /// } + /// + /// let wrapper = Wrapper::Data(42); + /// + /// let data = match wrapper { + /// Wrapper::Data(i) => i, + /// }; + /// ``` + /// + /// The correct use would be: + /// ```rust + /// enum Wrapper { + /// Data(i32), + /// } + /// + /// let wrapper = Wrapper::Data(42); + /// let Wrapper::Data(data) = wrapper; + /// ``` + pub INFALLIBLE_DESTRUCTURING_MATCH, + style, + "a `match` statement with a single infallible arm instead of a `let`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for useless match that binds to only one value. + /// + /// **Why is this bad?** Readability and needless complexity. + /// + /// **Known problems:** Suggested replacements may be incorrect when `match` + /// is actually binding temporary value, bringing a 'dropped while borrowed' error. + /// + /// **Example:** + /// ```rust + /// # let a = 1; + /// # let b = 2; + /// + /// // Bad + /// match (a, b) { + /// (c, d) => { + /// // useless match + /// } + /// } + /// + /// // Good + /// let (c, d) = (a, b); + /// ``` + pub MATCH_SINGLE_BINDING, + complexity, + "a match with a single binding instead of using `let` statement" +} + +declare_clippy_lint! { + /// **What it does:** Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched. + /// + /// **Why is this bad?** Correctness and readability. It's like having a wildcard pattern after + /// matching all enum variants explicitly. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct A { a: i32 } + /// let a = A { a: 5 }; + /// + /// // Bad + /// match a { + /// A { a: 5, .. } => {}, + /// _ => {}, + /// } + /// + /// // Good + /// match a { + /// A { a: 5 } => {}, + /// _ => {}, + /// } + /// ``` + pub REST_PAT_IN_FULLY_BOUND_STRUCTS, + restriction, + "a match on a struct that binds all fields but still uses the wildcard pattern" +} + +declare_clippy_lint! { + /// **What it does:** Lint for redundant pattern matching over `Result`, `Option`, + /// `std::task::Poll` or `std::net::IpAddr` + /// + /// **Why is this bad?** It's more concise and clear to just use the proper + /// utility function + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// # use std::task::Poll; + /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + /// if let Ok(_) = Ok::(42) {} + /// if let Err(_) = Err::(42) {} + /// if let None = None::<()> {} + /// if let Some(_) = Some(42) {} + /// if let Poll::Pending = Poll::Pending::<()> {} + /// if let Poll::Ready(_) = Poll::Ready(42) {} + /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {} + /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {} + /// match Ok::(42) { + /// Ok(_) => true, + /// Err(_) => false, + /// }; + /// ``` + /// + /// The more idiomatic use would be: + /// + /// ```rust + /// # use std::task::Poll; + /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + /// if Ok::(42).is_ok() {} + /// if Err::(42).is_err() {} + /// if None::<()>.is_none() {} + /// if Some(42).is_some() {} + /// if Poll::Pending::<()>.is_pending() {} + /// if Poll::Ready(42).is_ready() {} + /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {} + /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + /// Ok::(42).is_ok(); + /// ``` + pub REDUNDANT_PATTERN_MATCHING, + style, + "use the proper utility function avoiding an `if let`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `match` or `if let` expressions producing a + /// `bool` that could be written using `matches!` + /// + /// **Why is this bad?** Readability and needless complexity. + /// + /// **Known problems:** This lint falsely triggers, if there are arms with + /// `cfg` attributes that remove an arm evaluating to `false`. + /// + /// **Example:** + /// ```rust + /// let x = Some(5); + /// + /// // Bad + /// let a = match x { + /// Some(0) => true, + /// _ => false, + /// }; + /// + /// let a = if let Some(0) = x { + /// true + /// } else { + /// false + /// }; + /// + /// // Good + /// let a = matches!(x, Some(0)); + /// ``` + pub MATCH_LIKE_MATCHES_MACRO, + style, + "a match that could be written with the matches! macro" +} + +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" +} + +#[derive(Default)] +pub struct Matches { + msrv: Option, + infallible_destructuring_match_linted: bool, +} + +impl Matches { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { + msrv, + ..Matches::default() + } + } +} + +impl_lint_pass!(Matches => [ + SINGLE_MATCH, + MATCH_REF_PATS, + MATCH_BOOL, + SINGLE_MATCH_ELSE, + MATCH_OVERLAPPING_ARM, + MATCH_WILD_ERR_ARM, + MATCH_AS_REF, + WILDCARD_ENUM_MATCH_ARM, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + WILDCARD_IN_OR_PATTERNS, + MATCH_SINGLE_BINDING, + INFALLIBLE_DESTRUCTURING_MATCH, + REST_PAT_IN_FULLY_BOUND_STRUCTS, + REDUNDANT_PATTERN_MATCHING, + MATCH_LIKE_MATCHES_MACRO, + MATCH_SAME_ARMS, +]); + +const MATCH_LIKE_MATCHES_MACRO_MSRV: RustcVersion = RustcVersion::new(1, 42, 0); + +impl<'tcx> LateLintPass<'tcx> for Matches { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) || in_macro(expr.span) { + return; + } + + redundant_pattern_match::check(cx, expr); + + if meets_msrv(self.msrv.as_ref(), &MATCH_LIKE_MATCHES_MACRO_MSRV) { + if !check_match_like_matches(cx, expr) { + lint_match_arms(cx, expr); + } + } else { + lint_match_arms(cx, expr); + } + + if let ExprKind::Match(ref ex, ref arms, MatchSource::Normal) = expr.kind { + check_single_match(cx, ex, arms, expr); + check_match_bool(cx, ex, arms, expr); + check_overlapping_arms(cx, ex, arms); + check_wild_err_arm(cx, ex, arms); + check_wild_enum_match(cx, ex, arms); + check_match_as_ref(cx, ex, arms, expr); + check_wild_in_or_pats(cx, arms); + + if self.infallible_destructuring_match_linted { + self.infallible_destructuring_match_linted = false; + } else { + check_match_single_binding(cx, ex, arms, expr); + } + } + if let ExprKind::Match(ref ex, ref arms, _) = expr.kind { + check_match_ref_pats(cx, ex, arms, expr); + } + } + + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { + if_chain! { + if !in_external_macro(cx.sess(), local.span); + if !in_macro(local.span); + if let Some(ref expr) = local.init; + if let ExprKind::Match(ref target, ref arms, MatchSource::Normal) = expr.kind; + if arms.len() == 1 && arms[0].guard.is_none(); + if let PatKind::TupleStruct( + QPath::Resolved(None, ref variant_name), ref args, _) = arms[0].pat.kind; + if args.len() == 1; + if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind; + let body = remove_blocks(&arms[0].body); + if path_to_local_id(body, arg); + + then { + let mut applicability = Applicability::MachineApplicable; + self.infallible_destructuring_match_linted = true; + span_lint_and_sugg( + cx, + INFALLIBLE_DESTRUCTURING_MATCH, + local.span, + "you seem to be trying to use `match` to destructure a single infallible pattern. \ + Consider using `let`", + "try this", + format!( + "let {}({}) = {};", + snippet_with_applicability(cx, variant_name.span, "..", &mut applicability), + snippet_with_applicability(cx, local.pat.span, "..", &mut applicability), + snippet_with_applicability(cx, target.span, "..", &mut applicability), + ), + applicability, + ); + } + } + } + + fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { + if_chain! { + if !in_external_macro(cx.sess(), pat.span); + if !in_macro(pat.span); + if let PatKind::Struct(QPath::Resolved(_, ref path), fields, true) = pat.kind; + if let Some(def_id) = path.res.opt_def_id(); + let ty = cx.tcx.type_of(def_id); + if let ty::Adt(def, _) = ty.kind(); + if def.is_struct() || def.is_union(); + if fields.len() == def.non_enum_variant().fields.len(); + + then { + span_lint_and_help( + cx, + REST_PAT_IN_FULLY_BOUND_STRUCTS, + pat.span, + "unnecessary use of `..` pattern in struct binding. All fields were already bound", + None, + "consider removing `..` from this binding", + ); + } + } + } + + extract_msrv_attr!(LateContext); +} + +#[rustfmt::skip] +fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { + if in_macro(expr.span) { + // Don't lint match expressions present in + // macro_rules! block + return; + } + if let PatKind::Or(..) = arms[0].pat.kind { + // don't lint for or patterns for now, this makes + // the lint noisy in unnecessary situations + return; + } + let els = arms[1].body; + let els = if is_unit_expr(remove_blocks(els)) { + None + } else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind { + if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() { + // single statement/expr "else" block, don't lint + return; + } + // block with 2+ statements or 1 expr and 1+ statement + Some(els) + } else { + // not a block, don't lint + return; + }; + + let ty = cx.typeck_results().expr_ty(ex); + if *ty.kind() != ty::Bool || is_allowed(cx, MATCH_BOOL, ex.hir_id) { + check_single_match_single_pattern(cx, ex, arms, expr, els); + check_single_match_opt_like(cx, ex, arms, expr, ty, els); + } + } +} + +fn check_single_match_single_pattern( + cx: &LateContext<'_>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + els: Option<&Expr<'_>>, +) { + if is_wild(&arms[1].pat) { + report_single_match_single_pattern(cx, ex, arms, expr, els); + } +} + +fn report_single_match_single_pattern( + cx: &LateContext<'_>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + els: Option<&Expr<'_>>, +) { + let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH }; + let els_str = els.map_or(String::new(), |els| { + format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span))) + }); + + let (msg, sugg) = if_chain! { + let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat); + if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind; + let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex)); + if let Some(trait_id) = cx.tcx.lang_items().structural_peq_trait(); + if ty.is_integral() || ty.is_char() || ty.is_str() || implements_trait(cx, ty, trait_id, &[]); + then { + // scrutinee derives PartialEq and the pattern is a constant. + let pat_ref_count = match pat.kind { + // string literals are already a reference. + PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1, + _ => pat_ref_count, + }; + // References are only implicitly added to the pattern, so no overflow here. + // e.g. will work: match &Some(_) { Some(_) => () } + // will not: match Some(_) { &Some(_) => () } + let ref_count_diff = ty_ref_count - pat_ref_count; + + // Try to remove address of expressions first. + let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff); + let ref_count_diff = ref_count_diff - removed; + + let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`"; + let sugg = format!( + "if {} == {}{} {}{}", + snippet(cx, ex.span, ".."), + // PartialEq for different reference counts may not exist. + "&".repeat(ref_count_diff), + snippet(cx, arms[0].pat.span, ".."), + expr_block(cx, &arms[0].body, None, "..", Some(expr.span)), + els_str, + ); + (msg, sugg) + } else { + let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"; + let sugg = format!( + "if let {} = {} {}{}", + snippet(cx, arms[0].pat.span, ".."), + snippet(cx, ex.span, ".."), + expr_block(cx, &arms[0].body, None, "..", Some(expr.span)), + els_str, + ); + (msg, sugg) + } + }; + + span_lint_and_sugg( + cx, + lint, + expr.span, + msg, + "try this", + sugg, + Applicability::HasPlaceholders, + ); +} + +fn check_single_match_opt_like( + cx: &LateContext<'_>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + ty: Ty<'_>, + els: Option<&Expr<'_>>, +) { + // list of candidate `Enum`s we know will never get any more members + let candidates = &[ + (&paths::COW, "Borrowed"), + (&paths::COW, "Cow::Borrowed"), + (&paths::COW, "Cow::Owned"), + (&paths::COW, "Owned"), + (&paths::OPTION, "None"), + (&paths::RESULT, "Err"), + (&paths::RESULT, "Ok"), + ]; + + let path = match arms[1].pat.kind { + PatKind::TupleStruct(ref path, ref inner, _) => { + // Contains any non wildcard patterns (e.g., `Err(err)`)? + if !inner.iter().all(is_wild) { + return; + } + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) + }, + PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => ident.to_string(), + PatKind::Path(ref path) => { + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) + }, + _ => return, + }; + + for &(ty_path, pat_path) in candidates { + if path == *pat_path && match_type(cx, ty, ty_path) { + report_single_match_single_pattern(cx, ex, arms, expr, els); + } + } +} + +fn check_match_bool(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + // Type of expression is `bool`. + if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool { + span_lint_and_then( + cx, + MATCH_BOOL, + expr.span, + "you seem to be trying to match on a boolean expression", + move |diag| { + if arms.len() == 2 { + // no guards + let exprs = if let PatKind::Lit(ref arm_bool) = arms[0].pat.kind { + if let ExprKind::Lit(ref lit) = arm_bool.kind { + match lit.node { + LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)), + LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)), + _ => None, + } + } else { + None + } + } else { + None + }; + + if let Some((true_expr, false_expr)) = exprs { + let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { + (false, false) => Some(format!( + "if {} {} else {}", + snippet(cx, ex.span, "b"), + expr_block(cx, true_expr, None, "..", Some(expr.span)), + expr_block(cx, false_expr, None, "..", Some(expr.span)) + )), + (false, true) => Some(format!( + "if {} {}", + snippet(cx, ex.span, "b"), + expr_block(cx, true_expr, None, "..", Some(expr.span)) + )), + (true, false) => { + let test = Sugg::hir(cx, ex, ".."); + Some(format!( + "if {} {}", + !test, + expr_block(cx, false_expr, None, "..", Some(expr.span)) + )) + }, + (true, true) => None, + }; + + if let Some(sugg) = sugg { + diag.span_suggestion( + expr.span, + "consider using an `if`/`else` expression", + sugg, + Applicability::HasPlaceholders, + ); + } + } + } + }, + ); + } +} + +fn check_overlapping_arms<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) { + if arms.len() >= 2 && cx.typeck_results().expr_ty(ex).is_integral() { + let ranges = all_ranges(cx, arms, cx.typeck_results().expr_ty(ex)); + let type_ranges = type_ranges(&ranges); + if !type_ranges.is_empty() { + if let Some((start, end)) = overlapping(&type_ranges) { + span_lint_and_note( + cx, + MATCH_OVERLAPPING_ARM, + start.span, + "some ranges overlap", + Some(end.span), + "overlaps with this", + ); + } + } + } +} + +fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) { + let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs(); + if is_type_diagnostic_item(cx, ex_ty, sym::result_type) { + for arm in arms { + if let PatKind::TupleStruct(ref path, ref inner, _) = arm.pat.kind { + let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)); + if path_str == "Err" { + let mut matching_wild = inner.iter().any(is_wild); + let mut ident_bind_name = String::from("_"); + if !matching_wild { + // Looking for unused bindings (i.e.: `_e`) + inner.iter().for_each(|pat| { + if let PatKind::Binding(_, id, ident, None) = pat.kind { + if ident.as_str().starts_with('_') + && !LocalUsedVisitor::new(cx, id).check_expr(arm.body) + { + ident_bind_name = (&ident.name.as_str()).to_string(); + matching_wild = true; + } + } + }); + } + if_chain! { + if matching_wild; + if let ExprKind::Block(ref block, _) = arm.body.kind; + if is_panic_block(block); + then { + // `Err(_)` or `Err(_e)` arm with `panic!` found + span_lint_and_note(cx, + MATCH_WILD_ERR_ARM, + arm.pat.span, + &format!("`Err({})` matches all errors", &ident_bind_name), + None, + "match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable", + ); + } + } + } + } + } + } +} + +fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { + let ty = cx.typeck_results().expr_ty(ex); + if !ty.is_enum() { + // If there isn't a nice closed set of possible values that can be conveniently enumerated, + // don't complain about not enumerating the mall. + return; + } + + // First pass - check for violation, but don't do much book-keeping because this is hopefully + // the uncommon case, and the book-keeping is slightly expensive. + let mut wildcard_span = None; + let mut wildcard_ident = None; + for arm in arms { + if let PatKind::Wild = arm.pat.kind { + wildcard_span = Some(arm.pat.span); + } else if let PatKind::Binding(_, _, ident, None) = arm.pat.kind { + wildcard_span = Some(arm.pat.span); + wildcard_ident = Some(ident); + } + } + + if let Some(wildcard_span) = wildcard_span { + // Accumulate the variants which should be put in place of the wildcard because they're not + // already covered. + + let mut missing_variants = vec![]; + if let ty::Adt(def, _) = ty.kind() { + for variant in &def.variants { + missing_variants.push(variant); + } + } + + for arm in arms { + if arm.guard.is_some() { + // Guards mean that this case probably isn't exhaustively covered. Technically + // this is incorrect, as we should really check whether each variant is exhaustively + // covered by the set of guards that cover it, but that's really hard to do. + continue; + } + if let PatKind::Path(ref path) = arm.pat.kind { + if let QPath::Resolved(_, p) = path { + missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); + } + } else if let PatKind::TupleStruct(QPath::Resolved(_, p), ref patterns, ..) = arm.pat.kind { + // Some simple checks for exhaustive patterns. + // There is a room for improvements to detect more cases, + // but it can be more expensive to do so. + let is_pattern_exhaustive = + |pat: &&Pat<'_>| matches!(pat.kind, PatKind::Wild | PatKind::Binding(.., None)); + if patterns.iter().all(is_pattern_exhaustive) { + missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); + } + } + } + + let mut suggestion: Vec = missing_variants + .iter() + .map(|v| { + let suffix = match v.ctor_kind { + CtorKind::Fn => "(..)", + CtorKind::Const | CtorKind::Fictive => "", + }; + let ident_str = if let Some(ident) = wildcard_ident { + format!("{} @ ", ident.name) + } else { + String::new() + }; + // This path assumes that the enum type is imported into scope. + format!("{}{}{}", ident_str, cx.tcx.def_path_str(v.def_id), suffix) + }) + .collect(); + + if suggestion.is_empty() { + return; + } + + let mut message = "wildcard match will miss any future added variants"; + + if let ty::Adt(def, _) = ty.kind() { + if def.is_variant_list_non_exhaustive() { + message = "match on non-exhaustive enum doesn't explicitly match all known variants"; + suggestion.push(String::from("_")); + } + } + + if suggestion.len() == 1 { + // No need to check for non-exhaustive enum as in that case len would be greater than 1 + span_lint_and_sugg( + cx, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + wildcard_span, + message, + "try this", + suggestion[0].clone(), + Applicability::MaybeIncorrect, + ) + }; + + span_lint_and_sugg( + cx, + WILDCARD_ENUM_MATCH_ARM, + wildcard_span, + message, + "try this", + suggestion.join(" | "), + Applicability::MaybeIncorrect, + ) + } +} + +// If the block contains only a `panic!` macro (as expression or statement) +fn is_panic_block(block: &Block<'_>) -> bool { + match (&block.expr, block.stmts.len(), block.stmts.first()) { + (&Some(ref exp), 0, _) => { + is_expn_of(exp.span, "panic").is_some() && is_expn_of(exp.span, "unreachable").is_none() + }, + (&None, 1, Some(stmt)) => { + is_expn_of(stmt.span, "panic").is_some() && is_expn_of(stmt.span, "unreachable").is_none() + }, + _ => false, + } +} + +fn check_match_ref_pats(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if has_only_ref_pats(arms) { + let mut suggs = Vec::with_capacity(arms.len() + 1); + let (title, msg) = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, ref inner) = ex.kind { + let span = ex.span.source_callsite(); + suggs.push((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string())); + ( + "you don't need to add `&` to both the expression and the patterns", + "try", + ) + } else { + let span = ex.span.source_callsite(); + suggs.push((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string())); + ( + "you don't need to add `&` to all patterns", + "instead of prefixing all patterns with `&`, you can dereference the expression", + ) + }; + + suggs.extend(arms.iter().filter_map(|a| { + if let PatKind::Ref(ref refp, _) = a.pat.kind { + Some((a.pat.span, snippet(cx, refp.span, "..").to_string())) + } else { + None + } + })); + + span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| { + if !expr.span.from_expansion() { + multispan_sugg(diag, msg, suggs); + } + }); + } +} + +fn check_match_as_ref(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { + let arm_ref: Option = if is_none_arm(&arms[0]) { + is_ref_some_arm(&arms[1]) + } else if is_none_arm(&arms[1]) { + is_ref_some_arm(&arms[0]) + } else { + None + }; + if let Some(rb) = arm_ref { + let suggestion = if rb == BindingAnnotation::Ref { + "as_ref" + } else { + "as_mut" + }; + + let output_ty = cx.typeck_results().expr_ty(expr); + let input_ty = cx.typeck_results().expr_ty(ex); + + let cast = if_chain! { + if let ty::Adt(_, substs) = input_ty.kind(); + let input_ty = substs.type_at(0); + if let ty::Adt(_, substs) = output_ty.kind(); + let output_ty = substs.type_at(0); + if let ty::Ref(_, output_ty, _) = *output_ty.kind(); + if input_ty != output_ty; + then { + ".map(|x| x as _)" + } else { + "" + } + }; + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + MATCH_AS_REF, + expr.span, + &format!("use `{}()` instead", suggestion), + "try this", + format!( + "{}.{}(){}", + snippet_with_applicability(cx, ex.span, "_", &mut applicability), + suggestion, + cast, + ), + applicability, + ) + } + } +} + +fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { + for arm in arms { + if let PatKind::Or(ref fields) = arm.pat.kind { + // look for multiple fields in this arm that contains at least one Wild pattern + if fields.len() > 1 && fields.iter().any(is_wild) { + span_lint_and_help( + cx, + WILDCARD_IN_OR_PATTERNS, + arm.pat.span, + "wildcard pattern covers any other pattern as it will match anyway", + None, + "consider handling `_` separately", + ); + } + } + } +} + +/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!` +fn check_match_like_matches<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + if let ExprKind::Match(ex, arms, ref match_source) = &expr.kind { + match match_source { + MatchSource::Normal => find_matches_sugg(cx, ex, arms, expr, false), + MatchSource::IfLetDesugar { .. } => find_matches_sugg(cx, ex, arms, expr, true), + _ => false, + } + } else { + false + } +} + +/// Lint a `match` or desugared `if let` for replacement by `matches!` +fn find_matches_sugg(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>, desugared: bool) -> bool { + if_chain! { + if arms.len() >= 2; + if cx.typeck_results().expr_ty(expr).is_bool(); + if let Some((b1_arm, b0_arms)) = arms.split_last(); + if let Some(b0) = find_bool_lit(&b0_arms[0].body.kind, desugared); + if let Some(b1) = find_bool_lit(&b1_arm.body.kind, desugared); + if is_wild(&b1_arm.pat); + if b0 != b1; + let if_guard = &b0_arms[0].guard; + if if_guard.is_none() || b0_arms.len() == 1; + if cx.tcx.hir().attrs(b0_arms[0].hir_id).is_empty(); + if b0_arms[1..].iter() + .all(|arm| { + find_bool_lit(&arm.body.kind, desugared).map_or(false, |b| b == b0) && + arm.guard.is_none() && cx.tcx.hir().attrs(arm.hir_id).is_empty() + }); + then { + // The suggestion may be incorrect, because some arms can have `cfg` attributes + // evaluated into `false` and so such arms will be stripped before. + let mut applicability = Applicability::MaybeIncorrect; + let pat = { + use itertools::Itertools as _; + b0_arms.iter() + .map(|arm| snippet_with_applicability(cx, arm.pat.span, "..", &mut applicability)) + .join(" | ") + }; + let pat_and_guard = if let Some(Guard::If(g)) = if_guard { + format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability)) + } else { + pat + }; + + // strip potential borrows (#6503), but only if the type is a reference + let mut ex_new = ex; + if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind { + if let ty::Ref(..) = cx.typeck_results().expr_ty(&ex_inner).kind() { + ex_new = ex_inner; + } + }; + span_lint_and_sugg( + cx, + MATCH_LIKE_MATCHES_MACRO, + expr.span, + &format!("{} expression looks like `matches!` macro", if desugared { "if let .. else" } else { "match" }), + "try this", + format!( + "{}matches!({}, {})", + if b0 { "" } else { "!" }, + snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), + pat_and_guard, + ), + applicability, + ); + true + } else { + false + } + } +} + +/// Extract a `bool` or `{ bool }` +fn find_bool_lit(ex: &ExprKind<'_>, desugared: bool) -> Option { + match ex { + ExprKind::Lit(Spanned { + node: LitKind::Bool(b), .. + }) => Some(*b), + ExprKind::Block( + rustc_hir::Block { + stmts: &[], + expr: Some(exp), + .. + }, + _, + ) if desugared => { + if let ExprKind::Lit(Spanned { + node: LitKind::Bool(b), .. + }) = exp.kind + { + Some(b) + } else { + None + } + }, + _ => None, + } +} + +fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if in_macro(expr.span) || arms.len() != 1 || is_refutable(cx, arms[0].pat) { + return; + } + + // HACK: + // This is a hack to deal with arms that are excluded by macros like `#[cfg]`. It is only used here + // to prevent false positives as there is currently no better way to detect if code was excluded by + // a macro. See PR #6435 + if_chain! { + if let Some(match_snippet) = snippet_opt(cx, expr.span); + if let Some(arm_snippet) = snippet_opt(cx, arms[0].span); + if let Some(ex_snippet) = snippet_opt(cx, ex.span); + let rest_snippet = match_snippet.replace(&arm_snippet, "").replace(&ex_snippet, ""); + if rest_snippet.contains("=>"); + then { + // The code it self contains another thick arrow "=>" + // -> Either another arm or a comment + return; + } + } + + let matched_vars = ex.span; + let bind_names = arms[0].pat.span; + let match_body = remove_blocks(&arms[0].body); + let mut snippet_body = if match_body.span.from_expansion() { + Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string() + } else { + snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string() + }; + + // Do we need to add ';' to suggestion ? + match match_body.kind { + ExprKind::Block(block, _) => { + // macro + expr_ty(body) == () + if block.span.from_expansion() && cx.typeck_results().expr_ty(&match_body).is_unit() { + snippet_body.push(';'); + } + }, + _ => { + // expr_ty(body) == () + if cx.typeck_results().expr_ty(&match_body).is_unit() { + snippet_body.push(';'); + } + }, + } + + let mut applicability = Applicability::MaybeIncorrect; + match arms[0].pat.kind { + PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => { + // If this match is in a local (`let`) stmt + let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) { + ( + parent_let_node.span, + format!( + "let {} = {};\n{}let {} = {};", + snippet_with_applicability(cx, bind_names, "..", &mut applicability), + snippet_with_applicability(cx, matched_vars, "..", &mut applicability), + " ".repeat(indent_of(cx, expr.span).unwrap_or(0)), + snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability), + snippet_body + ), + ) + } else { + // If we are in closure, we need curly braces around suggestion + let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); + let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string()); + if let Some(parent_expr) = get_parent_expr(cx, expr) { + if let ExprKind::Closure(..) = parent_expr.kind { + cbrace_end = format!("\n{}}}", indent); + // Fix body indent due to the closure + indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); + cbrace_start = format!("{{\n{}", indent); + } + }; + ( + expr.span, + format!( + "{}let {} = {};\n{}{}{}", + cbrace_start, + snippet_with_applicability(cx, bind_names, "..", &mut applicability), + snippet_with_applicability(cx, matched_vars, "..", &mut applicability), + indent, + snippet_body, + cbrace_end + ), + ) + }; + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + target_span, + "this match could be written as a `let` statement", + "consider using `let` statement", + sugg, + applicability, + ); + }, + PatKind::Wild => { + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + expr.span, + "this match could be replaced by its body itself", + "consider using the match body instead", + snippet_body, + Applicability::MachineApplicable, + ); + }, + _ => (), + } +} + +/// Returns true if the `ex` match expression is in a local (`let`) statement +fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> { + if_chain! { + let map = &cx.tcx.hir(); + if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)); + if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id)); + then { + return Some(parent_let_expr); + } + } + None +} + +/// Gets all arms that are unbounded `PatRange`s. +fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec> { + arms.iter() + .flat_map(|arm| { + if let Arm { + ref pat, guard: None, .. + } = *arm + { + if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind { + let lhs = match lhs { + Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0, + None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?, + }; + let rhs = match rhs { + Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0, + None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?, + }; + let rhs = match range_end { + RangeEnd::Included => Bound::Included(rhs), + RangeEnd::Excluded => Bound::Excluded(rhs), + }; + return Some(SpannedRange { + span: pat.span, + node: (lhs, rhs), + }); + } + + if let PatKind::Lit(ref value) = pat.kind { + let value = constant(cx, cx.typeck_results(), value)?.0; + return Some(SpannedRange { + span: pat.span, + node: (value.clone(), Bound::Included(value)), + }); + } + } + None + }) + .collect() +} + +#[derive(Debug, Eq, PartialEq)] +pub struct SpannedRange { + pub span: Span, + pub node: (T, Bound), +} + +type TypedRanges = Vec>; + +/// Gets all `Int` ranges or all `Uint` ranges. Mixed types are an error anyway +/// and other types than +/// `Uint` and `Int` probably don't make sense. +fn type_ranges(ranges: &[SpannedRange]) -> TypedRanges { + ranges + .iter() + .filter_map(|range| match range.node { + (Constant::Int(start), Bound::Included(Constant::Int(end))) => Some(SpannedRange { + span: range.span, + node: (start, Bound::Included(end)), + }), + (Constant::Int(start), Bound::Excluded(Constant::Int(end))) => Some(SpannedRange { + span: range.span, + node: (start, Bound::Excluded(end)), + }), + (Constant::Int(start), Bound::Unbounded) => Some(SpannedRange { + span: range.span, + node: (start, Bound::Unbounded), + }), + _ => None, + }) + .collect() +} + +fn is_unit_expr(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Tup(ref v) if v.is_empty() => true, + ExprKind::Block(ref b, _) if b.stmts.is_empty() && b.expr.is_none() => true, + _ => false, + } +} + +// Checks if arm has the form `None => None` +fn is_none_arm(arm: &Arm<'_>) -> bool { + matches!(arm.pat.kind, PatKind::Path(ref path) if match_qpath(path, &paths::OPTION_NONE)) +} + +// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) +fn is_ref_some_arm(arm: &Arm<'_>) -> Option { + if_chain! { + if let PatKind::TupleStruct(ref path, ref pats, _) = arm.pat.kind; + if pats.len() == 1 && match_qpath(path, &paths::OPTION_SOME); + if let PatKind::Binding(rb, .., ident, _) = pats[0].kind; + if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; + if let ExprKind::Call(ref e, ref args) = remove_blocks(&arm.body).kind; + if let ExprKind::Path(ref some_path) = e.kind; + if match_qpath(some_path, &paths::OPTION_SOME) && args.len() == 1; + if let ExprKind::Path(QPath::Resolved(_, ref path2)) = args[0].kind; + if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name; + then { + return Some(rb) + } + } + None +} + +fn has_only_ref_pats(arms: &[Arm<'_>]) -> bool { + let mapped = arms + .iter() + .map(|a| { + match a.pat.kind { + PatKind::Ref(..) => Some(true), // &-patterns + PatKind::Wild => Some(false), // an "anything" wildcard is also fine + _ => None, // any other pattern is not fine + } + }) + .collect::>>(); + // look for Some(v) where there's at least one true element + mapped.map_or(false, |v| v.iter().any(|el| *el)) +} + +pub fn overlapping(ranges: &[SpannedRange]) -> Option<(&SpannedRange, &SpannedRange)> +where + T: Copy + Ord, +{ + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + enum Kind<'a, T> { + Start(T, &'a SpannedRange), + End(Bound, &'a SpannedRange), + } + + impl<'a, T: Copy> Kind<'a, T> { + fn range(&self) -> &'a SpannedRange { + match *self { + Kind::Start(_, r) | Kind::End(_, r) => r, + } + } + + fn value(self) -> Bound { + match self { + Kind::Start(t, _) => Bound::Included(t), + Kind::End(t, _) => t, + } + } + } + + impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, T> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl<'a, T: Copy + Ord> Ord for Kind<'a, T> { + fn cmp(&self, other: &Self) -> Ordering { + match (self.value(), other.value()) { + (Bound::Included(a), Bound::Included(b)) | (Bound::Excluded(a), Bound::Excluded(b)) => a.cmp(&b), + // Range patterns cannot be unbounded (yet) + (Bound::Unbounded, _) | (_, Bound::Unbounded) => unimplemented!(), + (Bound::Included(a), Bound::Excluded(b)) => match a.cmp(&b) { + Ordering::Equal => Ordering::Greater, + other => other, + }, + (Bound::Excluded(a), Bound::Included(b)) => match a.cmp(&b) { + Ordering::Equal => Ordering::Less, + other => other, + }, + } + } + } + + let mut values = Vec::with_capacity(2 * ranges.len()); + + for r in ranges { + values.push(Kind::Start(r.node.0, r)); + values.push(Kind::End(r.node.1, r)); + } + + values.sort(); + + for (a, b) in values.iter().zip(values.iter().skip(1)) { + match (a, b) { + (&Kind::Start(_, ra), &Kind::End(_, rb)) => { + if ra.node != rb.node { + return Some((ra, rb)); + } + }, + (&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (), + _ => { + // skip if the range `a` is completely included into the range `b` + if let Ordering::Equal | Ordering::Less = a.cmp(&b) { + let kind_a = Kind::End(a.range().node.1, a.range()); + let kind_b = Kind::End(b.range().node.1, b.range()); + if let Ordering::Equal | Ordering::Greater = kind_a.cmp(&kind_b) { + return None; + } + } + return Some((a.range(), b.range())); + }, + } + } + + None +} + +mod redundant_pattern_match { + use super::REDUNDANT_PATTERN_MATCHING; + use crate::utils::{match_qpath, match_trait_method, paths, snippet, span_lint_and_then}; + use if_chain::if_chain; + use rustc_ast::ast::LitKind; + use rustc_errors::Applicability; + use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind, QPath}; + use rustc_lint::LateContext; + use rustc_span::sym; + + pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Match(op, arms, ref match_source) = &expr.kind { + match match_source { + MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms), + MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, arms, "if"), + MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, arms, "while"), + _ => {}, + } + } + } + + fn find_sugg_for_if_let<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: &Expr<'_>, + arms: &[Arm<'_>], + keyword: &'static str, + ) { + let good_method = match arms[0].pat.kind { + PatKind::TupleStruct(ref path, ref patterns, _) if patterns.len() == 1 => { + if let PatKind::Wild = patterns[0].kind { + if match_qpath(path, &paths::RESULT_OK) { + "is_ok()" + } else if match_qpath(path, &paths::RESULT_ERR) { + "is_err()" + } else if match_qpath(path, &paths::OPTION_SOME) { + "is_some()" + } else if match_qpath(path, &paths::POLL_READY) { + "is_ready()" + } else if match_qpath(path, &paths::IPADDR_V4) { + "is_ipv4()" + } else if match_qpath(path, &paths::IPADDR_V6) { + "is_ipv6()" + } else { + return; + } + } else { + return; + } + }, + PatKind::Path(ref path) => { + if match_qpath(path, &paths::OPTION_NONE) { + "is_none()" + } else if match_qpath(path, &paths::POLL_PENDING) { + "is_pending()" + } else { + return; + } + }, + _ => return, + }; + + // check that `while_let_on_iterator` lint does not trigger + if_chain! { + if keyword == "while"; + if let ExprKind::MethodCall(method_path, _, _, _) = op.kind; + if method_path.ident.name == sym::next; + if match_trait_method(cx, op, &paths::ITERATOR); + then { + return; + } + } + + let result_expr = match &op.kind { + ExprKind::AddrOf(_, _, borrowed) => borrowed, + _ => op, + }; + span_lint_and_then( + cx, + REDUNDANT_PATTERN_MATCHING, + arms[0].pat.span, + &format!("redundant pattern matching, consider using `{}`", good_method), + |diag| { + // while let ... = ... { ... } + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + let expr_span = expr.span; + + // while let ... = ... { ... } + // ^^^ + let op_span = result_expr.span.source_callsite(); + + // while let ... = ... { ... } + // ^^^^^^^^^^^^^^^^^^^ + let span = expr_span.until(op_span.shrink_to_hi()); + diag.span_suggestion( + span, + "try this", + format!("{} {}.{}", keyword, snippet(cx, op_span, "_"), good_method), + Applicability::MachineApplicable, // snippet + ); + }, + ); + } + + fn find_sugg_for_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) { + if arms.len() == 2 { + let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind); + + let found_good_method = match node_pair { + ( + PatKind::TupleStruct(ref path_left, ref patterns_left, _), + PatKind::TupleStruct(ref path_right, ref patterns_right, _), + ) if patterns_left.len() == 1 && patterns_right.len() == 1 => { + if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { + find_good_method_for_match( + arms, + path_left, + path_right, + &paths::RESULT_OK, + &paths::RESULT_ERR, + "is_ok()", + "is_err()", + ) + .or_else(|| { + find_good_method_for_match( + arms, + path_left, + path_right, + &paths::IPADDR_V4, + &paths::IPADDR_V6, + "is_ipv4()", + "is_ipv6()", + ) + }) + } else { + None + } + }, + (PatKind::TupleStruct(ref path_left, ref patterns, _), PatKind::Path(ref path_right)) + | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, ref patterns, _)) + if patterns.len() == 1 => + { + if let PatKind::Wild = patterns[0].kind { + find_good_method_for_match( + arms, + path_left, + path_right, + &paths::OPTION_SOME, + &paths::OPTION_NONE, + "is_some()", + "is_none()", + ) + .or_else(|| { + find_good_method_for_match( + arms, + path_left, + path_right, + &paths::POLL_READY, + &paths::POLL_PENDING, + "is_ready()", + "is_pending()", + ) + }) + } else { + None + } + }, + _ => None, + }; + + if let Some(good_method) = found_good_method { + let span = expr.span.to(op.span); + let result_expr = match &op.kind { + ExprKind::AddrOf(_, _, borrowed) => borrowed, + _ => op, + }; + span_lint_and_then( + cx, + REDUNDANT_PATTERN_MATCHING, + expr.span, + &format!("redundant pattern matching, consider using `{}`", good_method), + |diag| { + diag.span_suggestion( + span, + "try this", + format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method), + Applicability::MaybeIncorrect, // snippet + ); + }, + ); + } + } + } + + fn find_good_method_for_match<'a>( + arms: &[Arm<'_>], + path_left: &QPath<'_>, + path_right: &QPath<'_>, + expected_left: &[&str], + expected_right: &[&str], + should_be_left: &'a str, + should_be_right: &'a str, + ) -> Option<&'a str> { + let body_node_pair = if match_qpath(path_left, expected_left) && match_qpath(path_right, expected_right) { + (&(*arms[0].body).kind, &(*arms[1].body).kind) + } else if match_qpath(path_right, expected_left) && match_qpath(path_left, expected_right) { + (&(*arms[1].body).kind, &(*arms[0].body).kind) + } else { + return None; + }; + + match body_node_pair { + (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) { + (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left), + (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right), + _ => None, + }, + _ => None, + } + } +} + +#[test] +fn test_overlapping() { + use rustc_span::source_map::DUMMY_SP; + + let sp = |s, e| SpannedRange { + span: DUMMY_SP, + node: (s, e), + }; + + assert_eq!(None, overlapping::(&[])); + assert_eq!(None, overlapping(&[sp(1, Bound::Included(4))])); + assert_eq!( + None, + overlapping(&[sp(1, Bound::Included(4)), sp(5, Bound::Included(6))]) + ); + assert_eq!( + None, + overlapping(&[ + sp(1, Bound::Included(4)), + sp(5, Bound::Included(6)), + sp(10, Bound::Included(11)) + ],) + ); + assert_eq!( + Some((&sp(1, Bound::Included(4)), &sp(3, Bound::Included(6)))), + overlapping(&[sp(1, Bound::Included(4)), sp(3, Bound::Included(6))]) + ); + assert_eq!( + Some((&sp(5, Bound::Included(6)), &sp(6, Bound::Included(11)))), + overlapping(&[ + sp(1, Bound::Included(4)), + sp(5, Bound::Included(6)), + sp(6, Bound::Included(11)) + ],) + ); +} + +/// Implementation of `MATCH_SAME_ARMS`. +fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { + if let ExprKind::Match(_, ref arms, MatchSource::Normal) = expr.kind { + let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { + let mut h = SpanlessHash::new(cx); + 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); + + let mut local_map: FxHashMap = FxHashMap::default(); + let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { + if_chain! { + if let Some(a_id) = path_to_local(a); + if let Some(b_id) = path_to_local(b); + let entry = match local_map.entry(a_id) { + Entry::Vacant(entry) => entry, + // check if using the same bindings as before + Entry::Occupied(entry) => return *entry.get() == b_id, + }; + // the names technically don't have to match; this makes the lint more conservative + if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id); + if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b)); + if pat_contains_local(lhs.pat, a_id); + if pat_contains_local(rhs.pat, b_id); + then { + entry.insert(b_id); + true + } else { + false + } + } + }; + // Arms with a guard are ignored, those can’t always be merged together + // This is also the case for arms in-between each there is an arm with a guard + (min_index..=max_index).all(|index| arms[index].guard.is_none()) + && SpanlessEq::new(cx) + .expr_fallback(eq_fallback) + .eq_expr(&lhs.body, &rhs.body) + // these checks could be removed to allow unused bindings + && bindings_eq(lhs.pat, local_map.keys().copied().collect()) + && bindings_eq(rhs.pat, local_map.values().copied().collect()) + }; + + let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); + for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) { + span_lint_and_then( + cx, + MATCH_SAME_ARMS, + j.body.span, + "this `match` has identical arm bodies", + |diag| { + diag.span_note(i.body.span, "same as this"); + + // Note: this does not use `span_suggestion` on purpose: + // there is no clean way + // to remove the other arm. Building a span and suggest to replace it to "" + // makes an even more confusing error message. Also in order not to make up a + // span for the whole pattern, the suggestion is only shown when there is only + // one pattern. The user should know about `|` if they are already using it… + + let lhs = snippet(cx, i.pat.span, ""); + let rhs = snippet(cx, j.pat.span, ""); + + if let PatKind::Wild = j.pat.kind { + // if the last arm is _, then i could be integrated into _ + // note that i.pat cannot be _, because that would mean that we're + // hiding all the subsequent arms, and rust won't compile + diag.span_note( + i.body.span, + &format!( + "`{}` has the same arm body as the `_` wildcard, consider removing it", + lhs + ), + ); + } else { + diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs)); + } + }, + ); + } + } +} + +fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool { + let mut result = false; + pat.walk_short(|p| { + result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id); + !result + }); + result +} + +/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa +fn bindings_eq(pat: &Pat<'_>, mut ids: FxHashSet) -> bool { + let mut result = true; + pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id)); + result && ids.is_empty() +} diff --git a/src/tools/clippy/clippy_lints/src/mem_discriminant.rs b/src/tools/clippy/clippy_lints/src/mem_discriminant.rs new file mode 100644 index 0000000000..c71c2ee7d7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mem_discriminant.rs @@ -0,0 +1,81 @@ +use crate::utils::{match_def_path, paths, snippet, span_lint_and_then, walk_ptrs_ty_depth}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use std::iter; + +declare_clippy_lint! { + /// **What it does:** Checks for calls of `mem::discriminant()` on a non-enum type. + /// + /// **Why is this bad?** The value of `mem::discriminant()` on non-enum types + /// is unspecified. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// use std::mem; + /// + /// mem::discriminant(&"hello"); + /// mem::discriminant(&&Some(2)); + /// ``` + pub MEM_DISCRIMINANT_NON_ENUM, + correctness, + "calling `mem::descriminant` on non-enum type" +} + +declare_lint_pass!(MemDiscriminant => [MEM_DISCRIMINANT_NON_ENUM]); + +impl<'tcx> LateLintPass<'tcx> for MemDiscriminant { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref func, ref func_args) = expr.kind; + // is `mem::discriminant` + if let ExprKind::Path(ref func_qpath) = func.kind; + if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::MEM_DISCRIMINANT); + // type is non-enum + let ty_param = cx.typeck_results().node_substs(func.hir_id).type_at(0); + if !ty_param.is_enum(); + + then { + span_lint_and_then( + cx, + MEM_DISCRIMINANT_NON_ENUM, + expr.span, + &format!("calling `mem::discriminant` on non-enum type `{}`", ty_param), + |diag| { + // if this is a reference to an enum, suggest dereferencing + let (base_ty, ptr_depth) = walk_ptrs_ty_depth(ty_param); + if ptr_depth >= 1 && base_ty.is_enum() { + let param = &func_args[0]; + + // cancel out '&'s first + let mut derefs_needed = ptr_depth; + let mut cur_expr = param; + while derefs_needed > 0 { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref inner_expr) = cur_expr.kind { + derefs_needed -= 1; + cur_expr = inner_expr; + } else { + break; + } + } + + let derefs: String = iter::repeat('*').take(derefs_needed).collect(); + diag.span_suggestion( + param.span, + "try dereferencing", + format!("{}{}", derefs, snippet(cx, cur_expr.span, "")), + Applicability::MachineApplicable, + ); + } + }, + ) + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/mem_forget.rs b/src/tools/clippy/clippy_lints/src/mem_forget.rs new file mode 100644 index 0000000000..d34f9761e2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mem_forget.rs @@ -0,0 +1,44 @@ +use crate::utils::{match_def_path, paths, span_lint}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `std::mem::forget(t)` where `t` is + /// `Drop`. + /// + /// **Why is this bad?** `std::mem::forget(t)` prevents `t` from running its + /// destructor, possibly causing leaks. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::mem; + /// # use std::rc::Rc; + /// mem::forget(Rc::new(55)) + /// ``` + pub MEM_FORGET, + restriction, + "`mem::forget` usage on `Drop` types, likely to cause memory leaks" +} + +declare_lint_pass!(MemForget => [MEM_FORGET]); + +impl<'tcx> LateLintPass<'tcx> for MemForget { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Call(ref path_expr, ref args) = e.kind { + if let ExprKind::Path(ref qpath) = path_expr.kind { + if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() { + if match_def_path(cx, def_id, &paths::MEM_FORGET) { + let forgot_ty = cx.typeck_results().expr_ty(&args[0]); + + if forgot_ty.ty_adt_def().map_or(false, |def| def.has_dtor(cx.tcx)) { + span_lint(cx, MEM_FORGET, e.span, "usage of `mem::forget` on `Drop` type"); + } + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/mem_replace.rs b/src/tools/clippy/clippy_lints/src/mem_replace.rs new file mode 100644 index 0000000000..19087b0207 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mem_replace.rs @@ -0,0 +1,260 @@ +use crate::utils::{ + in_macro, match_def_path, match_qpath, meets_msrv, paths, snippet, snippet_with_applicability, span_lint_and_help, + span_lint_and_sugg, span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; +use rustc_span::symbol::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for `mem::replace()` on an `Option` with + /// `None`. + /// + /// **Why is this bad?** `Option` already has the method `take()` for + /// taking its current value (Some(..) or None) and replacing it with + /// `None`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// use std::mem; + /// + /// let mut an_option = Some(0); + /// let replaced = mem::replace(&mut an_option, None); + /// ``` + /// Is better expressed with: + /// ```rust + /// let mut an_option = Some(0); + /// let taken = an_option.take(); + /// ``` + pub MEM_REPLACE_OPTION_WITH_NONE, + style, + "replacing an `Option` with `None` instead of `take()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `mem::replace(&mut _, mem::uninitialized())` + /// and `mem::replace(&mut _, mem::zeroed())`. + /// + /// **Why is this bad?** This will lead to undefined behavior even if the + /// value is overwritten later, because the uninitialized value may be + /// observed in the case of a panic. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ``` + /// use std::mem; + ///# fn may_panic(v: Vec) -> Vec { v } + /// + /// #[allow(deprecated, invalid_value)] + /// fn myfunc (v: &mut Vec) { + /// let taken_v = unsafe { mem::replace(v, mem::uninitialized()) }; + /// let new_v = may_panic(taken_v); // undefined behavior on panic + /// mem::forget(mem::replace(v, new_v)); + /// } + /// ``` + /// + /// The [take_mut](https://docs.rs/take_mut) crate offers a sound solution, + /// at the cost of either lazily creating a replacement value or aborting + /// on panic, to ensure that the uninitialized value cannot be observed. + pub MEM_REPLACE_WITH_UNINIT, + correctness, + "`mem::replace(&mut _, mem::uninitialized())` or `mem::replace(&mut _, mem::zeroed())`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `std::mem::replace` on a value of type + /// `T` with `T::default()`. + /// + /// **Why is this bad?** `std::mem` module already has the method `take` to + /// take the current value and replace it with the default value of that type. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let mut text = String::from("foo"); + /// let replaced = std::mem::replace(&mut text, String::default()); + /// ``` + /// Is better expressed with: + /// ```rust + /// let mut text = String::from("foo"); + /// let taken = std::mem::take(&mut text); + /// ``` + pub MEM_REPLACE_WITH_DEFAULT, + style, + "replacing a value of type `T` with `T::default()` instead of using `std::mem::take`" +} + +impl_lint_pass!(MemReplace => + [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]); + +fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { + if let ExprKind::Path(ref replacement_qpath) = src.kind { + // Check that second argument is `Option::None` + if match_qpath(replacement_qpath, &paths::OPTION_NONE) { + // Since this is a late pass (already type-checked), + // and we already know that the second argument is an + // `Option`, we do not need to check the first + // argument's type. All that's left is to get + // replacee's path. + let replaced_path = match dest.kind { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, ref replaced) => { + if let ExprKind::Path(QPath::Resolved(None, ref replaced_path)) = replaced.kind { + replaced_path + } else { + return; + } + }, + ExprKind::Path(QPath::Resolved(None, ref replaced_path)) => replaced_path, + _ => return, + }; + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + MEM_REPLACE_OPTION_WITH_NONE, + expr_span, + "replacing an `Option` with `None`", + "consider `Option::take()` instead", + format!( + "{}.take()", + snippet_with_applicability(cx, replaced_path.span, "", &mut applicability) + ), + applicability, + ); + } + } +} + +fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { + if_chain! { + // check if replacement is mem::MaybeUninit::uninit().assume_init() + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(src.hir_id); + if cx.tcx.is_diagnostic_item(sym::assume_init, method_def_id); + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + MEM_REPLACE_WITH_UNINIT, + expr_span, + "replacing with `mem::MaybeUninit::uninit().assume_init()`", + "consider using", + format!( + "std::ptr::read({})", + snippet_with_applicability(cx, dest.span, "", &mut applicability) + ), + applicability, + ); + return; + } + } + + if_chain! { + if let ExprKind::Call(ref repl_func, ref repl_args) = src.kind; + if repl_args.is_empty(); + if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind; + if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id(); + then { + if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + MEM_REPLACE_WITH_UNINIT, + expr_span, + "replacing with `mem::uninitialized()`", + "consider using", + format!( + "std::ptr::read({})", + snippet_with_applicability(cx, dest.span, "", &mut applicability) + ), + applicability, + ); + } else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id) && + !cx.typeck_results().expr_ty(src).is_primitive() { + span_lint_and_help( + cx, + MEM_REPLACE_WITH_UNINIT, + expr_span, + "replacing with `mem::zeroed()`", + None, + "consider using a default value or the `take_mut` crate instead", + ); + } + } + } +} + +fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { + if let ExprKind::Call(ref repl_func, _) = src.kind { + if_chain! { + if !in_external_macro(cx.tcx.sess, expr_span); + if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind; + if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id(); + if match_def_path(cx, repl_def_id, &paths::DEFAULT_TRAIT_METHOD); + then { + span_lint_and_then( + cx, + MEM_REPLACE_WITH_DEFAULT, + expr_span, + "replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`", + |diag| { + if !in_macro(expr_span) { + let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, "")); + + diag.span_suggestion( + expr_span, + "consider using", + suggestion, + Applicability::MachineApplicable + ); + } + } + ); + } + } + } +} + +const MEM_REPLACE_WITH_DEFAULT_MSRV: RustcVersion = RustcVersion::new(1, 40, 0); + +pub struct MemReplace { + msrv: Option, +} + +impl MemReplace { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl<'tcx> LateLintPass<'tcx> for MemReplace { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + // Check that `expr` is a call to `mem::replace()` + if let ExprKind::Call(ref func, ref func_args) = expr.kind; + if let ExprKind::Path(ref func_qpath) = func.kind; + if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::MEM_REPLACE); + if let [dest, src] = &**func_args; + then { + check_replace_option_with_none(cx, src, dest, expr.span); + check_replace_with_uninit(cx, src, dest, expr.span); + if meets_msrv(self.msrv.as_ref(), &MEM_REPLACE_WITH_DEFAULT_MSRV) { + check_replace_with_default(cx, src, dest, expr.span); + } + } + } + } + extract_msrv_attr!(LateContext); +} diff --git a/src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs b/src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs new file mode 100644 index 0000000000..5decb81d9f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs @@ -0,0 +1,193 @@ +use super::{contains_return, BIND_INSTEAD_OF_MAP}; +use crate::utils::{ + in_macro, match_qpath, match_type, method_calls, multispan_sugg_with_applicability, paths, remove_blocks, snippet, + snippet_with_macro_callsite, span_lint_and_sugg, span_lint_and_then, visitors::find_all_ret_expressions, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::Span; + +pub(crate) struct OptionAndThenSome; + +impl BindInsteadOfMap for OptionAndThenSome { + const TYPE_NAME: &'static str = "Option"; + const TYPE_QPATH: &'static [&'static str] = &paths::OPTION; + + const BAD_METHOD_NAME: &'static str = "and_then"; + const BAD_VARIANT_NAME: &'static str = "Some"; + const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::OPTION_SOME; + + const GOOD_METHOD_NAME: &'static str = "map"; +} + +pub(crate) struct ResultAndThenOk; + +impl BindInsteadOfMap for ResultAndThenOk { + const TYPE_NAME: &'static str = "Result"; + const TYPE_QPATH: &'static [&'static str] = &paths::RESULT; + + const BAD_METHOD_NAME: &'static str = "and_then"; + const BAD_VARIANT_NAME: &'static str = "Ok"; + const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_OK; + + const GOOD_METHOD_NAME: &'static str = "map"; +} + +pub(crate) struct ResultOrElseErrInfo; + +impl BindInsteadOfMap for ResultOrElseErrInfo { + const TYPE_NAME: &'static str = "Result"; + const TYPE_QPATH: &'static [&'static str] = &paths::RESULT; + + const BAD_METHOD_NAME: &'static str = "or_else"; + const BAD_VARIANT_NAME: &'static str = "Err"; + const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_ERR; + + const GOOD_METHOD_NAME: &'static str = "map_err"; +} + +pub(crate) trait BindInsteadOfMap { + const TYPE_NAME: &'static str; + const TYPE_QPATH: &'static [&'static str]; + + const BAD_METHOD_NAME: &'static str; + const BAD_VARIANT_NAME: &'static str; + const BAD_VARIANT_QPATH: &'static [&'static str]; + + const GOOD_METHOD_NAME: &'static str; + + fn no_op_msg() -> String { + format!( + "using `{}.{}({})`, which is a no-op", + Self::TYPE_NAME, + Self::BAD_METHOD_NAME, + Self::BAD_VARIANT_NAME + ) + } + + fn lint_msg() -> String { + format!( + "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`", + Self::TYPE_NAME, + Self::BAD_METHOD_NAME, + Self::BAD_VARIANT_NAME, + Self::GOOD_METHOD_NAME + ) + } + + fn lint_closure_autofixable( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + args: &[hir::Expr<'_>], + closure_expr: &hir::Expr<'_>, + closure_args_span: Span, + ) -> bool { + if_chain! { + if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.kind; + if let hir::ExprKind::Path(ref qpath) = some_expr.kind; + if match_qpath(qpath, Self::BAD_VARIANT_QPATH); + if some_args.len() == 1; + then { + let inner_expr = &some_args[0]; + + if contains_return(inner_expr) { + return false; + } + + let some_inner_snip = if inner_expr.span.from_expansion() { + snippet_with_macro_callsite(cx, inner_expr.span, "_") + } else { + snippet(cx, inner_expr.span, "_") + }; + + let closure_args_snip = snippet(cx, closure_args_span, ".."); + let option_snip = snippet(cx, args[0].span, ".."); + let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip); + span_lint_and_sugg( + cx, + BIND_INSTEAD_OF_MAP, + expr.span, + Self::lint_msg().as_ref(), + "try this", + note, + Applicability::MachineApplicable, + ); + true + } else { + false + } + } + } + + fn lint_closure(cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) -> bool { + let mut suggs = Vec::new(); + let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| { + if_chain! { + if !in_macro(ret_expr.span); + if let hir::ExprKind::Call(ref func_path, ref args) = ret_expr.kind; + if let hir::ExprKind::Path(ref qpath) = func_path.kind; + if match_qpath(qpath, Self::BAD_VARIANT_QPATH); + if args.len() == 1; + if !contains_return(&args[0]); + then { + suggs.push((ret_expr.span, args[0].span.source_callsite())); + true + } else { + false + } + } + }); + + if can_sugg { + span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, Self::lint_msg().as_ref(), |diag| { + multispan_sugg_with_applicability( + diag, + "try this", + Applicability::MachineApplicable, + std::iter::once((*method_calls(expr, 1).2.get(0).unwrap(), Self::GOOD_METHOD_NAME.into())).chain( + suggs + .into_iter() + .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())), + ), + ) + }); + } + can_sugg + } + + /// Lint use of `_.and_then(|x| Some(y))` for `Option`s + fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) -> bool { + if !match_type(cx, cx.typeck_results().expr_ty(&args[0]), Self::TYPE_QPATH) { + return false; + } + + match args[1].kind { + hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => { + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + + if Self::lint_closure_autofixable(cx, expr, args, closure_expr, closure_args_span) { + true + } else { + Self::lint_closure(cx, expr, closure_expr) + } + }, + // `_.and_then(Some)` case, which is no-op. + hir::ExprKind::Path(ref qpath) if match_qpath(qpath, Self::BAD_VARIANT_QPATH) => { + span_lint_and_sugg( + cx, + BIND_INSTEAD_OF_MAP, + expr.span, + Self::no_op_msg().as_ref(), + "use the expression directly", + snippet(cx, args[0].span, "..").into(), + Applicability::MachineApplicable, + ); + true + }, + _ => false, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs b/src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs new file mode 100644 index 0000000000..71a7e195e4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs @@ -0,0 +1,39 @@ +use crate::utils::{is_type_diagnostic_item, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::BYTES_NTH; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'tcx [Expr<'tcx>]) { + if_chain! { + if let ExprKind::MethodCall(_, _, ref args, _) = expr.kind; + let ty = cx.typeck_results().expr_ty(&iter_args[0]).peel_refs(); + let caller_type = if is_type_diagnostic_item(cx, ty, sym::string_type) { + Some("String") + } else if ty.is_str() { + Some("str") + } else { + None + }; + if let Some(caller_type) = caller_type; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + BYTES_NTH, + expr.span, + &format!("called `.byte().nth()` on a `{}`", caller_type), + "try", + format!( + "{}.as_bytes().get({})", + snippet_with_applicability(cx, iter_args[0].span, "..", &mut applicability), + snippet_with_applicability(cx, args[1].span, "..", &mut applicability) + ), + applicability, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs b/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs new file mode 100644 index 0000000000..4a130ed47d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs @@ -0,0 +1,109 @@ +use crate::utils::{is_copy, span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use std::iter; + +use super::CLONE_DOUBLE_REF; +use super::CLONE_ON_COPY; + +/// Checks for the `CLONE_ON_COPY` lint. +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, arg_ty: Ty<'_>) { + let ty = cx.typeck_results().expr_ty(expr); + if let ty::Ref(_, inner, _) = arg_ty.kind() { + if let ty::Ref(_, innermost, _) = inner.kind() { + span_lint_and_then( + cx, + CLONE_DOUBLE_REF, + expr.span, + &format!( + "using `clone` on a double-reference; \ + this will copy the reference of type `{}` instead of cloning the inner type", + ty + ), + |diag| { + if let Some(snip) = sugg::Sugg::hir_opt(cx, arg) { + let mut ty = innermost; + let mut n = 0; + while let ty::Ref(_, inner, _) = ty.kind() { + ty = inner; + n += 1; + } + let refs: String = iter::repeat('&').take(n + 1).collect(); + let derefs: String = iter::repeat('*').take(n).collect(); + let explicit = format!("<{}{}>::clone({})", refs, ty, snip); + diag.span_suggestion( + expr.span, + "try dereferencing it", + format!("{}({}{}).clone()", refs, derefs, snip.deref()), + Applicability::MaybeIncorrect, + ); + diag.span_suggestion( + expr.span, + "or try being explicit if you are sure, that you want to clone a reference", + explicit, + Applicability::MaybeIncorrect, + ); + } + }, + ); + return; // don't report clone_on_copy + } + } + + if is_copy(cx, ty) { + let snip; + if let Some(snippet) = sugg::Sugg::hir_opt(cx, arg) { + let parent = cx.tcx.hir().get_parent_node(expr.hir_id); + match &cx.tcx.hir().get(parent) { + hir::Node::Expr(parent) => match parent.kind { + // &*x is a nop, &x.clone() is not + hir::ExprKind::AddrOf(..) => return, + // (*x).func() is useless, x.clone().func() can work in case func borrows mutably + hir::ExprKind::MethodCall(_, _, parent_args, _) if expr.hir_id == parent_args[0].hir_id => { + return; + }, + + _ => {}, + }, + hir::Node::Stmt(stmt) => { + if let hir::StmtKind::Local(ref loc) = stmt.kind { + if let hir::PatKind::Ref(..) = loc.pat.kind { + // let ref y = *x borrows x, let ref y = x.clone() does not + return; + } + } + }, + _ => {}, + } + + // x.clone() might have dereferenced x, possibly through Deref impls + if cx.typeck_results().expr_ty(arg) == ty { + snip = Some(("try removing the `clone` call", format!("{}", snippet))); + } else { + let deref_count = cx + .typeck_results() + .expr_adjustments(arg) + .iter() + .filter(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_))) + .count(); + let derefs: String = iter::repeat('*').take(deref_count).collect(); + snip = Some(("try dereferencing it", format!("{}{}", derefs, snippet))); + } + } else { + snip = None; + } + span_lint_and_then( + cx, + CLONE_ON_COPY, + expr.span, + &format!("using `clone` on type `{}` which implements the `Copy` trait", ty), + |diag| { + if let Some((text, snip)) = snip { + diag.span_suggestion(expr.span, text, snip, Applicability::MachineApplicable); + } + }, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs b/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs new file mode 100644 index 0000000000..3d5a68d69d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs @@ -0,0 +1,36 @@ +use crate::utils::{is_type_diagnostic_item, match_type, paths, snippet_with_macro_callsite, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +use super::CLONE_ON_REF_PTR; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) { + let obj_ty = cx.typeck_results().expr_ty(arg).peel_refs(); + + if let ty::Adt(_, subst) = obj_ty.kind() { + let caller_type = if is_type_diagnostic_item(cx, obj_ty, sym::Rc) { + "Rc" + } else if is_type_diagnostic_item(cx, obj_ty, sym::Arc) { + "Arc" + } else if match_type(cx, obj_ty, &paths::WEAK_RC) || match_type(cx, obj_ty, &paths::WEAK_ARC) { + "Weak" + } else { + return; + }; + + let snippet = snippet_with_macro_callsite(cx, arg.span, ".."); + + span_lint_and_sugg( + cx, + CLONE_ON_REF_PTR, + expr.span, + "using `.clone()` on a ref-counted pointer", + "try this", + format!("{}::<{}>::clone(&{})", caller_type, subst.type_at(0), snippet), + Applicability::Unspecified, // Sometimes unnecessary ::<_> after Rc/Arc/Weak + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs new file mode 100644 index 0000000000..6866e9c652 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs @@ -0,0 +1,199 @@ +use crate::utils::{is_expn_of, is_type_diagnostic_item, snippet, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Span; +use rustc_span::symbol::sym; +use std::borrow::Cow; + +use super::EXPECT_FUN_CALL; + +/// Checks for the `EXPECT_FUN_CALL` lint. +#[allow(clippy::too_many_lines)] +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Span, name: &str, args: &[hir::Expr<'_>]) { + // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or + // `&str` + fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { + let mut arg_root = arg; + loop { + arg_root = match &arg_root.kind { + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, + hir::ExprKind::MethodCall(method_name, _, call_args, _) => { + if call_args.len() == 1 + && (method_name.ident.name == sym::as_str || method_name.ident.name == sym!(as_ref)) + && { + let arg_type = cx.typeck_results().expr_ty(&call_args[0]); + let base_type = arg_type.peel_refs(); + *base_type.kind() == ty::Str || is_type_diagnostic_item(cx, base_type, sym::string_type) + } + { + &call_args[0] + } else { + break; + } + }, + _ => break, + }; + } + arg_root + } + + // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be + // converted to string. + fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { + let arg_ty = cx.typeck_results().expr_ty(arg); + if is_type_diagnostic_item(cx, arg_ty, sym::string_type) { + return false; + } + if let ty::Ref(_, ty, ..) = arg_ty.kind() { + if *ty.kind() == ty::Str && can_be_static_str(cx, arg) { + return false; + } + }; + true + } + + // Check if an expression could have type `&'static str`, knowing that it + // has type `&str` for some lifetime. + fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { + match arg.kind { + hir::ExprKind::Lit(_) => true, + hir::ExprKind::Call(fun, _) => { + if let hir::ExprKind::Path(ref p) = fun.kind { + match cx.qpath_res(p, fun.hir_id) { + hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!( + cx.tcx.fn_sig(def_id).output().skip_binder().kind(), + ty::Ref(ty::ReStatic, ..) + ), + _ => false, + } + } else { + false + } + }, + hir::ExprKind::MethodCall(..) => { + cx.typeck_results() + .type_dependent_def_id(arg.hir_id) + .map_or(false, |method_id| { + matches!( + cx.tcx.fn_sig(method_id).output().skip_binder().kind(), + ty::Ref(ty::ReStatic, ..) + ) + }) + }, + hir::ExprKind::Path(ref p) => matches!( + cx.qpath_res(p, arg.hir_id), + hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _) + ), + _ => false, + } + } + + fn generate_format_arg_snippet( + cx: &LateContext<'_>, + a: &hir::Expr<'_>, + applicability: &mut Applicability, + ) -> Vec { + if_chain! { + if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind; + if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind; + if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind; + + then { + format_arg_expr_tup + .iter() + .map(|a| snippet_with_applicability(cx, a.span, "..", applicability).into_owned()) + .collect() + } else { + unreachable!() + } + } + } + + fn is_call(node: &hir::ExprKind<'_>) -> bool { + match node { + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => { + is_call(&expr.kind) + }, + hir::ExprKind::Call(..) + | hir::ExprKind::MethodCall(..) + // These variants are debatable or require further examination + | hir::ExprKind::If(..) + | hir::ExprKind::Match(..) + | hir::ExprKind::Block{ .. } => true, + _ => false, + } + } + + if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) { + return; + } + + let receiver_type = cx.typeck_results().expr_ty_adjusted(&args[0]); + let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::option_type) { + "||" + } else if is_type_diagnostic_item(cx, receiver_type, sym::result_type) { + "|_|" + } else { + return; + }; + + let arg_root = get_arg_root(cx, &args[1]); + + let span_replace_word = method_span.with_hi(expr.span.hi()); + + let mut applicability = Applicability::MachineApplicable; + + //Special handling for `format!` as arg_root + if_chain! { + if let hir::ExprKind::Block(block, None) = &arg_root.kind; + if block.stmts.len() == 1; + if let hir::StmtKind::Local(local) = &block.stmts[0].kind; + if let Some(arg_root) = &local.init; + if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind; + if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1; + if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind; + then { + let fmt_spec = &format_args[0]; + let fmt_args = &format_args[1]; + + let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()]; + + args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability)); + + let sugg = args.join(", "); + + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + &format!("use of `{}` followed by a function call", name), + "try this", + format!("unwrap_or_else({} panic!({}))", closure_args, sugg), + applicability, + ); + + return; + } + } + + let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability); + if requires_to_string(cx, arg_root) { + arg_root_snippet.to_mut().push_str(".to_string()"); + } + + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + &format!("use of `{}` followed by a function call", name), + "try this", + format!( + "unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})", + closure_args, arg_root_snippet + ), + applicability, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_used.rs b/src/tools/clippy/clippy_lints/src/methods/expect_used.rs new file mode 100644 index 0000000000..90b781bd9d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/expect_used.rs @@ -0,0 +1,30 @@ +use crate::utils::{is_type_diagnostic_item, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::EXPECT_USED; + +/// lint use of `expect()` for `Option`s and `Result`s +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, expect_args: &[hir::Expr<'_>]) { + let obj_ty = cx.typeck_results().expr_ty(&expect_args[0]).peel_refs(); + + let mess = if is_type_diagnostic_item(cx, obj_ty, sym::option_type) { + Some((EXPECT_USED, "an Option", "None")) + } else if is_type_diagnostic_item(cx, obj_ty, sym::result_type) { + Some((EXPECT_USED, "a Result", "Err")) + } else { + None + }; + + if let Some((lint, kind, none_value)) = mess { + span_lint_and_help( + cx, + lint, + expr.span, + &format!("used `expect()` on `{}` value", kind,), + None, + &format!("if this value is an `{}`, it will panic", none_value,), + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/filetype_is_file.rs b/src/tools/clippy/clippy_lints/src/methods/filetype_is_file.rs new file mode 100644 index 0000000000..b03835f97e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/filetype_is_file.rs @@ -0,0 +1,39 @@ +use crate::utils::{get_parent_expr, match_type, paths, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use super::FILETYPE_IS_FILE; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let ty = cx.typeck_results().expr_ty(&args[0]); + + if !match_type(cx, ty, &paths::FILE_TYPE) { + return; + } + + let span: Span; + let verb: &str; + let lint_unary: &str; + let help_unary: &str; + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let hir::ExprKind::Unary(op, _) = parent.kind; + if op == hir::UnOp::Not; + then { + lint_unary = "!"; + verb = "denies"; + help_unary = ""; + span = parent.span; + } else { + lint_unary = ""; + verb = "covers"; + help_unary = "!"; + span = expr.span; + } + } + let lint_msg = format!("`{}FileType::is_file()` only {} regular files", lint_unary, verb); + let help_msg = format!("use `{}FileType::is_dir()` instead", help_unary); + span_lint_and_help(cx, FILETYPE_IS_FILE, span, &lint_msg, None, &help_msg); +} diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_flat_map.rs b/src/tools/clippy/clippy_lints/src/methods/filter_flat_map.rs new file mode 100644 index 0000000000..8da867fce5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/filter_flat_map.rs @@ -0,0 +1,21 @@ +use crate::utils::{match_trait_method, paths, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::FILTER_MAP; + +/// lint use of `filter().flat_map()` for `Iterators` +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + _filter_args: &'tcx [hir::Expr<'_>], + _map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter().flat_map()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter(..).flat_map(..)` on an `Iterator`"; + let hint = "this is more succinctly expressed by calling `.flat_map(..)` \ + and filtering by returning `iter::empty()`"; + span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs new file mode 100644 index 0000000000..f559160004 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs @@ -0,0 +1,85 @@ +use crate::utils::{match_trait_method, path_to_local_id, paths, snippet, span_lint_and_sugg, SpanlessEq}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::{Expr, ExprKind, PatKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty::TyS; +use rustc_span::symbol::sym; + +use super::MANUAL_FILTER_MAP; +use super::MANUAL_FIND_MAP; + +/// lint use of `filter().map()` or `find().map()` for `Iterators` +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool) { + if_chain! { + if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind; + if let ExprKind::MethodCall(_, _, [_, filter_arg], filter_span) = map_recv.kind; + if match_trait_method(cx, map_recv, &paths::ITERATOR); + + // filter(|x| ...is_some())... + if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind; + let filter_body = cx.tcx.hir().body(filter_body_id); + if let [filter_param] = filter_body.params; + // optional ref pattern: `filter(|&x| ..)` + let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { + (ref_pat, true) + } else { + (filter_param.pat, false) + }; + // closure ends with is_some() or is_ok() + if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; + if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind; + if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def(); + if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) { + Some(false) + } else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) { + Some(true) + } else { + None + }; + if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; + + // ...map(|x| ...unwrap()) + if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind; + let map_body = cx.tcx.hir().body(map_body_id); + if let [map_param] = map_body.params; + if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; + // closure ends with expect() or unwrap() + if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind; + if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); + + let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { + // in `filter(|x| ..)`, replace `*x` with `x` + let a_path = if_chain! { + if !is_filter_param_ref; + if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; + then { expr_path } else { a } + }; + // let the filter closure arg and the map closure arg be equal + if_chain! { + if path_to_local_id(a_path, filter_param_id); + if path_to_local_id(b, map_param_id); + if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b)); + then { + return true; + } + } + false + }; + if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg); + then { + let span = filter_span.to(map_span); + let (filter_name, lint) = if is_find { + ("find", MANUAL_FIND_MAP) + } else { + ("filter", MANUAL_FILTER_MAP) + }; + let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name); + let to_opt = if is_result { ".ok()" } else { "" }; + let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident, + snippet(cx, map_arg.span, ".."), to_opt); + span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map_flat_map.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map_flat_map.rs new file mode 100644 index 0000000000..a6db138623 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map_flat_map.rs @@ -0,0 +1,21 @@ +use crate::utils::{match_trait_method, paths, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::FILTER_MAP; + +/// lint use of `filter_map().flat_map()` for `Iterators` +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + _filter_args: &'tcx [hir::Expr<'_>], + _map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter_map().flat_map()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter_map(..).flat_map(..)` on an `Iterator`"; + let hint = "this is more succinctly expressed by calling `.flat_map(..)` \ + and filtering by returning `iter::empty()`"; + span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map_identity.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map_identity.rs new file mode 100644 index 0000000000..9e646360a4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map_identity.rs @@ -0,0 +1,52 @@ +use crate::utils::{match_qpath, match_trait_method, path_to_local_id, paths, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use super::FILTER_MAP_IDENTITY; + +pub(super) fn check( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + filter_map_args: &[hir::Expr<'_>], + filter_map_span: Span, +) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + let arg_node = &filter_map_args[1].kind; + + let apply_lint = |message: &str| { + span_lint_and_sugg( + cx, + FILTER_MAP_IDENTITY, + filter_map_span.with_hi(expr.span.hi()), + message, + "try", + "flatten()".to_string(), + Applicability::MachineApplicable, + ); + }; + + if_chain! { + if let hir::ExprKind::Closure(_, _, body_id, _, _) = arg_node; + let body = cx.tcx.hir().body(*body_id); + + if let hir::PatKind::Binding(_, binding_id, ..) = body.params[0].pat.kind; + if path_to_local_id(&body.value, binding_id); + then { + apply_lint("called `filter_map(|x| x)` on an `Iterator`"); + } + } + + if_chain! { + if let hir::ExprKind::Path(ref qpath) = arg_node; + + if match_qpath(qpath, &paths::STD_CONVERT_IDENTITY); + + then { + apply_lint("called `filter_map(std::convert::identity)` on an `Iterator`"); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map_map.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map_map.rs new file mode 100644 index 0000000000..d015b4c7b3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map_map.rs @@ -0,0 +1,20 @@ +use crate::utils::{match_trait_method, paths, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::FILTER_MAP; + +/// lint use of `filter_map().map()` for `Iterators` +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + _filter_args: &'tcx [hir::Expr<'_>], + _map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter_map().map()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter_map(..).map(..)` on an `Iterator`"; + let hint = "this is more succinctly expressed by only calling `.filter_map(..)` instead"; + span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map_next.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map_next.rs new file mode 100644 index 0000000000..a789df922f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map_next.rs @@ -0,0 +1,40 @@ +use crate::utils::{match_trait_method, meets_msrv, paths, snippet, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_semver::RustcVersion; + +use super::FILTER_MAP_NEXT; + +const FILTER_MAP_NEXT_MSRV: RustcVersion = RustcVersion::new(1, 30, 0); + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + filter_args: &'tcx [hir::Expr<'_>], + msrv: Option<&RustcVersion>, +) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + if !meets_msrv(msrv, &FILTER_MAP_NEXT_MSRV) { + return; + } + + let msg = "called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ + `.find_map(..)` instead"; + let filter_snippet = snippet(cx, filter_args[1].span, ".."); + if filter_snippet.lines().count() <= 1 { + let iter_snippet = snippet(cx, filter_args[0].span, ".."); + span_lint_and_sugg( + cx, + FILTER_MAP_NEXT, + expr.span, + msg, + "try this", + format!("{}.find_map({})", iter_snippet, filter_snippet), + Applicability::MachineApplicable, + ); + } else { + span_lint(cx, FILTER_MAP_NEXT, expr.span, msg); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_next.rs b/src/tools/clippy/clippy_lints/src/methods/filter_next.rs new file mode 100644 index 0000000000..81619e7301 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/filter_next.rs @@ -0,0 +1,31 @@ +use crate::utils::{match_trait_method, paths, snippet, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::FILTER_NEXT; + +/// lint use of `filter().next()` for `Iterators` +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, filter_args: &'tcx [hir::Expr<'_>]) { + // lint if caller of `.filter().next()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ + `.find(..)` instead"; + let filter_snippet = snippet(cx, filter_args[1].span, ".."); + if filter_snippet.lines().count() <= 1 { + let iter_snippet = snippet(cx, filter_args[0].span, ".."); + // add note if not multi-line + span_lint_and_sugg( + cx, + FILTER_NEXT, + expr.span, + msg, + "try this", + format!("{}.find({})", iter_snippet, filter_snippet), + Applicability::MachineApplicable, + ); + } else { + span_lint(cx, FILTER_NEXT, expr.span, msg); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/flat_map_identity.rs b/src/tools/clippy/clippy_lints/src/methods/flat_map_identity.rs new file mode 100644 index 0000000000..ce3194f8a2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/flat_map_identity.rs @@ -0,0 +1,57 @@ +use crate::utils::{match_qpath, match_trait_method, paths, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use super::FLAT_MAP_IDENTITY; + +/// lint use of `flat_map` for `Iterators` where `flatten` would be sufficient +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + flat_map_args: &'tcx [hir::Expr<'_>], + flat_map_span: Span, +) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + let arg_node = &flat_map_args[1].kind; + + let apply_lint = |message: &str| { + span_lint_and_sugg( + cx, + FLAT_MAP_IDENTITY, + flat_map_span.with_hi(expr.span.hi()), + message, + "try", + "flatten()".to_string(), + Applicability::MachineApplicable, + ); + }; + + if_chain! { + if let hir::ExprKind::Closure(_, _, body_id, _, _) = arg_node; + let body = cx.tcx.hir().body(*body_id); + + if let hir::PatKind::Binding(_, _, binding_ident, _) = body.params[0].pat.kind; + if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = body.value.kind; + + if path.segments.len() == 1; + if path.segments[0].ident.name == binding_ident.name; + + then { + apply_lint("called `flat_map(|x| x)` on an `Iterator`"); + } + } + + if_chain! { + if let hir::ExprKind::Path(ref qpath) = arg_node; + + if match_qpath(qpath, &paths::STD_CONVERT_IDENTITY); + + then { + apply_lint("called `flat_map(std::convert::identity)` on an `Iterator`"); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/from_iter_instead_of_collect.rs b/src/tools/clippy/clippy_lints/src/methods/from_iter_instead_of_collect.rs new file mode 100644 index 0000000000..e50d0a3340 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/from_iter_instead_of_collect.rs @@ -0,0 +1,67 @@ +use crate::utils::{get_trait_def_id, implements_trait, paths, span_lint_and_sugg, sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::ty::Ty; + +use super::FROM_ITER_INSTEAD_OF_COLLECT; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let ty = cx.typeck_results().expr_ty(expr); + let arg_ty = cx.typeck_results().expr_ty(&args[0]); + + if_chain! { + if let Some(from_iter_id) = get_trait_def_id(cx, &paths::FROM_ITERATOR); + if let Some(iter_id) = get_trait_def_id(cx, &paths::ITERATOR); + + if implements_trait(cx, ty, from_iter_id, &[]) && implements_trait(cx, arg_ty, iter_id, &[]); + then { + // `expr` implements `FromIterator` trait + let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_par(); + let turbofish = extract_turbofish(cx, expr, ty); + let sugg = format!("{}.collect::<{}>()", iter_expr, turbofish); + span_lint_and_sugg( + cx, + FROM_ITER_INSTEAD_OF_COLLECT, + expr.span, + "usage of `FromIterator::from_iter`", + "use `.collect()` instead of `::from_iter()`", + sugg, + Applicability::MaybeIncorrect, + ); + } + } +} + +fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -> String { + if_chain! { + let call_site = expr.span.source_callsite(); + if let Ok(snippet) = cx.sess().source_map().span_to_snippet(call_site); + let snippet_split = snippet.split("::").collect::>(); + if let Some((_, elements)) = snippet_split.split_last(); + + then { + // is there a type specifier? (i.e.: like `` in `collections::BTreeSet::::`) + if let Some(type_specifier) = snippet_split.iter().find(|e| e.starts_with('<') && e.ends_with('>')) { + // remove the type specifier from the path elements + let without_ts = elements.iter().filter_map(|e| { + if e == type_specifier { None } else { Some((*e).to_string()) } + }).collect::>(); + // join and add the type specifier at the end (i.e.: `collections::BTreeSet`) + format!("{}{}", without_ts.join("::"), type_specifier) + } else { + // type is not explicitly specified so wildcards are needed + // i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>` + let ty_str = ty.to_string(); + let start = ty_str.find('<').unwrap_or(0); + let end = ty_str.find('>').unwrap_or_else(|| ty_str.len()); + let nb_wildcard = ty_str[start..end].split(',').count(); + let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1)); + format!("{}<{}>", elements.join("::"), wildcards) + } + } else { + ty.to_string() + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/get_unwrap.rs b/src/tools/clippy/clippy_lints/src/methods/get_unwrap.rs new file mode 100644 index 0000000000..e157db2712 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/get_unwrap.rs @@ -0,0 +1,84 @@ +use crate::methods::derefs_to_slice; +use crate::utils::{ + get_parent_expr, is_type_diagnostic_item, match_type, paths, snippet_with_applicability, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::GET_UNWRAP; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, get_args: &'tcx [hir::Expr<'_>], is_mut: bool) { + // Note: we don't want to lint `get_mut().unwrap` for `HashMap` or `BTreeMap`, + // because they do not implement `IndexMut` + let mut applicability = Applicability::MachineApplicable; + let expr_ty = cx.typeck_results().expr_ty(&get_args[0]); + let get_args_str = if get_args.len() > 1 { + snippet_with_applicability(cx, get_args[1].span, "..", &mut applicability) + } else { + return; // not linting on a .get().unwrap() chain or variant + }; + let mut needs_ref; + let caller_type = if derefs_to_slice(cx, &get_args[0], expr_ty).is_some() { + needs_ref = get_args_str.parse::().is_ok(); + "slice" + } else if is_type_diagnostic_item(cx, expr_ty, sym::vec_type) { + needs_ref = get_args_str.parse::().is_ok(); + "Vec" + } else if is_type_diagnostic_item(cx, expr_ty, sym::vecdeque_type) { + needs_ref = get_args_str.parse::().is_ok(); + "VecDeque" + } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::hashmap_type) { + needs_ref = true; + "HashMap" + } else if !is_mut && match_type(cx, expr_ty, &paths::BTREEMAP) { + needs_ref = true; + "BTreeMap" + } else { + return; // caller is not a type that we want to lint + }; + + let mut span = expr.span; + + // Handle the case where the result is immediately dereferenced + // by not requiring ref and pulling the dereference into the + // suggestion. + if_chain! { + if needs_ref; + if let Some(parent) = get_parent_expr(cx, expr); + if let hir::ExprKind::Unary(hir::UnOp::Deref, _) = parent.kind; + then { + needs_ref = false; + span = parent.span; + } + } + + let mut_str = if is_mut { "_mut" } else { "" }; + let borrow_str = if !needs_ref { + "" + } else if is_mut { + "&mut " + } else { + "&" + }; + + span_lint_and_sugg( + cx, + GET_UNWRAP, + span, + &format!( + "called `.get{0}().unwrap()` on a {1}. Using `[]` is more clear and more concise", + mut_str, caller_type + ), + "try this", + format!( + "{}{}[{}]", + borrow_str, + snippet_with_applicability(cx, get_args[0].span, "..", &mut applicability), + get_args_str + ), + applicability, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs b/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs new file mode 100644 index 0000000000..a769493d11 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs @@ -0,0 +1,32 @@ +use crate::utils::span_lint_and_sugg; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::ExprKind; +use rustc_lint::LateContext; +use rustc_middle::ty::TyS; +use rustc_span::symbol::Symbol; + +use super::IMPLICIT_CLONE; +use clippy_utils::is_diagnostic_assoc_item; + +pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, trait_diagnostic: Symbol) { + if_chain! { + if let ExprKind::MethodCall(method_path, _, [arg], _) = &expr.kind; + let return_type = cx.typeck_results().expr_ty(&expr); + let input_type = cx.typeck_results().expr_ty(arg).peel_refs(); + if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did)); + if TyS::same_type(return_type, input_type); + if is_diagnostic_assoc_item(cx, expr_def_id, trait_diagnostic); + then { + span_lint_and_sugg( + cx,IMPLICIT_CLONE,method_path.ident.span, + &format!("implicitly cloning a `{}` by calling `{}` on its dereferenced type", ty_name, method_path.ident.name), + "consider using", + "clone".to_string(), + Applicability::MachineApplicable + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/inefficient_to_string.rs b/src/tools/clippy/clippy_lints/src/methods/inefficient_to_string.rs new file mode 100644 index 0000000000..3045b09c23 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/inefficient_to_string.rs @@ -0,0 +1,63 @@ +use super::INEFFICIENT_TO_STRING; +use crate::utils::{ + is_type_diagnostic_item, match_def_path, paths, snippet_with_applicability, span_lint_and_then, walk_ptrs_ty_depth, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::sym; + +/// Checks for the `INEFFICIENT_TO_STRING` lint +pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, arg_ty: Ty<'tcx>) { + if_chain! { + if let Some(to_string_meth_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if match_def_path(cx, to_string_meth_did, &paths::TO_STRING_METHOD); + if let Some(substs) = cx.typeck_results().node_substs_opt(expr.hir_id); + let self_ty = substs.type_at(0); + let (deref_self_ty, deref_count) = walk_ptrs_ty_depth(self_ty); + if deref_count >= 1; + if specializes_tostring(cx, deref_self_ty); + then { + span_lint_and_then( + cx, + INEFFICIENT_TO_STRING, + expr.span, + &format!("calling `to_string` on `{}`", arg_ty), + |diag| { + diag.help(&format!( + "`{}` implements `ToString` through a slower blanket impl, but `{}` has a fast specialization of `ToString`", + self_ty, deref_self_ty + )); + let mut applicability = Applicability::MachineApplicable; + let arg_snippet = snippet_with_applicability(cx, arg.span, "..", &mut applicability); + diag.span_suggestion( + expr.span, + "try dereferencing the receiver", + format!("({}{}).to_string()", "*".repeat(deref_count), arg_snippet), + applicability, + ); + }, + ); + } + } +} + +/// Returns whether `ty` specializes `ToString`. +/// Currently, these are `str`, `String`, and `Cow<'_, str>`. +fn specializes_tostring(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + if let ty::Str = ty.kind() { + return true; + } + + if is_type_diagnostic_item(cx, ty, sym::string_type) { + return true; + } + + if let ty::Adt(adt, substs) = ty.kind() { + match_def_path(cx, adt.did, &paths::COW) && substs.type_at(1).is_str() + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/inspect_for_each.rs b/src/tools/clippy/clippy_lints/src/methods/inspect_for_each.rs new file mode 100644 index 0000000000..959457a5bf --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/inspect_for_each.rs @@ -0,0 +1,23 @@ +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use crate::utils::{match_trait_method, paths, span_lint_and_help}; + +use super::INSPECT_FOR_EACH; + +/// lint use of `inspect().for_each()` for `Iterators` +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, inspect_span: Span) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `inspect(..).for_each(..)` on an `Iterator`"; + let hint = "move the code from `inspect(..)` to `for_each(..)` and remove the `inspect(..)`"; + span_lint_and_help( + cx, + INSPECT_FOR_EACH, + inspect_span.with_hi(expr.span.hi()), + msg, + None, + hint, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/into_iter_on_ref.rs b/src/tools/clippy/clippy_lints/src/methods/into_iter_on_ref.rs new file mode 100644 index 0000000000..1e8315dbee --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/into_iter_on_ref.rs @@ -0,0 +1,43 @@ +use crate::utils::{has_iter_method, match_trait_method, paths, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::source_map::Span; +use rustc_span::symbol::Symbol; + +use super::INTO_ITER_ON_REF; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, self_ref_ty: Ty<'_>, method_span: Span) { + if !match_trait_method(cx, expr, &paths::INTO_ITERATOR) { + return; + } + if let Some((kind, method_name)) = ty_has_iter_method(cx, self_ref_ty) { + span_lint_and_sugg( + cx, + INTO_ITER_ON_REF, + method_span, + &format!( + "this `.into_iter()` call is equivalent to `.{}()` and will not consume the `{}`", + method_name, kind, + ), + "call directly", + method_name.to_string(), + Applicability::MachineApplicable, + ); + } +} + +fn ty_has_iter_method(cx: &LateContext<'_>, self_ref_ty: Ty<'_>) -> Option<(Symbol, &'static str)> { + has_iter_method(cx, self_ref_ty).map(|ty_name| { + let mutbl = match self_ref_ty.kind() { + ty::Ref(_, _, mutbl) => mutbl, + _ => unreachable!(), + }; + let method_name = match mutbl { + hir::Mutability::Not => "iter", + hir::Mutability::Mut => "iter_mut", + }; + (ty_name, method_name) + }) +} diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_cloned_collect.rs b/src/tools/clippy/clippy_lints/src/methods/iter_cloned_collect.rs new file mode 100644 index 0000000000..c3e48ffa5f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/iter_cloned_collect.rs @@ -0,0 +1,30 @@ +use crate::methods::derefs_to_slice; +use crate::utils::{is_type_diagnostic_item, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::ITER_CLONED_COLLECT; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) { + if_chain! { + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::vec_type); + if let Some(slice) = derefs_to_slice(cx, &iter_args[0], cx.typeck_results().expr_ty(&iter_args[0])); + if let Some(to_replace) = expr.span.trim_start(slice.span.source_callsite()); + + then { + span_lint_and_sugg( + cx, + ITER_CLONED_COLLECT, + to_replace, + "called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \ + more readable", + "try", + ".to_vec()".to_string(), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_count.rs b/src/tools/clippy/clippy_lints/src/methods/iter_count.rs new file mode 100644 index 0000000000..869440e016 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/iter_count.rs @@ -0,0 +1,47 @@ +use crate::methods::derefs_to_slice; +use crate::utils::{is_type_diagnostic_item, match_type, paths, snippet_with_applicability, span_lint_and_sugg}; + +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::ITER_COUNT; + +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'tcx [Expr<'tcx>], iter_method: &str) { + let ty = cx.typeck_results().expr_ty(&iter_args[0]); + let caller_type = if derefs_to_slice(cx, &iter_args[0], ty).is_some() { + "slice" + } else if is_type_diagnostic_item(cx, ty, sym::vec_type) { + "Vec" + } else if is_type_diagnostic_item(cx, ty, sym::vecdeque_type) { + "VecDeque" + } else if is_type_diagnostic_item(cx, ty, sym::hashset_type) { + "HashSet" + } else if is_type_diagnostic_item(cx, ty, sym::hashmap_type) { + "HashMap" + } else if match_type(cx, ty, &paths::BTREEMAP) { + "BTreeMap" + } else if match_type(cx, ty, &paths::BTREESET) { + "BTreeSet" + } else if match_type(cx, ty, &paths::LINKED_LIST) { + "LinkedList" + } else if match_type(cx, ty, &paths::BINARY_HEAP) { + "BinaryHeap" + } else { + return; + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_COUNT, + expr.span, + &format!("called `.{}().count()` on a `{}`", iter_method, caller_type), + "try", + format!( + "{}.len()", + snippet_with_applicability(cx, iter_args[0].span, "..", &mut applicability), + ), + applicability, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_next_slice.rs b/src/tools/clippy/clippy_lints/src/methods/iter_next_slice.rs new file mode 100644 index 0000000000..3c03a949cf --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/iter_next_slice.rs @@ -0,0 +1,68 @@ +use crate::methods::derefs_to_slice; +use crate::utils::{get_parent_expr, higher, is_type_diagnostic_item, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +use super::ITER_NEXT_SLICE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) { + let caller_expr = &iter_args[0]; + + // Skip lint if the `iter().next()` expression is a for loop argument, + // since it is already covered by `&loops::ITER_NEXT_LOOP` + let mut parent_expr_opt = get_parent_expr(cx, expr); + while let Some(parent_expr) = parent_expr_opt { + if higher::for_loop(parent_expr).is_some() { + return; + } + parent_expr_opt = get_parent_expr(cx, parent_expr); + } + + if derefs_to_slice(cx, caller_expr, cx.typeck_results().expr_ty(caller_expr)).is_some() { + // caller is a Slice + if_chain! { + if let hir::ExprKind::Index(ref caller_var, ref index_expr) = &caller_expr.kind; + if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen }) + = higher::range(index_expr); + if let hir::ExprKind::Lit(ref start_lit) = &start_expr.kind; + if let ast::LitKind::Int(start_idx, _) = start_lit.node; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NEXT_SLICE, + expr.span, + "using `.iter().next()` on a Slice without end index", + "try calling", + format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx), + applicability, + ); + } + } + } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(caller_expr), sym::vec_type) + || matches!( + &cx.typeck_results().expr_ty(caller_expr).peel_refs().kind(), + ty::Array(_, _) + ) + { + // caller is a Vec or an Array + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NEXT_SLICE, + expr.span, + "using `.iter().next()` on an array", + "try calling", + format!( + "{}.get(0)", + snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability) + ), + applicability, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_nth.rs b/src/tools/clippy/clippy_lints/src/methods/iter_nth.rs new file mode 100644 index 0000000000..cc3e56ea87 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/iter_nth.rs @@ -0,0 +1,38 @@ +use crate::methods::derefs_to_slice; +use crate::methods::iter_nth_zero; +use crate::utils::{is_type_diagnostic_item, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use super::ITER_NTH; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + nth_and_iter_args: &[&'tcx [hir::Expr<'tcx>]], + is_mut: bool, +) { + let iter_args = nth_and_iter_args[1]; + let mut_str = if is_mut { "_mut" } else { "" }; + let caller_type = if derefs_to_slice(cx, &iter_args[0], cx.typeck_results().expr_ty(&iter_args[0])).is_some() { + "slice" + } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym::vec_type) { + "Vec" + } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym::vecdeque_type) { + "VecDeque" + } else { + let nth_args = nth_and_iter_args[0]; + iter_nth_zero::check(cx, expr, &nth_args); + return; // caller is not a type that we want to lint + }; + + span_lint_and_help( + cx, + ITER_NTH, + expr.span, + &format!("called `.iter{0}().nth()` on a {1}", mut_str, caller_type), + None, + &format!("calling `.get{}()` is both faster and more readable", mut_str), + ); +} diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_nth_zero.rs b/src/tools/clippy/clippy_lints/src/methods/iter_nth_zero.rs new file mode 100644 index 0000000000..247192d81f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/iter_nth_zero.rs @@ -0,0 +1,27 @@ +use crate::consts::{constant, Constant}; +use crate::utils::{match_trait_method, paths, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::ITER_NTH_ZERO; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, nth_args: &'tcx [hir::Expr<'_>]) { + if_chain! { + if match_trait_method(cx, expr, &paths::ITERATOR); + if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), &nth_args[1]); + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NTH_ZERO, + expr.span, + "called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent", + "try calling `.next()` instead of `.nth(0)`", + format!("{}.next()", snippet_with_applicability(cx, nth_args[0].span, "..", &mut applicability)), + applicability, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_skip_next.rs b/src/tools/clippy/clippy_lints/src/methods/iter_skip_next.rs new file mode 100644 index 0000000000..5f5969134e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/iter_skip_next.rs @@ -0,0 +1,24 @@ +use crate::utils::{match_trait_method, paths, snippet, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::ITER_SKIP_NEXT; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, skip_args: &[hir::Expr<'_>]) { + // lint if caller of skip is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + if let [caller, n] = skip_args { + let hint = format!(".nth({})", snippet(cx, n.span, "..")); + span_lint_and_sugg( + cx, + ITER_SKIP_NEXT, + expr.span.trim_start(caller.span).unwrap(), + "called `skip(..).next()` on an iterator", + "use `nth` instead", + hint, + Applicability::MachineApplicable, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/iterator_step_by_zero.rs b/src/tools/clippy/clippy_lints/src/methods/iterator_step_by_zero.rs new file mode 100644 index 0000000000..3e05d7f76b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/iterator_step_by_zero.rs @@ -0,0 +1,19 @@ +use crate::consts::{constant, Constant}; +use crate::utils::{match_trait_method, paths, span_lint}; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::ITERATOR_STEP_BY_ZERO; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, args: &'tcx [hir::Expr<'_>]) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), &args[1]) { + span_lint( + cx, + ITERATOR_STEP_BY_ZERO, + expr.span, + "`Iterator::step_by(0)` will panic at runtime", + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/src/tools/clippy/clippy_lints/src/methods/manual_saturating_arithmetic.rs new file mode 100644 index 0000000000..0b414e0eb9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/manual_saturating_arithmetic.rs @@ -0,0 +1,175 @@ +use crate::utils::{match_qpath, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_target::abi::LayoutOf; + +pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[&[hir::Expr<'_>]], arith: &str) { + let unwrap_arg = &args[0][1]; + let arith_lhs = &args[1][0]; + let arith_rhs = &args[1][1]; + + let ty = cx.typeck_results().expr_ty(arith_lhs); + if !ty.is_integral() { + return; + } + + let mm = if let Some(mm) = is_min_or_max(cx, unwrap_arg) { + mm + } else { + return; + }; + + if ty.is_signed() { + use self::{ + MinMax::{Max, Min}, + Sign::{Neg, Pos}, + }; + + let sign = if let Some(sign) = lit_sign(arith_rhs) { + sign + } else { + return; + }; + + match (arith, sign, mm) { + ("add", Pos, Max) | ("add", Neg, Min) | ("sub", Neg, Max) | ("sub", Pos, Min) => (), + // "mul" is omitted because lhs can be negative. + _ => return, + } + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + super::MANUAL_SATURATING_ARITHMETIC, + expr.span, + "manual saturating arithmetic", + &format!("try using `saturating_{}`", arith), + format!( + "{}.saturating_{}({})", + snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability), + arith, + snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability), + ), + applicability, + ); + } else { + match (mm, arith) { + (MinMax::Max, "add" | "mul") | (MinMax::Min, "sub") => (), + _ => return, + } + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + super::MANUAL_SATURATING_ARITHMETIC, + expr.span, + "manual saturating arithmetic", + &format!("try using `saturating_{}`", arith), + format!( + "{}.saturating_{}({})", + snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability), + arith, + snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability), + ), + applicability, + ); + } +} + +#[derive(PartialEq, Eq)] +enum MinMax { + Min, + Max, +} + +fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option { + // `T::max_value()` `T::min_value()` inherent methods + if_chain! { + if let hir::ExprKind::Call(func, args) = &expr.kind; + if args.is_empty(); + if let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind; + then { + match &*segment.ident.as_str() { + "max_value" => return Some(MinMax::Max), + "min_value" => return Some(MinMax::Min), + _ => {} + } + } + } + + let ty = cx.typeck_results().expr_ty(expr); + let ty_str = ty.to_string(); + + // `std::T::MAX` `std::T::MIN` constants + if let hir::ExprKind::Path(path) = &expr.kind { + if match_qpath(path, &["core", &ty_str, "MAX"][..]) { + return Some(MinMax::Max); + } + + if match_qpath(path, &["core", &ty_str, "MIN"][..]) { + return Some(MinMax::Min); + } + } + + // Literals + let bits = cx.layout_of(ty).unwrap().size.bits(); + let (minval, maxval): (u128, u128) = if ty.is_signed() { + let minval = 1 << (bits - 1); + let mut maxval = !(1 << (bits - 1)); + if bits != 128 { + maxval &= (1 << bits) - 1; + } + (minval, maxval) + } else { + (0, if bits == 128 { !0 } else { (1 << bits) - 1 }) + }; + + let check_lit = |expr: &hir::Expr<'_>, check_min: bool| { + if let hir::ExprKind::Lit(lit) = &expr.kind { + if let ast::LitKind::Int(value, _) = lit.node { + if value == maxval { + return Some(MinMax::Max); + } + + if check_min && value == minval { + return Some(MinMax::Min); + } + } + } + + None + }; + + if let r @ Some(_) = check_lit(expr, !ty.is_signed()) { + return r; + } + + if ty.is_signed() { + if let hir::ExprKind::Unary(hir::UnOp::Neg, val) = &expr.kind { + return check_lit(val, true); + } + } + + None +} + +#[derive(PartialEq, Eq)] +enum Sign { + Pos, + Neg, +} + +fn lit_sign(expr: &hir::Expr<'_>) -> Option { + if let hir::ExprKind::Unary(hir::UnOp::Neg, inner) = &expr.kind { + if let hir::ExprKind::Lit(..) = &inner.kind { + return Some(Sign::Neg); + } + } else if let hir::ExprKind::Lit(..) = &expr.kind { + return Some(Sign::Pos); + } + + None +} diff --git a/src/tools/clippy/clippy_lints/src/methods/map_collect_result_unit.rs b/src/tools/clippy/clippy_lints/src/methods/map_collect_result_unit.rs new file mode 100644 index 0000000000..5b20e268d9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/map_collect_result_unit.rs @@ -0,0 +1,45 @@ +use crate::utils::{is_type_diagnostic_item, match_trait_method, paths, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +use super::MAP_COLLECT_RESULT_UNIT; + +pub(super) fn check( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + map_args: &[hir::Expr<'_>], + collect_args: &[hir::Expr<'_>], +) { + if_chain! { + // called on Iterator + if let [map_expr] = collect_args; + if match_trait_method(cx, map_expr, &paths::ITERATOR); + // return of collect `Result<(),_>` + let collect_ret_ty = cx.typeck_results().expr_ty(expr); + if is_type_diagnostic_item(cx, collect_ret_ty, sym::result_type); + if let ty::Adt(_, substs) = collect_ret_ty.kind(); + if let Some(result_t) = substs.types().next(); + if result_t.is_unit(); + // get parts for snippet + if let [iter, map_fn] = map_args; + then { + span_lint_and_sugg( + cx, + MAP_COLLECT_RESULT_UNIT, + expr.span, + "`.map().collect()` can be replaced with `.try_for_each()`", + "try this", + format!( + "{}.try_for_each({})", + snippet(cx, iter.span, ".."), + snippet(cx, map_fn.span, "..") + ), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs b/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs new file mode 100644 index 0000000000..14a14e4f9e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs @@ -0,0 +1,61 @@ +use crate::utils::{is_type_diagnostic_item, match_trait_method, paths, snippet, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +use super::MAP_FLATTEN; + +/// lint use of `map().flatten()` for `Iterators` and 'Options' +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>]) { + // lint if caller of `.map().flatten()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let map_closure_ty = cx.typeck_results().expr_ty(&map_args[1]); + let is_map_to_option = match map_closure_ty.kind() { + ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { + let map_closure_sig = match map_closure_ty.kind() { + ty::Closure(_, substs) => substs.as_closure().sig(), + _ => map_closure_ty.fn_sig(cx.tcx), + }; + let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output()); + is_type_diagnostic_item(cx, map_closure_return_ty, sym::option_type) + }, + _ => false, + }; + + let method_to_use = if is_map_to_option { + // `(...).map(...)` has type `impl Iterator> + "filter_map" + } else { + // `(...).map(...)` has type `impl Iterator> + "flat_map" + }; + let func_snippet = snippet(cx, map_args[1].span, ".."); + let hint = format!(".{0}({1})", method_to_use, func_snippet); + span_lint_and_sugg( + cx, + MAP_FLATTEN, + expr.span.with_lo(map_args[0].span.hi()), + "called `map(..).flatten()` on an `Iterator`", + &format!("try using `{}` instead", method_to_use), + hint, + Applicability::MachineApplicable, + ); + } + + // lint if caller of `.map().flatten()` is an Option + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type) { + let func_snippet = snippet(cx, map_args[1].span, ".."); + let hint = format!(".and_then({})", func_snippet); + span_lint_and_sugg( + cx, + MAP_FLATTEN, + expr.span.with_lo(map_args[0].span.hi()), + "called `map(..).flatten()` on an `Option`", + "try using `and_then` instead", + hint, + Applicability::MachineApplicable, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/map_unwrap_or.rs b/src/tools/clippy/clippy_lints/src/methods/map_unwrap_or.rs new file mode 100644 index 0000000000..63b2cf87f3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/map_unwrap_or.rs @@ -0,0 +1,76 @@ +use crate::utils::usage::mutated_variables; +use crate::utils::{is_type_diagnostic_item, meets_msrv, snippet, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_semver::RustcVersion; +use rustc_span::symbol::sym; + +use super::MAP_UNWRAP_OR; + +const MAP_UNWRAP_OR_MSRV: RustcVersion = RustcVersion::new(1, 41, 0); + +/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s +/// Return true if lint triggered +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + map_args: &'tcx [hir::Expr<'_>], + unwrap_args: &'tcx [hir::Expr<'_>], + msrv: Option<&RustcVersion>, +) -> bool { + if !meets_msrv(msrv, &MAP_UNWRAP_OR_MSRV) { + return false; + } + // lint if the caller of `map()` is an `Option` + let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type); + let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::result_type); + + if is_option || is_result { + // Don't make a suggestion that may fail to compile due to mutably borrowing + // the same variable twice. + let map_mutated_vars = mutated_variables(&map_args[0], cx); + let unwrap_mutated_vars = mutated_variables(&unwrap_args[1], cx); + if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) { + if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() { + return false; + } + } else { + return false; + } + + // lint message + let msg = if is_option { + "called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling \ + `map_or_else(, )` instead" + } else { + "called `map().unwrap_or_else()` on a `Result` value. This can be done more directly by calling \ + `.map_or_else(, )` instead" + }; + // get snippets for args to map() and unwrap_or_else() + let map_snippet = snippet(cx, map_args[1].span, ".."); + let unwrap_snippet = snippet(cx, unwrap_args[1].span, ".."); + // lint, with note if neither arg is > 1 line and both map() and + // unwrap_or_else() have the same span + let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1; + let same_span = map_args[1].span.ctxt() == unwrap_args[1].span.ctxt(); + if same_span && !multiline { + let var_snippet = snippet(cx, map_args[0].span, ".."); + span_lint_and_sugg( + cx, + MAP_UNWRAP_OR, + expr.span, + msg, + "try this", + format!("{}.map_or_else({}, {})", var_snippet, unwrap_snippet, map_snippet), + Applicability::MachineApplicable, + ); + return true; + } else if same_span && multiline { + span_lint(cx, MAP_UNWRAP_OR, expr.span, msg); + return true; + } + } + + false +} diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs new file mode 100644 index 0000000000..7fd14c4f9b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -0,0 +1,2381 @@ +mod bind_instead_of_map; +mod bytes_nth; +mod clone_on_copy; +mod clone_on_ref_ptr; +mod expect_fun_call; +mod expect_used; +mod filetype_is_file; +mod filter_flat_map; +mod filter_map; +mod filter_map_flat_map; +mod filter_map_identity; +mod filter_map_map; +mod filter_map_next; +mod filter_next; +mod flat_map_identity; +mod from_iter_instead_of_collect; +mod get_unwrap; +mod implicit_clone; +mod inefficient_to_string; +mod inspect_for_each; +mod into_iter_on_ref; +mod iter_cloned_collect; +mod iter_count; +mod iter_next_slice; +mod iter_nth; +mod iter_nth_zero; +mod iter_skip_next; +mod iterator_step_by_zero; +mod manual_saturating_arithmetic; +mod map_collect_result_unit; +mod map_flatten; +mod map_unwrap_or; +mod ok_expect; +mod option_as_ref_deref; +mod option_map_or_none; +mod option_map_unwrap_or; +mod or_fun_call; +mod search_is_some; +mod single_char_insert_string; +mod single_char_pattern; +mod single_char_push_string; +mod skip_while_next; +mod string_extend_chars; +mod suspicious_map; +mod uninit_assumed_init; +mod unnecessary_filter_map; +mod unnecessary_fold; +mod unnecessary_lazy_eval; +mod unwrap_used; +mod useless_asref; +mod wrong_self_convention; +mod zst_offset; + +use bind_instead_of_map::BindInsteadOfMap; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::{TraitItem, TraitItemKind}; +use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, TraitRef, Ty, TyS}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::{sym, SymbolStr}; +use rustc_typeck::hir_ty_to_ty; + +use crate::utils::{ + contains_return, contains_ty, get_trait_def_id, implements_trait, in_macro, is_copy, is_type_diagnostic_item, + iter_input_pats, match_def_path, match_qpath, method_calls, method_chain_args, paths, return_ty, + single_segment_path, snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for `.unwrap()` calls on `Option`s and on `Result`s. + /// + /// **Why is this bad?** It is better to handle the `None` or `Err` case, + /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of + /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is + /// `Allow` by default. + /// + /// `result.unwrap()` will let the thread panic on `Err` values. + /// Normally, you want to implement more sophisticated error handling, + /// and propagate errors upwards with `?` operator. + /// + /// Even if you want to panic on errors, not all `Error`s implement good + /// messages on display. Therefore, it may be beneficial to look at the places + /// where they may get displayed. Activate this lint to do just that. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + /// # let opt = Some(1); + /// + /// // Bad + /// opt.unwrap(); + /// + /// // Good + /// opt.expect("more helpful message"); + /// ``` + /// + /// // or + /// + /// ```rust + /// # let res: Result = Ok(1); + /// + /// // Bad + /// res.unwrap(); + /// + /// // Good + /// res.expect("more helpful message"); + /// ``` + pub UNWRAP_USED, + restriction, + "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `.expect()` calls on `Option`s and `Result`s. + /// + /// **Why is this bad?** Usually it is better to handle the `None` or `Err` case. + /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why + /// this lint is `Allow` by default. + /// + /// `result.expect()` will let the thread panic on `Err` + /// values. Normally, you want to implement more sophisticated error handling, + /// and propagate errors upwards with `?` operator. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust,ignore + /// # let opt = Some(1); + /// + /// // Bad + /// opt.expect("one"); + /// + /// // Good + /// let opt = Some(1); + /// opt?; + /// ``` + /// + /// // or + /// + /// ```rust + /// # let res: Result = Ok(1); + /// + /// // Bad + /// res.expect("one"); + /// + /// // Good + /// res?; + /// # Ok::<(), ()>(()) + /// ``` + pub EXPECT_USED, + restriction, + "using `.expect()` on `Result` or `Option`, which might be better handled" +} + +declare_clippy_lint! { + /// **What it does:** Checks for methods that should live in a trait + /// implementation of a `std` trait (see [llogiq's blog + /// post](http://llogiq.github.io/2015/07/30/traits.html) for further + /// information) instead of an inherent implementation. + /// + /// **Why is this bad?** Implementing the traits improve ergonomics for users of + /// the code, often with very little cost. Also people seeing a `mul(...)` + /// method + /// may expect `*` to work equally, so you should have good reason to disappoint + /// them. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// struct X; + /// impl X { + /// fn add(&self, other: &X) -> X { + /// // .. + /// # X + /// } + /// } + /// ``` + pub SHOULD_IMPLEMENT_TRAIT, + style, + "defining a method that should be implementing a std trait" +} + +declare_clippy_lint! { + /// **What it does:** Checks for methods with certain name prefixes and which + /// doesn't match how self is taken. The actual rules are: + /// + /// |Prefix |`self` taken | + /// |-------|----------------------| + /// |`as_` |`&self` or `&mut self`| + /// |`from_`| none | + /// |`into_`|`self` | + /// |`is_` |`&self` or none | + /// |`to_` |`&self` | + /// + /// **Why is this bad?** Consistency breeds readability. If you follow the + /// conventions, your users won't be surprised that they, e.g., need to supply a + /// mutable reference to a `as_..` function. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct X; + /// impl X { + /// fn as_str(self) -> &'static str { + /// // .. + /// # "" + /// } + /// } + /// ``` + pub WRONG_SELF_CONVENTION, + style, + "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention" +} + +declare_clippy_lint! { + /// **What it does:** This is the same as + /// [`wrong_self_convention`](#wrong_self_convention), but for public items. + /// + /// **Why is this bad?** See [`wrong_self_convention`](#wrong_self_convention). + /// + /// **Known problems:** Actually *renaming* the function may break clients if + /// the function is part of the public interface. In that case, be mindful of + /// the stability guarantees you've given your users. + /// + /// **Example:** + /// ```rust + /// # struct X; + /// impl<'a> X { + /// pub fn as_str(self) -> &'a str { + /// "foo" + /// } + /// } + /// ``` + pub WRONG_PUB_SELF_CONVENTION, + restriction, + "defining a public method named with an established prefix (like \"into_\") that takes `self` with the wrong convention" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `ok().expect(..)`. + /// + /// **Why is this bad?** Because you usually call `expect()` on the `Result` + /// directly to get a better error message. + /// + /// **Known problems:** The error type needs to implement `Debug` + /// + /// **Example:** + /// ```rust + /// # let x = Ok::<_, ()>(()); + /// + /// // Bad + /// x.ok().expect("why did I do this again?"); + /// + /// // Good + /// x.expect("why did I do this again?"); + /// ``` + pub OK_EXPECT, + style, + "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or + /// `result.map(_).unwrap_or_else(_)`. + /// + /// **Why is this bad?** Readability, these can be written more concisely (resp.) as + /// `option.map_or(_, _)`, `option.map_or_else(_, _)` and `result.map_or_else(_, _)`. + /// + /// **Known problems:** The order of the arguments is not in execution order + /// + /// **Examples:** + /// ```rust + /// # let x = Some(1); + /// + /// // Bad + /// x.map(|a| a + 1).unwrap_or(0); + /// + /// // Good + /// x.map_or(0, |a| a + 1); + /// ``` + /// + /// // or + /// + /// ```rust + /// # let x: Result = Ok(1); + /// # fn some_function(foo: ()) -> usize { 1 } + /// + /// // Bad + /// x.map(|a| a + 1).unwrap_or_else(some_function); + /// + /// // Good + /// x.map_or_else(some_function, |a| a + 1); + /// ``` + pub MAP_UNWRAP_OR, + pedantic, + "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.map_or(None, _)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.and_then(_)`. + /// + /// **Known problems:** The order of the arguments is not in execution order. + /// + /// **Example:** + /// ```rust + /// # let opt = Some(1); + /// + /// // Bad + /// opt.map_or(None, |a| Some(a + 1)); + /// + /// // Good + /// opt.and_then(|a| Some(a + 1)); + /// ``` + pub OPTION_MAP_OR_NONE, + style, + "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.map_or(None, Some)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.ok()`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// Bad: + /// ```rust + /// # let r: Result = Ok(1); + /// assert_eq!(Some(1), r.map_or(None, Some)); + /// ``` + /// + /// Good: + /// ```rust + /// # let r: Result = Ok(1); + /// assert_eq!(Some(1), r.ok()); + /// ``` + pub RESULT_MAP_OR_INTO_OPTION, + style, + "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` or + /// `_.or_else(|x| Err(y))`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.map(|x| y)` or `_.map_err(|x| y)`. + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// # fn opt() -> Option<&'static str> { Some("42") } + /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } + /// let _ = opt().and_then(|s| Some(s.len())); + /// let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) }); + /// let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) }); + /// ``` + /// + /// The correct use would be: + /// + /// ```rust + /// # fn opt() -> Option<&'static str> { Some("42") } + /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } + /// let _ = opt().map(|s| s.len()); + /// let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 }); + /// let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 }); + /// ``` + pub BIND_INSTEAD_OF_MAP, + complexity, + "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.filter(_).next()`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.find(_)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().filter(|x| **x == 0).next(); + /// ``` + /// Could be written as + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().find(|x| **x == 0); + /// ``` + pub FILTER_NEXT, + complexity, + "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.skip_while(condition).next()`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.find(!condition)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().skip_while(|x| **x == 0).next(); + /// ``` + /// Could be written as + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().find(|x| **x != 0); + /// ``` + pub SKIP_WHILE_NEXT, + complexity, + "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option` + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.flat_map(_)` + /// + /// **Known problems:** + /// + /// **Example:** + /// ```rust + /// let vec = vec![vec![1]]; + /// + /// // Bad + /// vec.iter().map(|x| x.iter()).flatten(); + /// + /// // Good + /// vec.iter().flat_map(|x| x.iter()); + /// ``` + pub MAP_FLATTEN, + pedantic, + "using combinations of `flatten` and `map` which can usually be written as a single method call" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.filter(_).map(_)`, + /// `_.filter(_).flat_map(_)`, `_.filter_map(_).flat_map(_)` and similar. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.filter_map(_)`. + /// + /// **Known problems:** Often requires a condition + Option/Iterator creation + /// inside the closure. + /// + /// **Example:** + /// ```rust + /// let vec = vec![1]; + /// + /// // Bad + /// vec.iter().filter(|x| **x == 0).map(|x| *x * 2); + /// + /// // Good + /// vec.iter().filter_map(|x| if *x == 0 { + /// Some(*x * 2) + /// } else { + /// None + /// }); + /// ``` + pub FILTER_MAP, + pedantic, + "using combinations of `filter`, `map`, `filter_map` and `flat_map` which can usually be written as a single method call" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.filter(_).map(_)` that can be written more simply + /// as `filter_map(_)`. + /// + /// **Why is this bad?** Redundant code in the `filter` and `map` operations is poor style and + /// less performant. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust + /// (0_i32..10) + /// .filter(|n| n.checked_add(1).is_some()) + /// .map(|n| n.checked_add(1).unwrap()); + /// ``` + /// + /// Good: + /// ```rust + /// (0_i32..10).filter_map(|n| n.checked_add(1)); + /// ``` + pub MANUAL_FILTER_MAP, + complexity, + "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.find(_).map(_)` that can be written more simply + /// as `find_map(_)`. + /// + /// **Why is this bad?** Redundant code in the `find` and `map` operations is poor style and + /// less performant. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust + /// (0_i32..10) + /// .find(|n| n.checked_add(1).is_some()) + /// .map(|n| n.checked_add(1).unwrap()); + /// ``` + /// + /// Good: + /// ```rust + /// (0_i32..10).find_map(|n| n.checked_add(1)); + /// ``` + pub MANUAL_FIND_MAP, + complexity, + "using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.filter_map(_).next()`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.find_map(_)`. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next(); + /// ``` + /// Can be written as + /// + /// ```rust + /// (0..3).find_map(|x| if x == 2 { Some(x) } else { None }); + /// ``` + pub FILTER_MAP_NEXT, + pedantic, + "using combination of `filter_map` and `next` which can usually be written as a single method call" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `flat_map(|x| x)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely by using `flatten`. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// # let iter = vec![vec![0]].into_iter(); + /// iter.flat_map(|x| x); + /// ``` + /// Can be written as + /// ```rust + /// # let iter = vec![vec![0]].into_iter(); + /// iter.flatten(); + /// ``` + pub FLAT_MAP_IDENTITY, + complexity, + "call to `flat_map` where `flatten` is sufficient" +} + +declare_clippy_lint! { + /// **What it does:** Checks for an iterator or string search (such as `find()`, + /// `position()`, or `rposition()`) followed by a call to `is_some()`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.any(_)` or `_.contains(_)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().find(|x| **x == 0).is_some(); + /// ``` + /// Could be written as + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().any(|x| *x == 0); + /// ``` + pub SEARCH_IS_SOME, + complexity, + "using an iterator or string search followed by `is_some()`, which is more succinctly expressed as a call to `any()` or `contains()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.chars().next()` on a `str` to check + /// if it starts with a given char. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.starts_with(_)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let name = "foo"; + /// if name.chars().next() == Some('_') {}; + /// ``` + /// Could be written as + /// ```rust + /// let name = "foo"; + /// if name.starts_with('_') {}; + /// ``` + pub CHARS_NEXT_CMP, + style, + "using `.chars().next()` to check if a string starts with a char" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`, + /// etc., and suggests to use `or_else`, `unwrap_or_else`, etc., or + /// `unwrap_or_default` instead. + /// + /// **Why is this bad?** The function will always be called and potentially + /// allocate an object acting as the default. + /// + /// **Known problems:** If the function has side-effects, not calling it will + /// change the semantic of the program, but you shouldn't rely on that anyway. + /// + /// **Example:** + /// ```rust + /// # let foo = Some(String::new()); + /// foo.unwrap_or(String::new()); + /// ``` + /// this can instead be written: + /// ```rust + /// # let foo = Some(String::new()); + /// foo.unwrap_or_else(String::new); + /// ``` + /// or + /// ```rust + /// # let foo = Some(String::new()); + /// foo.unwrap_or_default(); + /// ``` + pub OR_FUN_CALL, + perf, + "using any `*or` method with a function call, which suggests `*or_else`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`, + /// etc., and suggests to use `unwrap_or_else` instead + /// + /// **Why is this bad?** The function will always be called. + /// + /// **Known problems:** If the function has side-effects, not calling it will + /// change the semantics of the program, but you shouldn't rely on that anyway. + /// + /// **Example:** + /// ```rust + /// # let foo = Some(String::new()); + /// # let err_code = "418"; + /// # let err_msg = "I'm a teapot"; + /// foo.expect(&format!("Err {}: {}", err_code, err_msg)); + /// ``` + /// or + /// ```rust + /// # let foo = Some(String::new()); + /// # let err_code = "418"; + /// # let err_msg = "I'm a teapot"; + /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str()); + /// ``` + /// this can instead be written: + /// ```rust + /// # let foo = Some(String::new()); + /// # let err_code = "418"; + /// # let err_msg = "I'm a teapot"; + /// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg)); + /// ``` + pub EXPECT_FUN_CALL, + perf, + "using any `expect` method with a function call" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.clone()` on a `Copy` type. + /// + /// **Why is this bad?** The only reason `Copy` types implement `Clone` is for + /// generics, not for using the `clone` method on a concrete type. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// 42u64.clone(); + /// ``` + pub CLONE_ON_COPY, + complexity, + "using `clone` on a `Copy` type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.clone()` on a ref-counted pointer, + /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified + /// function syntax instead (e.g., `Rc::clone(foo)`). + /// + /// **Why is this bad?** Calling '.clone()' on an Rc, Arc, or Weak + /// can obscure the fact that only the pointer is being cloned, not the underlying + /// data. + /// + /// **Example:** + /// ```rust + /// # use std::rc::Rc; + /// let x = Rc::new(1); + /// + /// // Bad + /// x.clone(); + /// + /// // Good + /// Rc::clone(&x); + /// ``` + pub CLONE_ON_REF_PTR, + restriction, + "using 'clone' on a ref-counted pointer" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.clone()` on an `&&T`. + /// + /// **Why is this bad?** Cloning an `&&T` copies the inner `&T`, instead of + /// cloning the underlying `T`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn main() { + /// let x = vec![1]; + /// let y = &&x; + /// let z = y.clone(); + /// println!("{:p} {:p}", *y, z); // prints out the same pointer + /// } + /// ``` + pub CLONE_DOUBLE_REF, + correctness, + "using `clone` on `&&T`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.to_string()` on an `&&T` where + /// `T` implements `ToString` directly (like `&&str` or `&&String`). + /// + /// **Why is this bad?** This bypasses the specialized implementation of + /// `ToString` and instead goes through the more expensive string formatting + /// facilities. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Generic implementation for `T: Display` is used (slow) + /// ["foo", "bar"].iter().map(|s| s.to_string()); + /// + /// // OK, the specialized impl is used + /// ["foo", "bar"].iter().map(|&s| s.to_string()); + /// ``` + pub INEFFICIENT_TO_STRING, + pedantic, + "using `to_string` on `&&T` where `T: ToString`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `new` not returning a type that contains `Self`. + /// + /// **Why is this bad?** As a convention, `new` methods are used to make a new + /// instance of a type. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// In an impl block: + /// ```rust + /// # struct Foo; + /// # struct NotAFoo; + /// impl Foo { + /// fn new() -> NotAFoo { + /// # NotAFoo + /// } + /// } + /// ``` + /// + /// ```rust + /// # struct Foo; + /// struct Bar(Foo); + /// impl Foo { + /// // Bad. The type name must contain `Self` + /// fn new() -> Bar { + /// # Bar(Foo) + /// } + /// } + /// ``` + /// + /// ```rust + /// # struct Foo; + /// # struct FooError; + /// impl Foo { + /// // Good. Return type contains `Self` + /// fn new() -> Result { + /// # Ok(Foo) + /// } + /// } + /// ``` + /// + /// Or in a trait definition: + /// ```rust + /// pub trait Trait { + /// // Bad. The type name must contain `Self` + /// fn new(); + /// } + /// ``` + /// + /// ```rust + /// pub trait Trait { + /// // Good. Return type contains `Self` + /// fn new() -> Self; + /// } + /// ``` + pub NEW_RET_NO_SELF, + style, + "not returning type containing `Self` in a `new` method" +} + +declare_clippy_lint! { + /// **What it does:** Checks for string methods that receive a single-character + /// `str` as an argument, e.g., `_.split("x")`. + /// + /// **Why is this bad?** Performing these methods using a `char` is faster than + /// using a `str`. + /// + /// **Known problems:** Does not catch multi-byte unicode characters. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// _.split("x"); + /// + /// // Good + /// _.split('x'); + pub SINGLE_CHAR_PATTERN, + perf, + "using a single-character str where a char could be used, e.g., `_.split(\"x\")`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calling `.step_by(0)` on iterators which panics. + /// + /// **Why is this bad?** This very much looks like an oversight. Use `panic!()` instead if you + /// actually intend to panic. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,should_panic + /// for x in (0..100).step_by(0) { + /// //.. + /// } + /// ``` + pub ITERATOR_STEP_BY_ZERO, + correctness, + "using `Iterator::step_by(0)`, which will panic at runtime" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of `iter.nth(0)`. + /// + /// **Why is this bad?** `iter.next()` is equivalent to + /// `iter.nth(0)`, as they both consume the next element, + /// but is more readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// # use std::collections::HashSet; + /// // Bad + /// # let mut s = HashSet::new(); + /// # s.insert(1); + /// let x = s.iter().nth(0); + /// + /// // Good + /// # let mut s = HashSet::new(); + /// # s.insert(1); + /// let x = s.iter().next(); + /// ``` + pub ITER_NTH_ZERO, + style, + "replace `iter.nth(0)` with `iter.next()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `.iter().nth()` (and the related + /// `.iter_mut().nth()`) on standard library types with O(1) element access. + /// + /// **Why is this bad?** `.get()` and `.get_mut()` are more efficient and more + /// readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().nth(3); + /// let bad_slice = &some_vec[..].iter().nth(3); + /// ``` + /// The correct use would be: + /// ```rust + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.get(3); + /// let bad_slice = &some_vec[..].get(3); + /// ``` + pub ITER_NTH, + perf, + "using `.iter().nth()` on a standard library type with O(1) element access" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `.skip(x).next()` on iterators. + /// + /// **Why is this bad?** `.nth(x)` is cleaner + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().skip(3).next(); + /// let bad_slice = &some_vec[..].iter().skip(3).next(); + /// ``` + /// The correct use would be: + /// ```rust + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().nth(3); + /// let bad_slice = &some_vec[..].iter().nth(3); + /// ``` + pub ITER_SKIP_NEXT, + style, + "using `.skip(x).next()` on an iterator" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `.get().unwrap()` (or + /// `.get_mut().unwrap`) on a standard library type which implements `Index` + /// + /// **Why is this bad?** Using the Index trait (`[]`) is more clear and more + /// concise. + /// + /// **Known problems:** Not a replacement for error handling: Using either + /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic` + /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a + /// temporary placeholder for dealing with the `Option` type, then this does + /// not mitigate the need for error handling. If there is a chance that `.get()` + /// will be `None` in your program, then it is advisable that the `None` case + /// is handled in a future refactor instead of using `.unwrap()` or the Index + /// trait. + /// + /// **Example:** + /// ```rust + /// let mut some_vec = vec![0, 1, 2, 3]; + /// let last = some_vec.get(3).unwrap(); + /// *some_vec.get_mut(0).unwrap() = 1; + /// ``` + /// The correct use would be: + /// ```rust + /// let mut some_vec = vec![0, 1, 2, 3]; + /// let last = some_vec[3]; + /// some_vec[0] = 1; + /// ``` + pub GET_UNWRAP, + restriction, + "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of `.extend(s.chars())` where s is a + /// `&str` or `String`. + /// + /// **Why is this bad?** `.push_str(s)` is clearer + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let abc = "abc"; + /// let def = String::from("def"); + /// let mut s = String::new(); + /// s.extend(abc.chars()); + /// s.extend(def.chars()); + /// ``` + /// The correct use would be: + /// ```rust + /// let abc = "abc"; + /// let def = String::from("def"); + /// let mut s = String::new(); + /// s.push_str(abc); + /// s.push_str(&def); + /// ``` + pub STRING_EXTEND_CHARS, + style, + "using `x.extend(s.chars())` where s is a `&str` or `String`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of `.cloned().collect()` on slice to + /// create a `Vec`. + /// + /// **Why is this bad?** `.to_vec()` is clearer + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let s = [1, 2, 3, 4, 5]; + /// let s2: Vec = s[..].iter().cloned().collect(); + /// ``` + /// The better use would be: + /// ```rust + /// let s = [1, 2, 3, 4, 5]; + /// let s2: Vec = s.to_vec(); + /// ``` + pub ITER_CLONED_COLLECT, + style, + "using `.cloned().collect()` on slice to create a `Vec`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.chars().last()` or + /// `_.chars().next_back()` on a `str` to check if it ends with a given char. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.ends_with(_)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let name = "_"; + /// + /// // Bad + /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-'); + /// + /// // Good + /// name.ends_with('_') || name.ends_with('-'); + /// ``` + pub CHARS_LAST_CMP, + style, + "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.as_ref()` or `.as_mut()` where the + /// types before and after the call are the same. + /// + /// **Why is this bad?** The call is unnecessary. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn do_stuff(x: &[i32]) {} + /// let x: &[i32] = &[1, 2, 3, 4, 5]; + /// do_stuff(x.as_ref()); + /// ``` + /// The correct use would be: + /// ```rust + /// # fn do_stuff(x: &[i32]) {} + /// let x: &[i32] = &[1, 2, 3, 4, 5]; + /// do_stuff(x); + /// ``` + pub USELESS_ASREF, + complexity, + "using `as_ref` where the types before and after the call are the same" +} + +declare_clippy_lint! { + /// **What it does:** Checks for using `fold` when a more succinct alternative exists. + /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`, + /// `sum` or `product`. + /// + /// **Why is this bad?** Readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let _ = (0..3).fold(false, |acc, x| acc || x > 2); + /// ``` + /// This could be written as: + /// ```rust + /// let _ = (0..3).any(|x| x > 2); + /// ``` + pub UNNECESSARY_FOLD, + style, + "using `fold` when a more succinct alternative exists" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `filter_map` calls which could be replaced by `filter` or `map`. + /// More specifically it checks if the closure provided is only performing one of the + /// filter or map operations and suggests the appropriate option. + /// + /// **Why is this bad?** Complexity. The intent is also clearer if only a single + /// operation is being performed. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None }); + /// + /// // As there is no transformation of the argument this could be written as: + /// let _ = (0..3).filter(|&x| x > 2); + /// ``` + /// + /// ```rust + /// let _ = (0..4).filter_map(|x| Some(x + 1)); + /// + /// // As there is no conditional check on the argument this could be written as: + /// let _ = (0..4).map(|x| x + 1); + /// ``` + pub UNNECESSARY_FILTER_MAP, + complexity, + "using `filter_map` when a more succinct alternative exists" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `into_iter` calls on references which should be replaced by `iter` + /// or `iter_mut`. + /// + /// **Why is this bad?** Readability. Calling `into_iter` on a reference will not move out its + /// content into the resulting iterator, which is confusing. It is better just call `iter` or + /// `iter_mut` directly. + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// let _ = (&vec![3, 4, 5]).into_iter(); + /// + /// // Good + /// let _ = (&vec![3, 4, 5]).iter(); + /// ``` + pub INTO_ITER_ON_REF, + style, + "using `.into_iter()` on a reference" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `map` followed by a `count`. + /// + /// **Why is this bad?** It looks suspicious. Maybe `map` was confused with `filter`. + /// If the `map` call is intentional, this should be rewritten. Or, if you intend to + /// drive the iterator to completion, you can just use `for_each` instead. + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// let _ = (0..3).map(|x| x + 2).count(); + /// ``` + pub SUSPICIOUS_MAP, + complexity, + "suspicious usage of map" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `MaybeUninit::uninit().assume_init()`. + /// + /// **Why is this bad?** For most types, this is undefined behavior. + /// + /// **Known problems:** For now, we accept empty tuples and tuples / arrays + /// of `MaybeUninit`. There may be other types that allow uninitialized + /// data, but those are not yet rigorously defined. + /// + /// **Example:** + /// + /// ```rust + /// // Beware the UB + /// use std::mem::MaybeUninit; + /// + /// let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; + /// ``` + /// + /// Note that the following is OK: + /// + /// ```rust + /// use std::mem::MaybeUninit; + /// + /// let _: [MaybeUninit; 5] = unsafe { + /// MaybeUninit::uninit().assume_init() + /// }; + /// ``` + pub UNINIT_ASSUMED_INIT, + correctness, + "`MaybeUninit::uninit().assume_init()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`. + /// + /// **Why is this bad?** These can be written simply with `saturating_add/sub` methods. + /// + /// **Example:** + /// + /// ```rust + /// # let y: u32 = 0; + /// # let x: u32 = 100; + /// let add = x.checked_add(y).unwrap_or(u32::MAX); + /// let sub = x.checked_sub(y).unwrap_or(u32::MIN); + /// ``` + /// + /// can be written using dedicated methods for saturating addition/subtraction as: + /// + /// ```rust + /// # let y: u32 = 0; + /// # let x: u32 = 100; + /// let add = x.saturating_add(y); + /// let sub = x.saturating_sub(y); + /// ``` + pub MANUAL_SATURATING_ARITHMETIC, + style, + "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to + /// zero-sized types + /// + /// **Why is this bad?** This is a no-op, and likely unintended + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// unsafe { (&() as *const ()).offset(1) }; + /// ``` + pub ZST_OFFSET, + correctness, + "Check for offset calculations on raw pointers to zero-sized types" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `FileType::is_file()`. + /// + /// **Why is this bad?** When people testing a file type with `FileType::is_file` + /// they are testing whether a path is something they can get bytes from. But + /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover + /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention. + /// + /// **Example:** + /// + /// ```rust + /// # || { + /// let metadata = std::fs::metadata("foo.txt")?; + /// let filetype = metadata.file_type(); + /// + /// if filetype.is_file() { + /// // read file + /// } + /// # Ok::<_, std::io::Error>(()) + /// # }; + /// ``` + /// + /// should be written as: + /// + /// ```rust + /// # || { + /// let metadata = std::fs::metadata("foo.txt")?; + /// let filetype = metadata.file_type(); + /// + /// if !filetype.is_dir() { + /// // read file + /// } + /// # Ok::<_, std::io::Error>(()) + /// # }; + /// ``` + pub FILETYPE_IS_FILE, + restriction, + "`FileType::is_file` is not recommended to test for readable file type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str). + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.as_deref()`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let opt = Some("".to_string()); + /// opt.as_ref().map(String::as_str) + /// # ; + /// ``` + /// Can be written as + /// ```rust + /// # let opt = Some("".to_string()); + /// opt.as_deref() + /// # ; + /// ``` + pub OPTION_AS_REF_DEREF, + complexity, + "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `iter().next()` on a Slice or an Array + /// + /// **Why is this bad?** These can be shortened into `.get()` + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let a = [1, 2, 3]; + /// # let b = vec![1, 2, 3]; + /// a[2..].iter().next(); + /// b.iter().next(); + /// ``` + /// should be written as: + /// ```rust + /// # let a = [1, 2, 3]; + /// # let b = vec![1, 2, 3]; + /// a.get(2); + /// b.get(0); + /// ``` + pub ITER_NEXT_SLICE, + style, + "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`" +} + +declare_clippy_lint! { + /// **What it does:** Warns when using `push_str`/`insert_str` with a single-character string literal + /// where `push`/`insert` with a `char` would work fine. + /// + /// **Why is this bad?** It's less clear that we are pushing a single character. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let mut string = String::new(); + /// string.insert_str(0, "R"); + /// string.push_str("R"); + /// ``` + /// Could be written as + /// ```rust + /// let mut string = String::new(); + /// string.insert(0, 'R'); + /// string.push('R'); + /// ``` + pub SINGLE_CHAR_ADD_STR, + style, + "`push_str()` or `insert_str()` used with a single-character string literal as parameter" +} + +declare_clippy_lint! { + /// **What it does:** As the counterpart to `or_fun_call`, this lint looks for unnecessary + /// lazily evaluated closures on `Option` and `Result`. + /// + /// This lint suggests changing the following functions, when eager evaluation results in + /// simpler code: + /// - `unwrap_or_else` to `unwrap_or` + /// - `and_then` to `and` + /// - `or_else` to `or` + /// - `get_or_insert_with` to `get_or_insert` + /// - `ok_or_else` to `ok_or` + /// + /// **Why is this bad?** Using eager evaluation is shorter and simpler in some cases. + /// + /// **Known problems:** It is possible, but not recommended for `Deref` and `Index` to have + /// side effects. Eagerly evaluating them can change the semantics of the program. + /// + /// **Example:** + /// + /// ```rust + /// // example code where clippy issues a warning + /// let opt: Option = None; + /// + /// opt.unwrap_or_else(|| 42); + /// ``` + /// Use instead: + /// ```rust + /// let opt: Option = None; + /// + /// opt.unwrap_or(42); + /// ``` + pub UNNECESSARY_LAZY_EVALUATIONS, + style, + "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.map(_).collect::()`. + /// + /// **Why is this bad?** Using `try_for_each` instead is more readable and idiomatic. + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// (0..3).map(|t| Err(t)).collect::>(); + /// ``` + /// Use instead: + /// ```rust + /// (0..3).try_for_each(|t| Err(t)); + /// ``` + pub MAP_COLLECT_RESULT_UNIT, + style, + "using `.map(_).collect::()`, which can be replaced with `try_for_each`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `from_iter()` function calls on types that implement the `FromIterator` + /// trait. + /// + /// **Why is this bad?** It is recommended style to use collect. See + /// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html) + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// use std::iter::FromIterator; + /// + /// let five_fives = std::iter::repeat(5).take(5); + /// + /// let v = Vec::from_iter(five_fives); + /// + /// assert_eq!(v, vec![5, 5, 5, 5, 5]); + /// ``` + /// Use instead: + /// ```rust + /// let five_fives = std::iter::repeat(5).take(5); + /// + /// let v: Vec = five_fives.collect(); + /// + /// assert_eq!(v, vec![5, 5, 5, 5, 5]); + /// ``` + pub FROM_ITER_INSTEAD_OF_COLLECT, + style, + "use `.collect()` instead of `::from_iter()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `inspect().for_each()`. + /// + /// **Why is this bad?** It is the same as performing the computation + /// inside `inspect` at the beginning of the closure in `for_each`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// [1,2,3,4,5].iter() + /// .inspect(|&x| println!("inspect the number: {}", x)) + /// .for_each(|&x| { + /// assert!(x >= 0); + /// }); + /// ``` + /// Can be written as + /// ```rust + /// [1,2,3,4,5].iter() + /// .for_each(|&x| { + /// println!("inspect the number: {}", x); + /// assert!(x >= 0); + /// }); + /// ``` + pub INSPECT_FOR_EACH, + complexity, + "using `.inspect().for_each()`, which can be replaced with `.for_each()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `filter_map(|x| x)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely by using `flatten`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// # let iter = vec![Some(1)].into_iter(); + /// iter.filter_map(|x| x); + /// ``` + /// Use instead: + /// ```rust + /// # let iter = vec![Some(1)].into_iter(); + /// iter.flatten(); + /// ``` + pub FILTER_MAP_IDENTITY, + complexity, + "call to `filter_map` where `flatten` is sufficient" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of `.bytes().nth()`. + /// + /// **Why is this bad?** `.as_bytes().get()` is more efficient and more + /// readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// let _ = "Hello".bytes().nth(3); + /// + /// // Good + /// let _ = "Hello".as_bytes().get(3); + /// ``` + pub BYTES_NTH, + style, + "replace `.bytes().nth()` with `.as_bytes().get()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer. + /// + /// **Why is this bad?** These methods do the same thing as `_.clone()` but may be confusing as + /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let a = vec![1, 2, 3]; + /// let b = a.to_vec(); + /// let c = a.to_owned(); + /// ``` + /// Use instead: + /// ```rust + /// let a = vec![1, 2, 3]; + /// let b = a.clone(); + /// let c = a.clone(); + /// ``` + pub IMPLICIT_CLONE, + pedantic, + "implicitly cloning a value by invoking a function on its dereferenced type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of `.iter().count()`. + /// + /// **Why is this bad?** `.len()` is more efficient and more + /// readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// let some_vec = vec![0, 1, 2, 3]; + /// let _ = some_vec.iter().count(); + /// let _ = &some_vec[..].iter().count(); + /// + /// // Good + /// let some_vec = vec![0, 1, 2, 3]; + /// let _ = some_vec.len(); + /// let _ = &some_vec[..].len(); + /// ``` + pub ITER_COUNT, + complexity, + "replace `.iter().count()` with `.len()`" +} + +pub struct Methods { + msrv: Option, +} + +impl Methods { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(Methods => [ + UNWRAP_USED, + EXPECT_USED, + SHOULD_IMPLEMENT_TRAIT, + WRONG_SELF_CONVENTION, + WRONG_PUB_SELF_CONVENTION, + OK_EXPECT, + MAP_UNWRAP_OR, + RESULT_MAP_OR_INTO_OPTION, + OPTION_MAP_OR_NONE, + BIND_INSTEAD_OF_MAP, + OR_FUN_CALL, + EXPECT_FUN_CALL, + CHARS_NEXT_CMP, + CHARS_LAST_CMP, + CLONE_ON_COPY, + CLONE_ON_REF_PTR, + CLONE_DOUBLE_REF, + INEFFICIENT_TO_STRING, + NEW_RET_NO_SELF, + SINGLE_CHAR_PATTERN, + SINGLE_CHAR_ADD_STR, + SEARCH_IS_SOME, + FILTER_NEXT, + SKIP_WHILE_NEXT, + FILTER_MAP, + FILTER_MAP_IDENTITY, + MANUAL_FILTER_MAP, + MANUAL_FIND_MAP, + FILTER_MAP_NEXT, + FLAT_MAP_IDENTITY, + MAP_FLATTEN, + ITERATOR_STEP_BY_ZERO, + ITER_NEXT_SLICE, + ITER_COUNT, + ITER_NTH, + ITER_NTH_ZERO, + BYTES_NTH, + ITER_SKIP_NEXT, + GET_UNWRAP, + STRING_EXTEND_CHARS, + ITER_CLONED_COLLECT, + USELESS_ASREF, + UNNECESSARY_FOLD, + UNNECESSARY_FILTER_MAP, + INTO_ITER_ON_REF, + SUSPICIOUS_MAP, + UNINIT_ASSUMED_INIT, + MANUAL_SATURATING_ARITHMETIC, + ZST_OFFSET, + FILETYPE_IS_FILE, + OPTION_AS_REF_DEREF, + UNNECESSARY_LAZY_EVALUATIONS, + MAP_COLLECT_RESULT_UNIT, + FROM_ITER_INSTEAD_OF_COLLECT, + INSPECT_FOR_EACH, + IMPLICIT_CLONE +]); + +impl<'tcx> LateLintPass<'tcx> for Methods { + #[allow(clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if in_macro(expr.span) { + return; + } + + let (method_names, arg_lists, method_spans) = method_calls(expr, 2); + let method_names: Vec = method_names.iter().map(|s| s.as_str()).collect(); + let method_names: Vec<&str> = method_names.iter().map(|s| &**s).collect(); + + match method_names.as_slice() { + ["unwrap", "get"] => get_unwrap::check(cx, expr, arg_lists[1], false), + ["unwrap", "get_mut"] => get_unwrap::check(cx, expr, arg_lists[1], true), + ["unwrap", ..] => unwrap_used::check(cx, expr, arg_lists[0]), + ["expect", "ok"] => ok_expect::check(cx, expr, arg_lists[1]), + ["expect", ..] => expect_used::check(cx, expr, arg_lists[0]), + ["unwrap_or", "map"] => option_map_unwrap_or::check(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]), + ["unwrap_or_else", "map"] => { + if !map_unwrap_or::check(cx, expr, arg_lists[1], arg_lists[0], self.msrv.as_ref()) { + unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "unwrap_or"); + } + }, + ["map_or", ..] => option_map_or_none::check(cx, expr, arg_lists[0]), + ["and_then", ..] => { + let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, arg_lists[0]); + let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, arg_lists[0]); + if !biom_option_linted && !biom_result_linted { + unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "and"); + } + }, + ["or_else", ..] => { + if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, arg_lists[0]) { + unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "or"); + } + }, + ["next", "filter"] => filter_next::check(cx, expr, arg_lists[1]), + ["next", "skip_while"] => skip_while_next::check(cx, expr, arg_lists[1]), + ["next", "iter"] => iter_next_slice::check(cx, expr, arg_lists[1]), + ["map", "filter"] => filter_map::check(cx, expr, false), + ["map", "filter_map"] => filter_map_map::check(cx, expr, arg_lists[1], arg_lists[0]), + ["next", "filter_map"] => filter_map_next::check(cx, expr, arg_lists[1], self.msrv.as_ref()), + ["map", "find"] => filter_map::check(cx, expr, true), + ["flat_map", "filter"] => filter_flat_map::check(cx, expr, arg_lists[1], arg_lists[0]), + ["flat_map", "filter_map"] => filter_map_flat_map::check(cx, expr, arg_lists[1], arg_lists[0]), + ["flat_map", ..] => flat_map_identity::check(cx, expr, arg_lists[0], method_spans[0]), + ["flatten", "map"] => map_flatten::check(cx, expr, arg_lists[1]), + ["is_some", "find"] => search_is_some::check(cx, expr, "find", arg_lists[1], arg_lists[0], method_spans[1]), + ["is_some", "position"] => { + search_is_some::check(cx, expr, "position", arg_lists[1], arg_lists[0], method_spans[1]) + }, + ["is_some", "rposition"] => { + search_is_some::check(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1]) + }, + ["extend", ..] => string_extend_chars::check(cx, expr, arg_lists[0]), + ["count", "into_iter"] => iter_count::check(cx, expr, &arg_lists[1], "into_iter"), + ["count", "iter"] => iter_count::check(cx, expr, &arg_lists[1], "iter"), + ["count", "iter_mut"] => iter_count::check(cx, expr, &arg_lists[1], "iter_mut"), + ["nth", "iter"] => iter_nth::check(cx, expr, &arg_lists, false), + ["nth", "iter_mut"] => iter_nth::check(cx, expr, &arg_lists, true), + ["nth", "bytes"] => bytes_nth::check(cx, expr, &arg_lists[1]), + ["nth", ..] => iter_nth_zero::check(cx, expr, arg_lists[0]), + ["step_by", ..] => iterator_step_by_zero::check(cx, expr, arg_lists[0]), + ["next", "skip"] => iter_skip_next::check(cx, expr, arg_lists[1]), + ["collect", "cloned"] => iter_cloned_collect::check(cx, expr, arg_lists[1]), + ["as_ref"] => useless_asref::check(cx, expr, "as_ref", arg_lists[0]), + ["as_mut"] => useless_asref::check(cx, expr, "as_mut", arg_lists[0]), + ["fold", ..] => unnecessary_fold::check(cx, expr, arg_lists[0], method_spans[0]), + ["filter_map", ..] => { + unnecessary_filter_map::check(cx, expr, arg_lists[0]); + filter_map_identity::check(cx, expr, arg_lists[0], method_spans[0]); + }, + ["count", "map"] => suspicious_map::check(cx, expr), + ["assume_init"] => uninit_assumed_init::check(cx, &arg_lists[0][0], expr), + ["unwrap_or", arith @ ("checked_add" | "checked_sub" | "checked_mul")] => { + manual_saturating_arithmetic::check(cx, expr, &arg_lists, &arith["checked_".len()..]) + }, + ["add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub"] => { + zst_offset::check(cx, expr, arg_lists[0]) + }, + ["is_file", ..] => filetype_is_file::check(cx, expr, arg_lists[0]), + ["map", "as_ref"] => { + option_as_ref_deref::check(cx, expr, arg_lists[1], arg_lists[0], false, self.msrv.as_ref()) + }, + ["map", "as_mut"] => { + option_as_ref_deref::check(cx, expr, arg_lists[1], arg_lists[0], true, self.msrv.as_ref()) + }, + ["unwrap_or_else", ..] => unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "unwrap_or"), + ["get_or_insert_with", ..] => unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "get_or_insert"), + ["ok_or_else", ..] => unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "ok_or"), + ["collect", "map"] => map_collect_result_unit::check(cx, expr, arg_lists[1], arg_lists[0]), + ["for_each", "inspect"] => inspect_for_each::check(cx, expr, method_spans[1]), + ["to_owned", ..] => implicit_clone::check(cx, expr, sym::ToOwned), + ["to_os_string", ..] => implicit_clone::check(cx, expr, sym::OsStr), + ["to_path_buf", ..] => implicit_clone::check(cx, expr, sym::Path), + ["to_vec", ..] => implicit_clone::check(cx, expr, sym::slice), + _ => {}, + } + + match expr.kind { + hir::ExprKind::Call(ref func, ref args) => { + if let hir::ExprKind::Path(path) = &func.kind { + if match_qpath(path, &["from_iter"]) { + from_iter_instead_of_collect::check(cx, expr, args); + } + } + }, + hir::ExprKind::MethodCall(ref method_call, ref method_span, ref args, _) => { + or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args); + expect_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args); + + let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0]); + if args.len() == 1 && method_call.ident.name == sym::clone { + clone_on_copy::check(cx, expr, &args[0], self_ty); + clone_on_ref_ptr::check(cx, expr, &args[0]); + } + if args.len() == 1 && method_call.ident.name == sym!(to_string) { + inefficient_to_string::check(cx, expr, &args[0], self_ty); + } + + if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { + if match_def_path(cx, fn_def_id, &paths::PUSH_STR) { + single_char_push_string::check(cx, expr, args); + } else if match_def_path(cx, fn_def_id, &paths::INSERT_STR) { + single_char_insert_string::check(cx, expr, args); + } + } + + match self_ty.kind() { + ty::Ref(_, ty, _) if *ty.kind() == ty::Str => { + for &(method, pos) in &PATTERN_METHODS { + if method_call.ident.name.as_str() == method && args.len() > pos { + single_char_pattern::check(cx, expr, &args[pos]); + } + } + }, + ty::Ref(..) if method_call.ident.name == sym::into_iter => { + into_iter_on_ref::check(cx, expr, self_ty, *method_span); + }, + _ => (), + } + }, + hir::ExprKind::Binary(op, ref lhs, ref rhs) + if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => + { + let mut info = BinaryExprInfo { + expr, + chain: lhs, + other: rhs, + eq: op.node == hir::BinOpKind::Eq, + }; + lint_binary_expr_with_method_call(cx, &mut info); + } + _ => (), + } + } + + #[allow(clippy::too_many_lines)] + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { + if in_external_macro(cx.sess(), impl_item.span) { + return; + } + let name = impl_item.ident.name.as_str(); + let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id()); + let item = cx.tcx.hir().expect_item(parent); + let self_ty = cx.tcx.type_of(item.def_id); + + // if this impl block implements a trait, lint in trait definition instead + if let hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) = item.kind { + return; + } + + if_chain! { + if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind; + if let Some(first_arg) = iter_input_pats(&sig.decl, cx.tcx.hir().body(id)).next(); + + let method_sig = cx.tcx.fn_sig(impl_item.def_id); + let method_sig = cx.tcx.erase_late_bound_regions(method_sig); + + let first_arg_ty = &method_sig.inputs().iter().next(); + + // check conventions w.r.t. conversion method names and predicates + if let Some(first_arg_ty) = first_arg_ty; + + then { + if cx.access_levels.is_exported(impl_item.hir_id()) { + // check missing trait implementations + for method_config in &TRAIT_METHODS { + if name == method_config.method_name && + sig.decl.inputs.len() == method_config.param_count && + method_config.output_type.matches(cx, &sig.decl.output) && + method_config.self_kind.matches(cx, self_ty, first_arg_ty) && + fn_header_equals(method_config.fn_header, sig.header) && + method_config.lifetime_param_cond(&impl_item) + { + span_lint_and_help( + cx, + SHOULD_IMPLEMENT_TRAIT, + impl_item.span, + &format!( + "method `{}` can be confused for the standard trait method `{}::{}`", + method_config.method_name, + method_config.trait_name, + method_config.method_name + ), + None, + &format!( + "consider implementing the trait `{}` or choosing a less ambiguous method name", + method_config.trait_name + ) + ); + } + } + } + + wrong_self_convention::check( + cx, + &name, + item.vis.node.is_pub(), + self_ty, + first_arg_ty, + first_arg.pat.span + ); + } + } + + if let hir::ImplItemKind::Fn(_, _) = impl_item.kind { + let ret_ty = return_ty(cx, impl_item.hir_id()); + + // walk the return type and check for Self (this does not check associated types) + if contains_ty(ret_ty, self_ty) { + return; + } + + // if return type is impl trait, check the associated types + if let ty::Opaque(def_id, _) = *ret_ty.kind() { + // one of the associated types must be Self + for &(predicate, _span) in cx.tcx.explicit_item_bounds(def_id) { + if let ty::PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() { + // walk the associated type and check for Self + if contains_ty(projection_predicate.ty, self_ty) { + return; + } + } + } + } + + if name == "new" && !TyS::same_type(ret_ty, self_ty) { + span_lint( + cx, + NEW_RET_NO_SELF, + impl_item.span, + "methods called `new` usually return `Self`", + ); + } + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + if in_external_macro(cx.tcx.sess, item.span) { + return; + } + + if_chain! { + if let TraitItemKind::Fn(ref sig, _) = item.kind; + if let Some(first_arg_ty) = sig.decl.inputs.iter().next(); + let first_arg_span = first_arg_ty.span; + let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty); + let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty(); + + then { + wrong_self_convention::check( + cx, + &item.ident.name.as_str(), + false, + self_ty, + first_arg_ty, + first_arg_span + ); + } + } + + if_chain! { + if item.ident.name == sym::new; + if let TraitItemKind::Fn(_, _) = item.kind; + let ret_ty = return_ty(cx, item.hir_id()); + let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty(); + if !contains_ty(ret_ty, self_ty); + + then { + span_lint( + cx, + NEW_RET_NO_SELF, + item.span, + "methods called `new` usually return `Self`", + ); + } + } + } + + extract_msrv_attr!(LateContext); +} + +fn derefs_to_slice<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ty: Ty<'tcx>, +) -> Option<&'tcx hir::Expr<'tcx>> { + fn may_slice<'a>(cx: &LateContext<'a>, ty: Ty<'a>) -> bool { + match ty.kind() { + ty::Slice(_) => true, + ty::Adt(def, _) if def.is_box() => may_slice(cx, ty.boxed_ty()), + ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym::vec_type), + ty::Array(_, size) => size + .try_eval_usize(cx.tcx, cx.param_env) + .map_or(false, |size| size < 32), + ty::Ref(_, inner, _) => may_slice(cx, inner), + _ => false, + } + } + + if let hir::ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind { + if path.ident.name == sym::iter && may_slice(cx, cx.typeck_results().expr_ty(&args[0])) { + Some(&args[0]) + } else { + None + } + } else { + match ty.kind() { + ty::Slice(_) => Some(expr), + ty::Adt(def, _) if def.is_box() && may_slice(cx, ty.boxed_ty()) => Some(expr), + ty::Ref(_, inner, _) => { + if may_slice(cx, inner) { + Some(expr) + } else { + None + } + }, + _ => None, + } + } +} + +/// Used for `lint_binary_expr_with_method_call`. +#[derive(Copy, Clone)] +struct BinaryExprInfo<'a> { + expr: &'a hir::Expr<'a>, + chain: &'a hir::Expr<'a>, + other: &'a hir::Expr<'a>, + eq: bool, +} + +/// Checks for the `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. +fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExprInfo<'_>) { + macro_rules! lint_with_both_lhs_and_rhs { + ($func:ident, $cx:expr, $info:ident) => { + if !$func($cx, $info) { + ::std::mem::swap(&mut $info.chain, &mut $info.other); + if $func($cx, $info) { + return; + } + } + }; + } + + lint_with_both_lhs_and_rhs!(lint_chars_next_cmp, cx, info); + lint_with_both_lhs_and_rhs!(lint_chars_last_cmp, cx, info); + lint_with_both_lhs_and_rhs!(lint_chars_next_cmp_with_unwrap, cx, info); + lint_with_both_lhs_and_rhs!(lint_chars_last_cmp_with_unwrap, cx, info); +} + +/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. +fn lint_chars_cmp( + cx: &LateContext<'_>, + info: &BinaryExprInfo<'_>, + chain_methods: &[&str], + lint: &'static Lint, + suggest: &str, +) -> bool { + if_chain! { + if let Some(args) = method_chain_args(info.chain, chain_methods); + if let hir::ExprKind::Call(ref fun, ref arg_char) = info.other.kind; + if arg_char.len() == 1; + if let hir::ExprKind::Path(ref qpath) = fun.kind; + if let Some(segment) = single_segment_path(qpath); + if segment.ident.name == sym::Some; + then { + let mut applicability = Applicability::MachineApplicable; + let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0][0]).peel_refs(); + + if *self_ty.kind() != ty::Str { + return false; + } + + span_lint_and_sugg( + cx, + lint, + info.expr.span, + &format!("you should use the `{}` method", suggest), + "like this", + format!("{}{}.{}({})", + if info.eq { "" } else { "!" }, + snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability), + suggest, + snippet_with_applicability(cx, arg_char[0].span, "..", &mut applicability)), + applicability, + ); + + return true; + } + } + + false +} + +/// Checks for the `CHARS_NEXT_CMP` lint. +fn lint_chars_next_cmp<'tcx>(cx: &LateContext<'tcx>, info: &BinaryExprInfo<'_>) -> bool { + lint_chars_cmp(cx, info, &["chars", "next"], CHARS_NEXT_CMP, "starts_with") +} + +/// Checks for the `CHARS_LAST_CMP` lint. +fn lint_chars_last_cmp<'tcx>(cx: &LateContext<'tcx>, info: &BinaryExprInfo<'_>) -> bool { + if lint_chars_cmp(cx, info, &["chars", "last"], CHARS_LAST_CMP, "ends_with") { + true + } else { + lint_chars_cmp(cx, info, &["chars", "next_back"], CHARS_LAST_CMP, "ends_with") + } +} + +/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`. +fn lint_chars_cmp_with_unwrap<'tcx>( + cx: &LateContext<'tcx>, + info: &BinaryExprInfo<'_>, + chain_methods: &[&str], + lint: &'static Lint, + suggest: &str, +) -> bool { + if_chain! { + if let Some(args) = method_chain_args(info.chain, chain_methods); + if let hir::ExprKind::Lit(ref lit) = info.other.kind; + if let ast::LitKind::Char(c) = lit.node; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + lint, + info.expr.span, + &format!("you should use the `{}` method", suggest), + "like this", + format!("{}{}.{}('{}')", + if info.eq { "" } else { "!" }, + snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability), + suggest, + c), + applicability, + ); + + true + } else { + false + } + } +} + +/// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`. +fn lint_chars_next_cmp_with_unwrap<'tcx>(cx: &LateContext<'tcx>, info: &BinaryExprInfo<'_>) -> bool { + lint_chars_cmp_with_unwrap(cx, info, &["chars", "next", "unwrap"], CHARS_NEXT_CMP, "starts_with") +} + +/// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`. +fn lint_chars_last_cmp_with_unwrap<'tcx>(cx: &LateContext<'tcx>, info: &BinaryExprInfo<'_>) -> bool { + if lint_chars_cmp_with_unwrap(cx, info, &["chars", "last", "unwrap"], CHARS_LAST_CMP, "ends_with") { + true + } else { + lint_chars_cmp_with_unwrap(cx, info, &["chars", "next_back", "unwrap"], CHARS_LAST_CMP, "ends_with") + } +} + +fn get_hint_if_single_char_arg( + cx: &LateContext<'_>, + arg: &hir::Expr<'_>, + applicability: &mut Applicability, +) -> Option { + if_chain! { + if let hir::ExprKind::Lit(lit) = &arg.kind; + if let ast::LitKind::Str(r, style) = lit.node; + let string = r.as_str(); + if string.chars().count() == 1; + then { + let snip = snippet_with_applicability(cx, arg.span, &string, applicability); + let ch = if let ast::StrStyle::Raw(nhash) = style { + let nhash = nhash as usize; + // for raw string: r##"a"## + &snip[(nhash + 2)..(snip.len() - 1 - nhash)] + } else { + // for regular string: "a" + &snip[1..(snip.len() - 1)] + }; + let hint = format!("'{}'", if ch == "'" { "\\'" } else { ch }); + Some(hint) + } else { + None + } + } +} + +const FN_HEADER: hir::FnHeader = hir::FnHeader { + unsafety: hir::Unsafety::Normal, + constness: hir::Constness::NotConst, + asyncness: hir::IsAsync::NotAsync, + abi: rustc_target::spec::abi::Abi::Rust, +}; + +struct ShouldImplTraitCase { + trait_name: &'static str, + method_name: &'static str, + param_count: usize, + fn_header: hir::FnHeader, + // implicit self kind expected (none, self, &self, ...) + self_kind: SelfKind, + // checks against the output type + output_type: OutType, + // certain methods with explicit lifetimes can't implement the equivalent trait method + lint_explicit_lifetime: bool, +} +impl ShouldImplTraitCase { + const fn new( + trait_name: &'static str, + method_name: &'static str, + param_count: usize, + fn_header: hir::FnHeader, + self_kind: SelfKind, + output_type: OutType, + lint_explicit_lifetime: bool, + ) -> ShouldImplTraitCase { + ShouldImplTraitCase { + trait_name, + method_name, + param_count, + fn_header, + self_kind, + output_type, + lint_explicit_lifetime, + } + } + + fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool { + self.lint_explicit_lifetime + || !impl_item.generics.params.iter().any(|p| { + matches!( + p.kind, + hir::GenericParamKind::Lifetime { + kind: hir::LifetimeParamKind::Explicit + } + ) + }) + } +} + +#[rustfmt::skip] +const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ + ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), + ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), + ShouldImplTraitCase::new("std::ops::BitAnd", "bitand", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::BitOr", "bitor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::BitXor", "bitxor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::borrow::Borrow", "borrow", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), + ShouldImplTraitCase::new("std::borrow::BorrowMut", "borrow_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), + ShouldImplTraitCase::new("std::clone::Clone", "clone", 1, FN_HEADER, SelfKind::Ref, OutType::Any, true), + ShouldImplTraitCase::new("std::cmp::Ord", "cmp", 2, FN_HEADER, SelfKind::Ref, OutType::Any, true), + // FIXME: default doesn't work + ShouldImplTraitCase::new("std::default::Default", "default", 0, FN_HEADER, SelfKind::No, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Deref", "deref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), + ShouldImplTraitCase::new("std::ops::DerefMut", "deref_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), + ShouldImplTraitCase::new("std::ops::Div", "div", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Drop", "drop", 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true), + ShouldImplTraitCase::new("std::cmp::PartialEq", "eq", 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true), + ShouldImplTraitCase::new("std::iter::FromIterator", "from_iter", 1, FN_HEADER, SelfKind::No, OutType::Any, true), + ShouldImplTraitCase::new("std::str::FromStr", "from_str", 1, FN_HEADER, SelfKind::No, OutType::Any, true), + ShouldImplTraitCase::new("std::hash::Hash", "hash", 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true), + ShouldImplTraitCase::new("std::ops::Index", "index", 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true), + ShouldImplTraitCase::new("std::ops::IndexMut", "index_mut", 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), + ShouldImplTraitCase::new("std::iter::IntoIterator", "into_iter", 1, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Mul", "mul", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Neg", "neg", 1, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::iter::Iterator", "next", 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false), + ShouldImplTraitCase::new("std::ops::Not", "not", 1, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Rem", "rem", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Shl", "shl", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Shr", "shr", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), +]; + +#[rustfmt::skip] +const PATTERN_METHODS: [(&str, usize); 17] = [ + ("contains", 1), + ("starts_with", 1), + ("ends_with", 1), + ("find", 1), + ("rfind", 1), + ("split", 1), + ("rsplit", 1), + ("split_terminator", 1), + ("rsplit_terminator", 1), + ("splitn", 2), + ("rsplitn", 2), + ("matches", 1), + ("rmatches", 1), + ("match_indices", 1), + ("rmatch_indices", 1), + ("trim_start_matches", 1), + ("trim_end_matches", 1), +]; + +#[derive(Clone, Copy, PartialEq, Debug)] +enum SelfKind { + Value, + Ref, + RefMut, + No, +} + +impl SelfKind { + fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'_>, ty: Ty<'_>) -> bool { + if ty == parent_ty { + true + } else if ty.is_box() { + ty.boxed_ty() == parent_ty + } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) { + if let ty::Adt(_, substs) = ty.kind() { + substs.types().next().map_or(false, |t| t == parent_ty) + } else { + false + } + } else { + false + } + } + + fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + if let ty::Ref(_, t, m) = *ty.kind() { + return m == mutability && t == parent_ty; + } + + let trait_path = match mutability { + hir::Mutability::Not => &paths::ASREF_TRAIT, + hir::Mutability::Mut => &paths::ASMUT_TRAIT, + }; + + let trait_def_id = match get_trait_def_id(cx, trait_path) { + Some(did) => did, + None => return false, + }; + implements_trait(cx, ty, trait_def_id, &[parent_ty.into()]) + } + + match self { + Self::Value => matches_value(cx, parent_ty, ty), + Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty), + Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty), + Self::No => ty != parent_ty, + } + } + + #[must_use] + fn description(self) -> &'static str { + match self { + Self::Value => "self by value", + Self::Ref => "self by reference", + Self::RefMut => "self by mutable reference", + Self::No => "no self", + } + } +} + +#[derive(Clone, Copy)] +enum OutType { + Unit, + Bool, + Any, + Ref, +} + +impl OutType { + fn matches(self, cx: &LateContext<'_>, ty: &hir::FnRetTy<'_>) -> bool { + let is_unit = |ty: &hir::Ty<'_>| SpanlessEq::new(cx).eq_ty_kind(&ty.kind, &hir::TyKind::Tup(&[])); + match (self, ty) { + (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true, + (Self::Unit, &hir::FnRetTy::Return(ref ty)) if is_unit(ty) => true, + (Self::Bool, &hir::FnRetTy::Return(ref ty)) if is_bool(ty) => true, + (Self::Any, &hir::FnRetTy::Return(ref ty)) if !is_unit(ty) => true, + (Self::Ref, &hir::FnRetTy::Return(ref ty)) => matches!(ty.kind, hir::TyKind::Rptr(_, _)), + _ => false, + } + } +} + +fn is_bool(ty: &hir::Ty<'_>) -> bool { + if let hir::TyKind::Path(ref p) = ty.kind { + match_qpath(p, &["bool"]) + } else { + false + } +} + +fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { + expected.constness == actual.constness + && expected.unsafety == actual.unsafety + && expected.asyncness == actual.asyncness +} diff --git a/src/tools/clippy/clippy_lints/src/methods/ok_expect.rs b/src/tools/clippy/clippy_lints/src/methods/ok_expect.rs new file mode 100644 index 0000000000..c1706cc7cc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/ok_expect.rs @@ -0,0 +1,45 @@ +use crate::utils::{implements_trait, is_type_diagnostic_item, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::sym; + +use super::OK_EXPECT; + +/// lint use of `ok().expect()` for `Result`s +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ok_args: &[hir::Expr<'_>]) { + if_chain! { + // lint if the caller of `ok()` is a `Result` + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&ok_args[0]), sym::result_type); + let result_type = cx.typeck_results().expr_ty(&ok_args[0]); + if let Some(error_type) = get_error_type(cx, result_type); + if has_debug_impl(error_type, cx); + + then { + span_lint_and_help( + cx, + OK_EXPECT, + expr.span, + "called `ok().expect()` on a `Result` value", + None, + "you can call `expect()` directly on the `Result`", + ); + } + } +} + +/// Given a `Result` type, return its error type (`E`). +fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option> { + match ty.kind() { + ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym::result_type) => substs.types().nth(1), + _ => None, + } +} + +/// This checks whether a given type is known to implement Debug. +fn has_debug_impl<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { + cx.tcx + .get_diagnostic_item(sym::debug_trait) + .map_or(false, |debug| implements_trait(cx, ty, debug, &[])) +} diff --git a/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs b/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs new file mode 100644 index 0000000000..89067dbfe0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs @@ -0,0 +1,122 @@ +use crate::utils::{ + is_type_diagnostic_item, match_def_path, meets_msrv, path_to_local_id, paths, remove_blocks, snippet, + span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_semver::RustcVersion; +use rustc_span::sym; + +use super::OPTION_AS_REF_DEREF; + +const OPTION_AS_REF_DEREF_MSRV: RustcVersion = RustcVersion::new(1, 40, 0); + +/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + as_ref_args: &[hir::Expr<'_>], + map_args: &[hir::Expr<'_>], + is_mut: bool, + msrv: Option<&RustcVersion>, +) { + if !meets_msrv(msrv, &OPTION_AS_REF_DEREF_MSRV) { + return; + } + + let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not); + + let option_ty = cx.typeck_results().expr_ty(&as_ref_args[0]); + if !is_type_diagnostic_item(cx, option_ty, sym::option_type) { + return; + } + + let deref_aliases: [&[&str]; 9] = [ + &paths::DEREF_TRAIT_METHOD, + &paths::DEREF_MUT_TRAIT_METHOD, + &paths::CSTRING_AS_C_STR, + &paths::OS_STRING_AS_OS_STR, + &paths::PATH_BUF_AS_PATH, + &paths::STRING_AS_STR, + &paths::STRING_AS_MUT_STR, + &paths::VEC_AS_SLICE, + &paths::VEC_AS_MUT_SLICE, + ]; + + let is_deref = match map_args[1].kind { + hir::ExprKind::Path(ref expr_qpath) => cx + .qpath_res(expr_qpath, map_args[1].hir_id) + .opt_def_id() + .map_or(false, |fun_def_id| { + deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path)) + }), + hir::ExprKind::Closure(_, _, body_id, _, _) => { + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + + match &closure_expr.kind { + hir::ExprKind::MethodCall(_, _, args, _) => { + if_chain! { + if args.len() == 1; + if path_to_local_id(&args[0], closure_body.params[0].pat.hir_id); + let adj = cx + .typeck_results() + .expr_adjustments(&args[0]) + .iter() + .map(|x| &x.kind) + .collect::>(); + if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj; + then { + let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap(); + deref_aliases.iter().any(|path| match_def_path(cx, method_did, path)) + } else { + false + } + } + }, + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, ref inner) if same_mutability(m) => { + if_chain! { + if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner1) = inner.kind; + if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner2) = inner1.kind; + then { + path_to_local_id(inner2, closure_body.params[0].pat.hir_id) + } else { + false + } + } + }, + _ => false, + } + }, + _ => false, + }; + + if is_deref { + let current_method = if is_mut { + format!(".as_mut().map({})", snippet(cx, map_args[1].span, "..")) + } else { + format!(".as_ref().map({})", snippet(cx, map_args[1].span, "..")) + }; + let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" }; + let hint = format!("{}.{}()", snippet(cx, as_ref_args[0].span, ".."), method_hint); + let suggestion = format!("try using {} instead", method_hint); + + let msg = format!( + "called `{0}` on an Option value. This can be done more directly \ + by calling `{1}` instead", + current_method, hint + ); + span_lint_and_sugg( + cx, + OPTION_AS_REF_DEREF, + expr.span, + &msg, + &suggestion, + hint, + Applicability::MachineApplicable, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs b/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs new file mode 100644 index 0000000000..64f6ebc506 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs @@ -0,0 +1,78 @@ +use crate::utils::{is_type_diagnostic_item, match_qpath, paths, snippet, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use super::OPTION_MAP_OR_NONE; +use super::RESULT_MAP_OR_INTO_OPTION; + +/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_or_args: &'tcx [hir::Expr<'_>]) { + let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_or_args[0]), sym::option_type); + let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_or_args[0]), sym::result_type); + + // There are two variants of this `map_or` lint: + // (1) using `map_or` as an adapter from `Result` to `Option` + // (2) using `map_or` as a combinator instead of `and_then` + // + // (For this lint) we don't care if any other type calls `map_or` + if !is_option && !is_result { + return; + } + + let (lint_name, msg, instead, hint) = { + let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = map_or_args[1].kind { + match_qpath(qpath, &paths::OPTION_NONE) + } else { + return; + }; + + if !default_arg_is_none { + // nothing to lint! + return; + } + + let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_or_args[2].kind { + match_qpath(qpath, &paths::OPTION_SOME) + } else { + false + }; + + if is_option { + let self_snippet = snippet(cx, map_or_args[0].span, ".."); + let func_snippet = snippet(cx, map_or_args[2].span, ".."); + let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ + `and_then(..)` instead"; + ( + OPTION_MAP_OR_NONE, + msg, + "try using `and_then` instead", + format!("{0}.and_then({1})", self_snippet, func_snippet), + ) + } else if f_arg_is_some { + let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \ + `ok()` instead"; + let self_snippet = snippet(cx, map_or_args[0].span, ".."); + ( + RESULT_MAP_OR_INTO_OPTION, + msg, + "try using `ok` instead", + format!("{0}.ok()", self_snippet), + ) + } else { + // nothing to lint! + return; + } + }; + + span_lint_and_sugg( + cx, + lint_name, + expr.span, + msg, + instead, + hint, + Applicability::MachineApplicable, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs b/src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs new file mode 100644 index 0000000000..7cdd49bbf0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -0,0 +1,135 @@ +use crate::utils::{differing_macro_contexts, snippet_with_applicability, span_lint_and_then}; +use crate::utils::{is_copy, is_type_diagnostic_item}; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_path, NestedVisitorMap, Visitor}; +use rustc_hir::{self, HirId, Path}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::source_map::Span; +use rustc_span::{sym, Symbol}; + +use super::MAP_UNWRAP_OR; + +/// lint use of `map().unwrap_or()` for `Option`s +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &rustc_hir::Expr<'_>, + map_args: &'tcx [rustc_hir::Expr<'_>], + unwrap_args: &'tcx [rustc_hir::Expr<'_>], + map_span: Span, +) { + // lint if the caller of `map()` is an `Option` + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type) { + if !is_copy(cx, cx.typeck_results().expr_ty(&unwrap_args[1])) { + // Do not lint if the `map` argument uses identifiers in the `map` + // argument that are also used in the `unwrap_or` argument + + let mut unwrap_visitor = UnwrapVisitor { + cx, + identifiers: FxHashSet::default(), + }; + unwrap_visitor.visit_expr(&unwrap_args[1]); + + let mut map_expr_visitor = MapExprVisitor { + cx, + identifiers: unwrap_visitor.identifiers, + found_identifier: false, + }; + map_expr_visitor.visit_expr(&map_args[1]); + + if map_expr_visitor.found_identifier { + return; + } + } + + if differing_macro_contexts(unwrap_args[1].span, map_span) { + return; + } + + let mut applicability = Applicability::MachineApplicable; + // get snippet for unwrap_or() + let unwrap_snippet = snippet_with_applicability(cx, unwrap_args[1].span, "..", &mut applicability); + // lint message + // comparing the snippet from source to raw text ("None") below is safe + // because we already have checked the type. + let arg = if unwrap_snippet == "None" { "None" } else { "" }; + let unwrap_snippet_none = unwrap_snippet == "None"; + let suggest = if unwrap_snippet_none { + "and_then()" + } else { + "map_or(, )" + }; + let msg = &format!( + "called `map().unwrap_or({})` on an `Option` value. \ + This can be done more directly by calling `{}` instead", + arg, suggest + ); + + span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| { + let map_arg_span = map_args[1].span; + + let mut suggestion = vec![ + ( + map_span, + String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }), + ), + (expr.span.with_lo(unwrap_args[0].span.hi()), String::from("")), + ]; + + if !unwrap_snippet_none { + suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{}, ", unwrap_snippet))); + } + + diag.multipart_suggestion(&format!("use `{}` instead", suggest), suggestion, applicability); + }); + } +} + +struct UnwrapVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + identifiers: FxHashSet, +} + +impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) { + self.identifiers.insert(ident(path)); + walk_path(self, path); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} + +struct MapExprVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + identifiers: FxHashSet, + found_identifier: bool, +} + +impl<'a, 'tcx> Visitor<'tcx> for MapExprVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) { + if self.identifiers.contains(&ident(path)) { + self.found_identifier = true; + return; + } + walk_path(self, path); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} + +fn ident(path: &Path<'_>) -> Symbol { + path.segments + .last() + .expect("segments should be composed of at least 1 element") + .ident + .name +} diff --git a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs new file mode 100644 index 0000000000..5f7fc431d2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs @@ -0,0 +1,173 @@ +use crate::utils::eager_or_lazy::is_lazyness_candidate; +use crate::utils::{ + contains_return, get_trait_def_id, implements_trait, is_type_diagnostic_item, last_path_segment, match_type, paths, + snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Span; +use rustc_span::symbol::sym; +use std::borrow::Cow; + +use super::OR_FUN_CALL; + +/// Checks for the `OR_FUN_CALL` lint. +#[allow(clippy::too_many_lines)] +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + method_span: Span, + name: &str, + args: &'tcx [hir::Expr<'_>], +) { + /// Checks for `unwrap_or(T::new())` or `unwrap_or(T::default())`. + fn check_unwrap_or_default( + cx: &LateContext<'_>, + name: &str, + fun: &hir::Expr<'_>, + self_expr: &hir::Expr<'_>, + arg: &hir::Expr<'_>, + or_has_args: bool, + span: Span, + ) -> bool { + if_chain! { + if !or_has_args; + if name == "unwrap_or"; + if let hir::ExprKind::Path(ref qpath) = fun.kind; + let path = &*last_path_segment(qpath).ident.as_str(); + if ["default", "new"].contains(&path); + let arg_ty = cx.typeck_results().expr_ty(arg); + if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); + if implements_trait(cx, arg_ty, default_trait_id, &[]); + + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + OR_FUN_CALL, + span, + &format!("use of `{}` followed by a call to `{}`", name, path), + "try this", + format!( + "{}.unwrap_or_default()", + snippet_with_applicability(cx, self_expr.span, "..", &mut applicability) + ), + applicability, + ); + + true + } else { + false + } + } + } + + /// Checks for `*or(foo())`. + #[allow(clippy::too_many_arguments)] + fn check_general_case<'tcx>( + cx: &LateContext<'tcx>, + name: &str, + method_span: Span, + self_expr: &hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, + span: Span, + // None if lambda is required + fun_span: Option, + ) { + // (path, fn_has_argument, methods, suffix) + static KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [ + (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"), + (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"), + (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"), + (&paths::RESULT, true, &["or", "unwrap_or"], "else"), + ]; + + if let hir::ExprKind::MethodCall(ref path, _, ref args, _) = &arg.kind { + if path.ident.as_str() == "len" { + let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); + + match ty.kind() { + ty::Slice(_) | ty::Array(_, _) => return, + _ => (), + } + + if is_type_diagnostic_item(cx, ty, sym::vec_type) { + return; + } + } + } + + if_chain! { + if KNOW_TYPES.iter().any(|k| k.2.contains(&name)); + + if is_lazyness_candidate(cx, arg); + if !contains_return(&arg); + + let self_ty = cx.typeck_results().expr_ty(self_expr); + + if let Some(&(_, fn_has_arguments, poss, suffix)) = + KNOW_TYPES.iter().find(|&&i| match_type(cx, self_ty, i.0)); + + if poss.contains(&name); + + then { + let macro_expanded_snipped; + let sugg: Cow<'_, str> = { + let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { + (false, Some(fun_span)) => (fun_span, false), + _ => (arg.span, true), + }; + let snippet = { + let not_macro_argument_snippet = snippet_with_macro_callsite(cx, snippet_span, ".."); + if not_macro_argument_snippet == "vec![]" { + macro_expanded_snipped = snippet(cx, snippet_span, ".."); + match macro_expanded_snipped.strip_prefix("$crate::vec::") { + Some(stripped) => Cow::from(stripped), + None => macro_expanded_snipped + } + } + else { + not_macro_argument_snippet + } + }; + + if use_lambda { + let l_arg = if fn_has_arguments { "_" } else { "" }; + format!("|{}| {}", l_arg, snippet).into() + } else { + snippet + } + }; + let span_replace_word = method_span.with_hi(span.hi()); + span_lint_and_sugg( + cx, + OR_FUN_CALL, + span_replace_word, + &format!("use of `{}` followed by a function call", name), + "try this", + format!("{}_{}({})", name, suffix, sugg), + Applicability::HasPlaceholders, + ); + } + } + } + + if args.len() == 2 { + match args[1].kind { + hir::ExprKind::Call(ref fun, ref or_args) => { + let or_has_args = !or_args.is_empty(); + if !check_unwrap_or_default(cx, name, fun, &args[0], &args[1], or_has_args, expr.span) { + let fun_span = if or_has_args { None } else { Some(fun.span) }; + check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, fun_span); + } + }, + hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { + check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, None); + }, + _ => {}, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/search_is_some.rs b/src/tools/clippy/clippy_lints/src/methods/search_is_some.rs new file mode 100644 index 0000000000..e9e6544322 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/search_is_some.rs @@ -0,0 +1,101 @@ +use crate::utils::{ + is_type_diagnostic_item, match_trait_method, paths, snippet, snippet_with_applicability, span_lint_and_help, + span_lint_and_sugg, strip_pat_refs, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::PatKind; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Span; +use rustc_span::symbol::sym; + +use super::SEARCH_IS_SOME; + +/// lint searching an Iterator followed by `is_some()` +/// or calling `find()` on a string followed by `is_some()` +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + search_method: &str, + search_args: &'tcx [hir::Expr<'_>], + is_some_args: &'tcx [hir::Expr<'_>], + method_span: Span, +) { + // lint if caller of search is an Iterator + if match_trait_method(cx, &is_some_args[0], &paths::ITERATOR) { + let msg = format!( + "called `is_some()` after searching an `Iterator` with `{}`", + search_method + ); + let hint = "this is more succinctly expressed by calling `any()`"; + let search_snippet = snippet(cx, search_args[1].span, ".."); + if search_snippet.lines().count() <= 1 { + // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()` + // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()` + let any_search_snippet = if_chain! { + if search_method == "find"; + if let hir::ExprKind::Closure(_, _, body_id, ..) = search_args[1].kind; + let closure_body = cx.tcx.hir().body(body_id); + if let Some(closure_arg) = closure_body.params.get(0); + then { + if let hir::PatKind::Ref(..) = closure_arg.pat.kind { + Some(search_snippet.replacen('&', "", 1)) + } else if let PatKind::Binding(_, _, ident, _) = strip_pat_refs(&closure_arg.pat).kind { + let name = &*ident.name.as_str(); + Some(search_snippet.replace(&format!("*{}", name), name)) + } else { + None + } + } else { + None + } + }; + // add note if not multi-line + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + method_span.with_hi(expr.span.hi()), + &msg, + "use `any()` instead", + format!( + "any({})", + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ), + Applicability::MachineApplicable, + ); + } else { + span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, hint); + } + } + // lint if `find()` is called by `String` or `&str` + else if search_method == "find" { + let is_string_or_str_slice = |e| { + let self_ty = cx.typeck_results().expr_ty(e).peel_refs(); + if is_type_diagnostic_item(cx, self_ty, sym::string_type) { + true + } else { + *self_ty.kind() == ty::Str + } + }; + if_chain! { + if is_string_or_str_slice(&search_args[0]); + if is_string_or_str_slice(&search_args[1]); + then { + let msg = "called `is_some()` after calling `find()` on a string"; + let mut applicability = Applicability::MachineApplicable; + let find_arg = snippet_with_applicability(cx, search_args[1].span, "..", &mut applicability); + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + method_span.with_hi(expr.span.hi()), + msg, + "use `contains()` instead", + format!("contains({})", find_arg), + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/single_char_insert_string.rs b/src/tools/clippy/clippy_lints/src/methods/single_char_insert_string.rs new file mode 100644 index 0000000000..0ce8b66978 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/single_char_insert_string.rs @@ -0,0 +1,27 @@ +use crate::methods::get_hint_if_single_char_arg; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::SINGLE_CHAR_ADD_STR; + +/// lint for length-1 `str`s as argument for `insert_str` +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[2], &mut applicability) { + let base_string_snippet = + snippet_with_applicability(cx, args[0].span.source_callsite(), "_", &mut applicability); + let pos_arg = snippet_with_applicability(cx, args[1].span, "..", &mut applicability); + let sugg = format!("{}.insert({}, {})", base_string_snippet, pos_arg, extension_string); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `insert_str()` using a single-character string literal", + "consider using `insert` with a character literal", + sugg, + applicability, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs b/src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs new file mode 100644 index 0000000000..61cbc9d2f0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs @@ -0,0 +1,23 @@ +use crate::methods::get_hint_if_single_char_arg; +use crate::utils::span_lint_and_sugg; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::SINGLE_CHAR_PATTERN; + +/// lint for length-1 `str`s for methods in `PATTERN_METHODS` +pub(super) fn check(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) { + let mut applicability = Applicability::MachineApplicable; + if let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability) { + span_lint_and_sugg( + cx, + SINGLE_CHAR_PATTERN, + arg.span, + "single-character string constant used as pattern", + "try using a `char` instead", + hint, + applicability, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/single_char_push_string.rs b/src/tools/clippy/clippy_lints/src/methods/single_char_push_string.rs new file mode 100644 index 0000000000..deacc70b71 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/single_char_push_string.rs @@ -0,0 +1,26 @@ +use crate::methods::get_hint_if_single_char_arg; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::SINGLE_CHAR_ADD_STR; + +/// lint for length-1 `str`s as argument for `push_str` +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability) { + let base_string_snippet = + snippet_with_applicability(cx, args[0].span.source_callsite(), "..", &mut applicability); + let sugg = format!("{}.push({})", base_string_snippet, extension_string); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `push_str()` using a single-character string literal", + "consider using `push` with a character literal", + sugg, + applicability, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/skip_while_next.rs b/src/tools/clippy/clippy_lints/src/methods/skip_while_next.rs new file mode 100644 index 0000000000..8ba6ae9520 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/skip_while_next.rs @@ -0,0 +1,20 @@ +use crate::utils::{match_trait_method, paths, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::SKIP_WHILE_NEXT; + +/// lint use of `skip_while().next()` for `Iterators` +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, _skip_while_args: &'tcx [hir::Expr<'_>]) { + // lint if caller of `.skip_while().next()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + span_lint_and_help( + cx, + SKIP_WHILE_NEXT, + expr.span, + "called `skip_while(

).next()` on an `Iterator`", + None, + "this is more succinctly expressed by calling `.find(!

)` instead", + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs b/src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs new file mode 100644 index 0000000000..0a08ea2617 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs @@ -0,0 +1,42 @@ +use crate::utils::{is_type_diagnostic_item, method_chain_args, snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +use super::STRING_EXTEND_CHARS; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let obj_ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); + if is_type_diagnostic_item(cx, obj_ty, sym::string_type) { + let arg = &args[1]; + if let Some(arglists) = method_chain_args(arg, &["chars"]) { + let target = &arglists[0][0]; + let self_ty = cx.typeck_results().expr_ty(target).peel_refs(); + let ref_str = if *self_ty.kind() == ty::Str { + "" + } else if is_type_diagnostic_item(cx, self_ty, sym::string_type) { + "&" + } else { + return; + }; + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + STRING_EXTEND_CHARS, + expr.span, + "calling `.extend(_.chars())`", + "try this", + format!( + "{}.push_str({}{})", + snippet_with_applicability(cx, args[0].span, "..", &mut applicability), + ref_str, + snippet_with_applicability(cx, target.span, "..", &mut applicability) + ), + applicability, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs b/src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs new file mode 100644 index 0000000000..e135a826dc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs @@ -0,0 +1,16 @@ +use crate::utils::span_lint_and_help; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::SUSPICIOUS_MAP; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>) { + span_lint_and_help( + cx, + SUSPICIOUS_MAP, + expr.span, + "this call to `map()` won't have an effect on the call to `count()`", + None, + "make sure you did not confuse `map` with `filter` or `for_each`", + ); +} diff --git a/src/tools/clippy/clippy_lints/src/methods/uninit_assumed_init.rs b/src/tools/clippy/clippy_lints/src/methods/uninit_assumed_init.rs new file mode 100644 index 0000000000..798b66192c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/uninit_assumed_init.rs @@ -0,0 +1,35 @@ +use crate::utils::{match_def_path, match_qpath, paths, span_lint}; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +use super::UNINIT_ASSUMED_INIT; + +/// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter) +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, outer: &hir::Expr<'_>) { + if_chain! { + if let hir::ExprKind::Call(ref callee, ref args) = expr.kind; + if args.is_empty(); + if let hir::ExprKind::Path(ref path) = callee.kind; + if match_qpath(path, &paths::MEM_MAYBEUNINIT_UNINIT); + if !is_maybe_uninit_ty_valid(cx, cx.typeck_results().expr_ty_adjusted(outer)); + then { + span_lint( + cx, + UNINIT_ASSUMED_INIT, + outer.span, + "this call for this type may be undefined behavior" + ); + } + } +} + +fn is_maybe_uninit_ty_valid(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + match ty.kind() { + ty::Array(ref component, _) => is_maybe_uninit_ty_valid(cx, component), + ty::Tuple(ref types) => types.types().all(|ty| is_maybe_uninit_ty_valid(cx, ty)), + ty::Adt(ref adt, _) => match_def_path(cx, adt.did, &paths::MEM_MAYBEUNINIT), + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs new file mode 100644 index 0000000000..12b2cf0a16 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -0,0 +1,132 @@ +use crate::utils::usage::mutated_variables; +use crate::utils::{match_qpath, match_trait_method, path_to_local_id, paths, span_lint}; +use rustc_hir as hir; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; + +use if_chain::if_chain; + +use super::UNNECESSARY_FILTER_MAP; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + if !match_trait_method(cx, expr, &paths::ITERATOR) { + return; + } + + if let hir::ExprKind::Closure(_, _, body_id, ..) = args[1].kind { + let body = cx.tcx.hir().body(body_id); + let arg_id = body.params[0].pat.hir_id; + let mutates_arg = + mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id)); + + let (mut found_mapping, mut found_filtering) = check_expression(&cx, arg_id, &body.value); + + let mut return_visitor = ReturnVisitor::new(&cx, arg_id); + return_visitor.visit_expr(&body.value); + found_mapping |= return_visitor.found_mapping; + found_filtering |= return_visitor.found_filtering; + + if !found_filtering { + span_lint( + cx, + UNNECESSARY_FILTER_MAP, + expr.span, + "this `.filter_map` can be written more simply using `.map`", + ); + return; + } + + if !found_mapping && !mutates_arg { + span_lint( + cx, + UNNECESSARY_FILTER_MAP, + expr.span, + "this `.filter_map` can be written more simply using `.filter`", + ); + return; + } + } +} + +// returns (found_mapping, found_filtering) +fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> (bool, bool) { + match &expr.kind { + hir::ExprKind::Call(ref func, ref args) => { + if_chain! { + if let hir::ExprKind::Path(ref path) = func.kind; + then { + if match_qpath(path, &paths::OPTION_SOME) { + if path_to_local_id(&args[0], arg_id) { + return (false, false) + } + return (true, false); + } + // We don't know. It might do anything. + return (true, true); + } + } + (true, true) + }, + hir::ExprKind::Block(ref block, _) => block + .expr + .as_ref() + .map_or((false, false), |expr| check_expression(cx, arg_id, &expr)), + hir::ExprKind::Match(_, arms, _) => { + let mut found_mapping = false; + let mut found_filtering = false; + for arm in *arms { + let (m, f) = check_expression(cx, arg_id, &arm.body); + found_mapping |= m; + found_filtering |= f; + } + (found_mapping, found_filtering) + }, + // There must be an else_arm or there will be a type error + hir::ExprKind::If(_, ref if_arm, Some(ref else_arm)) => { + let if_check = check_expression(cx, arg_id, if_arm); + let else_check = check_expression(cx, arg_id, else_arm); + (if_check.0 | else_check.0, if_check.1 | else_check.1) + }, + hir::ExprKind::Path(path) if match_qpath(path, &paths::OPTION_NONE) => (false, true), + _ => (true, true), + } +} + +struct ReturnVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + arg_id: hir::HirId, + // Found a non-None return that isn't Some(input) + found_mapping: bool, + // Found a return that isn't Some + found_filtering: bool, +} + +impl<'a, 'tcx> ReturnVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>, arg_id: hir::HirId) -> ReturnVisitor<'a, 'tcx> { + ReturnVisitor { + cx, + arg_id, + found_mapping: false, + found_filtering: false, + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for ReturnVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + if let hir::ExprKind::Ret(Some(expr)) = &expr.kind { + let (found_mapping, found_filtering) = check_expression(self.cx, self.arg_id, expr); + self.found_mapping |= found_mapping; + self.found_filtering |= found_filtering; + } else { + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs new file mode 100644 index 0000000000..a26443f4ee --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs @@ -0,0 +1,101 @@ +use crate::utils::{ + match_trait_method, path_to_local_id, paths, remove_blocks, snippet_with_applicability, span_lint_and_sugg, + strip_pat_refs, +}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::PatKind; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use super::UNNECESSARY_FOLD; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, fold_args: &[hir::Expr<'_>], fold_span: Span) { + fn check_fold_with_op( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + fold_args: &[hir::Expr<'_>], + fold_span: Span, + op: hir::BinOpKind, + replacement_method_name: &str, + replacement_has_args: bool, + ) { + if_chain! { + // Extract the body of the closure passed to fold + if let hir::ExprKind::Closure(_, _, body_id, _, _) = fold_args[2].kind; + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + + // Check if the closure body is of the form `acc some_expr(x)` + if let hir::ExprKind::Binary(ref bin_op, ref left_expr, ref right_expr) = closure_expr.kind; + if bin_op.node == op; + + // Extract the names of the two arguments to the closure + if let [param_a, param_b] = closure_body.params; + if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(¶m_a.pat).kind; + if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(¶m_b.pat).kind; + + if path_to_local_id(left_expr, first_arg_id); + if replacement_has_args || path_to_local_id(right_expr, second_arg_id); + + then { + let mut applicability = Applicability::MachineApplicable; + let sugg = if replacement_has_args { + format!( + "{replacement}(|{s}| {r})", + replacement = replacement_method_name, + s = second_arg_ident, + r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability), + ) + } else { + format!( + "{replacement}()", + replacement = replacement_method_name, + ) + }; + + span_lint_and_sugg( + cx, + UNNECESSARY_FOLD, + fold_span.with_hi(expr.span.hi()), + // TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f) + "this `.fold` can be written more succinctly using another method", + "try", + sugg, + applicability, + ); + } + } + } + + // Check that this is a call to Iterator::fold rather than just some function called fold + if !match_trait_method(cx, expr, &paths::ITERATOR) { + return; + } + + assert!( + fold_args.len() == 3, + "Expected fold_args to have three entries - the receiver, the initial value and the closure" + ); + + // Check if the first argument to .fold is a suitable literal + if let hir::ExprKind::Lit(ref lit) = fold_args[1].kind { + match lit.node { + ast::LitKind::Bool(false) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Or, "any", true) + }, + ast::LitKind::Bool(true) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::And, "all", true) + }, + ast::LitKind::Int(0, _) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Add, "sum", false) + }, + ast::LitKind::Int(1, _) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Mul, "product", false) + }, + _ => (), + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs new file mode 100644 index 0000000000..a17259d697 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -0,0 +1,65 @@ +use crate::utils::{eager_or_lazy, usage}; +use crate::utils::{is_type_diagnostic_item, snippet, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::UNNECESSARY_LAZY_EVALUATIONS; + +/// lint use of `_else(simple closure)` for `Option`s and `Result`s that can be +/// replaced with `(return value of simple closure)` +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + args: &'tcx [hir::Expr<'_>], + simplify_using: &str, +) { + let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym::option_type); + let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym::result_type); + + if is_option || is_result { + if let hir::ExprKind::Closure(_, _, eid, _, _) = args[1].kind { + let body = cx.tcx.hir().body(eid); + let body_expr = &body.value; + + if usage::BindingUsageFinder::are_params_used(cx, body) { + return; + } + + if eager_or_lazy::is_eagerness_candidate(cx, body_expr) { + let msg = if is_option { + "unnecessary closure used to substitute value for `Option::None`" + } else { + "unnecessary closure used to substitute value for `Result::Err`" + }; + let applicability = if body + .params + .iter() + // bindings are checked to be unused above + .all(|param| matches!(param.pat.kind, hir::PatKind::Binding(..) | hir::PatKind::Wild)) + { + Applicability::MachineApplicable + } else { + // replacing the lambda may break type inference + Applicability::MaybeIncorrect + }; + + span_lint_and_sugg( + cx, + UNNECESSARY_LAZY_EVALUATIONS, + expr.span, + msg, + &format!("use `{}` instead", simplify_using), + format!( + "{0}.{1}({2})", + snippet(cx, args[0].span, ".."), + simplify_using, + snippet(cx, body_expr.span, ".."), + ), + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs b/src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs new file mode 100644 index 0000000000..094c3fc45c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs @@ -0,0 +1,34 @@ +use crate::utils::{is_type_diagnostic_item, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::UNWRAP_USED; + +/// lint use of `unwrap()` for `Option`s and `Result`s +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, unwrap_args: &[hir::Expr<'_>]) { + let obj_ty = cx.typeck_results().expr_ty(&unwrap_args[0]).peel_refs(); + + let mess = if is_type_diagnostic_item(cx, obj_ty, sym::option_type) { + Some((UNWRAP_USED, "an Option", "None")) + } else if is_type_diagnostic_item(cx, obj_ty, sym::result_type) { + Some((UNWRAP_USED, "a Result", "Err")) + } else { + None + }; + + if let Some((lint, kind, none_value)) = mess { + span_lint_and_help( + cx, + lint, + expr.span, + &format!("used `unwrap()` on `{}` value", kind,), + None, + &format!( + "if you don't want to handle the `{}` case gracefully, consider \ + using `expect()` to provide a better panic message", + none_value, + ), + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/useless_asref.rs b/src/tools/clippy/clippy_lints/src/methods/useless_asref.rs new file mode 100644 index 0000000000..e4554f8d48 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/useless_asref.rs @@ -0,0 +1,45 @@ +use crate::utils::{ + get_parent_expr, match_trait_method, paths, snippet_with_applicability, span_lint_and_sugg, walk_ptrs_ty_depth, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::USELESS_ASREF; + +/// Checks for the `USELESS_ASREF` lint. +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, as_ref_args: &[hir::Expr<'_>]) { + // when we get here, we've already checked that the call name is "as_ref" or "as_mut" + // check if the call is to the actual `AsRef` or `AsMut` trait + if match_trait_method(cx, expr, &paths::ASREF_TRAIT) || match_trait_method(cx, expr, &paths::ASMUT_TRAIT) { + // check if the type after `as_ref` or `as_mut` is the same as before + let recvr = &as_ref_args[0]; + let rcv_ty = cx.typeck_results().expr_ty(recvr); + let res_ty = cx.typeck_results().expr_ty(expr); + let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty); + let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty); + if base_rcv_ty == base_res_ty && rcv_depth >= res_depth { + // allow the `as_ref` or `as_mut` if it is followed by another method call + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let hir::ExprKind::MethodCall(_, ref span, _, _) = parent.kind; + if span != &expr.span; + then { + return; + } + } + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + USELESS_ASREF, + expr.span, + &format!("this call to `{}` does nothing", call_name), + "try this", + snippet_with_applicability(cx, recvr.span, "..", &mut applicability).to_string(), + applicability, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/wrong_self_convention.rs b/src/tools/clippy/clippy_lints/src/methods/wrong_self_convention.rs new file mode 100644 index 0000000000..90fab57743 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/wrong_self_convention.rs @@ -0,0 +1,76 @@ +use crate::methods::SelfKind; +use crate::utils::span_lint; +use rustc_lint::LateContext; +use rustc_middle::ty::TyS; +use rustc_span::source_map::Span; +use std::fmt; + +use super::WRONG_PUB_SELF_CONVENTION; +use super::WRONG_SELF_CONVENTION; + +#[rustfmt::skip] +const CONVENTIONS: [(Convention, &[SelfKind]); 7] = [ + (Convention::Eq("new"), &[SelfKind::No]), + (Convention::StartsWith("as_"), &[SelfKind::Ref, SelfKind::RefMut]), + (Convention::StartsWith("from_"), &[SelfKind::No]), + (Convention::StartsWith("into_"), &[SelfKind::Value]), + (Convention::StartsWith("is_"), &[SelfKind::Ref, SelfKind::No]), + (Convention::Eq("to_mut"), &[SelfKind::RefMut]), + (Convention::StartsWith("to_"), &[SelfKind::Ref]), +]; +enum Convention { + Eq(&'static str), + StartsWith(&'static str), +} + +impl Convention { + #[must_use] + fn check(&self, other: &str) -> bool { + match *self { + Self::Eq(this) => this == other, + Self::StartsWith(this) => other.starts_with(this) && this != other, + } + } +} + +impl fmt::Display for Convention { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match *self { + Self::Eq(this) => this.fmt(f), + Self::StartsWith(this) => this.fmt(f).and_then(|_| '*'.fmt(f)), + } + } +} + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + item_name: &str, + is_pub: bool, + self_ty: &'tcx TyS<'tcx>, + first_arg_ty: &'tcx TyS<'tcx>, + first_arg_span: Span, +) { + let lint = if is_pub { + WRONG_PUB_SELF_CONVENTION + } else { + WRONG_SELF_CONVENTION + }; + if let Some((ref conv, self_kinds)) = &CONVENTIONS.iter().find(|(ref conv, _)| conv.check(item_name)) { + if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) { + span_lint( + cx, + lint, + first_arg_span, + &format!( + "methods called `{}` usually take {}; consider choosing a less ambiguous name", + conv, + &self_kinds + .iter() + .map(|k| k.description()) + .collect::>() + .join(" or ") + ), + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/zst_offset.rs b/src/tools/clippy/clippy_lints/src/methods/zst_offset.rs new file mode 100644 index 0000000000..f133572673 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/zst_offset.rs @@ -0,0 +1,19 @@ +use crate::utils::span_lint; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::ZST_OFFSET; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + if_chain! { + if args.len() == 2; + if let ty::RawPtr(ty::TypeAndMut { ref ty, .. }) = cx.typeck_results().expr_ty(&args[0]).kind(); + if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)); + if layout.is_zst(); + then { + span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/minmax.rs b/src/tools/clippy/clippy_lints/src/minmax.rs new file mode 100644 index 0000000000..8d0c3b8e0f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/minmax.rs @@ -0,0 +1,123 @@ +use crate::consts::{constant_simple, Constant}; +use crate::utils::{match_def_path, match_trait_method, paths, span_lint}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::cmp::Ordering; + +declare_clippy_lint! { + /// **What it does:** Checks for expressions where `std::cmp::min` and `max` are + /// used to clamp values, but switched so that the result is constant. + /// + /// **Why is this bad?** This is in all probability not the intended outcome. At + /// the least it hurts readability of the code. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```ignore + /// min(0, max(100, x)) + /// ``` + /// or + /// ```ignore + /// x.max(100).min(0) + /// ``` + /// It will always be equal to `0`. Probably the author meant to clamp the value + /// between 0 and 100, but has erroneously swapped `min` and `max`. + pub MIN_MAX, + correctness, + "`min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant" +} + +declare_lint_pass!(MinMaxPass => [MIN_MAX]); + +impl<'tcx> LateLintPass<'tcx> for MinMaxPass { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some((outer_max, outer_c, oe)) = min_max(cx, expr) { + if let Some((inner_max, inner_c, ie)) = min_max(cx, oe) { + if outer_max == inner_max { + return; + } + match ( + outer_max, + Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(ie), &outer_c, &inner_c), + ) { + (_, None) | (MinMax::Max, Some(Ordering::Less)) | (MinMax::Min, Some(Ordering::Greater)) => (), + _ => { + span_lint( + cx, + MIN_MAX, + expr.span, + "this `min`/`max` combination leads to constant result", + ); + }, + } + } + } + } +} + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +enum MinMax { + Min, + Max, +} + +fn min_max<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant, &'a Expr<'a>)> { + match expr.kind { + ExprKind::Call(ref path, ref args) => { + if let ExprKind::Path(ref qpath) = path.kind { + cx.typeck_results() + .qpath_res(qpath, path.hir_id) + .opt_def_id() + .and_then(|def_id| { + if match_def_path(cx, def_id, &paths::CMP_MIN) { + fetch_const(cx, args, MinMax::Min) + } else if match_def_path(cx, def_id, &paths::CMP_MAX) { + fetch_const(cx, args, MinMax::Max) + } else { + None + } + }) + } else { + None + } + }, + ExprKind::MethodCall(ref path, _, ref args, _) => { + if_chain! { + if let [obj, _] = args; + if cx.typeck_results().expr_ty(obj).is_floating_point() || match_trait_method(cx, expr, &paths::ORD); + then { + if path.ident.name == sym!(max) { + fetch_const(cx, args, MinMax::Max) + } else if path.ident.name == sym!(min) { + fetch_const(cx, args, MinMax::Min) + } else { + None + } + } else { + None + } + } + }, + _ => None, + } +} + +fn fetch_const<'a>(cx: &LateContext<'_>, args: &'a [Expr<'a>], m: MinMax) -> Option<(MinMax, Constant, &'a Expr<'a>)> { + if args.len() != 2 { + return None; + } + constant_simple(cx, cx.typeck_results(), &args[0]).map_or_else( + || constant_simple(cx, cx.typeck_results(), &args[1]).map(|c| (m, c, &args[0])), + |c| { + if constant_simple(cx, cx.typeck_results(), &args[1]).is_none() { + // otherwise ignore + Some((m, c, &args[1])) + } else { + None + } + }, + ) +} diff --git a/src/tools/clippy/clippy_lints/src/misc.rs b/src/tools/clippy/clippy_lints/src/misc.rs new file mode 100644 index 0000000000..acdc245456 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/misc.rs @@ -0,0 +1,774 @@ +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{ + self as hir, def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt, + StmtKind, TyKind, UnOp, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::hygiene::DesugaringKind; +use rustc_span::source_map::{ExpnKind, Span}; +use rustc_span::symbol::sym; + +use crate::consts::{constant, Constant}; +use crate::utils::sugg::Sugg; +use crate::utils::{ + get_item_name, get_parent_expr, higher, implements_trait, in_constant, is_diagnostic_assoc_item, is_integer_const, + iter_input_pats, last_path_segment, match_qpath, snippet, snippet_opt, span_lint, span_lint_and_sugg, + span_lint_and_then, span_lint_hir_and_then, unsext, SpanlessEq, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for function arguments and let bindings denoted as + /// `ref`. + /// + /// **Why is this bad?** The `ref` declaration makes the function take an owned + /// value, but turns the argument into a reference (which means that the value + /// is destroyed when exiting the function). This adds not much value: either + /// take a reference type, or take an owned value and create references in the + /// body. + /// + /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The + /// type of `x` is more obvious with the former. + /// + /// **Known problems:** If the argument is dereferenced within the function, + /// removing the `ref` will lead to errors. This can be fixed by removing the + /// dereferences, e.g., changing `*x` to `x` within the function. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// fn foo(ref x: u8) -> bool { + /// true + /// } + /// + /// // Good + /// fn foo(x: &u8) -> bool { + /// true + /// } + /// ``` + pub TOPLEVEL_REF_ARG, + style, + "an entire binding declared as `ref`, in a function argument or a `let` statement" +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons to NaN. + /// + /// **Why is this bad?** NaN does not compare meaningfully to anything – not + /// even itself – so those comparisons are simply wrong. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = 1.0; + /// + /// // Bad + /// if x == f32::NAN { } + /// + /// // Good + /// if x.is_nan() { } + /// ``` + pub CMP_NAN, + correctness, + "comparisons to `NAN`, which will always return false, probably not intended" +} + +declare_clippy_lint! { + /// **What it does:** Checks for (in-)equality comparisons on floating-point + /// values (apart from zero), except in functions called `*eq*` (which probably + /// implement equality for a type involving floats). + /// + /// **Why is this bad?** Floating point calculations are usually imprecise, so + /// asking if two values are *exactly* equal is asking for trouble. For a good + /// guide on what to do, see [the floating point + /// guide](http://www.floating-point-gui.de/errors/comparison). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 1.2331f64; + /// let y = 1.2332f64; + /// + /// // Bad + /// if y == 1.23f64 { } + /// if y != x {} // where both are floats + /// + /// // Good + /// let error_margin = f64::EPSILON; // Use an epsilon for comparison + /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. + /// // let error_margin = std::f64::EPSILON; + /// if (y - 1.23f64).abs() < error_margin { } + /// if (y - x).abs() > error_margin { } + /// ``` + pub FLOAT_CMP, + correctness, + "using `==` or `!=` on float values instead of comparing difference with an epsilon" +} + +declare_clippy_lint! { + /// **What it does:** Checks for conversions to owned values just for the sake + /// of a comparison. + /// + /// **Why is this bad?** The comparison can operate on a reference, so creating + /// an owned value effectively throws it away directly afterwards, which is + /// needlessly consuming code and heap space. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = "foo"; + /// # let y = String::from("foo"); + /// if x.to_owned() == y {} + /// ``` + /// Could be written as + /// ```rust + /// # let x = "foo"; + /// # let y = String::from("foo"); + /// if x == y {} + /// ``` + pub CMP_OWNED, + perf, + "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for getting the remainder of a division by one or minus + /// one. + /// + /// **Why is this bad?** The result for a divisor of one can only ever be zero; for + /// minus one it can cause panic/overflow (if the left operand is the minimal value of + /// the respective integer type) or results in zero. No one will write such code + /// deliberately, unless trying to win an Underhanded Rust Contest. Even for that + /// contest, it's probably a bad idea. Use something more underhanded. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// let a = x % 1; + /// let a = x % -1; + /// ``` + pub MODULO_ONE, + correctness, + "taking a number modulo +/-1, which can either panic/overflow or always returns 0" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of bindings with a single leading + /// underscore. + /// + /// **Why is this bad?** A single leading underscore is usually used to indicate + /// that a binding will not be used. Using such a binding breaks this + /// expectation. + /// + /// **Known problems:** The lint does not work properly with desugaring and + /// macro, it has been allowed in the mean time. + /// + /// **Example:** + /// ```rust + /// let _x = 0; + /// let y = _x + 1; // Here we are using `_x`, even though it has a leading + /// // underscore. We should rename `_x` to `x` + /// ``` + pub USED_UNDERSCORE_BINDING, + pedantic, + "using a binding which is prefixed with an underscore" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of short circuit boolean conditions as + /// a + /// statement. + /// + /// **Why is this bad?** Using a short circuit boolean condition as a statement + /// may hide the fact that the second part is executed or not depending on the + /// outcome of the first part. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// f() && g(); // We should write `if f() { g(); }`. + /// ``` + pub SHORT_CIRCUIT_STATEMENT, + complexity, + "using a short circuit boolean condition as a statement" +} + +declare_clippy_lint! { + /// **What it does:** Catch casts from `0` to some pointer type + /// + /// **Why is this bad?** This generally means `null` and is better expressed as + /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// let a = 0 as *const u32; + /// + /// // Good + /// let a = std::ptr::null::(); + /// ``` + pub ZERO_PTR, + style, + "using `0 as *{const, mut} T`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for (in-)equality comparisons on floating-point + /// value and constant, except in functions called `*eq*` (which probably + /// implement equality for a type involving floats). + /// + /// **Why is this bad?** Floating point calculations are usually imprecise, so + /// asking if two values are *exactly* equal is asking for trouble. For a good + /// guide on what to do, see [the floating point + /// guide](http://www.floating-point-gui.de/errors/comparison). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x: f64 = 1.0; + /// const ONE: f64 = 1.00; + /// + /// // Bad + /// if x == ONE { } // where both are floats + /// + /// // Good + /// let error_margin = f64::EPSILON; // Use an epsilon for comparison + /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. + /// // let error_margin = std::f64::EPSILON; + /// if (x - ONE).abs() < error_margin { } + /// ``` + pub FLOAT_CMP_CONST, + restriction, + "using `==` or `!=` on float constants instead of comparing difference with an epsilon" +} + +declare_lint_pass!(MiscLints => [ + TOPLEVEL_REF_ARG, + CMP_NAN, + FLOAT_CMP, + CMP_OWNED, + MODULO_ONE, + USED_UNDERSCORE_BINDING, + SHORT_CIRCUIT_STATEMENT, + ZERO_PTR, + FLOAT_CMP_CONST +]); + +impl<'tcx> LateLintPass<'tcx> for MiscLints { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + k: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + span: Span, + _: HirId, + ) { + if let FnKind::Closure = k { + // Does not apply to closures + return; + } + if in_external_macro(cx.tcx.sess, span) { + return; + } + for arg in iter_input_pats(decl, body) { + if let PatKind::Binding(BindingAnnotation::Ref | BindingAnnotation::RefMut, ..) = arg.pat.kind { + span_lint( + cx, + TOPLEVEL_REF_ARG, + arg.pat.span, + "`ref` directly on a function argument is ignored. \ + Consider using a reference type instead", + ); + } + } + } + + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if_chain! { + if !in_external_macro(cx.tcx.sess, stmt.span); + if let StmtKind::Local(ref local) = stmt.kind; + if let PatKind::Binding(an, .., name, None) = local.pat.kind; + if let Some(ref init) = local.init; + if !higher::is_from_for_desugar(local); + then { + if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut { + // use the macro callsite when the init span (but not the whole local span) + // comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];` + let sugg_init = if init.span.from_expansion() && !local.span.from_expansion() { + Sugg::hir_with_macro_callsite(cx, init, "..") + } else { + Sugg::hir(cx, init, "..") + }; + let (mutopt, initref) = if an == BindingAnnotation::RefMut { + ("mut ", sugg_init.mut_addr()) + } else { + ("", sugg_init.addr()) + }; + let tyopt = if let Some(ref ty) = local.ty { + format!(": &{mutopt}{ty}", mutopt=mutopt, ty=snippet(cx, ty.span, "..")) + } else { + String::new() + }; + span_lint_hir_and_then( + cx, + TOPLEVEL_REF_ARG, + init.hir_id, + local.pat.span, + "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead", + |diag| { + diag.span_suggestion( + stmt.span, + "try", + format!( + "let {name}{tyopt} = {initref};", + name=snippet(cx, name.span, ".."), + tyopt=tyopt, + initref=initref, + ), + Applicability::MachineApplicable, + ); + } + ); + } + } + }; + if_chain! { + if let StmtKind::Semi(ref expr) = stmt.kind; + if let ExprKind::Binary(ref binop, ref a, ref b) = expr.kind; + if binop.node == BinOpKind::And || binop.node == BinOpKind::Or; + if let Some(sugg) = Sugg::hir_opt(cx, a); + then { + span_lint_and_then(cx, + SHORT_CIRCUIT_STATEMENT, + stmt.span, + "boolean short circuit operator in statement may be clearer using an explicit test", + |diag| { + let sugg = if binop.node == BinOpKind::Or { !sugg } else { sugg }; + diag.span_suggestion( + stmt.span, + "replace it with", + format!( + "if {} {{ {}; }}", + sugg, + &snippet(cx, b.span, ".."), + ), + Applicability::MachineApplicable, // snippet + ); + }); + } + }; + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + match expr.kind { + ExprKind::Cast(ref e, ref ty) => { + check_cast(cx, expr.span, e, ty); + return; + }, + ExprKind::Binary(ref cmp, ref left, ref right) => { + check_binary(cx, expr, cmp, left, right); + return; + }, + _ => {}, + } + if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) { + // Don't lint things expanded by #[derive(...)], etc or `await` desugaring + return; + } + let binding = match expr.kind { + ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => { + let binding = last_path_segment(qpath).ident.as_str(); + if binding.starts_with('_') && + !binding.starts_with("__") && + binding != "_result" && // FIXME: #944 + is_used(cx, expr) && + // don't lint if the declaration is in a macro + non_macro_local(cx, cx.qpath_res(qpath, expr.hir_id)) + { + Some(binding) + } else { + None + } + }, + ExprKind::Field(_, ident) => { + let name = ident.as_str(); + if name.starts_with('_') && !name.starts_with("__") { + Some(name) + } else { + None + } + }, + _ => None, + }; + if let Some(binding) = binding { + span_lint( + cx, + USED_UNDERSCORE_BINDING, + expr.span, + &format!( + "used binding `{}` which is prefixed with an underscore. A leading \ + underscore signals that a binding will not be used", + binding + ), + ); + } + } +} + +fn get_lint_and_message( + is_comparing_constants: bool, + is_comparing_arrays: bool, +) -> (&'static rustc_lint::Lint, &'static str) { + if is_comparing_constants { + ( + FLOAT_CMP_CONST, + if is_comparing_arrays { + "strict comparison of `f32` or `f64` constant arrays" + } else { + "strict comparison of `f32` or `f64` constant" + }, + ) + } else { + ( + FLOAT_CMP, + if is_comparing_arrays { + "strict comparison of `f32` or `f64` arrays" + } else { + "strict comparison of `f32` or `f64`" + }, + ) + } +} + +fn check_nan(cx: &LateContext<'_>, expr: &Expr<'_>, cmp_expr: &Expr<'_>) { + if_chain! { + if !in_constant(cx, cmp_expr.hir_id); + if let Some((value, _)) = constant(cx, cx.typeck_results(), expr); + then { + let needs_lint = match value { + Constant::F32(num) => num.is_nan(), + Constant::F64(num) => num.is_nan(), + _ => false, + }; + + if needs_lint { + span_lint( + cx, + CMP_NAN, + cmp_expr.span, + "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead", + ); + } + } + } +} + +fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) { + res + } else { + false + } +} + +fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + match constant(cx, cx.typeck_results(), expr) { + Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(), + Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(), + Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f { + Constant::F32(f) => *f == 0.0 || (*f).is_infinite(), + Constant::F64(f) => *f == 0.0 || (*f).is_infinite(), + _ => false, + }), + _ => false, + } +} + +// Return true if `expr` is the result of `signum()` invoked on a float value. +fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + // The negation of a signum is still a signum + if let ExprKind::Unary(UnOp::Neg, ref child_expr) = expr.kind { + return is_signum(cx, &child_expr); + } + + if_chain! { + if let ExprKind::MethodCall(ref method_name, _, ref expressions, _) = expr.kind; + if sym!(signum) == method_name.ident.name; + // Check that the receiver of the signum() is a float (expressions[0] is the receiver of + // the method call) + then { + return is_float(cx, &expressions[0]); + } + } + false +} + +fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let value = &cx.typeck_results().expr_ty(expr).peel_refs().kind(); + + if let ty::Array(arr_ty, _) = value { + return matches!(arr_ty.kind(), ty::Float(_)); + }; + + matches!(value, ty::Float(_)) +} + +fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _)) +} + +fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) { + #[derive(Default)] + struct EqImpl { + ty_eq_other: bool, + other_eq_ty: bool, + } + + impl EqImpl { + fn is_implemented(&self) -> bool { + self.ty_eq_other || self.other_eq_ty + } + } + + fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option { + cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl { + ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]), + other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]), + }) + } + + let (arg_ty, snip) = match expr.kind { + ExprKind::MethodCall(.., ref args, _) if args.len() == 1 => { + if_chain!( + if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if is_diagnostic_assoc_item(cx, expr_def_id, sym::ToString) + || is_diagnostic_assoc_item(cx, expr_def_id, sym::ToOwned); + then { + (cx.typeck_results().expr_ty(&args[0]), snippet(cx, args[0].span, "..")) + } else { + return; + } + ) + }, + ExprKind::Call(ref path, ref v) if v.len() == 1 => { + if let ExprKind::Path(ref path) = path.kind { + if match_qpath(path, &["String", "from_str"]) || match_qpath(path, &["String", "from"]) { + (cx.typeck_results().expr_ty(&v[0]), snippet(cx, v[0].span, "..")) + } else { + return; + } + } else { + return; + } + }, + _ => return, + }; + + let other_ty = cx.typeck_results().expr_ty(other); + + let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default(); + let with_deref = arg_ty + .builtin_deref(true) + .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty)) + .unwrap_or_default(); + + if !with_deref.is_implemented() && !without_deref.is_implemented() { + return; + } + + let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _)); + + let lint_span = if other_gets_derefed { + expr.span.to(other.span) + } else { + expr.span + }; + + span_lint_and_then( + cx, + CMP_OWNED, + lint_span, + "this creates an owned instance just for comparison", + |diag| { + // This also catches `PartialEq` implementations that call `to_owned`. + if other_gets_derefed { + diag.span_label(lint_span, "try implementing the comparison without allocating"); + return; + } + + let expr_snip; + let eq_impl; + if with_deref.is_implemented() { + expr_snip = format!("*{}", snip); + eq_impl = with_deref; + } else { + expr_snip = snip.to_string(); + eq_impl = without_deref; + }; + + let span; + let hint; + if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) { + span = expr.span; + hint = expr_snip; + } else { + span = expr.span.to(other.span); + if eq_impl.ty_eq_other { + hint = format!("{} == {}", expr_snip, snippet(cx, other.span, "..")); + } else { + hint = format!("{} == {}", snippet(cx, other.span, ".."), expr_snip); + } + } + + diag.span_suggestion( + span, + "try", + hint, + Applicability::MachineApplicable, // snippet + ); + }, + ); +} + +/// Heuristic to see if an expression is used. Should be compatible with +/// `unused_variables`'s idea +/// of what it means for an expression to be "used". +fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + get_parent_expr(cx, expr).map_or(true, |parent| match parent.kind { + ExprKind::Assign(_, ref rhs, _) | ExprKind::AssignOp(_, _, ref rhs) => SpanlessEq::new(cx).eq_expr(rhs, expr), + _ => is_used(cx, parent), + }) +} + +/// Tests whether an expression is in a macro expansion (e.g., something +/// generated by `#[derive(...)]` or the like). +fn in_attributes_expansion(expr: &Expr<'_>) -> bool { + use rustc_span::hygiene::MacroKind; + if expr.span.from_expansion() { + let data = expr.span.ctxt().outer_expn_data(); + matches!(data.kind, ExpnKind::Macro(MacroKind::Attr, _)) + } else { + false + } +} + +/// Tests whether `res` is a variable defined outside a macro. +fn non_macro_local(cx: &LateContext<'_>, res: def::Res) -> bool { + if let def::Res::Local(id) = res { + !cx.tcx.hir().span(id).from_expansion() + } else { + false + } +} + +fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) { + if_chain! { + if let TyKind::Ptr(ref mut_ty) = ty.kind; + if let ExprKind::Lit(ref lit) = e.kind; + if let LitKind::Int(0, _) = lit.node; + if !in_constant(cx, e.hir_id); + then { + let (msg, sugg_fn) = match mut_ty.mutbl { + Mutability::Mut => ("`0 as *mut _` detected", "std::ptr::null_mut"), + Mutability::Not => ("`0 as *const _` detected", "std::ptr::null"), + }; + + let (sugg, appl) = if let TyKind::Infer = mut_ty.ty.kind { + (format!("{}()", sugg_fn), Applicability::MachineApplicable) + } else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) { + (format!("{}::<{}>()", sugg_fn, mut_ty_snip), Applicability::MachineApplicable) + } else { + // `MaybeIncorrect` as type inference may not work with the suggested code + (format!("{}()", sugg_fn), Applicability::MaybeIncorrect) + }; + span_lint_and_sugg(cx, ZERO_PTR, span, msg, "try", sugg, appl); + } + } +} + +fn check_binary( + cx: &LateContext<'a>, + expr: &Expr<'_>, + cmp: &rustc_span::source_map::Spanned, + left: &'a Expr<'_>, + right: &'a Expr<'_>, +) { + let op = cmp.node; + if op.is_comparison() { + check_nan(cx, left, expr); + check_nan(cx, right, expr); + check_to_owned(cx, left, right, true); + check_to_owned(cx, right, left, false); + } + if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) { + if is_allowed(cx, left) || is_allowed(cx, right) { + return; + } + + // Allow comparing the results of signum() + if is_signum(cx, left) && is_signum(cx, right) { + return; + } + + if let Some(name) = get_item_name(cx, expr) { + let name = name.as_str(); + if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") { + return; + } + } + let is_comparing_arrays = is_array(cx, left) || is_array(cx, right); + let (lint, msg) = get_lint_and_message( + is_named_constant(cx, left) || is_named_constant(cx, right), + is_comparing_arrays, + ); + span_lint_and_then(cx, lint, expr.span, msg, |diag| { + let lhs = Sugg::hir(cx, left, ".."); + let rhs = Sugg::hir(cx, right, ".."); + + if !is_comparing_arrays { + diag.span_suggestion( + expr.span, + "consider comparing them within some margin of error", + format!( + "({}).abs() {} error_margin", + lhs - rhs, + if op == BinOpKind::Eq { '<' } else { '>' } + ), + Applicability::HasPlaceholders, // snippet + ); + } + diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`"); + }); + } else if op == BinOpKind::Rem { + if is_integer_const(cx, right, 1) { + span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0"); + } + + if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() { + if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) { + span_lint( + cx, + MODULO_ONE, + expr.span, + "any number modulo -1 will panic/overflow or result in 0", + ); + } + }; + } +} diff --git a/src/tools/clippy/clippy_lints/src/misc_early.rs b/src/tools/clippy/clippy_lints/src/misc_early.rs new file mode 100644 index 0000000000..84a0df92f5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/misc_early.rs @@ -0,0 +1,568 @@ +use crate::utils::{snippet_opt, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; +use rustc_ast::ast::{ + BindingMode, Expr, ExprKind, GenericParamKind, Generics, Lit, LitFloatType, LitIntType, LitKind, Mutability, + NodeId, Pat, PatKind, UnOp, +}; +use rustc_ast::visit::FnKind; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::Applicability; +use rustc_hir::PrimTy; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for structure field patterns bound to wildcards. + /// + /// **Why is this bad?** Using `..` instead is shorter and leaves the focus on + /// the fields that are actually bound. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct Foo { + /// # a: i32, + /// # b: i32, + /// # c: i32, + /// # } + /// let f = Foo { a: 0, b: 0, c: 0 }; + /// + /// // Bad + /// match f { + /// Foo { a: _, b: 0, .. } => {}, + /// Foo { a: _, b: _, c: _ } => {}, + /// } + /// + /// // Good + /// match f { + /// Foo { b: 0, .. } => {}, + /// Foo { .. } => {}, + /// } + /// ``` + pub UNNEEDED_FIELD_PATTERN, + restriction, + "struct fields bound to a wildcard instead of using `..`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for function arguments having the similar names + /// differing by an underscore. + /// + /// **Why is this bad?** It affects code readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// fn foo(a: i32, _a: i32) {} + /// + /// // Good + /// fn bar(a: i32, _b: i32) {} + /// ``` + pub DUPLICATE_UNDERSCORE_ARGUMENT, + style, + "function arguments having names which only differ by an underscore" +} + +declare_clippy_lint! { + /// **What it does:** Detects expressions of the form `--x`. + /// + /// **Why is this bad?** It can mislead C/C++ programmers to think `x` was + /// decremented. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let mut x = 3; + /// --x; + /// ``` + pub DOUBLE_NEG, + style, + "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++" +} + +declare_clippy_lint! { + /// **What it does:** Warns on hexadecimal literals with mixed-case letter + /// digits. + /// + /// **Why is this bad?** It looks confusing. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// let y = 0x1a9BAcD; + /// + /// // Good + /// let y = 0x1A9BACD; + /// ``` + pub MIXED_CASE_HEX_LITERALS, + style, + "hex literals whose letter digits are not consistently upper- or lowercased" +} + +declare_clippy_lint! { + /// **What it does:** Warns if literal suffixes are not separated by an + /// underscore. + /// + /// **Why is this bad?** It is much less readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// let y = 123832i32; + /// + /// // Good + /// let y = 123832_i32; + /// ``` + pub UNSEPARATED_LITERAL_SUFFIX, + pedantic, + "literals whose suffix is not separated by an underscore" +} + +declare_clippy_lint! { + /// **What it does:** Warns if an integral constant literal starts with `0`. + /// + /// **Why is this bad?** In some languages (including the infamous C language + /// and most of its + /// family), this marks an octal constant. In Rust however, this is a decimal + /// constant. This could + /// be confusing for both the writer and a reader of the constant. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// In Rust: + /// ```rust + /// fn main() { + /// let a = 0123; + /// println!("{}", a); + /// } + /// ``` + /// + /// prints `123`, while in C: + /// + /// ```c + /// #include + /// + /// int main() { + /// int a = 0123; + /// printf("%d\n", a); + /// } + /// ``` + /// + /// prints `83` (as `83 == 0o123` while `123 == 0o173`). + pub ZERO_PREFIXED_LITERAL, + complexity, + "integer literals starting with `0`" +} + +declare_clippy_lint! { + /// **What it does:** Warns if a generic shadows a built-in type. + /// + /// **Why is this bad?** This gives surprising type errors. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```ignore + /// impl Foo { + /// fn impl_func(&self) -> u32 { + /// 42 + /// } + /// } + /// ``` + pub BUILTIN_TYPE_SHADOW, + style, + "shadowing a builtin type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for patterns in the form `name @ _`. + /// + /// **Why is this bad?** It's almost always more readable to just use direct + /// bindings. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let v = Some("abc"); + /// + /// // Bad + /// match v { + /// Some(x) => (), + /// y @ _ => (), + /// } + /// + /// // Good + /// match v { + /// Some(x) => (), + /// y => (), + /// } + /// ``` + pub REDUNDANT_PATTERN, + style, + "using `name @ _` in a pattern" +} + +declare_clippy_lint! { + /// **What it does:** Checks for tuple patterns with a wildcard + /// pattern (`_`) is next to a rest pattern (`..`). + /// + /// _NOTE_: While `_, ..` means there is at least one element left, `..` + /// means there are 0 or more elements left. This can make a difference + /// when refactoring, but shouldn't result in errors in the refactored code, + /// since the wildcard pattern isn't used anyway. + /// **Why is this bad?** The wildcard pattern is unneeded as the rest pattern + /// can match that element as well. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct TupleStruct(u32, u32, u32); + /// # let t = TupleStruct(1, 2, 3); + /// // Bad + /// match t { + /// TupleStruct(0, .., _) => (), + /// _ => (), + /// } + /// + /// // Good + /// match t { + /// TupleStruct(0, ..) => (), + /// _ => (), + /// } + /// ``` + pub UNNEEDED_WILDCARD_PATTERN, + complexity, + "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)" +} + +declare_lint_pass!(MiscEarlyLints => [ + UNNEEDED_FIELD_PATTERN, + DUPLICATE_UNDERSCORE_ARGUMENT, + DOUBLE_NEG, + MIXED_CASE_HEX_LITERALS, + UNSEPARATED_LITERAL_SUFFIX, + ZERO_PREFIXED_LITERAL, + BUILTIN_TYPE_SHADOW, + REDUNDANT_PATTERN, + UNNEEDED_WILDCARD_PATTERN, +]); + +impl EarlyLintPass for MiscEarlyLints { + fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) { + for param in &gen.params { + if let GenericParamKind::Type { .. } = param.kind { + if let Some(prim_ty) = PrimTy::from_name(param.ident.name) { + span_lint( + cx, + BUILTIN_TYPE_SHADOW, + param.ident.span, + &format!("this generic shadows the built-in type `{}`", prim_ty.name()), + ); + } + } + } + } + + fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) { + if let PatKind::Struct(ref npat, ref pfields, _) = pat.kind { + let mut wilds = 0; + let type_name = npat + .segments + .last() + .expect("A path must have at least one segment") + .ident + .name; + + for field in pfields { + if let PatKind::Wild = field.pat.kind { + wilds += 1; + } + } + if !pfields.is_empty() && wilds == pfields.len() { + span_lint_and_help( + cx, + UNNEEDED_FIELD_PATTERN, + pat.span, + "all the struct fields are matched to a wildcard pattern, consider using `..`", + None, + &format!("try with `{} {{ .. }}` instead", type_name), + ); + return; + } + if wilds > 0 { + for field in pfields { + if let PatKind::Wild = field.pat.kind { + wilds -= 1; + if wilds > 0 { + span_lint( + cx, + UNNEEDED_FIELD_PATTERN, + field.span, + "you matched a field with a wildcard pattern, consider using `..` instead", + ); + } else { + let mut normal = vec![]; + + for field in pfields { + match field.pat.kind { + PatKind::Wild => {}, + _ => { + if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) { + normal.push(n); + } + }, + } + } + + span_lint_and_help( + cx, + UNNEEDED_FIELD_PATTERN, + field.span, + "you matched a field with a wildcard pattern, consider using `..` \ + instead", + None, + &format!("try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")), + ); + } + } + } + } + } + + if let PatKind::Ident(left, ident, Some(ref right)) = pat.kind { + let left_binding = match left { + BindingMode::ByRef(Mutability::Mut) => "ref mut ", + BindingMode::ByRef(Mutability::Not) => "ref ", + BindingMode::ByValue(..) => "", + }; + + if let PatKind::Wild = right.kind { + span_lint_and_sugg( + cx, + REDUNDANT_PATTERN, + pat.span, + &format!( + "the `{} @ _` pattern can be written as just `{}`", + ident.name, ident.name, + ), + "try", + format!("{}{}", left_binding, ident.name), + Applicability::MachineApplicable, + ); + } + } + + check_unneeded_wildcard_pattern(cx, pat); + } + + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) { + let mut registered_names: FxHashMap = FxHashMap::default(); + + for arg in &fn_kind.decl().inputs { + if let PatKind::Ident(_, ident, None) = arg.pat.kind { + let arg_name = ident.to_string(); + + if let Some(arg_name) = arg_name.strip_prefix('_') { + if let Some(correspondence) = registered_names.get(arg_name) { + span_lint( + cx, + DUPLICATE_UNDERSCORE_ARGUMENT, + *correspondence, + &format!( + "`{}` already exists, having another argument having almost the same \ + name makes code comprehension and documentation more difficult", + arg_name + ), + ); + } + } else { + registered_names.insert(arg_name, arg.pat.span); + } + } + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + match expr.kind { + ExprKind::Unary(UnOp::Neg, ref inner) => { + if let ExprKind::Unary(UnOp::Neg, _) = inner.kind { + span_lint( + cx, + DOUBLE_NEG, + expr.span, + "`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op", + ); + } + }, + ExprKind::Lit(ref lit) => Self::check_lit(cx, lit), + _ => (), + } + } +} + +impl MiscEarlyLints { + fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) { + // We test if first character in snippet is a number, because the snippet could be an expansion + // from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`. + // Note that this check also covers special case that `line!()` is eagerly expanded by compiler. + // See for a regression. + // FIXME: Find a better way to detect those cases. + let lit_snip = match snippet_opt(cx, lit.span) { + Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip, + _ => return, + }; + + if let LitKind::Int(value, lit_int_type) = lit.kind { + let suffix = match lit_int_type { + LitIntType::Signed(ty) => ty.name_str(), + LitIntType::Unsigned(ty) => ty.name_str(), + LitIntType::Unsuffixed => "", + }; + + let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) { + val + } else { + return; // It's useless so shouldn't lint. + }; + // Do not lint when literal is unsuffixed. + if !suffix.is_empty() && lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' { + span_lint_and_sugg( + cx, + UNSEPARATED_LITERAL_SUFFIX, + lit.span, + "integer type suffix should be separated by an underscore", + "add an underscore", + format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix), + Applicability::MachineApplicable, + ); + } + + if lit_snip.starts_with("0x") { + if maybe_last_sep_idx <= 2 { + // It's meaningless or causes range error. + return; + } + let mut seen = (false, false); + for ch in lit_snip.as_bytes()[2..=maybe_last_sep_idx].iter() { + match ch { + b'a'..=b'f' => seen.0 = true, + b'A'..=b'F' => seen.1 = true, + _ => {}, + } + if seen.0 && seen.1 { + span_lint( + cx, + MIXED_CASE_HEX_LITERALS, + lit.span, + "inconsistent casing in hexadecimal literal", + ); + break; + } + } + } else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") { + /* nothing to do */ + } else if value != 0 && lit_snip.starts_with('0') { + span_lint_and_then( + cx, + ZERO_PREFIXED_LITERAL, + lit.span, + "this is a decimal constant", + |diag| { + diag.span_suggestion( + lit.span, + "if you mean to use a decimal constant, remove the `0` to avoid confusion", + lit_snip.trim_start_matches(|c| c == '_' || c == '0').to_string(), + Applicability::MaybeIncorrect, + ); + diag.span_suggestion( + lit.span, + "if you mean to use an octal constant, use `0o`", + format!("0o{}", lit_snip.trim_start_matches(|c| c == '_' || c == '0')), + Applicability::MaybeIncorrect, + ); + }, + ); + } + } else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind { + let suffix = float_ty.name_str(); + let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) { + val + } else { + return; // It's useless so shouldn't lint. + }; + if lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' { + span_lint_and_sugg( + cx, + UNSEPARATED_LITERAL_SUFFIX, + lit.span, + "float type suffix should be separated by an underscore", + "add an underscore", + format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix), + Applicability::MachineApplicable, + ); + } + } + } +} + +fn check_unneeded_wildcard_pattern(cx: &EarlyContext<'_>, pat: &Pat) { + if let PatKind::TupleStruct(_, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind { + fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) { + span_lint_and_sugg( + cx, + UNNEEDED_WILDCARD_PATTERN, + span, + if only_one { + "this pattern is unneeded as the `..` pattern can match that element" + } else { + "these patterns are unneeded as the `..` pattern can match those elements" + }, + if only_one { "remove it" } else { "remove them" }, + "".to_string(), + Applicability::MachineApplicable, + ); + } + + if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) { + if let Some((left_index, left_pat)) = patterns[..rest_index] + .iter() + .rev() + .take_while(|pat| matches!(pat.kind, PatKind::Wild)) + .enumerate() + .last() + { + span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0); + } + + if let Some((right_index, right_pat)) = patterns[rest_index + 1..] + .iter() + .take_while(|pat| matches!(pat.kind, PatKind::Wild)) + .enumerate() + .last() + { + span_lint( + cx, + patterns[rest_index].span.shrink_to_hi().to(right_pat.span), + right_index == 0, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs new file mode 100644 index 0000000000..b0998a8012 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs @@ -0,0 +1,166 @@ +use crate::utils::qualify_min_const_fn::is_min_const_fn; +use crate::utils::{ + fn_has_unsatisfiable_preds, has_drop, is_entrypoint_fn, meets_msrv, span_lint, trait_ref_of_method, +}; +use rustc_hir as hir; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; +use rustc_typeck::hir_ty_to_ty; + +const MISSING_CONST_FOR_FN_MSRV: RustcVersion = RustcVersion::new(1, 37, 0); + +declare_clippy_lint! { + /// **What it does:** + /// + /// Suggests the use of `const` in functions and methods where possible. + /// + /// **Why is this bad?** + /// + /// Not having the function const prevents callers of the function from being const as well. + /// + /// **Known problems:** + /// + /// Const functions are currently still being worked on, with some features only being available + /// on nightly. This lint does not consider all edge cases currently and the suggestions may be + /// incorrect if you are using this lint on stable. + /// + /// Also, the lint only runs one pass over the code. Consider these two non-const functions: + /// + /// ```rust + /// fn a() -> i32 { + /// 0 + /// } + /// fn b() -> i32 { + /// a() + /// } + /// ``` + /// + /// When running Clippy, the lint will only suggest to make `a` const, because `b` at this time + /// can't be const as it calls a non-const function. Making `a` const and running Clippy again, + /// will suggest to make `b` const, too. + /// + /// **Example:** + /// + /// ```rust + /// # struct Foo { + /// # random_number: usize, + /// # } + /// # impl Foo { + /// fn new() -> Self { + /// Self { random_number: 42 } + /// } + /// # } + /// ``` + /// + /// Could be a const fn: + /// + /// ```rust + /// # struct Foo { + /// # random_number: usize, + /// # } + /// # impl Foo { + /// const fn new() -> Self { + /// Self { random_number: 42 } + /// } + /// # } + /// ``` + pub MISSING_CONST_FOR_FN, + nursery, + "Lint functions definitions that could be made `const fn`" +} + +impl_lint_pass!(MissingConstForFn => [MISSING_CONST_FOR_FN]); + +pub struct MissingConstForFn { + msrv: Option, +} + +impl MissingConstForFn { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { + fn check_fn( + &mut self, + cx: &LateContext<'_>, + kind: FnKind<'_>, + _: &FnDecl<'_>, + _: &Body<'_>, + span: Span, + hir_id: HirId, + ) { + if !meets_msrv(self.msrv.as_ref(), &MISSING_CONST_FOR_FN_MSRV) { + return; + } + + let def_id = cx.tcx.hir().local_def_id(hir_id); + + if in_external_macro(cx.tcx.sess, span) || is_entrypoint_fn(cx, def_id.to_def_id()) { + return; + } + + // Building MIR for `fn`s with unsatisfiable preds results in ICE. + if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { + return; + } + + // Perform some preliminary checks that rule out constness on the Clippy side. This way we + // can skip the actual const check and return early. + match kind { + FnKind::ItemFn(_, generics, header, ..) => { + let has_const_generic_params = generics + .params + .iter() + .any(|param| matches!(param.kind, GenericParamKind::Const { .. })); + + if already_const(header) || has_const_generic_params { + return; + } + }, + FnKind::Method(_, sig, ..) => { + if trait_ref_of_method(cx, hir_id).is_some() + || already_const(sig.header) + || method_accepts_dropable(cx, sig.decl.inputs) + { + return; + } + }, + FnKind::Closure => return, + } + + let mir = cx.tcx.optimized_mir(def_id); + + if let Err((span, err)) = is_min_const_fn(cx.tcx, &mir) { + if rustc_mir::const_eval::is_min_const_fn(cx.tcx, def_id.to_def_id()) { + cx.tcx.sess.span_err(span, &err); + } + } else { + span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`"); + } + } + extract_msrv_attr!(LateContext); +} + +/// Returns true if any of the method parameters is a type that implements `Drop`. The method +/// can't be made const then, because `drop` can't be const-evaluated. +fn method_accepts_dropable(cx: &LateContext<'_>, param_tys: &[hir::Ty<'_>]) -> bool { + // If any of the params are droppable, return true + param_tys.iter().any(|hir_ty| { + let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty); + has_drop(cx, ty_ty) + }) +} + +// We don't have to lint on something that's already `const` +#[must_use] +fn already_const(header: hir::FnHeader) -> bool { + header.constness == Constness::Const +} diff --git a/src/tools/clippy/clippy_lints/src/missing_doc.rs b/src/tools/clippy/clippy_lints/src/missing_doc.rs new file mode 100644 index 0000000000..985a66b6cf --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/missing_doc.rs @@ -0,0 +1,202 @@ +// Note: More specifically this lint is largely inspired (aka copied) from +// *rustc*'s +// [`missing_doc`]. +// +// [`missing_doc`]: https://github.com/rust-lang/rust/blob/cf9cf7c923eb01146971429044f216a3ca905e06/compiler/rustc_lint/src/builtin.rs#L415 +// + +use crate::utils::span_lint; +use if_chain::if_chain; +use rustc_ast::ast::{self, MetaItem, MetaItemKind}; +use rustc_ast::attr; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Warns if there is missing doc for any documentable item + /// (public or private). + /// + /// **Why is this bad?** Doc is good. *rustc* has a `MISSING_DOCS` + /// allowed-by-default lint for + /// public members, but has no way to enforce documentation of private items. + /// This lint fixes that. + /// + /// **Known problems:** None. + pub MISSING_DOCS_IN_PRIVATE_ITEMS, + restriction, + "detects missing documentation for public and private members" +} + +pub struct MissingDoc { + /// Stack of whether #[doc(hidden)] is set + /// at each level which has lint attributes. + doc_hidden_stack: Vec, +} + +impl Default for MissingDoc { + #[must_use] + fn default() -> Self { + Self::new() + } +} + +impl MissingDoc { + #[must_use] + pub fn new() -> Self { + Self { + doc_hidden_stack: vec![false], + } + } + + fn doc_hidden(&self) -> bool { + *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") + } + + fn has_include(meta: Option) -> bool { + if_chain! { + if let Some(meta) = meta; + if let MetaItemKind::List(list) = meta.kind; + if let Some(meta) = list.get(0); + if let Some(name) = meta.ident(); + then { + name.name == sym::include + } else { + false + } + } + } + + fn check_missing_docs_attrs( + &self, + cx: &LateContext<'_>, + attrs: &[ast::Attribute], + sp: Span, + article: &'static str, + desc: &'static str, + ) { + // If we're building a test harness, then warning about + // documentation is probably not really relevant right now. + if cx.sess().opts.test { + return; + } + + // `#[doc(hidden)]` disables missing_docs check. + if self.doc_hidden() { + return; + } + + if sp.from_expansion() { + return; + } + + let has_doc = attrs + .iter() + .any(|a| a.is_doc_comment() || a.doc_str().is_some() || a.is_value_str() || Self::has_include(a.meta())); + if !has_doc { + span_lint( + cx, + MISSING_DOCS_IN_PRIVATE_ITEMS, + sp, + &format!("missing documentation for {} {}", article, desc), + ); + } + } +} + +impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]); + +impl<'tcx> LateLintPass<'tcx> for MissingDoc { + fn enter_lint_attrs(&mut self, _: &LateContext<'tcx>, attrs: &'tcx [ast::Attribute]) { + let doc_hidden = self.doc_hidden() + || attrs.iter().any(|attr| { + attr.has_name(sym::doc) + && match attr.meta_item_list() { + None => false, + Some(l) => attr::list_contains_name(&l[..], sym::hidden), + } + }); + self.doc_hidden_stack.push(doc_hidden); + } + + fn exit_lint_attrs(&mut self, _: &LateContext<'tcx>, _: &'tcx [ast::Attribute]) { + self.doc_hidden_stack.pop().expect("empty doc_hidden_stack"); + } + + fn check_crate(&mut self, cx: &LateContext<'tcx>, krate: &'tcx hir::Crate<'_>) { + let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID); + self.check_missing_docs_attrs(cx, attrs, krate.item.span, "the", "crate"); + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { + match it.kind { + hir::ItemKind::Fn(..) => { + // ignore main() + if it.ident.name == sym::main { + let def_key = cx.tcx.hir().def_key(it.def_id); + if def_key.parent == Some(hir::def_id::CRATE_DEF_INDEX) { + return; + } + } + }, + hir::ItemKind::Const(..) + | hir::ItemKind::Enum(..) + | hir::ItemKind::Mod(..) + | hir::ItemKind::Static(..) + | hir::ItemKind::Struct(..) + | hir::ItemKind::Trait(..) + | hir::ItemKind::TraitAlias(..) + | hir::ItemKind::TyAlias(..) + | hir::ItemKind::Union(..) + | hir::ItemKind::OpaqueTy(..) => {}, + hir::ItemKind::ExternCrate(..) + | hir::ItemKind::ForeignMod { .. } + | hir::ItemKind::GlobalAsm(..) + | hir::ItemKind::Impl { .. } + | hir::ItemKind::Use(..) => return, + }; + + let (article, desc) = cx.tcx.article_and_description(it.def_id.to_def_id()); + + let attrs = cx.tcx.hir().attrs(it.hir_id()); + self.check_missing_docs_attrs(cx, attrs, it.span, article, desc); + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx hir::TraitItem<'_>) { + let (article, desc) = cx.tcx.article_and_description(trait_item.def_id.to_def_id()); + + let attrs = cx.tcx.hir().attrs(trait_item.hir_id()); + self.check_missing_docs_attrs(cx, attrs, trait_item.span, article, desc); + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { + // If the method is an impl for a trait, don't doc. + match cx.tcx.associated_item(impl_item.def_id).container { + ty::TraitContainer(_) => return, + ty::ImplContainer(cid) => { + if cx.tcx.impl_trait_ref(cid).is_some() { + return; + } + }, + } + + let (article, desc) = cx.tcx.article_and_description(impl_item.def_id.to_def_id()); + let attrs = cx.tcx.hir().attrs(impl_item.hir_id()); + self.check_missing_docs_attrs(cx, attrs, impl_item.span, article, desc); + } + + fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) { + if !sf.is_positional() { + let attrs = cx.tcx.hir().attrs(sf.hir_id); + self.check_missing_docs_attrs(cx, attrs, sf.span, "a", "struct field"); + } + } + + fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) { + let attrs = cx.tcx.hir().attrs(v.id); + self.check_missing_docs_attrs(cx, attrs, v.span, "a", "variant"); + } +} diff --git a/src/tools/clippy/clippy_lints/src/missing_inline.rs b/src/tools/clippy/clippy_lints/src/missing_inline.rs new file mode 100644 index 0000000000..da59c82099 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/missing_inline.rs @@ -0,0 +1,168 @@ +use crate::utils::span_lint; +use rustc_ast::ast; +use rustc_hir as hir; +use rustc_lint::{self, LateContext, LateLintPass, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** it lints if an exported function, method, trait method with default impl, + /// or trait method impl is not `#[inline]`. + /// + /// **Why is this bad?** In general, it is not. Functions can be inlined across + /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled, + /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates + /// might intend for most of the methods in their public API to be able to be inlined across + /// crates even when LTO is disabled. For these types of crates, enabling this lint might make + /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and + /// then opt out for specific methods where this might not make sense. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// pub fn foo() {} // missing #[inline] + /// fn ok() {} // ok + /// #[inline] pub fn bar() {} // ok + /// #[inline(always)] pub fn baz() {} // ok + /// + /// pub trait Bar { + /// fn bar(); // ok + /// fn def_bar() {} // missing #[inline] + /// } + /// + /// struct Baz; + /// impl Baz { + /// fn private() {} // ok + /// } + /// + /// impl Bar for Baz { + /// fn bar() {} // ok - Baz is not exported + /// } + /// + /// pub struct PubBaz; + /// impl PubBaz { + /// fn private() {} // ok + /// pub fn not_ptrivate() {} // missing #[inline] + /// } + /// + /// impl Bar for PubBaz { + /// fn bar() {} // missing #[inline] + /// fn def_bar() {} // missing #[inline] + /// } + /// ``` + pub MISSING_INLINE_IN_PUBLIC_ITEMS, + restriction, + "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)" +} + +fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[ast::Attribute], sp: Span, desc: &'static str) { + let has_inline = attrs.iter().any(|a| a.has_name(sym::inline)); + if !has_inline { + span_lint( + cx, + MISSING_INLINE_IN_PUBLIC_ITEMS, + sp, + &format!("missing `#[inline]` for {}", desc), + ); + } +} + +fn is_executable_or_proc_macro(cx: &LateContext<'_>) -> bool { + use rustc_session::config::CrateType; + + cx.tcx + .sess + .crate_types() + .iter() + .any(|t: &CrateType| matches!(t, CrateType::Executable | CrateType::ProcMacro)) +} + +declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]); + +impl<'tcx> LateLintPass<'tcx> for MissingInline { + fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { + if rustc_middle::lint::in_external_macro(cx.sess(), it.span) || is_executable_or_proc_macro(cx) { + return; + } + + if !cx.access_levels.is_exported(it.hir_id()) { + return; + } + match it.kind { + hir::ItemKind::Fn(..) => { + let desc = "a function"; + let attrs = cx.tcx.hir().attrs(it.hir_id()); + check_missing_inline_attrs(cx, attrs, it.span, desc); + }, + hir::ItemKind::Trait(ref _is_auto, ref _unsafe, ref _generics, ref _bounds, trait_items) => { + // note: we need to check if the trait is exported so we can't use + // `LateLintPass::check_trait_item` here. + for tit in trait_items { + let tit_ = cx.tcx.hir().trait_item(tit.id); + match tit_.kind { + hir::TraitItemKind::Const(..) | hir::TraitItemKind::Type(..) => {}, + hir::TraitItemKind::Fn(..) => { + if tit.defaultness.has_value() { + // trait method with default body needs inline in case + // an impl is not provided + let desc = "a default trait method"; + let item = cx.tcx.hir().trait_item(tit.id); + let attrs = cx.tcx.hir().attrs(item.hir_id()); + check_missing_inline_attrs(cx, attrs, item.span, desc); + } + }, + } + } + }, + hir::ItemKind::Const(..) + | hir::ItemKind::Enum(..) + | hir::ItemKind::Mod(..) + | hir::ItemKind::Static(..) + | hir::ItemKind::Struct(..) + | hir::ItemKind::TraitAlias(..) + | hir::ItemKind::GlobalAsm(..) + | hir::ItemKind::TyAlias(..) + | hir::ItemKind::Union(..) + | hir::ItemKind::OpaqueTy(..) + | hir::ItemKind::ExternCrate(..) + | hir::ItemKind::ForeignMod { .. } + | hir::ItemKind::Impl { .. } + | hir::ItemKind::Use(..) => {}, + }; + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { + use rustc_middle::ty::{ImplContainer, TraitContainer}; + if rustc_middle::lint::in_external_macro(cx.sess(), impl_item.span) || is_executable_or_proc_macro(cx) { + return; + } + + // If the item being implemented is not exported, then we don't need #[inline] + if !cx.access_levels.is_exported(impl_item.hir_id()) { + return; + } + + let desc = match impl_item.kind { + hir::ImplItemKind::Fn(..) => "a method", + hir::ImplItemKind::Const(..) | hir::ImplItemKind::TyAlias(_) => return, + }; + + let trait_def_id = match cx.tcx.associated_item(impl_item.def_id).container { + TraitContainer(cid) => Some(cid), + ImplContainer(cid) => cx.tcx.impl_trait_ref(cid).map(|t| t.def_id), + }; + + if let Some(trait_def_id) = trait_def_id { + if trait_def_id.is_local() && !cx.access_levels.is_exported(impl_item.hir_id()) { + // If a trait is being implemented for an item, and the + // trait is not exported, we don't need #[inline] + return; + } + } + + let attrs = cx.tcx.hir().attrs(impl_item.hir_id()); + check_missing_inline_attrs(cx, attrs, impl_item.span, desc); + } +} diff --git a/src/tools/clippy/clippy_lints/src/modulo_arithmetic.rs b/src/tools/clippy/clippy_lints/src/modulo_arithmetic.rs new file mode 100644 index 0000000000..da3ae1d652 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/modulo_arithmetic.rs @@ -0,0 +1,148 @@ +use crate::consts::{constant, Constant}; +use crate::utils::{sext, span_lint_and_then}; +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::fmt::Display; + +declare_clippy_lint! { + /// **What it does:** Checks for modulo arithmetic. + /// + /// **Why is this bad?** The results of modulo (%) operation might differ + /// depending on the language, when negative numbers are involved. + /// If you interop with different languages it might be beneficial + /// to double check all places that use modulo arithmetic. + /// + /// For example, in Rust `17 % -3 = 2`, but in Python `17 % -3 = -1`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = -17 % 3; + /// ``` + pub MODULO_ARITHMETIC, + restriction, + "any modulo arithmetic statement" +} + +declare_lint_pass!(ModuloArithmetic => [MODULO_ARITHMETIC]); + +struct OperandInfo { + string_representation: Option, + is_negative: bool, + is_integral: bool, +} + +fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { + match constant(cx, cx.typeck_results(), operand) { + Some((Constant::Int(v), _)) => match *cx.typeck_results().expr_ty(expr).kind() { + ty::Int(ity) => { + let value = sext(cx.tcx, v, ity); + return Some(OperandInfo { + string_representation: Some(value.to_string()), + is_negative: value < 0, + is_integral: true, + }); + }, + ty::Uint(_) => { + return Some(OperandInfo { + string_representation: None, + is_negative: false, + is_integral: true, + }); + }, + _ => {}, + }, + Some((Constant::F32(f), _)) => { + return Some(floating_point_operand_info(&f)); + }, + Some((Constant::F64(f), _)) => { + return Some(floating_point_operand_info(&f)); + }, + _ => {}, + } + None +} + +fn floating_point_operand_info>(f: &T) -> OperandInfo { + OperandInfo { + string_representation: Some(format!("{:.3}", *f)), + is_negative: *f < 0.0.into(), + is_integral: false, + } +} + +fn might_have_negative_value(t: &ty::TyS<'_>) -> bool { + t.is_signed() || t.is_floating_point() +} + +fn check_const_operands<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + lhs_operand: &OperandInfo, + rhs_operand: &OperandInfo, +) { + if lhs_operand.is_negative ^ rhs_operand.is_negative { + span_lint_and_then( + cx, + MODULO_ARITHMETIC, + expr.span, + &format!( + "you are using modulo operator on constants with different signs: `{} % {}`", + lhs_operand.string_representation.as_ref().unwrap(), + rhs_operand.string_representation.as_ref().unwrap() + ), + |diag| { + diag.note("double check for expected result especially when interoperating with different languages"); + if lhs_operand.is_integral { + diag.note("or consider using `rem_euclid` or similar function"); + } + }, + ); + } +} + +fn check_non_const_operands<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, operand: &Expr<'_>) { + let operand_type = cx.typeck_results().expr_ty(operand); + if might_have_negative_value(operand_type) { + span_lint_and_then( + cx, + MODULO_ARITHMETIC, + expr.span, + "you are using modulo operator on types that might have different signs", + |diag| { + diag.note("double check for expected result especially when interoperating with different languages"); + if operand_type.is_integral() { + diag.note("or consider using `rem_euclid` or similar function"); + } + }, + ); + } +} + +impl<'tcx> LateLintPass<'tcx> for ModuloArithmetic { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + match &expr.kind { + ExprKind::Binary(op, lhs, rhs) | ExprKind::AssignOp(op, lhs, rhs) => { + if let BinOpKind::Rem = op.node { + let lhs_operand = analyze_operand(lhs, cx, expr); + let rhs_operand = analyze_operand(rhs, cx, expr); + if_chain! { + if let Some(lhs_operand) = lhs_operand; + if let Some(rhs_operand) = rhs_operand; + then { + check_const_operands(cx, expr, &lhs_operand, &rhs_operand); + } + else { + check_non_const_operands(cx, expr, lhs); + } + } + }; + }, + _ => {}, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/multiple_crate_versions.rs b/src/tools/clippy/clippy_lints/src/multiple_crate_versions.rs new file mode 100644 index 0000000000..c1773cef7a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/multiple_crate_versions.rs @@ -0,0 +1,96 @@ +//! lint on multiple versions of a crate being used + +use crate::utils::{run_lints, span_lint}; +use rustc_hir::def_id::LOCAL_CRATE; +use rustc_hir::{Crate, CRATE_HIR_ID}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::DUMMY_SP; + +use cargo_metadata::{DependencyKind, Node, Package, PackageId}; +use if_chain::if_chain; +use itertools::Itertools; + +declare_clippy_lint! { + /// **What it does:** Checks to see if multiple versions of a crate are being + /// used. + /// + /// **Why is this bad?** This bloats the size of targets, and can lead to + /// confusing error messages when structs or traits are used interchangeably + /// between different versions of a crate. + /// + /// **Known problems:** Because this can be caused purely by the dependencies + /// themselves, it's not always possible to fix this issue. + /// + /// **Example:** + /// ```toml + /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. + /// [dependencies] + /// ctrlc = "=3.1.0" + /// ansi_term = "=0.11.0" + /// ``` + pub MULTIPLE_CRATE_VERSIONS, + cargo, + "multiple versions of the same crate being used" +} + +declare_lint_pass!(MultipleCrateVersions => [MULTIPLE_CRATE_VERSIONS]); + +impl LateLintPass<'_> for MultipleCrateVersions { + fn check_crate(&mut self, cx: &LateContext<'_>, _: &Crate<'_>) { + if !run_lints(cx, &[MULTIPLE_CRATE_VERSIONS], CRATE_HIR_ID) { + return; + } + + let metadata = unwrap_cargo_metadata!(cx, MULTIPLE_CRATE_VERSIONS, true); + let local_name = cx.tcx.crate_name(LOCAL_CRATE).as_str(); + let mut packages = metadata.packages; + packages.sort_by(|a, b| a.name.cmp(&b.name)); + + if_chain! { + if let Some(resolve) = &metadata.resolve; + if let Some(local_id) = packages + .iter() + .find_map(|p| if p.name == *local_name { Some(&p.id) } else { None }); + then { + for (name, group) in &packages.iter().group_by(|p| p.name.clone()) { + let group: Vec<&Package> = group.collect(); + + if group.len() <= 1 { + continue; + } + + if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) { + let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect(); + versions.sort(); + let versions = versions.iter().join(", "); + + span_lint( + cx, + MULTIPLE_CRATE_VERSIONS, + DUMMY_SP, + &format!("multiple versions for dependency `{}`: {}", name, versions), + ); + } + } + } + } + } +} + +fn is_normal_dep(nodes: &[Node], local_id: &PackageId, dep_id: &PackageId) -> bool { + fn depends_on(node: &Node, dep_id: &PackageId) -> bool { + node.deps.iter().any(|dep| { + dep.pkg == *dep_id + && dep + .dep_kinds + .iter() + .any(|info| matches!(info.kind, DependencyKind::Normal)) + }) + } + + nodes + .iter() + .filter(|node| depends_on(node, dep_id)) + .any(|node| node.id == *local_id || is_normal_dep(nodes, local_id, &node.id)) +} diff --git a/src/tools/clippy/clippy_lints/src/mut_key.rs b/src/tools/clippy/clippy_lints/src/mut_key.rs new file mode 100644 index 0000000000..908b7bb7ce --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mut_key.rs @@ -0,0 +1,127 @@ +use crate::utils::{match_def_path, paths, span_lint, trait_ref_of_method}; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::TypeFoldable; +use rustc_middle::ty::{Adt, Array, RawPtr, Ref, Slice, Tuple, Ty, TypeAndMut}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for sets/maps with mutable key types. + /// + /// **Why is this bad?** All of `HashMap`, `HashSet`, `BTreeMap` and + /// `BtreeSet` rely on either the hash or the order of keys be unchanging, + /// so having types with interior mutability is a bad idea. + /// + /// **Known problems:** It's correct to use a struct, that contains interior mutability + /// as a key, when its `Hash` implementation doesn't access any of the interior mutable types. + /// However, this lint is unable to recognize this, so it causes a false positive in theses cases. + /// The `bytes` crate is a great example of this. + /// + /// **Example:** + /// ```rust + /// use std::cmp::{PartialEq, Eq}; + /// use std::collections::HashSet; + /// use std::hash::{Hash, Hasher}; + /// use std::sync::atomic::AtomicUsize; + ///# #[allow(unused)] + /// + /// struct Bad(AtomicUsize); + /// impl PartialEq for Bad { + /// fn eq(&self, rhs: &Self) -> bool { + /// .. + /// ; unimplemented!(); + /// } + /// } + /// + /// impl Eq for Bad {} + /// + /// impl Hash for Bad { + /// fn hash(&self, h: &mut H) { + /// .. + /// ; unimplemented!(); + /// } + /// } + /// + /// fn main() { + /// let _: HashSet = HashSet::new(); + /// } + /// ``` + pub MUTABLE_KEY_TYPE, + correctness, + "Check for mutable `Map`/`Set` key type" +} + +declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]); + +impl<'tcx> LateLintPass<'tcx> for MutableKeyType { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + if let hir::ItemKind::Fn(ref sig, ..) = item.kind { + check_sig(cx, item.hir_id(), &sig.decl); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) { + if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind { + if trait_ref_of_method(cx, item.hir_id()).is_none() { + check_sig(cx, item.hir_id(), &sig.decl); + } + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) { + if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind { + check_sig(cx, item.hir_id(), &sig.decl); + } + } + + fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) { + if let hir::PatKind::Wild = local.pat.kind { + return; + } + check_ty(cx, local.span, cx.typeck_results().pat_ty(&*local.pat)); + } +} + +fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) { + let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id); + let fn_sig = cx.tcx.fn_sig(fn_def_id); + for (hir_ty, ty) in decl.inputs.iter().zip(fn_sig.inputs().skip_binder().iter()) { + check_ty(cx, hir_ty.span, ty); + } + check_ty(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output())); +} + +// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased +// generics (because the compiler cannot ensure immutability for unknown types). +fn check_ty<'tcx>(cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) { + let ty = ty.peel_refs(); + if let Adt(def, substs) = ty.kind() { + if [&paths::HASHMAP, &paths::BTREEMAP, &paths::HASHSET, &paths::BTREESET] + .iter() + .any(|path| match_def_path(cx, def.did, &**path)) + && is_mutable_type(cx, substs.type_at(0), span) + { + span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type"); + } + } +} + +fn is_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span) -> bool { + match *ty.kind() { + RawPtr(TypeAndMut { ty: inner_ty, mutbl }) | Ref(_, inner_ty, mutbl) => { + mutbl == hir::Mutability::Mut || is_mutable_type(cx, inner_ty, span) + }, + Slice(inner_ty) => is_mutable_type(cx, inner_ty, span), + Array(inner_ty, size) => { + size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0) && is_mutable_type(cx, inner_ty, span) + }, + Tuple(..) => ty.tuple_fields().any(|ty| is_mutable_type(cx, ty, span)), + Adt(..) => { + cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() + && !ty.has_escaping_bound_vars() + && !ty.is_freeze(cx.tcx.at(span), cx.param_env) + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/mut_mut.rs b/src/tools/clippy/clippy_lints/src/mut_mut.rs new file mode 100644 index 0000000000..d7239b328b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mut_mut.rs @@ -0,0 +1,114 @@ +use crate::utils::{higher, span_lint}; +use rustc_hir as hir; +use rustc_hir::intravisit; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for instances of `mut mut` references. + /// + /// **Why is this bad?** Multiple `mut`s don't add anything meaningful to the + /// source. This is either a copy'n'paste error, or it shows a fundamental + /// misunderstanding of references. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let mut y = 1; + /// let x = &mut &mut y; + /// ``` + pub MUT_MUT, + pedantic, + "usage of double-mut refs, e.g., `&mut &mut ...`" +} + +declare_lint_pass!(MutMut => [MUT_MUT]); + +impl<'tcx> LateLintPass<'tcx> for MutMut { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { + intravisit::walk_block(&mut MutVisitor { cx }, block); + } + + fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_>) { + use rustc_hir::intravisit::Visitor; + + MutVisitor { cx }.visit_ty(ty); + } +} + +pub struct MutVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + if in_external_macro(self.cx.sess(), expr.span) { + return; + } + + if let Some((_, arg, body, _)) = higher::for_loop(expr) { + // A `for` loop lowers to: + // ```rust + // match ::std::iter::Iterator::next(&mut iter) { + // // ^^^^ + // ``` + // Let's ignore the generated code. + intravisit::walk_expr(self, arg); + intravisit::walk_expr(self, body); + } else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, ref e) = expr.kind { + if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind { + span_lint( + self.cx, + MUT_MUT, + expr.span, + "generally you want to avoid `&mut &mut _` if possible", + ); + } else if let ty::Ref(_, _, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() { + span_lint( + self.cx, + MUT_MUT, + expr.span, + "this expression mutably borrows a mutable reference. Consider reborrowing", + ); + } + } + } + + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) { + if let hir::TyKind::Rptr( + _, + hir::MutTy { + ty: ref pty, + mutbl: hir::Mutability::Mut, + }, + ) = ty.kind + { + if let hir::TyKind::Rptr( + _, + hir::MutTy { + mutbl: hir::Mutability::Mut, + .. + }, + ) = pty.kind + { + span_lint( + self.cx, + MUT_MUT, + ty.span, + "generally you want to avoid `&mut &mut _` if possible", + ); + } + } + + intravisit::walk_ty(self, ty); + } + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/mut_mutex_lock.rs b/src/tools/clippy/clippy_lints/src/mut_mutex_lock.rs new file mode 100644 index 0000000000..df1cecb328 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mut_mutex_lock.rs @@ -0,0 +1,68 @@ +use crate::utils::{is_type_diagnostic_item, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Mutability}; +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 `&mut Mutex::lock` calls + /// + /// **Why is this bad?** `Mutex::lock` is less efficient than + /// calling `Mutex::get_mut`. In addition you also have a statically + /// guarantee that the mutex isn't locked, instead of just a runtime + /// guarantee. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let mut value = value_mutex.lock().unwrap(); + /// *value += 1; + /// ``` + /// Use instead: + /// ```rust + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let value = value_mutex.get_mut().unwrap(); + /// *value += 1; + /// ``` + pub MUT_MUTEX_LOCK, + style, + "`&mut Mutex::lock` does unnecessary locking" +} + +declare_lint_pass!(MutMutexLock => [MUT_MUTEX_LOCK]); + +impl<'tcx> LateLintPass<'tcx> for MutMutexLock { + fn check_expr(&mut self, cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>) { + if_chain! { + if let ExprKind::MethodCall(path, method_span, args, _) = &ex.kind; + if path.ident.name == sym!(lock); + let ty = cx.typeck_results().expr_ty(&args[0]); + if let ty::Ref(_, inner_ty, Mutability::Mut) = ty.kind(); + if is_type_diagnostic_item(cx, inner_ty, sym!(mutex_type)); + then { + span_lint_and_sugg( + cx, + MUT_MUTEX_LOCK, + *method_span, + "calling `&mut Mutex::lock` unnecessarily locks an exclusive (mutable) reference", + "change this to", + "get_mut".to_owned(), + Applicability::MaybeIncorrect, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/mut_reference.rs b/src/tools/clippy/clippy_lints/src/mut_reference.rs new file mode 100644 index 0000000000..3f0b765df1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mut_reference.rs @@ -0,0 +1,88 @@ +use crate::utils::span_lint; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::subst::Subst; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Detects passing a mutable reference to a function that only + /// requires an immutable reference. + /// + /// **Why is this bad?** The mutable reference rules out all other references to + /// the value. Also the code misleads about the intent of the call site. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// // Bad + /// my_vec.push(&mut value) + /// + /// // Good + /// my_vec.push(&value) + /// ``` + pub UNNECESSARY_MUT_PASSED, + style, + "an argument passed as a mutable reference although the callee only demands an immutable reference" +} + +declare_lint_pass!(UnnecessaryMutPassed => [UNNECESSARY_MUT_PASSED]); + +impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + match e.kind { + ExprKind::Call(ref fn_expr, ref arguments) => { + if let ExprKind::Path(ref path) = fn_expr.kind { + check_arguments( + cx, + arguments, + cx.typeck_results().expr_ty(fn_expr), + &rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)), + "function", + ); + } + }, + ExprKind::MethodCall(ref path, _, ref arguments, _) => { + let def_id = cx.typeck_results().type_dependent_def_id(e.hir_id).unwrap(); + let substs = cx.typeck_results().node_substs(e.hir_id); + let method_type = cx.tcx.type_of(def_id).subst(cx.tcx, substs); + check_arguments(cx, arguments, method_type, &path.ident.as_str(), "method") + }, + _ => (), + } + } +} + +fn check_arguments<'tcx>( + cx: &LateContext<'tcx>, + arguments: &[Expr<'_>], + type_definition: Ty<'tcx>, + name: &str, + fn_kind: &str, +) { + match type_definition.kind() { + ty::FnDef(..) | ty::FnPtr(_) => { + let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs(); + for (argument, parameter) in arguments.iter().zip(parameters.iter()) { + match parameter.kind() { + ty::Ref(_, _, Mutability::Not) + | ty::RawPtr(ty::TypeAndMut { + mutbl: Mutability::Not, .. + }) => { + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) = argument.kind { + span_lint( + cx, + UNNECESSARY_MUT_PASSED, + argument.span, + &format!("the {} `{}` doesn't need a mutable reference", fn_kind, name), + ); + } + }, + _ => (), + } + } + }, + _ => (), + } +} diff --git a/src/tools/clippy/clippy_lints/src/mutable_debug_assertion.rs b/src/tools/clippy/clippy_lints/src/mutable_debug_assertion.rs new file mode 100644 index 0000000000..9caacb5db7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mutable_debug_assertion.rs @@ -0,0 +1,115 @@ +use crate::utils::{higher, is_direct_expn_of, span_lint}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for function/method calls with a mutable + /// parameter in `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!` macros. + /// + /// **Why is this bad?** In release builds `debug_assert!` macros are optimized out by the + /// compiler. + /// Therefore mutating something in a `debug_assert!` macro results in different behaviour + /// between a release and debug build. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust,ignore + /// debug_assert_eq!(vec![3].pop(), Some(3)); + /// // or + /// fn take_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() } + /// debug_assert!(take_a_mut_parameter(&mut 5)); + /// ``` + pub DEBUG_ASSERT_WITH_MUT_CALL, + nursery, + "mutable arguments in `debug_assert{,_ne,_eq}!`" +} + +declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]); + +const DEBUG_MACRO_NAMES: [&str; 3] = ["debug_assert", "debug_assert_eq", "debug_assert_ne"]; + +impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + for dmn in &DEBUG_MACRO_NAMES { + if is_direct_expn_of(e.span, dmn).is_some() { + if let Some(macro_args) = higher::extract_assert_macro_args(e) { + for arg in macro_args { + let mut visitor = MutArgVisitor::new(cx); + visitor.visit_expr(arg); + if let Some(span) = visitor.expr_span() { + span_lint( + cx, + DEBUG_ASSERT_WITH_MUT_CALL, + span, + &format!("do not call a function with mutable arguments inside of `{}!`", dmn), + ); + } + } + } + } + } + } +} + +struct MutArgVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + expr_span: Option, + found: bool, +} + +impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { + cx, + expr_span: None, + found: false, + } + } + + fn expr_span(&self) -> Option { + if self.found { self.expr_span } else { None } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + match expr.kind { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => { + self.found = true; + return; + }, + ExprKind::If(..) => { + self.found = true; + return; + }, + ExprKind::Path(_) => { + if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) { + if adj + .iter() + .any(|a| matches!(a.target.kind(), ty::Ref(_, _, Mutability::Mut))) + { + self.found = true; + return; + } + } + }, + // Don't check await desugars + ExprKind::Match(_, _, MatchSource::AwaitDesugar) => return, + _ if !self.found => self.expr_span = Some(expr.span), + _ => return, + } + walk_expr(self, expr) + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} diff --git a/src/tools/clippy/clippy_lints/src/mutex_atomic.rs b/src/tools/clippy/clippy_lints/src/mutex_atomic.rs new file mode 100644 index 0000000000..40b236493a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mutex_atomic.rs @@ -0,0 +1,97 @@ +//! Checks for uses of mutex where an atomic value could be used +//! +//! This lint is **warn** by default + +use crate::utils::{is_type_diagnostic_item, span_lint}; +use rustc_hir::Expr; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for usages of `Mutex` where an atomic will do. + /// + /// **Why is this bad?** Using a mutex just to make access to a plain bool or + /// reference sequential is shooting flies with cannons. + /// `std::sync::atomic::AtomicBool` and `std::sync::atomic::AtomicPtr` are leaner and + /// faster. + /// + /// **Known problems:** This lint cannot detect if the mutex is actually used + /// for waiting before a critical section. + /// + /// **Example:** + /// ```rust + /// # let y = true; + /// + /// // Bad + /// # use std::sync::Mutex; + /// let x = Mutex::new(&y); + /// + /// // Good + /// # use std::sync::atomic::AtomicBool; + /// let x = AtomicBool::new(y); + /// ``` + pub MUTEX_ATOMIC, + perf, + "using a mutex where an atomic value could be used instead" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usages of `Mutex` where `X` is an integral + /// type. + /// + /// **Why is this bad?** Using a mutex just to make access to a plain integer + /// sequential is + /// shooting flies with cannons. `std::sync::atomic::AtomicUsize` is leaner and faster. + /// + /// **Known problems:** This lint cannot detect if the mutex is actually used + /// for waiting before a critical section. + /// + /// **Example:** + /// ```rust + /// # use std::sync::Mutex; + /// let x = Mutex::new(0usize); + /// + /// // Good + /// # use std::sync::atomic::AtomicUsize; + /// let x = AtomicUsize::new(0usize); + /// ``` + pub MUTEX_INTEGER, + nursery, + "using a mutex for an integer type" +} + +declare_lint_pass!(Mutex => [MUTEX_ATOMIC, MUTEX_INTEGER]); + +impl<'tcx> LateLintPass<'tcx> for Mutex { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let ty = cx.typeck_results().expr_ty(expr); + if let ty::Adt(_, subst) = ty.kind() { + if is_type_diagnostic_item(cx, ty, sym!(mutex_type)) { + let mutex_param = subst.type_at(0); + if let Some(atomic_name) = get_atomic_name(mutex_param) { + let msg = format!( + "consider using an `{}` instead of a `Mutex` here; if you just want the locking \ + behavior and not the internal type, consider using `Mutex<()>`", + atomic_name + ); + match *mutex_param.kind() { + ty::Uint(t) if t != ty::UintTy::Usize => span_lint(cx, MUTEX_INTEGER, expr.span, &msg), + ty::Int(t) if t != ty::IntTy::Isize => span_lint(cx, MUTEX_INTEGER, expr.span, &msg), + _ => span_lint(cx, MUTEX_ATOMIC, expr.span, &msg), + }; + } + } + } + } +} + +fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> { + match ty.kind() { + ty::Bool => Some("AtomicBool"), + ty::Uint(_) => Some("AtomicUsize"), + ty::Int(_) => Some("AtomicIsize"), + ty::RawPtr(_) => Some("AtomicPtr"), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs b/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs new file mode 100644 index 0000000000..7687962bdd --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs @@ -0,0 +1,138 @@ +use crate::utils::{in_macro, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast::{BindingMode, Lifetime, Mutability, Param, PatKind, Path, TyKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::kw; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** The lint checks for `self` in fn parameters that + /// specify the `Self`-type explicitly + /// **Why is this bad?** Increases the amount and decreases the readability of code + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// enum ValType { + /// I32, + /// I64, + /// F32, + /// F64, + /// } + /// + /// impl ValType { + /// pub fn bytes(self: Self) -> usize { + /// match self { + /// Self::I32 | Self::F32 => 4, + /// Self::I64 | Self::F64 => 8, + /// } + /// } + /// } + /// ``` + /// + /// Could be rewritten as + /// + /// ```rust + /// enum ValType { + /// I32, + /// I64, + /// F32, + /// F64, + /// } + /// + /// impl ValType { + /// pub fn bytes(self) -> usize { + /// match self { + /// Self::I32 | Self::F32 => 4, + /// Self::I64 | Self::F64 => 8, + /// } + /// } + /// } + /// ``` + pub NEEDLESS_ARBITRARY_SELF_TYPE, + complexity, + "type of `self` parameter is already by default `Self`" +} + +declare_lint_pass!(NeedlessArbitrarySelfType => [NEEDLESS_ARBITRARY_SELF_TYPE]); + +enum Mode { + Ref(Option), + Value, +} + +fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mode: &Mode, mutbl: Mutability) { + if_chain! { + if let [segment] = &path.segments[..]; + if segment.ident.name == kw::SelfUpper; + then { + // In case we have a named lifetime, we check if the name comes from expansion. + // If it does, at this point we know the rest of the parameter was written by the user, + // so let them decide what the name of the lifetime should be. + // See #6089 for more details. + let mut applicability = Applicability::MachineApplicable; + let self_param = match (binding_mode, mutbl) { + (Mode::Ref(None), Mutability::Mut) => "&mut self".to_string(), + (Mode::Ref(Some(lifetime)), Mutability::Mut) => { + if in_macro(lifetime.ident.span) { + applicability = Applicability::HasPlaceholders; + "&'_ mut self".to_string() + } else { + format!("&{} mut self", &lifetime.ident.name) + } + }, + (Mode::Ref(None), Mutability::Not) => "&self".to_string(), + (Mode::Ref(Some(lifetime)), Mutability::Not) => { + if in_macro(lifetime.ident.span) { + applicability = Applicability::HasPlaceholders; + "&'_ self".to_string() + } else { + format!("&{} self", &lifetime.ident.name) + } + }, + (Mode::Value, Mutability::Mut) => "mut self".to_string(), + (Mode::Value, Mutability::Not) => "self".to_string(), + }; + + span_lint_and_sugg( + cx, + NEEDLESS_ARBITRARY_SELF_TYPE, + span, + "the type of the `self` parameter does not need to be arbitrary", + "consider to change this parameter to", + self_param, + applicability, + ) + } + } +} + +impl EarlyLintPass for NeedlessArbitrarySelfType { + fn check_param(&mut self, cx: &EarlyContext<'_>, p: &Param) { + // Bail out if the parameter it's not a receiver or was not written by the user + if !p.is_self() || in_macro(p.span) { + return; + } + + match &p.ty.kind { + TyKind::Path(None, path) => { + if let PatKind::Ident(BindingMode::ByValue(mutbl), _, _) = p.pat.kind { + check_param_inner(cx, path, p.span.to(p.ty.span), &Mode::Value, mutbl) + } + }, + TyKind::Rptr(lifetime, mut_ty) => { + if_chain! { + if let TyKind::Path(None, path) = &mut_ty.ty.kind; + if let PatKind::Ident(BindingMode::ByValue(Mutability::Not), _, _) = p.pat.kind; + then { + check_param_inner(cx, path, p.span.to(p.ty.span), &Mode::Ref(*lifetime), mut_ty.mutbl) + } + } + }, + _ => {}, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_bool.rs b/src/tools/clippy/clippy_lints/src/needless_bool.rs new file mode 100644 index 0000000000..f283ff1715 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_bool.rs @@ -0,0 +1,355 @@ +//! Checks for needless boolean results of if-else expressions +//! +//! This lint is **warn** by default + +use crate::utils::sugg::Sugg; +use crate::utils::{is_expn_of, parent_node_is_if_expr, snippet_with_applicability, span_lint, span_lint_and_sugg}; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for expressions of the form `if c { true } else { + /// false }` (or vice versa) and suggests using the condition directly. + /// + /// **Why is this bad?** Redundant code. + /// + /// **Known problems:** Maybe false positives: Sometimes, the two branches are + /// painstakingly documented (which we, of course, do not detect), so they *may* + /// have some value. Even then, the documentation can be rewritten to match the + /// shorter code. + /// + /// **Example:** + /// ```rust,ignore + /// if x { + /// false + /// } else { + /// true + /// } + /// ``` + /// Could be written as + /// ```rust,ignore + /// !x + /// ``` + pub NEEDLESS_BOOL, + complexity, + "if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for expressions of the form `x == true`, + /// `x != true` and order comparisons such as `x < true` (or vice versa) and + /// suggest using the variable directly. + /// + /// **Why is this bad?** Unnecessary code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// if x == true {} + /// if y == false {} + /// ``` + /// use `x` directly: + /// ```rust,ignore + /// if x {} + /// if !y {} + /// ``` + pub BOOL_COMPARISON, + complexity, + "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`" +} + +declare_lint_pass!(NeedlessBool => [NEEDLESS_BOOL]); + +impl<'tcx> LateLintPass<'tcx> for NeedlessBool { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + use self::Expression::{Bool, RetBool}; + if let ExprKind::If(ref pred, ref then_block, Some(ref else_expr)) = e.kind { + let reduce = |ret, not| { + let mut applicability = Applicability::MachineApplicable; + let snip = Sugg::hir_with_applicability(cx, pred, "", &mut applicability); + let mut snip = if not { !snip } else { snip }; + + if ret { + snip = snip.make_return(); + } + + if parent_node_is_if_expr(&e, &cx) { + snip = snip.blockify() + } + + span_lint_and_sugg( + cx, + NEEDLESS_BOOL, + e.span, + "this if-then-else expression returns a bool literal", + "you can reduce it to", + snip.to_string(), + applicability, + ); + }; + if let ExprKind::Block(ref then_block, _) = then_block.kind { + match (fetch_bool_block(then_block), fetch_bool_expr(else_expr)) { + (RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => { + span_lint( + cx, + NEEDLESS_BOOL, + e.span, + "this if-then-else expression will always return true", + ); + }, + (RetBool(false), RetBool(false)) | (Bool(false), Bool(false)) => { + span_lint( + cx, + NEEDLESS_BOOL, + e.span, + "this if-then-else expression will always return false", + ); + }, + (RetBool(true), RetBool(false)) => reduce(true, false), + (Bool(true), Bool(false)) => reduce(false, false), + (RetBool(false), RetBool(true)) => reduce(true, true), + (Bool(false), Bool(true)) => reduce(false, true), + _ => (), + } + } else { + panic!("IfExpr `then` node is not an `ExprKind::Block`"); + } + } + } +} + +declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]); + +impl<'tcx> LateLintPass<'tcx> for BoolComparison { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + return; + } + + if let ExprKind::Binary(Spanned { node, .. }, ..) = e.kind { + let ignore_case = None::<(fn(_) -> _, &str)>; + let ignore_no_literal = None::<(fn(_, _) -> _, &str)>; + match node { + BinOpKind::Eq => { + let true_case = Some((|h| h, "equality checks against true are unnecessary")); + let false_case = Some(( + |h: Sugg<'_>| !h, + "equality checks against false can be replaced by a negation", + )); + check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal) + }, + BinOpKind::Ne => { + let true_case = Some(( + |h: Sugg<'_>| !h, + "inequality checks against true can be replaced by a negation", + )); + let false_case = Some((|h| h, "inequality checks against false are unnecessary")); + check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal) + }, + BinOpKind::Lt => check_comparison( + cx, + e, + ignore_case, + Some((|h| h, "greater than checks against false are unnecessary")), + Some(( + |h: Sugg<'_>| !h, + "less than comparison against true can be replaced by a negation", + )), + ignore_case, + Some(( + |l: Sugg<'_>, r: Sugg<'_>| (!l).bit_and(&r), + "order comparisons between booleans can be simplified", + )), + ), + BinOpKind::Gt => check_comparison( + cx, + e, + Some(( + |h: Sugg<'_>| !h, + "less than comparison against true can be replaced by a negation", + )), + ignore_case, + ignore_case, + Some((|h| h, "greater than checks against false are unnecessary")), + Some(( + |l: Sugg<'_>, r: Sugg<'_>| l.bit_and(&(!r)), + "order comparisons between booleans can be simplified", + )), + ), + _ => (), + } + } + } +} + +struct ExpressionInfoWithSpan { + one_side_is_unary_not: bool, + left_span: Span, + right_span: Span, +} + +fn is_unary_not(e: &Expr<'_>) -> (bool, Span) { + if let ExprKind::Unary(UnOp::Not, operand) = e.kind { + return (true, operand.span); + } + (false, e.span) +} + +fn one_side_is_unary_not<'tcx>(left_side: &'tcx Expr<'_>, right_side: &'tcx Expr<'_>) -> ExpressionInfoWithSpan { + let left = is_unary_not(left_side); + let right = is_unary_not(right_side); + + ExpressionInfoWithSpan { + one_side_is_unary_not: left.0 != right.0, + left_span: left.1, + right_span: right.1, + } +} + +fn check_comparison<'a, 'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>, + left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>, + right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>, + right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>, + no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &str)>, +) { + use self::Expression::{Bool, Other}; + + if let ExprKind::Binary(op, ref left_side, ref right_side) = e.kind { + let (l_ty, r_ty) = ( + cx.typeck_results().expr_ty(left_side), + cx.typeck_results().expr_ty(right_side), + ); + if is_expn_of(left_side.span, "cfg").is_some() || is_expn_of(right_side.span, "cfg").is_some() { + return; + } + if l_ty.is_bool() && r_ty.is_bool() { + let mut applicability = Applicability::MachineApplicable; + + if let BinOpKind::Eq = op.node { + let expression_info = one_side_is_unary_not(&left_side, &right_side); + if expression_info.one_side_is_unary_not { + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + e.span, + "this comparison might be written more concisely", + "try simplifying it as shown", + format!( + "{} != {}", + snippet_with_applicability(cx, expression_info.left_span, "..", &mut applicability), + snippet_with_applicability(cx, expression_info.right_span, "..", &mut applicability) + ), + applicability, + ) + } + } + + match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { + (Bool(true), Other) => left_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, e, right_side, applicability, m, h) + }), + (Other, Bool(true)) => right_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, e, left_side, applicability, m, h) + }), + (Bool(false), Other) => left_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, e, right_side, applicability, m, h) + }), + (Other, Bool(false)) => right_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, e, left_side, applicability, m, h) + }), + (Other, Other) => no_literal.map_or((), |(h, m)| { + let left_side = Sugg::hir_with_applicability(cx, left_side, "..", &mut applicability); + let right_side = Sugg::hir_with_applicability(cx, right_side, "..", &mut applicability); + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + e.span, + m, + "try simplifying it as shown", + h(left_side, right_side).to_string(), + applicability, + ) + }), + _ => (), + } + } + } +} + +fn suggest_bool_comparison<'a, 'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + expr: &Expr<'_>, + mut applicability: Applicability, + message: &str, + conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, +) { + let hint = if expr.span.from_expansion() { + if applicability != Applicability::Unspecified { + applicability = Applicability::MaybeIncorrect; + } + Sugg::hir_with_macro_callsite(cx, expr, "..") + } else { + Sugg::hir_with_applicability(cx, expr, "..", &mut applicability) + }; + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + e.span, + message, + "try simplifying it as shown", + conv_hint(hint).to_string(), + applicability, + ); +} + +enum Expression { + Bool(bool), + RetBool(bool), + Other, +} + +fn fetch_bool_block(block: &Block<'_>) -> Expression { + match (&*block.stmts, block.expr.as_ref()) { + (&[], Some(e)) => fetch_bool_expr(&**e), + (&[ref e], None) => { + if let StmtKind::Semi(ref e) = e.kind { + if let ExprKind::Ret(_) = e.kind { + fetch_bool_expr(&**e) + } else { + Expression::Other + } + } else { + Expression::Other + } + }, + _ => Expression::Other, + } +} + +fn fetch_bool_expr(expr: &Expr<'_>) -> Expression { + match expr.kind { + ExprKind::Block(ref block, _) => fetch_bool_block(block), + ExprKind::Lit(ref lit_ptr) => { + if let LitKind::Bool(value) = lit_ptr.node { + Expression::Bool(value) + } else { + Expression::Other + } + }, + ExprKind::Ret(Some(ref expr)) => match fetch_bool_expr(expr) { + Expression::Bool(value) => Expression::RetBool(value), + _ => Expression::Other, + }, + _ => Expression::Other, + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_borrow.rs b/src/tools/clippy/clippy_lints/src/needless_borrow.rs new file mode 100644 index 0000000000..1aadcfd87b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_borrow.rs @@ -0,0 +1,133 @@ +//! Checks for needless address of operations (`&`) +//! +//! This lint is **warn** by default + +use crate::utils::{is_automatically_derived, snippet_opt, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BindingAnnotation, BorrowKind, Expr, ExprKind, Item, Mutability, Pat, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::{Adjust, Adjustment}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; + +declare_clippy_lint! { + /// **What it does:** Checks for address of operations (`&`) that are going to + /// be dereferenced immediately by the compiler. + /// + /// **Why is this bad?** Suggests that the receiver of the expression borrows + /// the expression. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// let x: &i32 = &&&&&&5; + /// + /// // Good + /// let x: &i32 = &5; + /// ``` + pub NEEDLESS_BORROW, + nursery, + "taking a reference that is going to be automatically dereferenced" +} + +#[derive(Default)] +pub struct NeedlessBorrow { + derived_item: Option, +} + +impl_lint_pass!(NeedlessBorrow => [NEEDLESS_BORROW]); + +impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() || self.derived_item.is_some() { + return; + } + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, ref inner) = e.kind { + if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(inner).kind() { + for adj3 in cx.typeck_results().expr_adjustments(e).windows(3) { + if let [Adjustment { + kind: Adjust::Deref(_), .. + }, Adjustment { + kind: Adjust::Deref(_), .. + }, Adjustment { + kind: Adjust::Borrow(_), + .. + }] = *adj3 + { + span_lint_and_then( + cx, + NEEDLESS_BORROW, + e.span, + &format!( + "this expression borrows a reference (`&{}`) that is immediately dereferenced \ + by the compiler", + ty + ), + |diag| { + if let Some(snippet) = snippet_opt(cx, inner.span) { + diag.span_suggestion( + e.span, + "change this to", + snippet, + Applicability::MachineApplicable, + ); + } + }, + ); + } + } + } + } + } + fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { + if pat.span.from_expansion() || self.derived_item.is_some() { + return; + } + if_chain! { + if let PatKind::Binding(BindingAnnotation::Ref, .., name, _) = pat.kind; + if let ty::Ref(_, tam, mutbl) = *cx.typeck_results().pat_ty(pat).kind(); + if mutbl == Mutability::Not; + if let ty::Ref(_, _, mutbl) = *tam.kind(); + // only lint immutable refs, because borrowed `&mut T` cannot be moved out + if mutbl == Mutability::Not; + then { + span_lint_and_then( + cx, + NEEDLESS_BORROW, + pat.span, + "this pattern creates a reference to a reference", + |diag| { + if let Some(snippet) = snippet_opt(cx, name.span) { + diag.span_suggestion( + pat.span, + "change this to", + snippet, + Applicability::MachineApplicable, + ); + } + } + ) + } + } + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + if is_automatically_derived(attrs) { + debug_assert!(self.derived_item.is_none()); + self.derived_item = Some(item.def_id); + } + } + + fn check_item_post(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if let Some(id) = self.derived_item { + if item.def_id == id { + self.derived_item = None; + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_borrowed_ref.rs b/src/tools/clippy/clippy_lints/src/needless_borrowed_ref.rs new file mode 100644 index 0000000000..f449f397e7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_borrowed_ref.rs @@ -0,0 +1,81 @@ +use crate::utils::{snippet_with_applicability, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BindingAnnotation, Mutability, Node, Pat, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for bindings that destructure a reference and borrow the inner + /// value with `&ref`. + /// + /// **Why is this bad?** This pattern has no effect in almost all cases. + /// + /// **Known problems:** In some cases, `&ref` is needed to avoid a lifetime mismatch error. + /// Example: + /// ```rust + /// fn foo(a: &Option, b: &Option) { + /// match (a, b) { + /// (None, &ref c) | (&ref c, None) => (), + /// (&Some(ref c), _) => (), + /// }; + /// } + /// ``` + /// + /// **Example:** + /// Bad: + /// ```rust + /// let mut v = Vec::::new(); + /// let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + /// ``` + /// + /// Good: + /// ```rust + /// let mut v = Vec::::new(); + /// let _ = v.iter_mut().filter(|a| a.is_empty()); + /// ``` + pub NEEDLESS_BORROWED_REFERENCE, + complexity, + "destructuring a reference and borrowing the inner value" +} + +declare_lint_pass!(NeedlessBorrowedRef => [NEEDLESS_BORROWED_REFERENCE]); + +impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowedRef { + fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { + if pat.span.from_expansion() { + // OK, simple enough, lints doesn't check in macro. + return; + } + + if_chain! { + // Only lint immutable refs, because `&mut ref T` may be useful. + if let PatKind::Ref(ref sub_pat, Mutability::Not) = pat.kind; + + // Check sub_pat got a `ref` keyword (excluding `ref mut`). + if let PatKind::Binding(BindingAnnotation::Ref, .., spanned_name, _) = sub_pat.kind; + let parent_id = cx.tcx.hir().get_parent_node(pat.hir_id); + if let Some(parent_node) = cx.tcx.hir().find(parent_id); + then { + // do not recurse within patterns, as they may have other references + // XXXManishearth we can relax this constraint if we only check patterns + // with a single ref pattern inside them + if let Node::Pat(_) = parent_node { + return; + } + let mut applicability = Applicability::MachineApplicable; + span_lint_and_then(cx, NEEDLESS_BORROWED_REFERENCE, pat.span, + "this pattern takes a reference on something that is being de-referenced", + |diag| { + let hint = snippet_with_applicability(cx, spanned_name.span, "..", &mut applicability).into_owned(); + diag.span_suggestion( + pat.span, + "try removing the `&ref` part and just keep", + hint, + applicability, + ); + }); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_continue.rs b/src/tools/clippy/clippy_lints/src/needless_continue.rs new file mode 100644 index 0000000000..30fe2d6225 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_continue.rs @@ -0,0 +1,459 @@ +//! Checks for continue statements in loops that are redundant. +//! +//! For example, the lint would catch +//! +//! ```rust +//! let mut a = 1; +//! let x = true; +//! +//! while a < 5 { +//! a = 6; +//! if x { +//! // ... +//! } else { +//! continue; +//! } +//! println!("Hello, world"); +//! } +//! ``` +//! +//! And suggest something like this: +//! +//! ```rust +//! let mut a = 1; +//! let x = true; +//! +//! while a < 5 { +//! a = 6; +//! if x { +//! // ... +//! println!("Hello, world"); +//! } +//! } +//! ``` +//! +//! This lint is **warn** by default. +use rustc_ast::ast; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::{original_sp, DUMMY_SP}; +use rustc_span::Span; + +use crate::utils::{indent_of, snippet, snippet_block, span_lint_and_help}; + +declare_clippy_lint! { + /// **What it does:** The lint checks for `if`-statements appearing in loops + /// that contain a `continue` statement in either their main blocks or their + /// `else`-blocks, when omitting the `else`-block possibly with some + /// rearrangement of code can make the code easier to understand. + /// + /// **Why is this bad?** Having explicit `else` blocks for `if` statements + /// containing `continue` in their THEN branch adds unnecessary branching and + /// nesting to the code. Having an else block containing just `continue` can + /// also be better written by grouping the statements following the whole `if` + /// statement within the THEN block and omitting the else block completely. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// # fn condition() -> bool { false } + /// # fn update_condition() {} + /// # let x = false; + /// while condition() { + /// update_condition(); + /// if x { + /// // ... + /// } else { + /// continue; + /// } + /// println!("Hello, world"); + /// } + /// ``` + /// + /// Could be rewritten as + /// + /// ```rust + /// # fn condition() -> bool { false } + /// # fn update_condition() {} + /// # let x = false; + /// while condition() { + /// update_condition(); + /// if x { + /// // ... + /// println!("Hello, world"); + /// } + /// } + /// ``` + /// + /// As another example, the following code + /// + /// ```rust + /// # fn waiting() -> bool { false } + /// loop { + /// if waiting() { + /// continue; + /// } else { + /// // Do something useful + /// } + /// # break; + /// } + /// ``` + /// Could be rewritten as + /// + /// ```rust + /// # fn waiting() -> bool { false } + /// loop { + /// if waiting() { + /// continue; + /// } + /// // Do something useful + /// # break; + /// } + /// ``` + pub NEEDLESS_CONTINUE, + pedantic, + "`continue` statements that can be replaced by a rearrangement of code" +} + +declare_lint_pass!(NeedlessContinue => [NEEDLESS_CONTINUE]); + +impl EarlyLintPass for NeedlessContinue { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { + if !expr.span.from_expansion() { + check_and_warn(cx, expr); + } + } +} + +/* This lint has to mainly deal with two cases of needless continue + * statements. */ +// Case 1 [Continue inside else block]: +// +// loop { +// // region A +// if cond { +// // region B +// } else { +// continue; +// } +// // region C +// } +// +// This code can better be written as follows: +// +// loop { +// // region A +// if cond { +// // region B +// // region C +// } +// } +// +// Case 2 [Continue inside then block]: +// +// loop { +// // region A +// if cond { +// continue; +// // potentially more code here. +// } else { +// // region B +// } +// // region C +// } +// +// +// This snippet can be refactored to: +// +// loop { +// // region A +// if !cond { +// // region B +// // region C +// } +// } +// + +/// Given an expression, returns true if either of the following is true +/// +/// - The expression is a `continue` node. +/// - The expression node is a block with the first statement being a +/// `continue`. +fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>) -> bool { + match else_expr.kind { + ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label), + ast::ExprKind::Continue(l) => compare_labels(label, l.as_ref()), + _ => false, + } +} + +fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool { + block.stmts.get(0).map_or(false, |stmt| match stmt.kind { + ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => { + if let ast::ExprKind::Continue(ref l) = e.kind { + compare_labels(label, l.as_ref()) + } else { + false + } + }, + _ => false, + }) +} + +/// If the `continue` has a label, check it matches the label of the loop. +fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::Label>) -> bool { + match (loop_label, continue_label) { + // `loop { continue; }` or `'a loop { continue; }` + (_, None) => true, + // `loop { continue 'a; }` + (None, _) => false, + // `'a loop { continue 'a; }` or `'a loop { continue 'b; }` + (Some(x), Some(y)) => x.ident == y.ident, + } +} + +/// If `expr` is a loop expression (while/while let/for/loop), calls `func` with +/// the AST object representing the loop block of `expr`. +fn with_loop_block(expr: &ast::Expr, mut func: F) +where + F: FnMut(&ast::Block, Option<&ast::Label>), +{ + if let ast::ExprKind::While(_, loop_block, label) + | ast::ExprKind::ForLoop(_, _, loop_block, label) + | ast::ExprKind::Loop(loop_block, label, ..) = &expr.kind + { + func(loop_block, label.as_ref()); + } +} + +/// If `stmt` is an if expression node with an `else` branch, calls func with +/// the +/// following: +/// +/// - The `if` expression itself, +/// - The `if` condition expression, +/// - The `then` block, and +/// - The `else` expression. +fn with_if_expr(stmt: &ast::Stmt, mut func: F) +where + F: FnMut(&ast::Expr, &ast::Expr, &ast::Block, &ast::Expr), +{ + match stmt.kind { + ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => { + if let ast::ExprKind::If(ref cond, ref if_block, Some(ref else_expr)) = e.kind { + func(e, cond, if_block, else_expr); + } + }, + _ => {}, + } +} + +/// A type to distinguish between the two distinct cases this lint handles. +#[derive(Copy, Clone, Debug)] +enum LintType { + ContinueInsideElseBlock, + ContinueInsideThenBlock, +} + +/// Data we pass around for construction of help messages. +struct LintData<'a> { + /// The `if` expression encountered in the above loop. + if_expr: &'a ast::Expr, + /// The condition expression for the above `if`. + if_cond: &'a ast::Expr, + /// The `then` block of the `if` statement. + if_block: &'a ast::Block, + /// The `else` block of the `if` statement. + /// Note that we only work with `if` exprs that have an `else` branch. + else_expr: &'a ast::Expr, + /// The 0-based index of the `if` statement in the containing loop block. + stmt_idx: usize, + /// The statements of the loop block. + block_stmts: &'a [ast::Stmt], +} + +const MSG_REDUNDANT_ELSE_BLOCK: &str = "this `else` block is redundant"; + +const MSG_ELSE_BLOCK_NOT_NEEDED: &str = "there is no need for an explicit `else` block for this `if` \ + expression"; + +const DROP_ELSE_BLOCK_AND_MERGE_MSG: &str = "consider dropping the `else` clause and merging the code that \ + follows (in the loop) with the `if` block"; + +const DROP_ELSE_BLOCK_MSG: &str = "consider dropping the `else` clause"; + +fn emit_warning<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>, header: &str, typ: LintType) { + // snip is the whole *help* message that appears after the warning. + // message is the warning message. + // expr is the expression which the lint warning message refers to. + let (snip, message, expr) = match typ { + LintType::ContinueInsideElseBlock => ( + suggestion_snippet_for_continue_inside_else(cx, data), + MSG_REDUNDANT_ELSE_BLOCK, + data.else_expr, + ), + LintType::ContinueInsideThenBlock => ( + suggestion_snippet_for_continue_inside_if(cx, data), + MSG_ELSE_BLOCK_NOT_NEEDED, + data.if_expr, + ), + }; + span_lint_and_help( + cx, + NEEDLESS_CONTINUE, + expr.span, + message, + None, + &format!("{}\n{}", header, snip), + ); +} + +fn suggestion_snippet_for_continue_inside_if<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>) -> String { + let cond_code = snippet(cx, data.if_cond.span, ".."); + + let continue_code = snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span)); + + let else_code = snippet_block(cx, data.else_expr.span, "..", Some(data.if_expr.span)); + + let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0); + format!( + "{indent}if {} {}\n{indent}{}", + cond_code, + continue_code, + else_code, + indent = " ".repeat(indent_if), + ) +} + +fn suggestion_snippet_for_continue_inside_else<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>) -> String { + let cond_code = snippet(cx, data.if_cond.span, ".."); + + // Region B + let block_code = erode_from_back(&snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span))); + + // Region C + // These is the code in the loop block that follows the if/else construction + // we are complaining about. We want to pull all of this code into the + // `then` block of the `if` statement. + let indent = span_of_first_expr_in_block(data.if_block) + .and_then(|span| indent_of(cx, span)) + .unwrap_or(0); + let to_annex = data.block_stmts[data.stmt_idx + 1..] + .iter() + .map(|stmt| original_sp(stmt.span, DUMMY_SP)) + .map(|span| { + let snip = snippet_block(cx, span, "..", None).into_owned(); + snip.lines() + .map(|line| format!("{}{}", " ".repeat(indent), line)) + .collect::>() + .join("\n") + }) + .collect::>() + .join("\n"); + + let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0); + format!( + "{indent_if}if {} {}\n{indent}// merged code follows:\n{}\n{indent_if}}}", + cond_code, + block_code, + to_annex, + indent = " ".repeat(indent), + indent_if = " ".repeat(indent_if), + ) +} + +fn check_and_warn<'a>(cx: &EarlyContext<'_>, expr: &'a ast::Expr) { + with_loop_block(expr, |loop_block, label| { + for (i, stmt) in loop_block.stmts.iter().enumerate() { + with_if_expr(stmt, |if_expr, cond, then_block, else_expr| { + let data = &LintData { + stmt_idx: i, + if_expr, + if_cond: cond, + if_block: then_block, + else_expr, + block_stmts: &loop_block.stmts, + }; + if needless_continue_in_else(else_expr, label) { + emit_warning( + cx, + data, + DROP_ELSE_BLOCK_AND_MERGE_MSG, + LintType::ContinueInsideElseBlock, + ); + } else if is_first_block_stmt_continue(then_block, label) { + emit_warning(cx, data, DROP_ELSE_BLOCK_MSG, LintType::ContinueInsideThenBlock); + } + }); + } + }); +} + +/// Eats at `s` from the end till a closing brace `}` is encountered, and then continues eating +/// till a non-whitespace character is found. e.g., the string. If no closing `}` is present, the +/// string will be preserved. +/// +/// ```rust +/// { +/// let x = 5; +/// } +/// ``` +/// +/// is transformed to +/// +/// ```ignore +/// { +/// let x = 5; +/// ``` +#[must_use] +fn erode_from_back(s: &str) -> String { + let mut ret = s.to_string(); + while ret.pop().map_or(false, |c| c != '}') {} + while let Some(c) = ret.pop() { + if !c.is_whitespace() { + ret.push(c); + break; + } + } + if ret.is_empty() { s.to_string() } else { ret } +} + +fn span_of_first_expr_in_block(block: &ast::Block) -> Option { + block.stmts.get(0).map(|stmt| stmt.span) +} + +#[cfg(test)] +mod test { + use super::erode_from_back; + + #[test] + #[rustfmt::skip] + fn test_erode_from_back() { + let input = "\ +{ + let x = 5; + let y = format!(\"{}\", 42); +}"; + + let expected = "\ +{ + let x = 5; + let y = format!(\"{}\", 42);"; + + let got = erode_from_back(input); + assert_eq!(expected, got); + } + + #[test] + #[rustfmt::skip] + fn test_erode_from_back_no_brace() { + let input = "\ +let x = 5; +let y = something(); +"; + let expected = input; + let got = erode_from_back(input); + assert_eq!(expected, got); + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs new file mode 100644 index 0000000000..d439577f9c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs @@ -0,0 +1,339 @@ +use crate::utils::ptr::get_spans; +use crate::utils::{ + get_trait_def_id, implements_trait, is_copy, is_self, is_type_diagnostic_item, multispan_sugg, paths, snippet, + snippet_opt, span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_ast::ast::Attribute; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{BindingAnnotation, Body, FnDecl, GenericArg, HirId, Impl, ItemKind, Node, PatKind, QPath, TyKind}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::FakeReadCause; +use rustc_middle::ty::{self, TypeFoldable}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::kw; +use rustc_span::{sym, Span}; +use rustc_target::spec::abi::Abi; +use rustc_trait_selection::traits; +use rustc_trait_selection::traits::misc::can_type_implement_copy; +use rustc_typeck::expr_use_visitor as euv; +use std::borrow::Cow; + +declare_clippy_lint! { + /// **What it does:** Checks for functions taking arguments by value, but not + /// consuming them in its + /// body. + /// + /// **Why is this bad?** Taking arguments by reference is more flexible and can + /// sometimes avoid + /// unnecessary allocations. + /// + /// **Known problems:** + /// * This lint suggests taking an argument by reference, + /// however sometimes it is better to let users decide the argument type + /// (by using `Borrow` trait, for example), depending on how the function is used. + /// + /// **Example:** + /// ```rust + /// fn foo(v: Vec) { + /// assert_eq!(v.len(), 42); + /// } + /// ``` + /// should be + /// ```rust + /// fn foo(v: &[i32]) { + /// assert_eq!(v.len(), 42); + /// } + /// ``` + pub NEEDLESS_PASS_BY_VALUE, + pedantic, + "functions taking arguments by value, but not consuming them in its body" +} + +declare_lint_pass!(NeedlessPassByValue => [NEEDLESS_PASS_BY_VALUE]); + +macro_rules! need { + ($e: expr) => { + if let Some(x) = $e { + x + } else { + return; + } + }; +} + +impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { + #[allow(clippy::too_many_lines)] + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + span: Span, + hir_id: HirId, + ) { + if span.from_expansion() { + return; + } + + match kind { + FnKind::ItemFn(.., header, _) => { + let attrs = cx.tcx.hir().attrs(hir_id); + if header.abi != Abi::Rust || requires_exact_signature(attrs) { + return; + } + }, + FnKind::Method(..) => (), + FnKind::Closure => return, + } + + // Exclude non-inherent impls + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { + if matches!( + item.kind, + ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) + ) { + return; + } + } + + // Allow `Borrow` or functions to be taken by value + let borrow_trait = need!(get_trait_def_id(cx, &paths::BORROW_TRAIT)); + let allowed_traits = [ + need!(cx.tcx.lang_items().fn_trait()), + need!(cx.tcx.lang_items().fn_once_trait()), + need!(cx.tcx.lang_items().fn_mut_trait()), + need!(get_trait_def_id(cx, &paths::RANGE_ARGUMENT_TRAIT)), + ]; + + let sized_trait = need!(cx.tcx.lang_items().sized_trait()); + + let fn_def_id = cx.tcx.hir().local_def_id(hir_id); + + let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds().iter()) + .filter(|p| !p.is_global()) + .filter_map(|obligation| { + // Note that we do not want to deal with qualified predicates here. + match obligation.predicate.kind().no_bound_vars() { + Some(ty::PredicateKind::Trait(pred, _)) if pred.def_id() != sized_trait => Some(pred), + _ => None, + } + }) + .collect::>(); + + // Collect moved variables and spans which will need dereferencings from the + // function body. + let MovedVariablesCtxt { + moved_vars, + spans_need_deref, + .. + } = { + let mut ctx = MovedVariablesCtxt::default(); + cx.tcx.infer_ctxt().enter(|infcx| { + euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()) + .consume_body(body); + }); + ctx + }; + + let fn_sig = cx.tcx.fn_sig(fn_def_id); + let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig); + + for (idx, ((input, &ty), arg)) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params).enumerate() { + // All spans generated from a proc-macro invocation are the same... + if span == input.span { + return; + } + + // Ignore `self`s. + if idx == 0 { + if let PatKind::Binding(.., ident, _) = arg.pat.kind { + if ident.name == kw::SelfLower { + continue; + } + } + } + + // + // * Exclude a type that is specifically bounded by `Borrow`. + // * Exclude a type whose reference also fulfills its bound. (e.g., `std::convert::AsRef`, + // `serde::Serialize`) + let (implements_borrow_trait, all_borrowable_trait) = { + let preds = preds.iter().filter(|t| t.self_ty() == ty).collect::>(); + + ( + preds.iter().any(|t| t.def_id() == borrow_trait), + !preds.is_empty() && { + let ty_empty_region = cx.tcx.mk_imm_ref(cx.tcx.lifetimes.re_root_empty, ty); + preds.iter().all(|t| { + let ty_params = t.trait_ref.substs.iter().skip(1).collect::>(); + implements_trait(cx, ty_empty_region, t.def_id(), &ty_params) + }) + }, + ) + }; + + if_chain! { + if !is_self(arg); + if !ty.is_mutable_ptr(); + if !is_copy(cx, ty); + if !allowed_traits.iter().any(|&t| implements_trait(cx, ty, t, &[])); + if !implements_borrow_trait; + if !all_borrowable_trait; + + if let PatKind::Binding(mode, canonical_id, ..) = arg.pat.kind; + if !moved_vars.contains(&canonical_id); + then { + if mode == BindingAnnotation::Mutable || mode == BindingAnnotation::RefMut { + continue; + } + + // Dereference suggestion + let sugg = |diag: &mut DiagnosticBuilder<'_>| { + if let ty::Adt(def, ..) = ty.kind() { + if let Some(span) = cx.tcx.hir().span_if_local(def.did) { + if can_type_implement_copy(cx.tcx, cx.param_env, ty).is_ok() { + diag.span_help(span, "consider marking this type as `Copy`"); + } + } + } + + let deref_span = spans_need_deref.get(&canonical_id); + if_chain! { + if is_type_diagnostic_item(cx, ty, sym::vec_type); + if let Some(clone_spans) = + get_spans(cx, Some(body.id()), idx, &[("clone", ".to_owned()")]); + if let TyKind::Path(QPath::Resolved(_, ref path)) = input.kind; + if let Some(elem_ty) = path.segments.iter() + .find(|seg| seg.ident.name == sym::Vec) + .and_then(|ps| ps.args.as_ref()) + .map(|params| params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }).unwrap()); + then { + let slice_ty = format!("&[{}]", snippet(cx, elem_ty.span, "_")); + diag.span_suggestion( + input.span, + "consider changing the type to", + slice_ty, + Applicability::Unspecified, + ); + + for (span, suggestion) in clone_spans { + diag.span_suggestion( + span, + &snippet_opt(cx, span) + .map_or( + "change the call to".into(), + |x| Cow::from(format!("change `{}` to", x)), + ), + suggestion.into(), + Applicability::Unspecified, + ); + } + + // cannot be destructured, no need for `*` suggestion + assert!(deref_span.is_none()); + return; + } + } + + if is_type_diagnostic_item(cx, ty, sym::string_type) { + if let Some(clone_spans) = + get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) { + diag.span_suggestion( + input.span, + "consider changing the type to", + "&str".to_string(), + Applicability::Unspecified, + ); + + for (span, suggestion) in clone_spans { + diag.span_suggestion( + span, + &snippet_opt(cx, span) + .map_or( + "change the call to".into(), + |x| Cow::from(format!("change `{}` to", x)) + ), + suggestion.into(), + Applicability::Unspecified, + ); + } + + assert!(deref_span.is_none()); + return; + } + } + + let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))]; + + // Suggests adding `*` to dereference the added reference. + if let Some(deref_span) = deref_span { + spans.extend( + deref_span + .iter() + .cloned() + .map(|span| (span, format!("*{}", snippet(cx, span, "")))), + ); + spans.sort_by_key(|&(span, _)| span); + } + multispan_sugg(diag, "consider taking a reference instead", spans); + }; + + span_lint_and_then( + cx, + NEEDLESS_PASS_BY_VALUE, + input.span, + "this argument is passed by value, but not consumed in the function body", + sugg, + ); + } + } + } + } +} + +/// Functions marked with these attributes must have the exact signature. +fn requires_exact_signature(attrs: &[Attribute]) -> bool { + attrs.iter().any(|attr| { + [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive] + .iter() + .any(|&allow| attr.has_name(allow)) + }) +} + +#[derive(Default)] +struct MovedVariablesCtxt { + moved_vars: FxHashSet, + /// Spans which need to be prefixed with `*` for dereferencing the + /// suggested additional reference. + spans_need_deref: FxHashMap>, +} + +impl MovedVariablesCtxt { + fn move_common(&mut self, cmt: &euv::PlaceWithHirId<'_>) { + if let euv::PlaceBase::Local(vid) = cmt.place.base { + self.moved_vars.insert(vid); + } + } +} + +impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt { + fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: HirId, mode: euv::ConsumeMode) { + if let euv::ConsumeMode::Move = mode { + self.move_common(cmt); + } + } + + fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {} + + fn mutate(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId) {} + + fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _: HirId) { } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_question_mark.rs b/src/tools/clippy/clippy_lints/src/needless_question_mark.rs new file mode 100644 index 0000000000..a3293f1b36 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_question_mark.rs @@ -0,0 +1,208 @@ +use rustc_errors::Applicability; +use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::sym; + +use crate::utils; +use if_chain::if_chain; + +declare_clippy_lint! { + /// **What it does:** + /// Suggests alternatives for useless applications of `?` in terminating expressions + /// + /// **Why is this bad?** There's no reason to use `?` to short-circuit when execution of the body will end there anyway. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// struct TO { + /// magic: Option, + /// } + /// + /// fn f(to: TO) -> Option { + /// Some(to.magic?) + /// } + /// + /// struct TR { + /// magic: Result, + /// } + /// + /// fn g(tr: Result) -> Result { + /// tr.and_then(|t| Ok(t.magic?)) + /// } + /// + /// ``` + /// Use instead: + /// ```rust + /// struct TO { + /// magic: Option, + /// } + /// + /// fn f(to: TO) -> Option { + /// to.magic + /// } + /// + /// struct TR { + /// magic: Result, + /// } + /// + /// fn g(tr: Result) -> Result { + /// tr.and_then(|t| t.magic) + /// } + /// ``` + pub NEEDLESS_QUESTION_MARK, + complexity, + "Suggest `value.inner_option` instead of `Some(value.inner_option?)`. The same goes for `Result`." +} + +const NEEDLESS_QUESTION_MARK_RESULT_MSRV: RustcVersion = RustcVersion::new(1, 13, 0); +const NEEDLESS_QUESTION_MARK_OPTION_MSRV: RustcVersion = RustcVersion::new(1, 22, 0); + +pub struct NeedlessQuestionMark { + msrv: Option, +} + +impl NeedlessQuestionMark { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]); + +#[derive(Debug)] +enum SomeOkCall<'a> { + SomeCall(&'a Expr<'a>, &'a Expr<'a>), + OkCall(&'a Expr<'a>, &'a Expr<'a>), +} + +impl LateLintPass<'_> for NeedlessQuestionMark { + /* + * The question mark operator is compatible with both Result and Option, + * from Rust 1.13 and 1.22 respectively. + */ + + /* + * What do we match: + * Expressions that look like this: + * Some(option?), Ok(result?) + * + * Where do we match: + * Last expression of a body + * Return statement + * A body's value (single line closure) + * + * What do we not match: + * Implicit calls to `from(..)` on the error value + */ + + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { + let e = match &expr.kind { + ExprKind::Ret(Some(e)) => e, + _ => return, + }; + + if let Some(ok_some_call) = is_some_or_ok_call(self, cx, e) { + emit_lint(cx, &ok_some_call); + } + } + + fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) { + // Function / Closure block + let expr_opt = if let ExprKind::Block(block, _) = &body.value.kind { + block.expr + } else { + // Single line closure + Some(&body.value) + }; + + if_chain! { + if let Some(expr) = expr_opt; + if let Some(ok_some_call) = is_some_or_ok_call(self, cx, expr); + then { + emit_lint(cx, &ok_some_call); + } + }; + } + + extract_msrv_attr!(LateContext); +} + +fn emit_lint(cx: &LateContext<'_>, expr: &SomeOkCall<'_>) { + let (entire_expr, inner_expr) = match expr { + SomeOkCall::OkCall(outer, inner) | SomeOkCall::SomeCall(outer, inner) => (outer, inner), + }; + + utils::span_lint_and_sugg( + cx, + NEEDLESS_QUESTION_MARK, + entire_expr.span, + "question mark operator is useless here", + "try", + format!("{}", utils::snippet(cx, inner_expr.span, r#""...""#)), + Applicability::MachineApplicable, + ); +} + +fn is_some_or_ok_call<'a>( + nqml: &NeedlessQuestionMark, + cx: &'a LateContext<'_>, + expr: &'a Expr<'_>, +) -> Option> { + if_chain! { + // Check outer expression matches CALL_IDENT(ARGUMENT) format + if let ExprKind::Call(path, args) = &expr.kind; + if let ExprKind::Path(QPath::Resolved(None, path)) = &path.kind; + if utils::is_some_ctor(cx, path.res) || utils::is_ok_ctor(cx, path.res); + + // Extract inner expression from ARGUMENT + if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &args[0].kind; + if let ExprKind::Call(called, args) = &inner_expr_with_q.kind; + if args.len() == 1; + + if let ExprKind::Path(QPath::LangItem(LangItem::TryIntoResult, _)) = &called.kind; + then { + // Extract inner expr type from match argument generated by + // question mark operator + let inner_expr = &args[0]; + + let inner_ty = cx.typeck_results().expr_ty(inner_expr); + let outer_ty = cx.typeck_results().expr_ty(expr); + + // Check if outer and inner type are Option + let outer_is_some = utils::is_type_diagnostic_item(cx, outer_ty, sym::option_type); + let inner_is_some = utils::is_type_diagnostic_item(cx, inner_ty, sym::option_type); + + // Check for Option MSRV + let meets_option_msrv = utils::meets_msrv(nqml.msrv.as_ref(), &NEEDLESS_QUESTION_MARK_OPTION_MSRV); + if outer_is_some && inner_is_some && meets_option_msrv { + return Some(SomeOkCall::SomeCall(expr, inner_expr)); + } + + // Check if outer and inner type are Result + let outer_is_result = utils::is_type_diagnostic_item(cx, outer_ty, sym::result_type); + let inner_is_result = utils::is_type_diagnostic_item(cx, inner_ty, sym::result_type); + + // Additional check: if the error type of the Result can be converted + // via the From trait, then don't match + let does_not_call_from = !has_implicit_error_from(cx, expr, inner_expr); + + // Must meet Result MSRV + let meets_result_msrv = utils::meets_msrv(nqml.msrv.as_ref(), &NEEDLESS_QUESTION_MARK_RESULT_MSRV); + if outer_is_result && inner_is_result && does_not_call_from && meets_result_msrv { + return Some(SomeOkCall::OkCall(expr, inner_expr)); + } + } + } + + None +} + +fn has_implicit_error_from(cx: &LateContext<'_>, entire_expr: &Expr<'_>, inner_result_expr: &Expr<'_>) -> bool { + return cx.typeck_results().expr_ty(entire_expr) != cx.typeck_results().expr_ty(inner_result_expr); +} diff --git a/src/tools/clippy/clippy_lints/src/needless_update.rs b/src/tools/clippy/clippy_lints/src/needless_update.rs new file mode 100644 index 0000000000..41cf541ecf --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_update.rs @@ -0,0 +1,68 @@ +use crate::utils::span_lint; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for needlessly including a base struct on update + /// when all fields are changed anyway. + /// + /// This lint is not applied to structs marked with + /// [non_exhaustive](https://doc.rust-lang.org/reference/attributes/type_system.html). + /// + /// **Why is this bad?** This will cost resources (because the base has to be + /// somewhere), and make the code less readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct Point { + /// # x: i32, + /// # y: i32, + /// # z: i32, + /// # } + /// # let zero_point = Point { x: 0, y: 0, z: 0 }; + /// + /// // Bad + /// Point { + /// x: 1, + /// y: 1, + /// z: 1, + /// ..zero_point + /// }; + /// + /// // Ok + /// Point { + /// x: 1, + /// y: 1, + /// ..zero_point + /// }; + /// ``` + pub NEEDLESS_UPDATE, + complexity, + "using `Foo { ..base }` when there are no missing fields" +} + +declare_lint_pass!(NeedlessUpdate => [NEEDLESS_UPDATE]); + +impl<'tcx> LateLintPass<'tcx> for NeedlessUpdate { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Struct(_, ref fields, Some(ref base)) = expr.kind { + let ty = cx.typeck_results().expr_ty(expr); + if let ty::Adt(def, _) = ty.kind() { + if fields.len() == def.non_enum_variant().fields.len() + && !def.variants[0_usize.into()].is_field_list_non_exhaustive() + { + span_lint( + cx, + NEEDLESS_UPDATE, + base.span, + "struct update has no effect, all the fields in the struct have already been specified", + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/neg_cmp_op_on_partial_ord.rs b/src/tools/clippy/clippy_lints/src/neg_cmp_op_on_partial_ord.rs new file mode 100644 index 0000000000..ec0ad58ca9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/neg_cmp_op_on_partial_ord.rs @@ -0,0 +1,91 @@ +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{self, paths, span_lint}; + +declare_clippy_lint! { + /// **What it does:** + /// Checks for the usage of negated comparison operators on types which only implement + /// `PartialOrd` (e.g., `f64`). + /// + /// **Why is this bad?** + /// These operators make it easy to forget that the underlying types actually allow not only three + /// potential Orderings (Less, Equal, Greater) but also a fourth one (Uncomparable). This is + /// especially easy to miss if the operator based comparison result is negated. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// use std::cmp::Ordering; + /// + /// // Bad + /// let a = 1.0; + /// let b = f64::NAN; + /// + /// let _not_less_or_equal = !(a <= b); + /// + /// // Good + /// let a = 1.0; + /// let b = f64::NAN; + /// + /// let _not_less_or_equal = match a.partial_cmp(&b) { + /// None | Some(Ordering::Greater) => true, + /// _ => false, + /// }; + /// ``` + pub NEG_CMP_OP_ON_PARTIAL_ORD, + complexity, + "The use of negated comparison operators on partially ordered types may produce confusing code." +} + +declare_lint_pass!(NoNegCompOpForPartialOrd => [NEG_CMP_OP_ON_PARTIAL_ORD]); + +impl<'tcx> LateLintPass<'tcx> for NoNegCompOpForPartialOrd { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + + if !in_external_macro(cx.sess(), expr.span); + if let ExprKind::Unary(UnOp::Not, ref inner) = expr.kind; + if let ExprKind::Binary(ref op, ref left, _) = inner.kind; + if let BinOpKind::Le | BinOpKind::Ge | BinOpKind::Lt | BinOpKind::Gt = op.node; + + then { + + let ty = cx.typeck_results().expr_ty(left); + + let implements_ord = { + if let Some(id) = utils::get_trait_def_id(cx, &paths::ORD) { + utils::implements_trait(cx, ty, id, &[]) + } else { + return; + } + }; + + let implements_partial_ord = { + if let Some(id) = cx.tcx.lang_items().partial_ord_trait() { + utils::implements_trait(cx, ty, id, &[]) + } else { + return; + } + }; + + if implements_partial_ord && !implements_ord { + span_lint( + cx, + NEG_CMP_OP_ON_PARTIAL_ORD, + expr.span, + "the use of negated comparison operators on partially ordered \ + types produces code that is hard to read and refactor, please \ + consider using the `partial_cmp` method instead, to make it \ + clear that the two values could be incomparable" + ) + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/neg_multiply.rs b/src/tools/clippy/clippy_lints/src/neg_multiply.rs new file mode 100644 index 0000000000..ef7cc65cfc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/neg_multiply.rs @@ -0,0 +1,53 @@ +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +use crate::consts::{self, Constant}; +use crate::utils::span_lint; + +declare_clippy_lint! { + /// **What it does:** Checks for multiplication by -1 as a form of negation. + /// + /// **Why is this bad?** It's more readable to just negate. + /// + /// **Known problems:** This only catches integers (for now). + /// + /// **Example:** + /// ```ignore + /// x * -1 + /// ``` + pub NEG_MULTIPLY, + style, + "multiplying integers with `-1`" +} + +declare_lint_pass!(NegMultiply => [NEG_MULTIPLY]); + +#[allow(clippy::match_same_arms)] +impl<'tcx> LateLintPass<'tcx> for NegMultiply { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Binary(ref op, ref left, ref right) = e.kind { + if BinOpKind::Mul == op.node { + match (&left.kind, &right.kind) { + (&ExprKind::Unary(..), &ExprKind::Unary(..)) => {}, + (&ExprKind::Unary(UnOp::Neg, ref lit), _) => check_mul(cx, e.span, lit, right), + (_, &ExprKind::Unary(UnOp::Neg, ref lit)) => check_mul(cx, e.span, lit, left), + _ => {}, + } + } + } + } +} + +fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { + if_chain! { + if let ExprKind::Lit(ref l) = lit.kind; + if let Constant::Int(1) = consts::lit_to_constant(&l.node, cx.typeck_results().expr_ty_opt(lit)); + if cx.typeck_results().expr_ty(exp).is_integral(); + then { + span_lint(cx, NEG_MULTIPLY, span, "negation by multiplying with `-1`"); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/new_without_default.rs b/src/tools/clippy/clippy_lints/src/new_without_default.rs new file mode 100644 index 0000000000..de2899c346 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/new_without_default.rs @@ -0,0 +1,165 @@ +use crate::utils::paths; +use crate::utils::sugg::DiagnosticBuilderExt; +use crate::utils::{get_trait_def_id, return_ty, span_lint_hir_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::HirIdSet; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{Ty, TyS}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for types with a `fn new() -> Self` method and no + /// implementation of + /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html). + /// + /// **Why is this bad?** The user might expect to be able to use + /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the + /// type can be constructed without arguments. + /// + /// **Known problems:** Hopefully none. + /// + /// **Example:** + /// + /// ```ignore + /// struct Foo(Bar); + /// + /// impl Foo { + /// fn new() -> Self { + /// Foo(Bar::new()) + /// } + /// } + /// ``` + /// + /// To fix the lint, add a `Default` implementation that delegates to `new`: + /// + /// ```ignore + /// struct Foo(Bar); + /// + /// impl Default for Foo { + /// fn default() -> Self { + /// Foo::new() + /// } + /// } + /// ``` + pub NEW_WITHOUT_DEFAULT, + style, + "`fn new() -> Self` method without `Default` implementation" +} + +#[derive(Clone, Default)] +pub struct NewWithoutDefault { + impling_types: Option, +} + +impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]); + +impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { + #[allow(clippy::too_many_lines)] + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { + if let hir::ItemKind::Impl(hir::Impl { + of_trait: None, items, .. + }) = item.kind + { + for assoc_item in items { + if let hir::AssocItemKind::Fn { has_self: false } = assoc_item.kind { + let impl_item = cx.tcx.hir().impl_item(assoc_item.id); + if in_external_macro(cx.sess(), impl_item.span) { + return; + } + if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind { + let name = impl_item.ident.name; + let id = impl_item.hir_id(); + if sig.header.constness == hir::Constness::Const { + // can't be implemented by default + return; + } + if sig.header.unsafety == hir::Unsafety::Unsafe { + // can't be implemented for unsafe new + return; + } + if impl_item + .generics + .params + .iter() + .any(|gen| matches!(gen.kind, hir::GenericParamKind::Type { .. })) + { + // when the result of `new()` depends on a type parameter we should not require + // an + // impl of `Default` + return; + } + if sig.decl.inputs.is_empty() && name == sym::new && cx.access_levels.is_reachable(id) { + let self_def_id = cx.tcx.hir().local_def_id(cx.tcx.hir().get_parent_item(id)); + let self_ty = cx.tcx.type_of(self_def_id); + if_chain! { + if TyS::same_type(self_ty, return_ty(cx, id)); + if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); + then { + if self.impling_types.is_none() { + let mut impls = HirIdSet::default(); + cx.tcx.for_each_impl(default_trait_id, |d| { + if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() { + if let Some(local_def_id) = ty_def.did.as_local() { + impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id)); + } + } + }); + self.impling_types = Some(impls); + } + + // Check if a Default implementation exists for the Self type, regardless of + // generics + if_chain! { + if let Some(ref impling_types) = self.impling_types; + if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def(); + if let Some(self_local_did) = self_def.did.as_local(); + then { + let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did); + if impling_types.contains(&self_id) { + return; + } + } + } + + span_lint_hir_and_then( + cx, + NEW_WITHOUT_DEFAULT, + id, + impl_item.span, + &format!( + "you should consider adding a `Default` implementation for `{}`", + self_ty + ), + |diag| { + diag.suggest_prepend_item( + cx, + item.span, + "try this", + &create_new_without_default_suggest_msg(self_ty), + Applicability::MaybeIncorrect, + ); + }, + ); + } + } + } + } + } + } + } + } +} + +fn create_new_without_default_suggest_msg(ty: Ty<'_>) -> String { + #[rustfmt::skip] + format!( +"impl Default for {} {{ + fn default() -> Self {{ + Self::new() + }} +}}", ty) +} diff --git a/src/tools/clippy/clippy_lints/src/no_effect.rs b/src/tools/clippy/clippy_lints/src/no_effect.rs new file mode 100644 index 0000000000..69302d695c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/no_effect.rs @@ -0,0 +1,178 @@ +use crate::utils::{has_drop, snippet_opt, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{BinOpKind, BlockCheckMode, Expr, ExprKind, Stmt, StmtKind, UnsafeSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::ops::Deref; + +declare_clippy_lint! { + /// **What it does:** Checks for statements which have no effect. + /// + /// **Why is this bad?** Similar to dead code, these statements are actually + /// executed. However, as they have no effect, all they do is make the code less + /// readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// 0; + /// ``` + pub NO_EFFECT, + complexity, + "statements with no effect" +} + +declare_clippy_lint! { + /// **What it does:** Checks for expression statements that can be reduced to a + /// sub-expression. + /// + /// **Why is this bad?** Expressions by themselves often have no side-effects. + /// Having such expressions reduces readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// compute_array()[0]; + /// ``` + pub UNNECESSARY_OPERATION, + complexity, + "outer expressions with no effect" +} + +fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if expr.span.from_expansion() { + return false; + } + match expr.kind { + ExprKind::Lit(..) | ExprKind::Closure(..) => true, + ExprKind::Path(..) => !has_drop(cx, cx.typeck_results().expr_ty(expr)), + ExprKind::Index(ref a, ref b) | ExprKind::Binary(_, ref a, ref b) => { + has_no_effect(cx, a) && has_no_effect(cx, b) + }, + ExprKind::Array(ref v) | ExprKind::Tup(ref v) => v.iter().all(|val| has_no_effect(cx, val)), + ExprKind::Repeat(ref inner, _) + | ExprKind::Cast(ref inner, _) + | ExprKind::Type(ref inner, _) + | ExprKind::Unary(_, ref inner) + | ExprKind::Field(ref inner, _) + | ExprKind::AddrOf(_, _, ref inner) + | ExprKind::Box(ref inner) => has_no_effect(cx, inner), + ExprKind::Struct(_, ref fields, ref base) => { + !has_drop(cx, cx.typeck_results().expr_ty(expr)) + && fields.iter().all(|field| has_no_effect(cx, &field.expr)) + && base.as_ref().map_or(true, |base| has_no_effect(cx, base)) + }, + ExprKind::Call(ref callee, ref args) => { + if let ExprKind::Path(ref qpath) = callee.kind { + let res = cx.qpath_res(qpath, callee.hir_id); + match res { + Res::Def(DefKind::Struct | DefKind::Variant | DefKind::Ctor(..), ..) => { + !has_drop(cx, cx.typeck_results().expr_ty(expr)) + && args.iter().all(|arg| has_no_effect(cx, arg)) + }, + _ => false, + } + } else { + false + } + }, + ExprKind::Block(ref block, _) => { + block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| has_no_effect(cx, expr)) + }, + _ => false, + } +} + +declare_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION]); + +impl<'tcx> LateLintPass<'tcx> for NoEffect { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if let StmtKind::Semi(ref expr) = stmt.kind { + if has_no_effect(cx, expr) { + span_lint(cx, NO_EFFECT, stmt.span, "statement with no effect"); + } else if let Some(reduced) = reduce_expression(cx, expr) { + let mut snippet = String::new(); + for e in reduced { + if e.span.from_expansion() { + return; + } + if let Some(snip) = snippet_opt(cx, e.span) { + snippet.push_str(&snip); + snippet.push(';'); + } else { + return; + } + } + span_lint_and_sugg( + cx, + UNNECESSARY_OPERATION, + stmt.span, + "statement can be reduced", + "replace it with", + snippet, + Applicability::MachineApplicable, + ); + } + } + } +} + +fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option>> { + if expr.span.from_expansion() { + return None; + } + match expr.kind { + ExprKind::Index(ref a, ref b) => Some(vec![&**a, &**b]), + ExprKind::Binary(ref binop, ref a, ref b) if binop.node != BinOpKind::And && binop.node != BinOpKind::Or => { + Some(vec![&**a, &**b]) + }, + ExprKind::Array(ref v) | ExprKind::Tup(ref v) => Some(v.iter().collect()), + ExprKind::Repeat(ref inner, _) + | ExprKind::Cast(ref inner, _) + | ExprKind::Type(ref inner, _) + | ExprKind::Unary(_, ref inner) + | ExprKind::Field(ref inner, _) + | ExprKind::AddrOf(_, _, ref inner) + | ExprKind::Box(ref inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])), + ExprKind::Struct(_, ref fields, ref base) => { + if has_drop(cx, cx.typeck_results().expr_ty(expr)) { + None + } else { + Some(fields.iter().map(|f| &f.expr).chain(base).map(Deref::deref).collect()) + } + }, + ExprKind::Call(ref callee, ref args) => { + if let ExprKind::Path(ref qpath) = callee.kind { + let res = cx.qpath_res(qpath, callee.hir_id); + match res { + Res::Def(DefKind::Struct | DefKind::Variant | DefKind::Ctor(..), ..) + if !has_drop(cx, cx.typeck_results().expr_ty(expr)) => + { + Some(args.iter().collect()) + }, + _ => None, + } + } else { + None + } + }, + ExprKind::Block(ref block, _) => { + if block.stmts.is_empty() { + block.expr.as_ref().and_then(|e| { + match block.rules { + BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None, + BlockCheckMode::DefaultBlock => Some(vec![&**e]), + // in case of compiler-inserted signaling blocks + _ => reduce_expression(cx, e), + } + }) + } else { + None + } + }, + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/non_copy_const.rs b/src/tools/clippy/clippy_lints/src/non_copy_const.rs new file mode 100644 index 0000000000..8aebce6791 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/non_copy_const.rs @@ -0,0 +1,425 @@ +//! Checks for uses of const which the type is not `Freeze` (`Cell`-free). +//! +//! This lint is **warn** by default. + +use std::ptr; + +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::{ + BodyId, Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind, UnOp, +}; +use rustc_infer::traits::specialization_graph; +use rustc_lint::{LateContext, LateLintPass, Lint}; +use rustc_middle::mir::interpret::{ConstValue, ErrorHandled}; +use rustc_middle::ty::adjustment::Adjust; +use rustc_middle::ty::{self, AssocKind, Const, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{InnerSpan, Span, DUMMY_SP}; +use rustc_typeck::hir_ty_to_ty; + +use crate::utils::{in_constant, span_lint_and_then}; +use if_chain::if_chain; + +// FIXME: this is a correctness problem but there's no suitable +// warn-by-default category. +declare_clippy_lint! { + /// **What it does:** Checks for declaration of `const` items which is interior + /// mutable (e.g., contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.). + /// + /// **Why is this bad?** Consts are copied everywhere they are referenced, i.e., + /// every time you refer to the const a fresh instance of the `Cell` or `Mutex` + /// or `AtomicXxxx` will be created, which defeats the whole purpose of using + /// these types in the first place. + /// + /// The `const` should better be replaced by a `static` item if a global + /// variable is wanted, or replaced by a `const fn` if a constructor is wanted. + /// + /// **Known problems:** A "non-constant" const item is a legacy way to supply an + /// initialized value to downstream `static` items (e.g., the + /// `std::sync::ONCE_INIT` constant). In this case the use of `const` is legit, + /// and this lint should be suppressed. + /// + /// Even though the lint avoids triggering on a constant whose type has enums that have variants + /// with interior mutability, and its value uses non interior mutable variants (see + /// [#3962](https://github.com/rust-lang/rust-clippy/issues/3962) and + /// [#3825](https://github.com/rust-lang/rust-clippy/issues/3825) for examples); + /// it complains about associated constants without default values only based on its types; + /// which might not be preferable. + /// There're other enums plus associated constants cases that the lint cannot handle. + /// + /// Types that have underlying or potential interior mutability trigger the lint whether + /// the interior mutable field is used or not. See issues + /// [#5812](https://github.com/rust-lang/rust-clippy/issues/5812) and + /// + /// **Example:** + /// ```rust + /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + /// + /// // Bad. + /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); + /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged + /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct + /// + /// // Good. + /// static STATIC_ATOM: AtomicUsize = AtomicUsize::new(15); + /// STATIC_ATOM.store(9, SeqCst); + /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance + /// ``` + pub DECLARE_INTERIOR_MUTABLE_CONST, + style, + "declaring `const` with interior mutability" +} + +// FIXME: this is a correctness problem but there's no suitable +// warn-by-default category. +declare_clippy_lint! { + /// **What it does:** Checks if `const` items which is interior mutable (e.g., + /// contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.) has been borrowed directly. + /// + /// **Why is this bad?** Consts are copied everywhere they are referenced, i.e., + /// every time you refer to the const a fresh instance of the `Cell` or `Mutex` + /// or `AtomicXxxx` will be created, which defeats the whole purpose of using + /// these types in the first place. + /// + /// The `const` value should be stored inside a `static` item. + /// + /// **Known problems:** When an enum has variants with interior mutability, use of its non + /// interior mutable variants can generate false positives. See issue + /// [#3962](https://github.com/rust-lang/rust-clippy/issues/3962) + /// + /// Types that have underlying or potential interior mutability trigger the lint whether + /// the interior mutable field is used or not. See issues + /// [#5812](https://github.com/rust-lang/rust-clippy/issues/5812) and + /// [#3825](https://github.com/rust-lang/rust-clippy/issues/3825) + /// + /// **Example:** + /// ```rust + /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); + /// + /// // Bad. + /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged + /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct + /// + /// // Good. + /// static STATIC_ATOM: AtomicUsize = CONST_ATOM; + /// STATIC_ATOM.store(9, SeqCst); + /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance + /// ``` + pub BORROW_INTERIOR_MUTABLE_CONST, + style, + "referencing `const` with interior mutability" +} + +fn is_unfrozen<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + // Ignore types whose layout is unknown since `is_freeze` reports every generic types as `!Freeze`, + // making it indistinguishable from `UnsafeCell`. i.e. it isn't a tool to prove a type is + // 'unfrozen'. However, this code causes a false negative in which + // a type contains a layout-unknown type, but also a unsafe cell like `const CELL: Cell`. + // Yet, it's better than `ty.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_PROJECTION)` + // since it works when a pointer indirection involves (`Cell<*const T>`). + // Making up a `ParamEnv` where every generic params and assoc types are `Freeze`is another option; + // but I'm not sure whether it's a decent way, if possible. + cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() && !ty.is_freeze(cx.tcx.at(DUMMY_SP), cx.param_env) +} + +fn is_value_unfrozen_raw<'tcx>( + cx: &LateContext<'tcx>, + result: Result, ErrorHandled>, + ty: Ty<'tcx>, +) -> bool { + fn inner<'tcx>(cx: &LateContext<'tcx>, val: &'tcx Const<'tcx>) -> bool { + match val.ty.kind() { + // the fact that we have to dig into every structs to search enums + // leads us to the point checking `UnsafeCell` directly is the only option. + ty::Adt(ty_def, ..) if Some(ty_def.did) == cx.tcx.lang_items().unsafe_cell_type() => true, + ty::Array(..) | ty::Adt(..) | ty::Tuple(..) => { + let val = cx.tcx.destructure_const(cx.param_env.and(val)); + val.fields.iter().any(|field| inner(cx, field)) + }, + _ => false, + } + } + + result.map_or_else( + |err| { + // Consider `TooGeneric` cases as being unfrozen. + // This causes a false positive where an assoc const whose type is unfrozen + // have a value that is a frozen variant with a generic param (an example is + // `declare_interior_mutable_const::enums::BothOfCellAndGeneric::GENERIC_VARIANT`). + // However, it prevents a number of false negatives that is, I think, important: + // 1. assoc consts in trait defs referring to consts of themselves + // (an example is `declare_interior_mutable_const::traits::ConcreteTypes::ANOTHER_ATOMIC`). + // 2. a path expr referring to assoc consts whose type is doesn't have + // any frozen variants in trait defs (i.e. without substitute for `Self`). + // (e.g. borrowing `borrow_interior_mutable_const::trait::ConcreteTypes::ATOMIC`) + // 3. similar to the false positive above; + // but the value is an unfrozen variant, or the type has no enums. (An example is + // `declare_interior_mutable_const::enums::BothOfCellAndGeneric::UNFROZEN_VARIANT` + // and `declare_interior_mutable_const::enums::BothOfCellAndGeneric::NO_ENUM`). + // One might be able to prevent these FNs correctly, and replace this with `false`; + // e.g. implementing `has_frozen_variant` described above, and not running this function + // when the type doesn't have any frozen variants would be the 'correct' way for the 2nd + // case (that actually removes another suboptimal behavior (I won't say 'false positive') where, + // similar to 2., but with the a frozen variant) (e.g. borrowing + // `borrow_interior_mutable_const::enums::AssocConsts::TO_BE_FROZEN_VARIANT`). + // I chose this way because unfrozen enums as assoc consts are rare (or, hopefully, none). + err == ErrorHandled::TooGeneric + }, + |val| inner(cx, Const::from_value(cx.tcx, val, ty)), + ) +} + +fn is_value_unfrozen_poly<'tcx>(cx: &LateContext<'tcx>, body_id: BodyId, ty: Ty<'tcx>) -> bool { + let result = cx.tcx.const_eval_poly(body_id.hir_id.owner.to_def_id()); + is_value_unfrozen_raw(cx, result, ty) +} + +fn is_value_unfrozen_expr<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId, def_id: DefId, ty: Ty<'tcx>) -> bool { + let substs = cx.typeck_results().node_substs(hir_id); + + let result = cx + .tcx + .const_eval_resolve(cx.param_env, ty::WithOptConstParam::unknown(def_id), substs, None, None); + is_value_unfrozen_raw(cx, result, ty) +} + +#[derive(Copy, Clone)] +enum Source { + Item { item: Span }, + Assoc { item: Span }, + Expr { expr: Span }, +} + +impl Source { + #[must_use] + fn lint(&self) -> (&'static Lint, &'static str, Span) { + match self { + Self::Item { item } | Self::Assoc { item, .. } => ( + DECLARE_INTERIOR_MUTABLE_CONST, + "a `const` item should never be interior mutable", + *item, + ), + Self::Expr { expr } => ( + BORROW_INTERIOR_MUTABLE_CONST, + "a `const` item with interior mutability should not be borrowed", + *expr, + ), + } + } +} + +fn lint(cx: &LateContext<'_>, source: Source) { + let (lint, msg, span) = source.lint(); + span_lint_and_then(cx, lint, span, msg, |diag| { + if span.from_expansion() { + return; // Don't give suggestions into macros. + } + match source { + Source::Item { .. } => { + let const_kw_span = span.from_inner(InnerSpan::new(0, 5)); + diag.span_label(const_kw_span, "make this a static item (maybe with lazy_static)"); + }, + Source::Assoc { .. } => (), + Source::Expr { .. } => { + diag.help("assign this const to a local or static variable, and use the variable here"); + }, + } + }); +} + +declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST]); + +impl<'tcx> LateLintPass<'tcx> for NonCopyConst { + fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) { + if let ItemKind::Const(hir_ty, body_id) = it.kind { + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + + if is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) { + lint(cx, Source::Item { item: it.span }); + } + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Const(hir_ty, body_id_opt) = &trait_item.kind { + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + + // Normalize assoc types because ones originated from generic params + // bounded other traits could have their bound. + let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty); + if is_unfrozen(cx, normalized) + // When there's no default value, lint it only according to its type; + // in other words, lint consts whose value *could* be unfrozen, not definitely is. + // This feels inconsistent with how the lint treats generic types, + // which avoids linting types which potentially become unfrozen. + // One could check whether a unfrozen type have a *frozen variant* + // (like `body_id_opt.map_or_else(|| !has_frozen_variant(...), ...)`), + // and do the same as the case of generic types at impl items. + // Note that it isn't sufficient to check if it has an enum + // since all of that enum's variants can be unfrozen: + // i.e. having an enum doesn't necessary mean a type has a frozen variant. + // And, implementing it isn't a trivial task; it'll probably end up + // re-implementing the trait predicate evaluation specific to `Freeze`. + && body_id_opt.map_or(true, |body_id| is_value_unfrozen_poly(cx, body_id, normalized)) + { + lint(cx, Source::Assoc { item: trait_item.span }); + } + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { + if let ImplItemKind::Const(hir_ty, body_id) = &impl_item.kind { + let item_hir_id = cx.tcx.hir().get_parent_node(impl_item.hir_id()); + let item = cx.tcx.hir().expect_item(item_hir_id); + + match &item.kind { + ItemKind::Impl(Impl { + of_trait: Some(of_trait_ref), + .. + }) => { + if_chain! { + // Lint a trait impl item only when the definition is a generic type, + // assuming a assoc const is not meant to be a interior mutable type. + if let Some(of_trait_def_id) = of_trait_ref.trait_def_id(); + if let Some(of_assoc_item) = specialization_graph::Node::Trait(of_trait_def_id) + .item(cx.tcx, impl_item.ident, AssocKind::Const, of_trait_def_id); + if cx + .tcx + .layout_of(cx.tcx.param_env(of_trait_def_id).and( + // Normalize assoc types because ones originated from generic params + // bounded other traits could have their bound at the trait defs; + // and, in that case, the definition is *not* generic. + cx.tcx.normalize_erasing_regions( + cx.tcx.param_env(of_trait_def_id), + cx.tcx.type_of(of_assoc_item.def_id), + ), + )) + .is_err(); + // If there were a function like `has_frozen_variant` described above, + // we should use here as a frozen variant is a potential to be frozen + // similar to unknown layouts. + // e.g. `layout_of(...).is_err() || has_frozen_variant(...);` + then { + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty); + if is_unfrozen(cx, normalized) + && is_value_unfrozen_poly(cx, *body_id, normalized) + { + lint( + cx, + Source::Assoc { + item: impl_item.span, + }, + ); + } + } + } + }, + ItemKind::Impl(Impl { of_trait: None, .. }) => { + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + // Normalize assoc types originated from generic params. + let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty); + + if is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, *body_id, normalized) { + lint(cx, Source::Assoc { item: impl_item.span }); + } + }, + _ => (), + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Path(qpath) = &expr.kind { + // Only lint if we use the const item inside a function. + if in_constant(cx, expr.hir_id) { + return; + } + + // Make sure it is a const item. + let item_def_id = match cx.qpath_res(qpath, expr.hir_id) { + Res::Def(DefKind::Const | DefKind::AssocConst, did) => did, + _ => return, + }; + + // Climb up to resolve any field access and explicit referencing. + let mut cur_expr = expr; + let mut dereferenced_expr = expr; + let mut needs_check_adjustment = true; + loop { + let parent_id = cx.tcx.hir().get_parent_node(cur_expr.hir_id); + if parent_id == cur_expr.hir_id { + break; + } + if let Some(Node::Expr(parent_expr)) = cx.tcx.hir().find(parent_id) { + match &parent_expr.kind { + ExprKind::AddrOf(..) => { + // `&e` => `e` must be referenced. + needs_check_adjustment = false; + }, + ExprKind::Field(..) => { + needs_check_adjustment = true; + + // Check whether implicit dereferences happened; + // if so, no need to go further up + // because of the same reason as the `ExprKind::Unary` case. + if cx + .typeck_results() + .expr_adjustments(dereferenced_expr) + .iter() + .any(|adj| matches!(adj.kind, Adjust::Deref(_))) + { + break; + } + + dereferenced_expr = parent_expr; + }, + ExprKind::Index(e, _) if ptr::eq(&**e, cur_expr) => { + // `e[i]` => desugared to `*Index::index(&e, i)`, + // meaning `e` must be referenced. + // no need to go further up since a method call is involved now. + needs_check_adjustment = false; + break; + }, + ExprKind::Unary(UnOp::Deref, _) => { + // `*e` => desugared to `*Deref::deref(&e)`, + // meaning `e` must be referenced. + // no need to go further up since a method call is involved now. + needs_check_adjustment = false; + break; + }, + _ => break, + } + cur_expr = parent_expr; + } else { + break; + } + } + + let ty = if needs_check_adjustment { + let adjustments = cx.typeck_results().expr_adjustments(dereferenced_expr); + if let Some(i) = adjustments + .iter() + .position(|adj| matches!(adj.kind, Adjust::Borrow(_) | Adjust::Deref(_))) + { + if i == 0 { + cx.typeck_results().expr_ty(dereferenced_expr) + } else { + adjustments[i - 1].target + } + } else { + // No borrow adjustments means the entire const is moved. + return; + } + } else { + cx.typeck_results().expr_ty(dereferenced_expr) + }; + + if is_unfrozen(cx, ty) && is_value_unfrozen_expr(cx, expr.hir_id, item_def_id, ty) { + lint(cx, Source::Expr { expr: expr.span }); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs new file mode 100644 index 0000000000..d5222a030d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs @@ -0,0 +1,426 @@ +use crate::utils::{span_lint, span_lint_and_then}; +use rustc_ast::ast::{ + Arm, AssocItem, AssocItemKind, Attribute, Block, FnDecl, FnKind, Item, ItemKind, Local, Pat, + PatKind, +}; +use rustc_ast::visit::{walk_block, walk_expr, walk_pat, Visitor}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; +use rustc_span::sym; +use rustc_span::symbol::{Ident, Symbol}; +use std::cmp::Ordering; + +declare_clippy_lint! { + /// **What it does:** Checks for names that are very similar and thus confusing. + /// + /// **Why is this bad?** It's hard to distinguish between names that differ only + /// by a single character. + /// + /// **Known problems:** None? + /// + /// **Example:** + /// ```ignore + /// let checked_exp = something; + /// let checked_expr = something_else; + /// ``` + pub SIMILAR_NAMES, + pedantic, + "similarly named items and bindings" +} + +declare_clippy_lint! { + /// **What it does:** Checks for too many variables whose name consists of a + /// single character. + /// + /// **Why is this bad?** It's hard to memorize what a variable means without a + /// descriptive name. + /// + /// **Known problems:** None? + /// + /// **Example:** + /// ```ignore + /// let (a, b, c, d, e, f, g) = (...); + /// ``` + pub MANY_SINGLE_CHAR_NAMES, + style, + "too many single character bindings" +} + +declare_clippy_lint! { + /// **What it does:** Checks if you have variables whose name consists of just + /// underscores and digits. + /// + /// **Why is this bad?** It's hard to memorize what a variable means without a + /// descriptive name. + /// + /// **Known problems:** None? + /// + /// **Example:** + /// ```rust + /// let _1 = 1; + /// let ___1 = 1; + /// let __1___2 = 11; + /// ``` + pub JUST_UNDERSCORES_AND_DIGITS, + style, + "unclear name" +} + +#[derive(Copy, Clone)] +pub struct NonExpressiveNames { + pub single_char_binding_names_threshold: u64, +} + +impl_lint_pass!(NonExpressiveNames => [SIMILAR_NAMES, MANY_SINGLE_CHAR_NAMES, JUST_UNDERSCORES_AND_DIGITS]); + +struct ExistingName { + interned: Symbol, + span: Span, + len: usize, + exemptions: &'static [&'static str], +} + +struct SimilarNamesLocalVisitor<'a, 'tcx> { + names: Vec, + cx: &'a EarlyContext<'tcx>, + lint: &'a NonExpressiveNames, + + /// A stack of scopes containing the single-character bindings in each scope. + single_char_names: Vec>, +} + +impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> { + fn check_single_char_names(&self) { + let num_single_char_names = self.single_char_names.iter().flatten().count(); + let threshold = self.lint.single_char_binding_names_threshold; + if num_single_char_names as u64 > threshold { + let span = self + .single_char_names + .iter() + .flatten() + .map(|ident| ident.span) + .collect::>(); + span_lint( + self.cx, + MANY_SINGLE_CHAR_NAMES, + span, + &format!( + "{} bindings with single-character names in scope", + num_single_char_names + ), + ); + } + } +} + +// this list contains lists of names that are allowed to be similar +// the assumption is that no name is ever contained in multiple lists. +#[rustfmt::skip] +const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[ + &["parsed", "parser"], + &["lhs", "rhs"], + &["tx", "rx"], + &["set", "get"], + &["args", "arms"], + &["qpath", "path"], + &["lit", "lint"], +]; + +struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a, 'tcx>); + +impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> { + fn visit_pat(&mut self, pat: &'tcx Pat) { + match pat.kind { + PatKind::Ident(_, ident, _) => { + if !pat.span.from_expansion() { + self.check_ident(ident); + } + }, + PatKind::Struct(_, ref fields, _) => { + for field in fields { + if !field.is_shorthand { + self.visit_pat(&field.pat); + } + } + }, + // just go through the first pattern, as either all patterns + // bind the same bindings or rustc would have errored much earlier + PatKind::Or(ref pats) => self.visit_pat(&pats[0]), + _ => walk_pat(self, pat), + } + } +} + +#[must_use] +fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> { + for &list in ALLOWED_TO_BE_SIMILAR { + if allowed_to_be_similar(interned_name, list) { + return Some(list); + } + } + None +} + +#[must_use] +fn allowed_to_be_similar(interned_name: &str, list: &[&str]) -> bool { + list.iter() + .any(|&name| interned_name.starts_with(name) || interned_name.ends_with(name)) +} + +impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> { + fn check_short_ident(&mut self, ident: Ident) { + // Ignore shadowing + if self + .0 + .single_char_names + .iter() + .flatten() + .any(|id| id.name == ident.name) + { + return; + } + + if let Some(scope) = &mut self.0.single_char_names.last_mut() { + scope.push(ident); + } + } + + #[allow(clippy::too_many_lines)] + fn check_ident(&mut self, ident: Ident) { + let interned_name = ident.name.as_str(); + if interned_name.chars().any(char::is_uppercase) { + return; + } + if interned_name.chars().all(|c| c.is_digit(10) || c == '_') { + span_lint( + self.0.cx, + JUST_UNDERSCORES_AND_DIGITS, + ident.span, + "consider choosing a more descriptive name", + ); + return; + } + if interned_name.starts_with('_') { + // these bindings are typically unused or represent an ignored portion of a destructuring pattern + return; + } + let count = interned_name.chars().count(); + if count < 3 { + if count == 1 { + self.check_short_ident(ident); + } + return; + } + for existing_name in &self.0.names { + if allowed_to_be_similar(&interned_name, existing_name.exemptions) { + continue; + } + let mut split_at = None; + match existing_name.len.cmp(&count) { + Ordering::Greater => { + if existing_name.len - count != 1 + || levenstein_not_1(&interned_name, &existing_name.interned.as_str()) + { + continue; + } + }, + Ordering::Less => { + if count - existing_name.len != 1 + || levenstein_not_1(&existing_name.interned.as_str(), &interned_name) + { + continue; + } + }, + Ordering::Equal => { + let mut interned_chars = interned_name.chars(); + let interned_str = existing_name.interned.as_str(); + let mut existing_chars = interned_str.chars(); + let first_i = interned_chars.next().expect("we know we have at least one char"); + let first_e = existing_chars.next().expect("we know we have at least one char"); + let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric(); + + if eq_or_numeric((first_i, first_e)) { + let last_i = interned_chars.next_back().expect("we know we have at least two chars"); + let last_e = existing_chars.next_back().expect("we know we have at least two chars"); + if eq_or_numeric((last_i, last_e)) { + if interned_chars + .zip(existing_chars) + .filter(|&ie| !eq_or_numeric(ie)) + .count() + != 1 + { + continue; + } + } else { + let second_last_i = interned_chars + .next_back() + .expect("we know we have at least three chars"); + let second_last_e = existing_chars + .next_back() + .expect("we know we have at least three chars"); + if !eq_or_numeric((second_last_i, second_last_e)) + || second_last_i == '_' + || !interned_chars.zip(existing_chars).all(eq_or_numeric) + { + // allowed similarity foo_x, foo_y + // or too many chars differ (foo_x, boo_y) or (foox, booy) + continue; + } + split_at = interned_name.char_indices().rev().next().map(|(i, _)| i); + } + } else { + let second_i = interned_chars.next().expect("we know we have at least two chars"); + let second_e = existing_chars.next().expect("we know we have at least two chars"); + if !eq_or_numeric((second_i, second_e)) + || second_i == '_' + || !interned_chars.zip(existing_chars).all(eq_or_numeric) + { + // allowed similarity x_foo, y_foo + // or too many chars differ (x_foo, y_boo) or (xfoo, yboo) + continue; + } + split_at = interned_name.chars().next().map(char::len_utf8); + } + }, + } + span_lint_and_then( + self.0.cx, + SIMILAR_NAMES, + ident.span, + "binding's name is too similar to existing binding", + |diag| { + diag.span_note(existing_name.span, "existing binding defined here"); + if let Some(split) = split_at { + diag.span_help( + ident.span, + &format!( + "separate the discriminating character by an \ + underscore like: `{}_{}`", + &interned_name[..split], + &interned_name[split..] + ), + ); + } + }, + ); + return; + } + self.0.names.push(ExistingName { + exemptions: get_exemptions(&interned_name).unwrap_or(&[]), + interned: ident.name, + span: ident.span, + len: count, + }); + } +} + +impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> { + /// ensure scoping rules work + fn apply Fn(&'c mut Self)>(&mut self, f: F) { + let n = self.names.len(); + let single_char_count = self.single_char_names.len(); + f(self); + self.names.truncate(n); + self.single_char_names.truncate(single_char_count); + } +} + +impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> { + fn visit_local(&mut self, local: &'tcx Local) { + if let Some(ref init) = local.init { + self.apply(|this| walk_expr(this, &**init)); + } + // add the pattern after the expression because the bindings aren't available + // yet in the init + // expression + SimilarNamesNameVisitor(self).visit_pat(&*local.pat); + } + fn visit_block(&mut self, blk: &'tcx Block) { + self.single_char_names.push(vec![]); + + self.apply(|this| walk_block(this, blk)); + + self.check_single_char_names(); + self.single_char_names.pop(); + } + fn visit_arm(&mut self, arm: &'tcx Arm) { + self.single_char_names.push(vec![]); + + self.apply(|this| { + SimilarNamesNameVisitor(this).visit_pat(&arm.pat); + this.apply(|this| walk_expr(this, &arm.body)); + }); + + self.check_single_char_names(); + self.single_char_names.pop(); + } + fn visit_item(&mut self, _: &Item) { + // do not recurse into inner items + } +} + +impl EarlyLintPass for NonExpressiveNames { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if in_external_macro(cx.sess, item.span) { + return; + } + + if let ItemKind::Fn(box FnKind(_, ref sig, _, Some(ref blk))) = item.kind { + do_check(self, cx, &item.attrs, &sig.decl, blk); + } + } + + fn check_impl_item(&mut self, cx: &EarlyContext<'_>, item: &AssocItem) { + if in_external_macro(cx.sess, item.span) { + return; + } + + if let AssocItemKind::Fn(box FnKind(_, ref sig, _, Some(ref blk))) = item.kind { + do_check(self, cx, &item.attrs, &sig.decl, blk); + } + } +} + +fn do_check(lint: &mut NonExpressiveNames, cx: &EarlyContext<'_>, attrs: &[Attribute], decl: &FnDecl, blk: &Block) { + if !attrs.iter().any(|attr| attr.has_name(sym::test)) { + let mut visitor = SimilarNamesLocalVisitor { + names: Vec::new(), + cx, + lint, + single_char_names: vec![vec![]], + }; + + // initialize with function arguments + for arg in &decl.inputs { + SimilarNamesNameVisitor(&mut visitor).visit_pat(&arg.pat); + } + // walk all other bindings + walk_block(&mut visitor, blk); + + visitor.check_single_char_names(); + } +} + +/// Precondition: `a_name.chars().count() < b_name.chars().count()`. +#[must_use] +fn levenstein_not_1(a_name: &str, b_name: &str) -> bool { + debug_assert!(a_name.chars().count() < b_name.chars().count()); + let mut a_chars = a_name.chars(); + let mut b_chars = b_name.chars(); + while let (Some(a), Some(b)) = (a_chars.next(), b_chars.next()) { + if a == b { + continue; + } + if let Some(b2) = b_chars.next() { + // check if there's just one character inserted + return a != b2 || a_chars.ne(b_chars); + } + // tuple + // ntuple + return true; + } + // for item in items + true +} diff --git a/src/tools/clippy/clippy_lints/src/open_options.rs b/src/tools/clippy/clippy_lints/src/open_options.rs new file mode 100644 index 0000000000..07ca196990 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/open_options.rs @@ -0,0 +1,199 @@ +use crate::utils::{match_type, paths, span_lint}; +use rustc_ast::ast::LitKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::{Span, Spanned}; + +declare_clippy_lint! { + /// **What it does:** Checks for duplicate open options as well as combinations + /// that make no sense. + /// + /// **Why is this bad?** In the best case, the code will be harder to read than + /// necessary. I don't know the worst case. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// use std::fs::OpenOptions; + /// + /// OpenOptions::new().read(true).truncate(true); + /// ``` + pub NONSENSICAL_OPEN_OPTIONS, + correctness, + "nonsensical combination of options for opening a file" +} + +declare_lint_pass!(OpenOptions => [NONSENSICAL_OPEN_OPTIONS]); + +impl<'tcx> LateLintPass<'tcx> for OpenOptions { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::MethodCall(ref path, _, ref arguments, _) = e.kind { + let obj_ty = cx.typeck_results().expr_ty(&arguments[0]).peel_refs(); + if path.ident.name == sym!(open) && match_type(cx, obj_ty, &paths::OPEN_OPTIONS) { + let mut options = Vec::new(); + get_open_options(cx, &arguments[0], &mut options); + check_open_options(cx, &options, e.span); + } + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum Argument { + True, + False, + Unknown, +} + +#[derive(Debug)] +enum OpenOption { + Write, + Read, + Truncate, + Create, + Append, +} + +fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec<(OpenOption, Argument)>) { + if let ExprKind::MethodCall(ref path, _, ref arguments, _) = argument.kind { + let obj_ty = cx.typeck_results().expr_ty(&arguments[0]).peel_refs(); + + // Only proceed if this is a call on some object of type std::fs::OpenOptions + if match_type(cx, obj_ty, &paths::OPEN_OPTIONS) && arguments.len() >= 2 { + let argument_option = match arguments[1].kind { + ExprKind::Lit(ref span) => { + if let Spanned { + node: LitKind::Bool(lit), + .. + } = *span + { + if lit { Argument::True } else { Argument::False } + } else { + // The function is called with a literal which is not a boolean literal. + // This is theoretically possible, but not very likely. + return; + } + }, + _ => Argument::Unknown, + }; + + match &*path.ident.as_str() { + "create" => { + options.push((OpenOption::Create, argument_option)); + }, + "append" => { + options.push((OpenOption::Append, argument_option)); + }, + "truncate" => { + options.push((OpenOption::Truncate, argument_option)); + }, + "read" => { + options.push((OpenOption::Read, argument_option)); + }, + "write" => { + options.push((OpenOption::Write, argument_option)); + }, + _ => (), + } + + get_open_options(cx, &arguments[0], options); + } + } +} + +fn check_open_options(cx: &LateContext<'_>, options: &[(OpenOption, Argument)], span: Span) { + let (mut create, mut append, mut truncate, mut read, mut write) = (false, false, false, false, false); + let (mut create_arg, mut append_arg, mut truncate_arg, mut read_arg, mut write_arg) = + (false, false, false, false, false); + // This code is almost duplicated (oh, the irony), but I haven't found a way to + // unify it. + + for option in options { + match *option { + (OpenOption::Create, arg) => { + if create { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "the method `create` is called more than once", + ); + } else { + create = true + } + create_arg = create_arg || (arg == Argument::True); + }, + (OpenOption::Append, arg) => { + if append { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "the method `append` is called more than once", + ); + } else { + append = true + } + append_arg = append_arg || (arg == Argument::True); + }, + (OpenOption::Truncate, arg) => { + if truncate { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "the method `truncate` is called more than once", + ); + } else { + truncate = true + } + truncate_arg = truncate_arg || (arg == Argument::True); + }, + (OpenOption::Read, arg) => { + if read { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "the method `read` is called more than once", + ); + } else { + read = true + } + read_arg = read_arg || (arg == Argument::True); + }, + (OpenOption::Write, arg) => { + if write { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "the method `write` is called more than once", + ); + } else { + write = true + } + write_arg = write_arg || (arg == Argument::True); + }, + } + } + + if read && truncate && read_arg && truncate_arg && !(write && write_arg) { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "file opened with `truncate` and `read`", + ); + } + if append && truncate && append_arg && truncate_arg { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "file opened with `append` and `truncate`", + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/option_env_unwrap.rs b/src/tools/clippy/clippy_lints/src/option_env_unwrap.rs new file mode 100644 index 0000000000..fd653044a1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/option_env_unwrap.rs @@ -0,0 +1,55 @@ +use crate::utils::{is_direct_expn_of, span_lint_and_help}; +use if_chain::if_chain; +use rustc_ast::ast::{Expr, ExprKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `option_env!(...).unwrap()` and + /// suggests usage of the `env!` macro. + /// + /// **Why is this bad?** Unwrapping the result of `option_env!` will panic + /// at run-time if the environment variable doesn't exist, whereas `env!` + /// catches it at compile-time. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,no_run + /// let _ = option_env!("HOME").unwrap(); + /// ``` + /// + /// Is better expressed as: + /// + /// ```rust,no_run + /// let _ = env!("HOME"); + /// ``` + pub OPTION_ENV_UNWRAP, + correctness, + "using `option_env!(...).unwrap()` to get environment variable" +} + +declare_lint_pass!(OptionEnvUnwrap => [OPTION_ENV_UNWRAP]); + +impl EarlyLintPass for OptionEnvUnwrap { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if_chain! { + if let ExprKind::MethodCall(path_segment, args, _) = &expr.kind; + let method_name = path_segment.ident.as_str(); + if method_name == "expect" || method_name == "unwrap"; + if let ExprKind::Call(caller, _) = &args[0].kind; + if is_direct_expn_of(caller.span, "option_env").is_some(); + then { + span_lint_and_help( + cx, + OPTION_ENV_UNWRAP, + expr.span, + "this will panic at run-time if the environment variable doesn't exist at compile-time", + None, + "consider using the `env!` macro instead" + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/option_if_let_else.rs b/src/tools/clippy/clippy_lints/src/option_if_let_else.rs new file mode 100644 index 0000000000..9ef0d267b0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/option_if_let_else.rs @@ -0,0 +1,222 @@ +use crate::utils; +use crate::utils::eager_or_lazy; +use crate::utils::sugg::Sugg; +use crate::utils::{is_type_diagnostic_item, paths, span_lint_and_sugg}; +use if_chain::if_chain; + +use rustc_errors::Applicability; +use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, PatKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** + /// Lints usage of `if let Some(v) = ... { y } else { x }` which is more + /// idiomatically done with `Option::map_or` (if the else bit is a pure + /// expression) or `Option::map_or_else` (if the else bit is an impure + /// expression). + /// + /// **Why is this bad?** + /// Using the dedicated functions of the Option type is clearer and + /// more concise than an `if let` expression. + /// + /// **Known problems:** + /// This lint uses a deliberately conservative metric for checking + /// if the inside of either body contains breaks or continues which will + /// cause it to not suggest a fix if either block contains a loop with + /// continues or breaks contained within the loop. + /// + /// **Example:** + /// + /// ```rust + /// # let optional: Option = Some(0); + /// # fn do_complicated_function() -> u32 { 5 }; + /// let _ = if let Some(foo) = optional { + /// foo + /// } else { + /// 5 + /// }; + /// let _ = if let Some(foo) = optional { + /// foo + /// } else { + /// let y = do_complicated_function(); + /// y*y + /// }; + /// ``` + /// + /// should be + /// + /// ```rust + /// # let optional: Option = Some(0); + /// # fn do_complicated_function() -> u32 { 5 }; + /// let _ = optional.map_or(5, |foo| foo); + /// let _ = optional.map_or_else(||{ + /// let y = do_complicated_function(); + /// y*y + /// }, |foo| foo); + /// ``` + pub OPTION_IF_LET_ELSE, + pedantic, + "reimplementation of Option::map_or" +} + +declare_lint_pass!(OptionIfLetElse => [OPTION_IF_LET_ELSE]); + +/// Returns true iff the given expression is the result of calling `Result::ok` +fn is_result_ok(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { + if let ExprKind::MethodCall(ref path, _, &[ref receiver], _) = &expr.kind { + path.ident.name.as_str() == "ok" + && is_type_diagnostic_item(cx, &cx.typeck_results().expr_ty(&receiver), sym::result_type) + } else { + false + } +} + +/// A struct containing information about occurrences of the +/// `if let Some(..) = .. else` construct that this lint detects. +struct OptionIfLetElseOccurence { + option: String, + method_sugg: String, + some_expr: String, + none_expr: String, + wrap_braces: bool, +} + +/// Extracts the body of a given arm. If the arm contains only an expression, +/// then it returns the expression. Otherwise, it returns the entire block +fn extract_body_from_arm<'a>(arm: &'a Arm<'a>) -> Option<&'a Expr<'a>> { + if let ExprKind::Block( + Block { + stmts: statements, + expr: Some(expr), + .. + }, + _, + ) = &arm.body.kind + { + if let [] = statements { + Some(&expr) + } else { + Some(&arm.body) + } + } else { + None + } +} + +/// If this is the else body of an if/else expression, then we need to wrap +/// it in curly braces. Otherwise, we don't. +fn should_wrap_in_braces(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + utils::get_enclosing_block(cx, expr.hir_id).map_or(false, |parent| { + let mut should_wrap = false; + + if let Some(Expr { + kind: + ExprKind::Match( + _, + arms, + MatchSource::IfLetDesugar { + contains_else_clause: true, + }, + ), + .. + }) = parent.expr + { + should_wrap = expr.hir_id == arms[1].body.hir_id; + } else if let Some(Expr { + kind: ExprKind::If(_, _, Some(else_clause)), + .. + }) = parent.expr + { + should_wrap = expr.hir_id == else_clause.hir_id; + } + + should_wrap + }) +} + +fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: bool, as_mut: bool) -> String { + format!( + "{}{}", + Sugg::hir(cx, cond_expr, "..").maybe_par(), + if as_mut { + ".as_mut()" + } else if as_ref { + ".as_ref()" + } else { + "" + } + ) +} + +/// If this expression is the option if let/else construct we're detecting, then +/// this function returns an `OptionIfLetElseOccurence` struct with details if +/// this construct is found, or None if this construct is not found. +fn detect_option_if_let_else<'tcx>( + cx: &'_ LateContext<'tcx>, + expr: &'_ Expr<'tcx>, +) -> Option { + if_chain! { + if !utils::in_macro(expr.span); // Don't lint macros, because it behaves weirdly + if let ExprKind::Match(cond_expr, arms, MatchSource::IfLetDesugar{contains_else_clause: true}) = &expr.kind; + if arms.len() == 2; + if !is_result_ok(cx, cond_expr); // Don't lint on Result::ok because a different lint does it already + if let PatKind::TupleStruct(struct_qpath, &[inner_pat], _) = &arms[0].pat.kind; + if utils::match_qpath(struct_qpath, &paths::OPTION_SOME); + if let PatKind::Binding(bind_annotation, _, id, _) = &inner_pat.kind; + if !utils::usage::contains_return_break_continue_macro(arms[0].body); + if !utils::usage::contains_return_break_continue_macro(arms[1].body); + then { + let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" }; + let some_body = extract_body_from_arm(&arms[0])?; + let none_body = extract_body_from_arm(&arms[1])?; + let method_sugg = if eager_or_lazy::is_eagerness_candidate(cx, none_body) { "map_or" } else { "map_or_else" }; + let capture_name = id.name.to_ident_string(); + let wrap_braces = should_wrap_in_braces(cx, expr); + let (as_ref, as_mut) = match &cond_expr.kind { + ExprKind::AddrOf(_, Mutability::Not, _) => (true, false), + ExprKind::AddrOf(_, Mutability::Mut, _) => (false, true), + _ => (bind_annotation == &BindingAnnotation::Ref, bind_annotation == &BindingAnnotation::RefMut), + }; + let cond_expr = match &cond_expr.kind { + // Pointer dereferencing happens automatically, so we can omit it in the suggestion + ExprKind::Unary(UnOp::Deref, expr) | ExprKind::AddrOf(_, _, expr) => expr, + _ => cond_expr, + }; + Some(OptionIfLetElseOccurence { + option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut), + method_sugg: method_sugg.to_string(), + some_expr: format!("|{}{}| {}", capture_mut, capture_name, Sugg::hir(cx, some_body, "..")), + none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir(cx, none_body, "..")), + wrap_braces, + }) + } else { + None + } + } +} + +impl<'tcx> LateLintPass<'tcx> for OptionIfLetElse { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if let Some(detection) = detect_option_if_let_else(cx, expr) { + span_lint_and_sugg( + cx, + OPTION_IF_LET_ELSE, + expr.span, + format!("use Option::{} instead of an if let/else", detection.method_sugg).as_str(), + "try", + format!( + "{}{}.{}({}, {}){}", + if detection.wrap_braces { "{ " } else { "" }, + detection.option, + detection.method_sugg, + detection.none_expr, + detection.some_expr, + if detection.wrap_braces { " }" } else { "" }, + ), + Applicability::MaybeIncorrect, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/overflow_check_conditional.rs b/src/tools/clippy/clippy_lints/src/overflow_check_conditional.rs new file mode 100644 index 0000000000..3c041bac23 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/overflow_check_conditional.rs @@ -0,0 +1,82 @@ +use crate::utils::{span_lint, SpanlessEq}; +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Detects classic underflow/overflow checks. + /// + /// **Why is this bad?** Most classic C underflow/overflow checks will fail in + /// Rust. Users can use functions like `overflowing_*` and `wrapping_*` instead. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let a = 1; + /// # let b = 2; + /// a + b < a; + /// ``` + pub OVERFLOW_CHECK_CONDITIONAL, + complexity, + "overflow checks inspired by C which are likely to panic" +} + +declare_lint_pass!(OverflowCheckConditional => [OVERFLOW_CHECK_CONDITIONAL]); + +impl<'tcx> LateLintPass<'tcx> for OverflowCheckConditional { + // a + b < a, a > a + b, a < a - b, a - b > a + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let eq = |l, r| SpanlessEq::new(cx).eq_path_segment(l, r); + if_chain! { + if let ExprKind::Binary(ref op, ref first, ref second) = expr.kind; + if let ExprKind::Binary(ref op2, ref ident1, ref ident2) = first.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path1)) = ident1.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path2)) = ident2.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path3)) = second.kind; + if eq(&path1.segments[0], &path3.segments[0]) || eq(&path2.segments[0], &path3.segments[0]); + if cx.typeck_results().expr_ty(ident1).is_integral(); + if cx.typeck_results().expr_ty(ident2).is_integral(); + then { + if let BinOpKind::Lt = op.node { + if let BinOpKind::Add = op2.node { + span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, + "you are trying to use classic C overflow conditions that will fail in Rust"); + } + } + if let BinOpKind::Gt = op.node { + if let BinOpKind::Sub = op2.node { + span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, + "you are trying to use classic C underflow conditions that will fail in Rust"); + } + } + } + } + + if_chain! { + if let ExprKind::Binary(ref op, ref first, ref second) = expr.kind; + if let ExprKind::Binary(ref op2, ref ident1, ref ident2) = second.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path1)) = ident1.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path2)) = ident2.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path3)) = first.kind; + if eq(&path1.segments[0], &path3.segments[0]) || eq(&path2.segments[0], &path3.segments[0]); + if cx.typeck_results().expr_ty(ident1).is_integral(); + if cx.typeck_results().expr_ty(ident2).is_integral(); + then { + if let BinOpKind::Gt = op.node { + if let BinOpKind::Add = op2.node { + span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, + "you are trying to use classic C overflow conditions that will fail in Rust"); + } + } + if let BinOpKind::Lt = op.node { + if let BinOpKind::Sub = op2.node { + span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, + "you are trying to use classic C underflow conditions that will fail in Rust"); + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/panic_in_result_fn.rs b/src/tools/clippy/clippy_lints/src/panic_in_result_fn.rs new file mode 100644 index 0000000000..207423a186 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/panic_in_result_fn.rs @@ -0,0 +1,82 @@ +use crate::utils::{find_macro_calls, is_type_diagnostic_item, return_ty, span_lint_and_then}; +use rustc_hir as hir; +use rustc_hir::intravisit::FnKind; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{sym, Span}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `panic!`, `unimplemented!`, `todo!`, `unreachable!` or assertions in a function of type result. + /// + /// **Why is this bad?** For some codebases, it is desirable for functions of type result to return an error instead of crashing. Hence panicking macros should be avoided. + /// + /// **Known problems:** Functions called from a function returning a `Result` may invoke a panicking macro. This is not checked. + /// + /// **Example:** + /// + /// ```rust + /// fn result_with_panic() -> Result + /// { + /// panic!("error"); + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn result_without_panic() -> Result { + /// Err(String::from("error")) + /// } + /// ``` + pub PANIC_IN_RESULT_FN, + restriction, + "functions of type `Result<..>` that contain `panic!()`, `todo!()`, `unreachable()`, `unimplemented()` or assertion" +} + +declare_lint_pass!(PanicInResultFn => [PANIC_IN_RESULT_FN]); + +impl<'tcx> LateLintPass<'tcx> for PanicInResultFn { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + fn_kind: FnKind<'tcx>, + _: &'tcx hir::FnDecl<'tcx>, + body: &'tcx hir::Body<'tcx>, + span: Span, + hir_id: hir::HirId, + ) { + if !matches!(fn_kind, FnKind::Closure) && is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::result_type) { + lint_impl_body(cx, span, body); + } + } +} + +fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir::Body<'tcx>) { + let panics = find_macro_calls( + &[ + "unimplemented", + "unreachable", + "panic", + "todo", + "assert", + "assert_eq", + "assert_ne", + "debug_assert", + "debug_assert_eq", + "debug_assert_ne", + ], + body, + ); + if !panics.is_empty() { + span_lint_and_then( + cx, + PANIC_IN_RESULT_FN, + impl_span, + "used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`", + move |diag| { + diag.help( + "`unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing", + ); + diag.span_note(panics, "return Err() instead of panicking"); + }, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs b/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs new file mode 100644 index 0000000000..359620cc07 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs @@ -0,0 +1,108 @@ +use crate::utils::{is_expn_of, match_panic_call, span_lint}; +use if_chain::if_chain; +use rustc_hir::Expr; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `panic!`. + /// + /// **Why is this bad?** `panic!` will stop the execution of the executable + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// panic!("even with a good reason"); + /// ``` + pub PANIC, + restriction, + "usage of the `panic!` macro" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `unimplemented!`. + /// + /// **Why is this bad?** This macro should not be present in production code + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// unimplemented!(); + /// ``` + pub UNIMPLEMENTED, + restriction, + "`unimplemented!` should not be present in production code" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `todo!`. + /// + /// **Why is this bad?** This macro should not be present in production code + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// todo!(); + /// ``` + pub TODO, + restriction, + "`todo!` should not be present in production code" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `unreachable!`. + /// + /// **Why is this bad?** This macro can cause code to panic + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// unreachable!(); + /// ``` + pub UNREACHABLE, + restriction, + "usage of the `unreachable!` macro" +} + +declare_lint_pass!(PanicUnimplemented => [UNIMPLEMENTED, UNREACHABLE, TODO, PANIC]); + +impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if match_panic_call(cx, expr).is_some() { + let span = get_outer_span(expr); + if is_expn_of(expr.span, "unimplemented").is_some() { + span_lint( + cx, + UNIMPLEMENTED, + span, + "`unimplemented` should not be present in production code", + ); + } else if is_expn_of(expr.span, "todo").is_some() { + span_lint(cx, TODO, span, "`todo` should not be present in production code"); + } else if is_expn_of(expr.span, "unreachable").is_some() { + span_lint(cx, UNREACHABLE, span, "usage of the `unreachable!` macro"); + } else if is_expn_of(expr.span, "panic").is_some() { + span_lint(cx, PANIC, span, "`panic` should not be present in production code"); + } + } + } +} + +fn get_outer_span(expr: &Expr<'_>) -> Span { + if_chain! { + if expr.span.from_expansion(); + let first = expr.span.ctxt().outer_expn_data(); + if first.call_site.from_expansion(); + let second = first.call_site.ctxt().outer_expn_data(); + then { + second.call_site + } else { + expr.span + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs b/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs new file mode 100644 index 0000000000..aca1ed5ca6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs @@ -0,0 +1,57 @@ +use crate::utils::{is_automatically_derived, span_lint_hir}; +use if_chain::if_chain; +use rustc_hir::{Impl, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for manual re-implementations of `PartialEq::ne`. + /// + /// **Why is this bad?** `PartialEq::ne` is required to always return the + /// negated result of `PartialEq::eq`, which is exactly what the default + /// implementation does. Therefore, there should never be any need to + /// re-implement it. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// struct Foo; + /// + /// impl PartialEq for Foo { + /// fn eq(&self, other: &Foo) -> bool { true } + /// fn ne(&self, other: &Foo) -> bool { !(self == other) } + /// } + /// ``` + pub PARTIALEQ_NE_IMPL, + complexity, + "re-implementing `PartialEq::ne`" +} + +declare_lint_pass!(PartialEqNeImpl => [PARTIALEQ_NE_IMPL]); + +impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if_chain! { + if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref), items: impl_items, .. }) = item.kind; + let attrs = cx.tcx.hir().attrs(item.hir_id()); + if !is_automatically_derived(attrs); + if let Some(eq_trait) = cx.tcx.lang_items().eq_trait(); + if trait_ref.path.res.def_id() == eq_trait; + then { + for impl_item in impl_items { + if impl_item.ident.name == sym::ne { + span_lint_hir( + cx, + PARTIALEQ_NE_IMPL, + impl_item.id.hir_id(), + impl_item.span, + "re-implementing `PartialEq::ne` is unnecessary", + ); + } + } + } + }; + } +} diff --git a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs new file mode 100644 index 0000000000..ff700aa514 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs @@ -0,0 +1,258 @@ +use std::cmp; + +use crate::utils::{is_copy, is_self_ty, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::attr; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{sym, Span}; +use rustc_target::abi::LayoutOf; +use rustc_target::spec::abi::Abi; +use rustc_target::spec::Target; + +declare_clippy_lint! { + /// **What it does:** Checks for functions taking arguments by reference, where + /// the argument type is `Copy` and small enough to be more efficient to always + /// pass by value. + /// + /// **Why is this bad?** In many calling conventions instances of structs will + /// be passed through registers if they fit into two or less general purpose + /// registers. + /// + /// **Known problems:** This lint is target register size dependent, it is + /// limited to 32-bit to try and reduce portability problems between 32 and + /// 64-bit, but if you are compiling for 8 or 16-bit targets then the limit + /// will be different. + /// + /// The configuration option `trivial_copy_size_limit` can be set to override + /// this limit for a project. + /// + /// This lint attempts to allow passing arguments by reference if a reference + /// to that argument is returned. This is implemented by comparing the lifetime + /// of the argument and return value for equality. However, this can cause + /// false positives in cases involving multiple lifetimes that are bounded by + /// each other. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// fn foo(v: &u32) {} + /// ``` + /// + /// ```rust + /// // Better + /// fn foo(v: u32) {} + /// ``` + pub TRIVIALLY_COPY_PASS_BY_REF, + pedantic, + "functions taking small copyable arguments by reference" +} + +declare_clippy_lint! { + /// **What it does:** Checks for functions taking arguments by value, where + /// the argument type is `Copy` and large enough to be worth considering + /// passing by reference. Does not trigger if the function is being exported, + /// because that might induce API breakage, if the parameter is declared as mutable, + /// or if the argument is a `self`. + /// + /// **Why is this bad?** Arguments passed by value might result in an unnecessary + /// shallow copy, taking up more space in the stack and requiring a call to + /// `memcpy`, which can be expensive. + /// + /// **Example:** + /// + /// ```rust + /// #[derive(Clone, Copy)] + /// struct TooLarge([u8; 2048]); + /// + /// // Bad + /// fn foo(v: TooLarge) {} + /// ``` + /// ```rust + /// #[derive(Clone, Copy)] + /// struct TooLarge([u8; 2048]); + /// + /// // Good + /// fn foo(v: &TooLarge) {} + /// ``` + pub LARGE_TYPES_PASSED_BY_VALUE, + pedantic, + "functions taking large arguments by value" +} + +#[derive(Copy, Clone)] +pub struct PassByRefOrValue { + ref_min_size: u64, + value_max_size: u64, +} + +impl<'tcx> PassByRefOrValue { + pub fn new(ref_min_size: Option, value_max_size: u64, target: &Target) -> Self { + let ref_min_size = ref_min_size.unwrap_or_else(|| { + let bit_width = u64::from(target.pointer_width); + // Cap the calculated bit width at 32-bits to reduce + // portability problems between 32 and 64-bit targets + let bit_width = cmp::min(bit_width, 32); + #[allow(clippy::integer_division)] + let byte_width = bit_width / 8; + // Use a limit of 2 times the register byte width + byte_width * 2 + }); + + Self { + ref_min_size, + value_max_size, + } + } + + fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, hir_id: HirId, decl: &FnDecl<'_>, span: Option) { + let fn_def_id = cx.tcx.hir().local_def_id(hir_id); + + let fn_sig = cx.tcx.fn_sig(fn_def_id); + let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig); + + let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id)); + + for (index, (input, &ty)) in decl.inputs.iter().zip(fn_sig.inputs()).enumerate() { + // All spans generated from a proc-macro invocation are the same... + match span { + Some(s) if s == input.span => return, + _ => (), + } + + match ty.kind() { + ty::Ref(input_lt, ty, Mutability::Not) => { + // Use lifetimes to determine if we're returning a reference to the + // argument. In that case we can't switch to pass-by-value as the + // argument will not live long enough. + let output_lts = match *fn_sig.output().kind() { + ty::Ref(output_lt, _, _) => vec![output_lt], + ty::Adt(_, substs) => substs.regions().collect(), + _ => vec![], + }; + + if_chain! { + if !output_lts.contains(&input_lt); + if is_copy(cx, ty); + if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes()); + if size <= self.ref_min_size; + if let hir::TyKind::Rptr(_, MutTy { ty: ref decl_ty, .. }) = input.kind; + then { + let value_type = if is_self_ty(decl_ty) { + "self".into() + } else { + snippet(cx, decl_ty.span, "_").into() + }; + span_lint_and_sugg( + cx, + TRIVIALLY_COPY_PASS_BY_REF, + input.span, + &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size), + "consider passing by value instead", + value_type, + Applicability::Unspecified, + ); + } + } + }, + + ty::Adt(_, _) | ty::Array(_, _) | ty::Tuple(_) => { + // if function has a body and parameter is annotated with mut, ignore + if let Some(param) = fn_body.and_then(|body| body.params.get(index)) { + match param.pat.kind { + PatKind::Binding(BindingAnnotation::Unannotated, _, _, _) => {}, + _ => continue, + } + } + + if_chain! { + if !cx.access_levels.is_exported(hir_id); + if is_copy(cx, ty); + if !is_self_ty(input); + if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes()); + if size > self.value_max_size; + then { + span_lint_and_sugg( + cx, + LARGE_TYPES_PASSED_BY_VALUE, + input.span, + &format!("this argument ({} byte) is passed by value, but might be more efficient if passed by reference (limit: {} byte)", size, self.value_max_size), + "consider passing by reference instead", + format!("&{}", snippet(cx, input.span, "_")), + Applicability::MaybeIncorrect, + ); + } + } + }, + + _ => {}, + } + } + } +} + +impl_lint_pass!(PassByRefOrValue => [TRIVIALLY_COPY_PASS_BY_REF, LARGE_TYPES_PASSED_BY_VALUE]); + +impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue { + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { + if item.span.from_expansion() { + return; + } + + if let hir::TraitItemKind::Fn(method_sig, _) = &item.kind { + self.check_poly_fn(cx, item.hir_id(), &*method_sig.decl, None); + } + } + + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + _body: &'tcx Body<'_>, + span: Span, + hir_id: HirId, + ) { + if span.from_expansion() { + return; + } + + match kind { + FnKind::ItemFn(.., header, _) => { + if header.abi != Abi::Rust { + return; + } + let attrs = cx.tcx.hir().attrs(hir_id); + for a in attrs { + if let Some(meta_items) = a.meta_item_list() { + if a.has_name(sym::proc_macro_derive) + || (a.has_name(sym::inline) && attr::list_contains_name(&meta_items, sym::always)) + { + return; + } + } + } + }, + FnKind::Method(..) => (), + FnKind::Closure => return, + } + + // Exclude non-inherent impls + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { + if matches!( + item.kind, + ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) + ) { + return; + } + } + + self.check_poly_fn(cx, hir_id, decl, Some(span)); + } +} diff --git a/src/tools/clippy/clippy_lints/src/path_buf_push_overwrite.rs b/src/tools/clippy/clippy_lints/src/path_buf_push_overwrite.rs new file mode 100644 index 0000000000..4a7b0ad07a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/path_buf_push_overwrite.rs @@ -0,0 +1,72 @@ +use crate::utils::{is_type_diagnostic_item, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; +use std::path::{Component, Path}; + +declare_clippy_lint! { + /// **What it does:*** Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push) + /// calls on `PathBuf` that can cause overwrites. + /// + /// **Why is this bad?** Calling `push` with a root path at the start can overwrite the + /// previous defined path. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("/bar"); + /// assert_eq!(x, PathBuf::from("/bar")); + /// ``` + /// Could be written: + /// + /// ```rust + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("bar"); + /// assert_eq!(x, PathBuf::from("/foo/bar")); + /// ``` + pub PATH_BUF_PUSH_OVERWRITE, + nursery, + "calling `push` with file system root on `PathBuf` can overwrite it" +} + +declare_lint_pass!(PathBufPushOverwrite => [PATH_BUF_PUSH_OVERWRITE]); + +impl<'tcx> LateLintPass<'tcx> for PathBufPushOverwrite { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind; + if path.ident.name == sym!(push); + if args.len() == 2; + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), sym::PathBuf); + if let Some(get_index_arg) = args.get(1); + if let ExprKind::Lit(ref lit) = get_index_arg.kind; + if let LitKind::Str(ref path_lit, _) = lit.node; + if let pushed_path = Path::new(&*path_lit.as_str()); + if let Some(pushed_path_lit) = pushed_path.to_str(); + if pushed_path.has_root(); + if let Some(root) = pushed_path.components().next(); + if root == Component::RootDir; + then { + span_lint_and_sugg( + cx, + PATH_BUF_PUSH_OVERWRITE, + lit.span, + "calling `push` with '/' or '\\' (file system root) will overwrite the previous path definition", + "try", + format!("\"{}\"", pushed_path_lit.trim_start_matches(|c| c == '/' || c == '\\')), + Applicability::MachineApplicable, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs b/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs new file mode 100644 index 0000000000..e76c8624b6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs @@ -0,0 +1,311 @@ +use crate::utils::{last_path_segment, span_lint_and_help}; +use rustc_hir::{ + intravisit, Body, Expr, ExprKind, PatField, FnDecl, HirId, LocalSource, MatchSource, Mutability, Pat, PatKind, + QPath, Stmt, StmtKind, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::{AdtDef, FieldDef, Ty, TyKind, VariantDef}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for patterns that aren't exact representations of the types + /// they are applied to. + /// + /// To satisfy this lint, you will have to adjust either the expression that is matched + /// against or the pattern itself, as well as the bindings that are introduced by the + /// adjusted patterns. For matching you will have to either dereference the expression + /// with the `*` operator, or amend the patterns to explicitly match against `&` + /// or `&mut ` depending on the reference mutability. For the bindings you need + /// to use the inverse. You can leave them as plain bindings if you wish for the value + /// to be copied, but you must use `ref mut ` or `ref ` to construct + /// a reference into the matched structure. + /// + /// If you are looking for a way to learn about ownership semantics in more detail, it + /// is recommended to look at IDE options available to you to highlight types, lifetimes + /// and reference semantics in your code. The available tooling would expose these things + /// in a general way even outside of the various pattern matching mechanics. Of course + /// this lint can still be used to highlight areas of interest and ensure a good understanding + /// of ownership semantics. + /// + /// **Why is this bad?** It isn't bad in general. But in some contexts it can be desirable + /// because it increases ownership hints in the code, and will guard against some changes + /// in ownership. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// This example shows the basic adjustments necessary to satisfy the lint. Note how + /// the matched expression is explicitly dereferenced with `*` and the `inner` variable + /// is bound to a shared borrow via `ref inner`. + /// + /// ```rust,ignore + /// // Bad + /// let value = &Some(Box::new(23)); + /// match value { + /// Some(inner) => println!("{}", inner), + /// None => println!("none"), + /// } + /// + /// // Good + /// let value = &Some(Box::new(23)); + /// match *value { + /// Some(ref inner) => println!("{}", inner), + /// None => println!("none"), + /// } + /// ``` + /// + /// The following example demonstrates one of the advantages of the more verbose style. + /// Note how the second version uses `ref mut a` to explicitly declare `a` a shared mutable + /// borrow, while `b` is simply taken by value. This ensures that the loop body cannot + /// accidentally modify the wrong part of the structure. + /// + /// ```rust,ignore + /// // Bad + /// let mut values = vec![(2, 3), (3, 4)]; + /// for (a, b) in &mut values { + /// *a += *b; + /// } + /// + /// // Good + /// let mut values = vec![(2, 3), (3, 4)]; + /// for &mut (ref mut a, b) in &mut values { + /// *a += b; + /// } + /// ``` + pub PATTERN_TYPE_MISMATCH, + restriction, + "type of pattern does not match the expression type" +} + +declare_lint_pass!(PatternTypeMismatch => [PATTERN_TYPE_MISMATCH]); + +impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if let StmtKind::Local(ref local) = stmt.kind { + if let Some(init) = &local.init { + if let Some(init_ty) = cx.typeck_results().node_type_opt(init.hir_id) { + let pat = &local.pat; + if in_external_macro(cx.sess(), pat.span) { + return; + } + let deref_possible = match local.source { + LocalSource::Normal => DerefPossible::Possible, + _ => DerefPossible::Impossible, + }; + apply_lint(cx, pat, init_ty, deref_possible); + } + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Match(ref expr, arms, source) = expr.kind { + match source { + MatchSource::Normal | MatchSource::IfLetDesugar { .. } | MatchSource::WhileLetDesugar => { + if let Some(expr_ty) = cx.typeck_results().node_type_opt(expr.hir_id) { + 'pattern_checks: for arm in arms { + let pat = &arm.pat; + if in_external_macro(cx.sess(), pat.span) { + continue 'pattern_checks; + } + if apply_lint(cx, pat, expr_ty, DerefPossible::Possible) { + break 'pattern_checks; + } + } + } + }, + _ => (), + } + } + } + + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: intravisit::FnKind<'tcx>, + _: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + hir_id: HirId, + ) { + if let Some(fn_sig) = cx.typeck_results().liberated_fn_sigs().get(hir_id) { + for (param, ty) in body.params.iter().zip(fn_sig.inputs().iter()) { + apply_lint(cx, ¶m.pat, ty, DerefPossible::Impossible); + } + } + } +} + +#[derive(Debug, Clone, Copy)] +enum DerefPossible { + Possible, + Impossible, +} + +fn apply_lint<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, expr_ty: Ty<'tcx>, deref_possible: DerefPossible) -> bool { + let maybe_mismatch = find_first_mismatch(cx, pat, expr_ty, Level::Top); + if let Some((span, mutability, level)) = maybe_mismatch { + span_lint_and_help( + cx, + PATTERN_TYPE_MISMATCH, + span, + "type of pattern does not match the expression type", + None, + &format!( + "{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings", + match (deref_possible, level) { + (DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ", + _ => "", + }, + match mutability { + Mutability::Mut => "&mut _", + Mutability::Not => "&_", + }, + ), + ); + true + } else { + false + } +} + +#[derive(Debug, Copy, Clone)] +enum Level { + Top, + Lower, +} + +#[allow(rustc::usage_of_ty_tykind)] +fn find_first_mismatch<'tcx>( + cx: &LateContext<'tcx>, + pat: &Pat<'_>, + ty: Ty<'tcx>, + level: Level, +) -> Option<(Span, Mutability, Level)> { + if let PatKind::Ref(ref sub_pat, _) = pat.kind { + if let TyKind::Ref(_, sub_ty, _) = ty.kind() { + return find_first_mismatch(cx, sub_pat, sub_ty, Level::Lower); + } + } + + if let TyKind::Ref(_, _, mutability) = *ty.kind() { + if is_non_ref_pattern(&pat.kind) { + return Some((pat.span, mutability, level)); + } + } + + if let PatKind::Struct(ref qpath, ref field_pats, _) = pat.kind { + if let TyKind::Adt(ref adt_def, ref substs_ref) = ty.kind() { + if let Some(variant) = get_variant(adt_def, qpath) { + let field_defs = &variant.fields; + return find_first_mismatch_in_struct(cx, field_pats, field_defs, substs_ref); + } + } + } + + if let PatKind::TupleStruct(ref qpath, ref pats, _) = pat.kind { + if let TyKind::Adt(ref adt_def, ref substs_ref) = ty.kind() { + if let Some(variant) = get_variant(adt_def, qpath) { + let field_defs = &variant.fields; + let ty_iter = field_defs.iter().map(|field_def| field_def.ty(cx.tcx, substs_ref)); + return find_first_mismatch_in_tuple(cx, pats, ty_iter); + } + } + } + + if let PatKind::Tuple(ref pats, _) = pat.kind { + if let TyKind::Tuple(..) = ty.kind() { + return find_first_mismatch_in_tuple(cx, pats, ty.tuple_fields()); + } + } + + if let PatKind::Or(sub_pats) = pat.kind { + for pat in sub_pats { + let maybe_mismatch = find_first_mismatch(cx, pat, ty, level); + if let Some(mismatch) = maybe_mismatch { + return Some(mismatch); + } + } + } + + None +} + +fn get_variant<'a>(adt_def: &'a AdtDef, qpath: &QPath<'_>) -> Option<&'a VariantDef> { + if adt_def.is_struct() { + if let Some(variant) = adt_def.variants.iter().next() { + return Some(variant); + } + } + + if adt_def.is_enum() { + let pat_ident = last_path_segment(qpath).ident; + for variant in &adt_def.variants { + if variant.ident == pat_ident { + return Some(variant); + } + } + } + + None +} + +fn find_first_mismatch_in_tuple<'tcx, I>( + cx: &LateContext<'tcx>, + pats: &[&Pat<'_>], + ty_iter_src: I, +) -> Option<(Span, Mutability, Level)> +where + I: IntoIterator>, +{ + let mut field_tys = ty_iter_src.into_iter(); + 'fields: for pat in pats { + let field_ty = if let Some(ty) = field_tys.next() { + ty + } else { + break 'fields; + }; + + let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower); + if let Some(mismatch) = maybe_mismatch { + return Some(mismatch); + } + } + + None +} + +fn find_first_mismatch_in_struct<'tcx>( + cx: &LateContext<'tcx>, + field_pats: &[PatField<'_>], + field_defs: &[FieldDef], + substs_ref: SubstsRef<'tcx>, +) -> Option<(Span, Mutability, Level)> { + for field_pat in field_pats { + 'definitions: for field_def in field_defs { + if field_pat.ident == field_def.ident { + let field_ty = field_def.ty(cx.tcx, substs_ref); + let pat = &field_pat.pat; + let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower); + if let Some(mismatch) = maybe_mismatch { + return Some(mismatch); + } + break 'definitions; + } + } + } + + None +} + +fn is_non_ref_pattern(pat_kind: &PatKind<'_>) -> bool { + match pat_kind { + PatKind::Struct(..) | PatKind::Tuple(..) | PatKind::TupleStruct(..) | PatKind::Path(..) => true, + PatKind::Or(sub_pats) => sub_pats.iter().any(|pat| is_non_ref_pattern(&pat.kind)), + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/precedence.rs b/src/tools/clippy/clippy_lints/src/precedence.rs new file mode 100644 index 0000000000..c9d18c3cb7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/precedence.rs @@ -0,0 +1,159 @@ +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; + +const ALLOWED_ODD_FUNCTIONS: [&str; 14] = [ + "asin", + "asinh", + "atan", + "atanh", + "cbrt", + "fract", + "round", + "signum", + "sin", + "sinh", + "tan", + "tanh", + "to_degrees", + "to_radians", +]; + +declare_clippy_lint! { + /// **What it does:** Checks for operations where precedence may be unclear + /// and suggests to add parentheses. Currently it catches the following: + /// * mixed usage of arithmetic and bit shifting/combining operators without + /// parentheses + /// * a "negative" numeric literal (which is really a unary `-` followed by a + /// numeric literal) + /// followed by a method call + /// + /// **Why is this bad?** Not everyone knows the precedence of those operators by + /// heart, so expressions like these may trip others trying to reason about the + /// code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// * `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7 + /// * `-1i32.abs()` equals -1, while `(-1i32).abs()` equals 1 + pub PRECEDENCE, + complexity, + "operations where precedence may be unclear" +} + +declare_lint_pass!(Precedence => [PRECEDENCE]); + +impl EarlyLintPass for Precedence { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if expr.span.from_expansion() { + return; + } + + if let ExprKind::Binary(Spanned { node: op, .. }, ref left, ref right) = expr.kind { + let span_sugg = |expr: &Expr, sugg, appl| { + span_lint_and_sugg( + cx, + PRECEDENCE, + expr.span, + "operator precedence can trip the unwary", + "consider parenthesizing your expression", + sugg, + appl, + ); + }; + + if !is_bit_op(op) { + return; + } + let mut applicability = Applicability::MachineApplicable; + match (is_arith_expr(left), is_arith_expr(right)) { + (true, true) => { + let sugg = format!( + "({}) {} ({})", + snippet_with_applicability(cx, left.span, "..", &mut applicability), + op.to_string(), + snippet_with_applicability(cx, right.span, "..", &mut applicability) + ); + span_sugg(expr, sugg, applicability); + }, + (true, false) => { + let sugg = format!( + "({}) {} {}", + snippet_with_applicability(cx, left.span, "..", &mut applicability), + op.to_string(), + snippet_with_applicability(cx, right.span, "..", &mut applicability) + ); + span_sugg(expr, sugg, applicability); + }, + (false, true) => { + let sugg = format!( + "{} {} ({})", + snippet_with_applicability(cx, left.span, "..", &mut applicability), + op.to_string(), + snippet_with_applicability(cx, right.span, "..", &mut applicability) + ); + span_sugg(expr, sugg, applicability); + }, + (false, false) => (), + } + } + + if let ExprKind::Unary(UnOp::Neg, operand) = &expr.kind { + let mut arg = operand; + + let mut all_odd = true; + while let ExprKind::MethodCall(path_segment, args, _) = &arg.kind { + let path_segment_str = path_segment.ident.name.as_str(); + all_odd &= ALLOWED_ODD_FUNCTIONS + .iter() + .any(|odd_function| **odd_function == *path_segment_str); + arg = args.first().expect("A method always has a receiver."); + } + + if_chain! { + if !all_odd; + if let ExprKind::Lit(lit) = &arg.kind; + if let LitKind::Int(..) | LitKind::Float(..) = &lit.kind; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + PRECEDENCE, + expr.span, + "unary minus has lower precedence than method call", + "consider adding parentheses to clarify your intent", + format!( + "-({})", + snippet_with_applicability(cx, operand.span, "..", &mut applicability) + ), + applicability, + ); + } + } + } + } +} + +fn is_arith_expr(expr: &Expr) -> bool { + match expr.kind { + ExprKind::Binary(Spanned { node: op, .. }, _, _) => is_arith_op(op), + _ => false, + } +} + +#[must_use] +fn is_bit_op(op: BinOpKind) -> bool { + use rustc_ast::ast::BinOpKind::{BitAnd, BitOr, BitXor, Shl, Shr}; + matches!(op, BitXor | BitAnd | BitOr | Shl | Shr) +} + +#[must_use] +fn is_arith_op(op: BinOpKind) -> bool { + use rustc_ast::ast::BinOpKind::{Add, Div, Mul, Rem, Sub}; + matches!(op, Add | Sub | Mul | Div | Rem) +} diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs new file mode 100644 index 0000000000..6ea2d8b06d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ptr.rs @@ -0,0 +1,358 @@ +//! Checks for usage of `&Vec[_]` and `&String`. + +use crate::utils::ptr::get_spans; +use crate::utils::{ + is_allowed, is_type_diagnostic_item, match_qpath, match_type, paths, snippet_opt, span_lint, span_lint_and_sugg, + span_lint_and_then, walk_ptrs_hir_ty, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{ + BinOpKind, BodyId, Expr, ExprKind, FnDecl, FnRetTy, GenericArg, HirId, Impl, ImplItem, ImplItemKind, Item, + ItemKind, Lifetime, MutTy, Mutability, Node, PathSegment, QPath, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::{sym, MultiSpan}; +use std::borrow::Cow; + +declare_clippy_lint! { + /// **What it does:** This lint checks for function arguments of type `&String` + /// or `&Vec` unless the references are mutable. It will also suggest you + /// replace `.clone()` calls with the appropriate `.to_owned()`/`to_string()` + /// calls. + /// + /// **Why is this bad?** Requiring the argument to be of the specific size + /// makes the function less useful for no benefit; slices in the form of `&[T]` + /// or `&str` usually suffice and can be obtained from other types, too. + /// + /// **Known problems:** The lint does not follow data. So if you have an + /// argument `x` and write `let y = x; y.clone()` the lint will not suggest + /// changing that `.clone()` to `.to_owned()`. + /// + /// Other functions called from this function taking a `&String` or `&Vec` + /// argument may also fail to compile if you change the argument. Applying + /// this lint on them will fix the problem, but they may be in other crates. + /// + /// One notable example of a function that may cause issues, and which cannot + /// easily be changed due to being in the standard library is `Vec::contains`. + /// when called on a `Vec>`. If a `&Vec` is passed to that method then + /// it will compile, but if a `&[T]` is passed then it will not compile. + /// + /// ```ignore + /// fn cannot_take_a_slice(v: &Vec) -> bool { + /// let vec_of_vecs: Vec> = some_other_fn(); + /// + /// vec_of_vecs.contains(v) + /// } + /// ``` + /// + /// Also there may be `fn(&Vec)`-typed references pointing to your function. + /// If you have them, you will get a compiler error after applying this lint's + /// suggestions. You then have the choice to undo your changes or change the + /// type of the reference. + /// + /// Note that if the function is part of your public interface, there may be + /// other crates referencing it, of which you may not be aware. Carefully + /// deprecate the function before applying the lint suggestions in this case. + /// + /// **Example:** + /// ```ignore + /// // Bad + /// fn foo(&Vec) { .. } + /// + /// // Good + /// fn foo(&[u32]) { .. } + /// ``` + pub PTR_ARG, + style, + "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively" +} + +declare_clippy_lint! { + /// **What it does:** This lint checks for equality comparisons with `ptr::null` + /// + /// **Why is this bad?** It's easier and more readable to use the inherent + /// `.is_null()` + /// method instead + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// // Bad + /// if x == ptr::null { + /// .. + /// } + /// + /// // Good + /// if x.is_null() { + /// .. + /// } + /// ``` + pub CMP_NULL, + style, + "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead." +} + +declare_clippy_lint! { + /// **What it does:** This lint checks for functions that take immutable + /// references and return mutable ones. + /// + /// **Why is this bad?** This is trivially unsound, as one can create two + /// mutable references from the same (immutable!) source. + /// This [error](https://github.com/rust-lang/rust/issues/39465) + /// actually lead to an interim Rust release 1.15.1. + /// + /// **Known problems:** To be on the conservative side, if there's at least one + /// mutable reference with the output lifetime, this lint will not trigger. + /// In practice, this case is unlikely anyway. + /// + /// **Example:** + /// ```ignore + /// fn foo(&Foo) -> &mut Bar { .. } + /// ``` + pub MUT_FROM_REF, + correctness, + "fns that create mutable refs from immutable ref args" +} + +declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF]); + +impl<'tcx> LateLintPass<'tcx> for Ptr { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Fn(ref sig, _, body_id) = item.kind { + check_fn(cx, &sig.decl, item.hir_id(), Some(body_id)); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { + if let ImplItemKind::Fn(ref sig, body_id) = item.kind { + let parent_item = cx.tcx.hir().get_parent_item(item.hir_id()); + if let Some(Node::Item(it)) = cx.tcx.hir().find(parent_item) { + if let ItemKind::Impl(Impl { of_trait: Some(_), .. }) = it.kind { + return; // ignore trait impls + } + } + check_fn(cx, &sig.decl, item.hir_id(), Some(body_id)); + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Fn(ref sig, ref trait_method) = item.kind { + let body_id = if let TraitFn::Provided(b) = *trait_method { + Some(b) + } else { + None + }; + check_fn(cx, &sig.decl, item.hir_id(), body_id); + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(ref op, ref l, ref r) = expr.kind { + if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(l) || is_null_path(r)) { + span_lint( + cx, + CMP_NULL, + expr.span, + "comparing with null is better expressed by the `.is_null()` method", + ); + } + } + } +} + +#[allow(clippy::too_many_lines)] +fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_id: Option) { + let fn_def_id = cx.tcx.hir().local_def_id(fn_id); + let sig = cx.tcx.fn_sig(fn_def_id); + let fn_ty = sig.skip_binder(); + let body = opt_body_id.map(|id| cx.tcx.hir().body(id)); + + for (idx, (arg, ty)) in decl.inputs.iter().zip(fn_ty.inputs()).enumerate() { + // Honor the allow attribute on parameters. See issue 5644. + if let Some(body) = &body { + if is_allowed(cx, PTR_ARG, body.params[idx].hir_id) { + continue; + } + } + + if let ty::Ref(_, ty, Mutability::Not) = ty.kind() { + if is_type_diagnostic_item(cx, ty, sym::vec_type) { + if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_owned()")]) { + span_lint_and_then( + cx, + PTR_ARG, + arg.span, + "writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used \ + with non-Vec-based slices", + |diag| { + if let Some(ref snippet) = get_only_generic_arg_snippet(cx, arg) { + diag.span_suggestion( + arg.span, + "change this to", + format!("&[{}]", snippet), + Applicability::Unspecified, + ); + } + for (clonespan, suggestion) in spans { + diag.span_suggestion( + clonespan, + &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| { + Cow::Owned(format!("change `{}` to", x)) + }), + suggestion.into(), + Applicability::Unspecified, + ); + } + }, + ); + } + } else if is_type_diagnostic_item(cx, ty, sym::string_type) { + if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_string()"), ("as_str", "")]) { + span_lint_and_then( + cx, + PTR_ARG, + arg.span, + "writing `&String` instead of `&str` involves a new object where a slice will do", + |diag| { + diag.span_suggestion(arg.span, "change this to", "&str".into(), Applicability::Unspecified); + for (clonespan, suggestion) in spans { + diag.span_suggestion_short( + clonespan, + &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| { + Cow::Owned(format!("change `{}` to", x)) + }), + suggestion.into(), + Applicability::Unspecified, + ); + } + }, + ); + } + } else if is_type_diagnostic_item(cx, ty, sym::PathBuf) { + if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_path_buf()"), ("as_path", "")]) { + span_lint_and_then( + cx, + PTR_ARG, + arg.span, + "writing `&PathBuf` instead of `&Path` involves a new object where a slice will do", + |diag| { + diag.span_suggestion( + arg.span, + "change this to", + "&Path".into(), + Applicability::Unspecified, + ); + for (clonespan, suggestion) in spans { + diag.span_suggestion_short( + clonespan, + &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| { + Cow::Owned(format!("change `{}` to", x)) + }), + suggestion.into(), + Applicability::Unspecified, + ); + } + }, + ); + } + } else if match_type(cx, ty, &paths::COW) { + if_chain! { + if let TyKind::Rptr(_, MutTy { ref ty, ..} ) = arg.kind; + if let TyKind::Path(QPath::Resolved(None, ref pp)) = ty.kind; + if let [ref bx] = *pp.segments; + if let Some(ref params) = bx.args; + if !params.parenthesized; + if let Some(inner) = params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + then { + let replacement = snippet_opt(cx, inner.span); + if let Some(r) = replacement { + span_lint_and_sugg( + cx, + PTR_ARG, + arg.span, + "using a reference to `Cow` is not recommended", + "change this to", + "&".to_owned() + &r, + Applicability::Unspecified, + ); + } + } + } + } + } + } + + if let FnRetTy::Return(ref ty) = decl.output { + if let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) { + let mut immutables = vec![]; + for (_, ref mutbl, ref argspan) in decl + .inputs + .iter() + .filter_map(|ty| get_rptr_lm(ty)) + .filter(|&(lt, _, _)| lt.name == out.name) + { + if *mutbl == Mutability::Mut { + return; + } + immutables.push(*argspan); + } + if immutables.is_empty() { + return; + } + span_lint_and_then( + cx, + MUT_FROM_REF, + ty.span, + "mutable borrow from immutable input(s)", + |diag| { + let ms = MultiSpan::from_spans(immutables); + diag.span_note(ms, "immutable borrow here"); + }, + ); + } + } +} + +fn get_only_generic_arg_snippet(cx: &LateContext<'_>, arg: &Ty<'_>) -> Option { + if_chain! { + if let TyKind::Path(QPath::Resolved(_, ref path)) = walk_ptrs_hir_ty(arg).kind; + if let Some(&PathSegment{args: Some(ref parameters), ..}) = path.segments.last(); + let types: Vec<_> = parameters.args.iter().filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }).collect(); + if types.len() == 1; + then { + snippet_opt(cx, types[0].span) + } else { + None + } + } +} + +fn get_rptr_lm<'tcx>(ty: &'tcx Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> { + if let TyKind::Rptr(ref lt, ref m) = ty.kind { + Some((lt, m.mutbl, ty.span)) + } else { + None + } +} + +fn is_null_path(expr: &Expr<'_>) -> bool { + if let ExprKind::Call(ref pathexp, ref args) = expr.kind { + if args.is_empty() { + if let ExprKind::Path(ref path) = pathexp.kind { + return match_qpath(path, &paths::PTR_NULL) || match_qpath(path, &paths::PTR_NULL_MUT); + } + } + } + false +} diff --git a/src/tools/clippy/clippy_lints/src/ptr_eq.rs b/src/tools/clippy/clippy_lints/src/ptr_eq.rs new file mode 100644 index 0000000000..3be792ce5e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ptr_eq.rs @@ -0,0 +1,96 @@ +use crate::utils; +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}; + +declare_clippy_lint! { + /// **What it does:** Use `std::ptr::eq` when applicable + /// + /// **Why is this bad?** `ptr::eq` can be used to compare `&T` references + /// (which coerce to `*const T` implicitly) by their address rather than + /// comparing the values they point to. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let a = &[1, 2, 3]; + /// let b = &[1, 2, 3]; + /// + /// assert!(a as *const _ as usize == b as *const _ as usize); + /// ``` + /// Use instead: + /// ```rust + /// let a = &[1, 2, 3]; + /// let b = &[1, 2, 3]; + /// + /// assert!(std::ptr::eq(a, b)); + /// ``` + pub PTR_EQ, + style, + "use `std::ptr::eq` when comparing raw pointers" +} + +declare_lint_pass!(PtrEq => [PTR_EQ]); + +static LINT_MSG: &str = "use `std::ptr::eq` when comparing raw pointers"; + +impl LateLintPass<'_> for PtrEq { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if utils::in_macro(expr.span) { + return; + } + + if let ExprKind::Binary(ref op, ref left, ref right) = expr.kind { + if BinOpKind::Eq == op.node { + let (left, right) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { + (Some(lhs), Some(rhs)) => (lhs, rhs), + _ => (&**left, &**right), + }; + + if_chain! { + if let Some(left_var) = expr_as_cast_to_raw_pointer(cx, left); + if let Some(right_var) = expr_as_cast_to_raw_pointer(cx, right); + if let Some(left_snip) = utils::snippet_opt(cx, left_var.span); + if let Some(right_snip) = utils::snippet_opt(cx, right_var.span); + then { + utils::span_lint_and_sugg( + cx, + PTR_EQ, + expr.span, + LINT_MSG, + "try", + format!("std::ptr::eq({}, {})", left_snip, right_snip), + Applicability::MachineApplicable, + ); + } + } + } + } + } +} + +// If the given expression is a cast to an usize, return the lhs of the cast +// E.g., `foo as *const _ as usize` returns `foo as *const _`. +fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize { + if let ExprKind::Cast(ref expr, _) = cast_expr.kind { + return Some(expr); + } + } + None +} + +// If the given expression is a cast to a `*const` pointer, return the lhs of the cast +// E.g., `foo as *const _` returns `foo`. +fn expr_as_cast_to_raw_pointer<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if cx.typeck_results().expr_ty(cast_expr).is_unsafe_ptr() { + if let ExprKind::Cast(ref expr, _) = cast_expr.kind { + return Some(expr); + } + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/ptr_offset_with_cast.rs b/src/tools/clippy/clippy_lints/src/ptr_offset_with_cast.rs new file mode 100644 index 0000000000..e0996804a5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ptr_offset_with_cast.rs @@ -0,0 +1,151 @@ +use crate::utils::{snippet_opt, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; +use std::fmt; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of the `offset` pointer method with a `usize` casted to an + /// `isize`. + /// + /// **Why is this bad?** If we’re always increasing the pointer address, we can avoid the numeric + /// cast by using the `add` method instead. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let vec = vec![b'a', b'b', b'c']; + /// let ptr = vec.as_ptr(); + /// let offset = 1_usize; + /// + /// unsafe { + /// ptr.offset(offset as isize); + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// let vec = vec![b'a', b'b', b'c']; + /// let ptr = vec.as_ptr(); + /// let offset = 1_usize; + /// + /// unsafe { + /// ptr.add(offset); + /// } + /// ``` + pub PTR_OFFSET_WITH_CAST, + complexity, + "unneeded pointer offset cast" +} + +declare_lint_pass!(PtrOffsetWithCast => [PTR_OFFSET_WITH_CAST]); + +impl<'tcx> LateLintPass<'tcx> for PtrOffsetWithCast { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // Check if the expressions is a ptr.offset or ptr.wrapping_offset method call + let (receiver_expr, arg_expr, method) = match expr_as_ptr_offset_call(cx, expr) { + Some(call_arg) => call_arg, + None => return, + }; + + // Check if the argument to the method call is a cast from usize + let cast_lhs_expr = match expr_as_cast_from_usize(cx, arg_expr) { + Some(cast_lhs_expr) => cast_lhs_expr, + None => return, + }; + + let msg = format!("use of `{}` with a `usize` casted to an `isize`", method); + if let Some(sugg) = build_suggestion(cx, method, receiver_expr, cast_lhs_expr) { + span_lint_and_sugg( + cx, + PTR_OFFSET_WITH_CAST, + expr.span, + &msg, + "try", + sugg, + Applicability::MachineApplicable, + ); + } else { + span_lint(cx, PTR_OFFSET_WITH_CAST, expr.span, &msg); + } + } +} + +// If the given expression is a cast from a usize, return the lhs of the cast +fn expr_as_cast_from_usize<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Cast(ref cast_lhs_expr, _) = expr.kind { + if is_expr_ty_usize(cx, &cast_lhs_expr) { + return Some(cast_lhs_expr); + } + } + None +} + +// If the given expression is a ptr::offset or ptr::wrapping_offset method call, return the +// receiver, the arg of the method call, and the method. +fn expr_as_ptr_offset_call<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, +) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> { + if let ExprKind::MethodCall(ref path_segment, _, ref args, _) = expr.kind { + if is_expr_ty_raw_ptr(cx, &args[0]) { + if path_segment.ident.name == sym::offset { + return Some((&args[0], &args[1], Method::Offset)); + } + if path_segment.ident.name == sym!(wrapping_offset) { + return Some((&args[0], &args[1], Method::WrappingOffset)); + } + } + } + None +} + +// Is the type of the expression a usize? +fn is_expr_ty_usize<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool { + cx.typeck_results().expr_ty(expr) == cx.tcx.types.usize +} + +// Is the type of the expression a raw pointer? +fn is_expr_ty_raw_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool { + cx.typeck_results().expr_ty(expr).is_unsafe_ptr() +} + +fn build_suggestion<'tcx>( + cx: &LateContext<'tcx>, + method: Method, + receiver_expr: &Expr<'_>, + cast_lhs_expr: &Expr<'_>, +) -> Option { + let receiver = snippet_opt(cx, receiver_expr.span)?; + let cast_lhs = snippet_opt(cx, cast_lhs_expr.span)?; + Some(format!("{}.{}({})", receiver, method.suggestion(), cast_lhs)) +} + +#[derive(Copy, Clone)] +enum Method { + Offset, + WrappingOffset, +} + +impl Method { + #[must_use] + fn suggestion(self) -> &'static str { + match self { + Self::Offset => "add", + Self::WrappingOffset => "wrapping_add", + } + } +} + +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Offset => write!(f, "offset"), + Self::WrappingOffset => write!(f, "wrapping_offset"), + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/question_mark.rs b/src/tools/clippy/clippy_lints/src/question_mark.rs new file mode 100644 index 0000000000..6c480d48c7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/question_mark.rs @@ -0,0 +1,204 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{def, BindingAnnotation, Block, Expr, ExprKind, MatchSource, PatKind, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +use crate::utils::sugg::Sugg; +use crate::utils::{ + eq_expr_value, is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet_with_applicability, + span_lint_and_sugg, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for expressions that could be replaced by the question mark operator. + /// + /// **Why is this bad?** Question mark usage is more idiomatic. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```ignore + /// if option.is_none() { + /// return None; + /// } + /// ``` + /// + /// Could be written: + /// + /// ```ignore + /// option?; + /// ``` + pub QUESTION_MARK, + style, + "checks for expressions that could be replaced by the question mark operator" +} + +declare_lint_pass!(QuestionMark => [QUESTION_MARK]); + +impl QuestionMark { + /// Checks if the given expression on the given context matches the following structure: + /// + /// ```ignore + /// if option.is_none() { + /// return None; + /// } + /// ``` + /// + /// If it matches, it will suggest to use the question mark operator instead + fn check_is_none_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::If(if_expr, body, else_) = &expr.kind; + if let ExprKind::MethodCall(segment, _, args, _) = &if_expr.kind; + if segment.ident.name == sym!(is_none); + if Self::expression_returns_none(cx, body); + if let Some(subject) = args.get(0); + if Self::is_option(cx, subject); + + then { + let mut applicability = Applicability::MachineApplicable; + let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability); + let mut replacement: Option = None; + if let Some(else_) = else_ { + if_chain! { + if let ExprKind::Block(block, None) = &else_.kind; + if block.stmts.is_empty(); + if let Some(block_expr) = &block.expr; + if eq_expr_value(cx, subject, block_expr); + then { + replacement = Some(format!("Some({}?)", receiver_str)); + } + } + } else if Self::moves_by_default(cx, subject) + && !matches!(subject.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) + { + replacement = Some(format!("{}.as_ref()?;", receiver_str)); + } else { + replacement = Some(format!("{}?;", receiver_str)); + } + + if let Some(replacement_str) = replacement { + span_lint_and_sugg( + cx, + QUESTION_MARK, + expr.span, + "this block may be rewritten with the `?` operator", + "replace it with", + replacement_str, + applicability, + ) + } + } + } + } + + fn check_if_let_some_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::Match(subject, arms, source) = &expr.kind; + if *source == MatchSource::IfLetDesugar { contains_else_clause: true }; + if Self::is_option(cx, subject); + + if let PatKind::TupleStruct(path1, fields, None) = &arms[0].pat.kind; + if match_qpath(path1, &["Some"]); + if let PatKind::Binding(annot, _, bind, _) = &fields[0].kind; + let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut); + + if let ExprKind::Block(block, None) = &arms[0].body.kind; + if block.stmts.is_empty(); + if let Some(trailing_expr) = &block.expr; + if let ExprKind::Path(path) = &trailing_expr.kind; + if match_qpath(path, &[&bind.as_str()]); + + if let PatKind::Wild = arms[1].pat.kind; + if Self::expression_returns_none(cx, arms[1].body); + then { + let mut applicability = Applicability::MachineApplicable; + let receiver_str = snippet_with_applicability(cx, subject.span, "..", &mut applicability); + let replacement = format!( + "{}{}?", + receiver_str, + if by_ref { ".as_ref()" } else { "" }, + ); + + span_lint_and_sugg( + cx, + QUESTION_MARK, + expr.span, + "this if-let-else may be rewritten with the `?` operator", + "replace it with", + replacement, + applicability, + ) + } + } + } + + fn moves_by_default(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { + let expr_ty = cx.typeck_results().expr_ty(expression); + + !expr_ty.is_copy_modulo_regions(cx.tcx.at(expression.span), cx.param_env) + } + + fn is_option(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { + let expr_ty = cx.typeck_results().expr_ty(expression); + + is_type_diagnostic_item(cx, expr_ty, sym::option_type) + } + + fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { + match expression.kind { + ExprKind::Block(ref block, _) => { + if let Some(return_expression) = Self::return_expression(block) { + return Self::expression_returns_none(cx, &return_expression); + } + + false + }, + ExprKind::Ret(Some(ref expr)) => Self::expression_returns_none(cx, expr), + ExprKind::Path(ref qp) => { + if let Res::Def(DefKind::Ctor(def::CtorOf::Variant, def::CtorKind::Const), def_id) = + cx.qpath_res(qp, expression.hir_id) + { + return match_def_path(cx, def_id, &paths::OPTION_NONE); + } + + false + }, + _ => false, + } + } + + fn return_expression<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + // Check if last expression is a return statement. Then, return the expression + if_chain! { + if block.stmts.len() == 1; + if let Some(expr) = block.stmts.iter().last(); + if let StmtKind::Semi(ref expr) = expr.kind; + if let ExprKind::Ret(Some(ret_expr)) = expr.kind; + + then { + return Some(ret_expr); + } + } + + // Check for `return` without a semicolon. + if_chain! { + if block.stmts.is_empty(); + if let Some(ExprKind::Ret(Some(ret_expr))) = block.expr.as_ref().map(|e| &e.kind); + then { + return Some(ret_expr); + } + } + + None + } +} + +impl<'tcx> LateLintPass<'tcx> for QuestionMark { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + Self::check_is_none_and_early_return_none(cx, expr); + Self::check_if_let_some_and_early_return_none(cx, expr); + } +} diff --git a/src/tools/clippy/clippy_lints/src/ranges.rs b/src/tools/clippy/clippy_lints/src/ranges.rs new file mode 100644 index 0000000000..59503817c0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ranges.rs @@ -0,0 +1,542 @@ +use crate::consts::{constant, Constant}; +use if_chain::if_chain; +use rustc_ast::ast::RangeLimits; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, QPath}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::{Span, Spanned}; +use rustc_span::sym; +use rustc_span::symbol::Ident; +use std::cmp::Ordering; + +use crate::utils::sugg::Sugg; +use crate::utils::{ + get_parent_expr, in_constant, is_integer_const, meets_msrv, single_segment_path, snippet, snippet_opt, + snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then, +}; +use crate::utils::{higher, SpanlessEq}; + +declare_clippy_lint! { + /// **What it does:** Checks for zipping a collection with the range of + /// `0.._.len()`. + /// + /// **Why is this bad?** The code is better expressed with `.enumerate()`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = vec![1]; + /// x.iter().zip(0..x.len()); + /// ``` + /// Could be written as + /// ```rust + /// # let x = vec![1]; + /// x.iter().enumerate(); + /// ``` + pub RANGE_ZIP_WITH_LEN, + complexity, + "zipping iterator with a range when `enumerate()` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for exclusive ranges where 1 is added to the + /// upper bound, e.g., `x..(y+1)`. + /// + /// **Why is this bad?** The code is more readable with an inclusive range + /// like `x..=y`. + /// + /// **Known problems:** Will add unnecessary pair of parentheses when the + /// expression is not wrapped in a pair but starts with a opening parenthesis + /// and ends with a closing one. + /// I.e., `let _ = (f()+1)..(f()+1)` results in `let _ = ((f()+1)..=f())`. + /// + /// Also in many cases, inclusive ranges are still slower to run than + /// exclusive ranges, because they essentially add an extra branch that + /// LLVM may fail to hoist out of the loop. + /// + /// This will cause a warning that cannot be fixed if the consumer of the + /// range only accepts a specific range type, instead of the generic + /// `RangeBounds` trait + /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)). + /// + /// **Example:** + /// ```rust,ignore + /// for x..(y+1) { .. } + /// ``` + /// Could be written as + /// ```rust,ignore + /// for x..=y { .. } + /// ``` + pub RANGE_PLUS_ONE, + pedantic, + "`x..(y+1)` reads better as `x..=y`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for inclusive ranges where 1 is subtracted from + /// the upper bound, e.g., `x..=(y-1)`. + /// + /// **Why is this bad?** The code is more readable with an exclusive range + /// like `x..y`. + /// + /// **Known problems:** This will cause a warning that cannot be fixed if + /// the consumer of the range only accepts a specific range type, instead of + /// the generic `RangeBounds` trait + /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)). + /// + /// **Example:** + /// ```rust,ignore + /// for x..=(y-1) { .. } + /// ``` + /// Could be written as + /// ```rust,ignore + /// for x..y { .. } + /// ``` + pub RANGE_MINUS_ONE, + pedantic, + "`x..=(y-1)` reads better as `x..y`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for range expressions `x..y` where both `x` and `y` + /// are constant and `x` is greater or equal to `y`. + /// + /// **Why is this bad?** Empty ranges yield no values so iterating them is a no-op. + /// Moreover, trying to use a reversed range to index a slice will panic at run-time. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,no_run + /// fn main() { + /// (10..=0).for_each(|x| println!("{}", x)); + /// + /// let arr = [1, 2, 3, 4, 5]; + /// let sub = &arr[3..1]; + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn main() { + /// (0..=10).rev().for_each(|x| println!("{}", x)); + /// + /// let arr = [1, 2, 3, 4, 5]; + /// let sub = &arr[1..3]; + /// } + /// ``` + pub REVERSED_EMPTY_RANGES, + correctness, + "reversing the limits of range expressions, resulting in empty ranges" +} + +declare_clippy_lint! { + /// **What it does:** Checks for expressions like `x >= 3 && x < 8` that could + /// be more readably expressed as `(3..8).contains(x)`. + /// + /// **Why is this bad?** `contains` expresses the intent better and has less + /// failure modes (such as fencepost errors or using `||` instead of `&&`). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // given + /// let x = 6; + /// + /// assert!(x >= 3 && x < 8); + /// ``` + /// Use instead: + /// ```rust + ///# let x = 6; + /// assert!((3..8).contains(&x)); + /// ``` + pub MANUAL_RANGE_CONTAINS, + style, + "manually reimplementing {`Range`, `RangeInclusive`}`::contains`" +} + +const MANUAL_RANGE_CONTAINS_MSRV: RustcVersion = RustcVersion::new(1, 35, 0); + +pub struct Ranges { + msrv: Option, +} + +impl Ranges { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(Ranges => [ + RANGE_ZIP_WITH_LEN, + RANGE_PLUS_ONE, + RANGE_MINUS_ONE, + REVERSED_EMPTY_RANGES, + MANUAL_RANGE_CONTAINS, +]); + +impl<'tcx> LateLintPass<'tcx> for Ranges { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + match expr.kind { + ExprKind::MethodCall(ref path, _, ref args, _) => { + check_range_zip_with_len(cx, path, args, expr.span); + }, + ExprKind::Binary(ref op, ref l, ref r) => { + if meets_msrv(self.msrv.as_ref(), &MANUAL_RANGE_CONTAINS_MSRV) { + check_possible_range_contains(cx, op.node, l, r, expr); + } + }, + _ => {}, + } + + check_exclusive_range_plus_one(cx, expr); + check_inclusive_range_minus_one(cx, expr); + check_reversed_empty_range(cx, expr); + } + extract_msrv_attr!(LateContext); +} + +fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<'_>, r: &Expr<'_>, expr: &Expr<'_>) { + if in_constant(cx, expr.hir_id) { + return; + } + + let span = expr.span; + let combine_and = match op { + BinOpKind::And | BinOpKind::BitAnd => true, + BinOpKind::Or | BinOpKind::BitOr => false, + _ => return, + }; + // value, name, order (higher/lower), inclusiveness + if let (Some((lval, lname, name_span, lval_span, lord, linc)), Some((rval, rname, _, rval_span, rord, rinc))) = + (check_range_bounds(cx, l), check_range_bounds(cx, r)) + { + // we only lint comparisons on the same name and with different + // direction + if lname != rname || lord == rord { + return; + } + let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l), &lval, &rval); + if combine_and && ord == Some(rord) { + // order lower bound and upper bound + let (l_span, u_span, l_inc, u_inc) = if rord == Ordering::Less { + (lval_span, rval_span, linc, rinc) + } else { + (rval_span, lval_span, rinc, linc) + }; + // we only lint inclusive lower bounds + if !l_inc { + return; + } + let (range_type, range_op) = if u_inc { + ("RangeInclusive", "..=") + } else { + ("Range", "..") + }; + let mut applicability = Applicability::MachineApplicable; + let name = snippet_with_applicability(cx, name_span, "_", &mut applicability); + let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability); + let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability); + let space = if lo.ends_with('.') { " " } else { "" }; + span_lint_and_sugg( + cx, + MANUAL_RANGE_CONTAINS, + span, + &format!("manual `{}::contains` implementation", range_type), + "use", + format!("({}{}{}{}).contains(&{})", lo, space, range_op, hi, name), + applicability, + ); + } else if !combine_and && ord == Some(lord) { + // `!_.contains(_)` + // order lower bound and upper bound + let (l_span, u_span, l_inc, u_inc) = if lord == Ordering::Less { + (lval_span, rval_span, linc, rinc) + } else { + (rval_span, lval_span, rinc, linc) + }; + if l_inc { + return; + } + let (range_type, range_op) = if u_inc { + ("Range", "..") + } else { + ("RangeInclusive", "..=") + }; + let mut applicability = Applicability::MachineApplicable; + let name = snippet_with_applicability(cx, name_span, "_", &mut applicability); + let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability); + let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability); + let space = if lo.ends_with('.') { " " } else { "" }; + span_lint_and_sugg( + cx, + MANUAL_RANGE_CONTAINS, + span, + &format!("manual `!{}::contains` implementation", range_type), + "use", + format!("!({}{}{}{}).contains(&{})", lo, space, range_op, hi, name), + applicability, + ); + } + } +} + +fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, Ident, Span, Span, Ordering, bool)> { + if let ExprKind::Binary(ref op, ref l, ref r) = ex.kind { + let (inclusive, ordering) = match op.node { + BinOpKind::Gt => (false, Ordering::Greater), + BinOpKind::Ge => (true, Ordering::Greater), + BinOpKind::Lt => (false, Ordering::Less), + BinOpKind::Le => (true, Ordering::Less), + _ => return None, + }; + if let Some(id) = match_ident(l) { + if let Some((c, _)) = constant(cx, cx.typeck_results(), r) { + return Some((c, id, l.span, r.span, ordering, inclusive)); + } + } else if let Some(id) = match_ident(r) { + if let Some((c, _)) = constant(cx, cx.typeck_results(), l) { + return Some((c, id, r.span, l.span, ordering.reverse(), inclusive)); + } + } + } + None +} + +fn match_ident(e: &Expr<'_>) -> Option { + if let ExprKind::Path(ref qpath) = e.kind { + if let Some(seg) = single_segment_path(qpath) { + if seg.args.is_none() { + return Some(seg.ident); + } + } + } + None +} + +fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args: &[Expr<'_>], span: Span) { + let name = path.ident.as_str(); + if name == "zip" && args.len() == 2 { + let iter = &args[0].kind; + let zip_arg = &args[1]; + if_chain! { + // `.iter()` call + if let ExprKind::MethodCall(ref iter_path, _, ref iter_args, _) = *iter; + if iter_path.ident.name == sym::iter; + // range expression in `.zip()` call: `0..x.len()` + if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::range(zip_arg); + if is_integer_const(cx, start, 0); + // `.len()` call + if let ExprKind::MethodCall(ref len_path, _, ref len_args, _) = end.kind; + if len_path.ident.name == sym!(len) && len_args.len() == 1; + // `.iter()` and `.len()` called on same `Path` + if let ExprKind::Path(QPath::Resolved(_, ref iter_path)) = iter_args[0].kind; + if let ExprKind::Path(QPath::Resolved(_, ref len_path)) = len_args[0].kind; + if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments); + then { + span_lint(cx, + RANGE_ZIP_WITH_LEN, + span, + &format!("it is more idiomatic to use `{}.iter().enumerate()`", + snippet(cx, iter_args[0].span, "_")) + ); + } + } + } +} + +// exclusive range plus one: `x..(y+1)` +fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { + if_chain! { + if let Some(higher::Range { + start, + end: Some(end), + limits: RangeLimits::HalfOpen + }) = higher::range(expr); + if let Some(y) = y_plus_one(cx, end); + then { + let span = if expr.span.from_expansion() { + expr.span + .ctxt() + .outer_expn_data() + .call_site + } else { + expr.span + }; + span_lint_and_then( + cx, + RANGE_PLUS_ONE, + span, + "an inclusive range would be more readable", + |diag| { + let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").to_string()); + let end = Sugg::hir(cx, y, "y"); + if let Some(is_wrapped) = &snippet_opt(cx, span) { + if is_wrapped.starts_with('(') && is_wrapped.ends_with(')') { + diag.span_suggestion( + span, + "use", + format!("({}..={})", start, end), + Applicability::MaybeIncorrect, + ); + } else { + diag.span_suggestion( + span, + "use", + format!("{}..={}", start, end), + Applicability::MachineApplicable, // snippet + ); + } + } + }, + ); + } + } +} + +// inclusive range minus one: `x..=(y-1)` +fn check_inclusive_range_minus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { + if_chain! { + if let Some(higher::Range { start, end: Some(end), limits: RangeLimits::Closed }) = higher::range(expr); + if let Some(y) = y_minus_one(cx, end); + then { + span_lint_and_then( + cx, + RANGE_MINUS_ONE, + expr.span, + "an exclusive range would be more readable", + |diag| { + let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").to_string()); + let end = Sugg::hir(cx, y, "y"); + diag.span_suggestion( + expr.span, + "use", + format!("{}..{}", start, end), + Applicability::MachineApplicable, // snippet + ); + }, + ); + } + } +} + +fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) { + fn inside_indexing_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + matches!( + get_parent_expr(cx, expr), + Some(Expr { + kind: ExprKind::Index(..), + .. + }) + ) + } + + fn is_for_loop_arg(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let mut cur_expr = expr; + while let Some(parent_expr) = get_parent_expr(cx, cur_expr) { + match higher::for_loop(parent_expr) { + Some((_, args, _, _)) if args.hir_id == expr.hir_id => return true, + _ => cur_expr = parent_expr, + } + } + + false + } + + fn is_empty_range(limits: RangeLimits, ordering: Ordering) -> bool { + match limits { + RangeLimits::HalfOpen => ordering != Ordering::Less, + RangeLimits::Closed => ordering == Ordering::Greater, + } + } + + if_chain! { + if let Some(higher::Range { start: Some(start), end: Some(end), limits }) = higher::range(expr); + let ty = cx.typeck_results().expr_ty(start); + if let ty::Int(_) | ty::Uint(_) = ty.kind(); + if let Some((start_idx, _)) = constant(cx, cx.typeck_results(), start); + if let Some((end_idx, _)) = constant(cx, cx.typeck_results(), end); + if let Some(ordering) = Constant::partial_cmp(cx.tcx, ty, &start_idx, &end_idx); + if is_empty_range(limits, ordering); + then { + if inside_indexing_expr(cx, expr) { + // Avoid linting `N..N` as it has proven to be useful, see #5689 and #5628 ... + if ordering != Ordering::Equal { + span_lint( + cx, + REVERSED_EMPTY_RANGES, + expr.span, + "this range is reversed and using it to index a slice will panic at run-time", + ); + } + // ... except in for loop arguments for backwards compatibility with `reverse_range_loop` + } else if ordering != Ordering::Equal || is_for_loop_arg(cx, expr) { + span_lint_and_then( + cx, + REVERSED_EMPTY_RANGES, + expr.span, + "this range is empty so it will yield no values", + |diag| { + if ordering != Ordering::Equal { + let start_snippet = snippet(cx, start.span, "_"); + let end_snippet = snippet(cx, end.span, "_"); + let dots = match limits { + RangeLimits::HalfOpen => "..", + RangeLimits::Closed => "..=" + }; + + diag.span_suggestion( + expr.span, + "consider using the following if you are attempting to iterate over this \ + range in reverse", + format!("({}{}{}).rev()", end_snippet, dots, start_snippet), + Applicability::MaybeIncorrect, + ); + } + }, + ); + } + } + } +} + +fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> { + match expr.kind { + ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + ref lhs, + ref rhs, + ) => { + if is_integer_const(cx, lhs, 1) { + Some(rhs) + } else if is_integer_const(cx, rhs, 1) { + Some(lhs) + } else { + None + } + }, + _ => None, + } +} + +fn y_minus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> { + match expr.kind { + ExprKind::Binary( + Spanned { + node: BinOpKind::Sub, .. + }, + ref lhs, + ref rhs, + ) if is_integer_const(cx, rhs, 1) => Some(lhs), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_clone.rs b/src/tools/clippy/clippy_lints/src/redundant_clone.rs new file mode 100644 index 0000000000..f90d482056 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_clone.rs @@ -0,0 +1,626 @@ +use crate::utils::{ + fn_has_unsatisfiable_preds, has_drop, is_copy, is_type_diagnostic_item, match_def_path, paths, snippet_opt, + span_lint_hir, span_lint_hir_and_then, walk_ptrs_ty_depth, +}; +use if_chain::if_chain; +use rustc_data_structures::{fx::FxHashMap, transitive_relation::TransitiveRelation}; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{def_id, Body, FnDecl, HirId}; +use rustc_index::bit_set::{BitSet, HybridBitSet}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::{ + self, traversal, + visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor as _}, +}; +use rustc_middle::ty::{self, fold::TypeVisitor, Ty}; +use rustc_mir::dataflow::{Analysis, AnalysisDomain, GenKill, GenKillAnalysis, ResultsCursor}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::{BytePos, Span}; +use rustc_span::sym; +use std::convert::TryFrom; +use std::ops::ControlFlow; + +macro_rules! unwrap_or_continue { + ($x:expr) => { + match $x { + Some(x) => x, + None => continue, + } + }; +} + +declare_clippy_lint! { + /// **What it does:** Checks for a redundant `clone()` (and its relatives) which clones an owned + /// value that is going to be dropped without further use. + /// + /// **Why is this bad?** It is not always possible for the compiler to eliminate useless + /// allocations and deallocations generated by redundant `clone()`s. + /// + /// **Known problems:** + /// + /// False-negatives: analysis performed by this lint is conservative and limited. + /// + /// **Example:** + /// ```rust + /// # use std::path::Path; + /// # #[derive(Clone)] + /// # struct Foo; + /// # impl Foo { + /// # fn new() -> Self { Foo {} } + /// # } + /// # fn call(x: Foo) {} + /// { + /// let x = Foo::new(); + /// call(x.clone()); + /// call(x.clone()); // this can just pass `x` + /// } + /// + /// ["lorem", "ipsum"].join(" ").to_string(); + /// + /// Path::new("/a/b").join("c").to_path_buf(); + /// ``` + pub REDUNDANT_CLONE, + perf, + "`clone()` of an owned value that is going to be dropped immediately" +} + +declare_lint_pass!(RedundantClone => [REDUNDANT_CLONE]); + +impl<'tcx> LateLintPass<'tcx> for RedundantClone { + #[allow(clippy::too_many_lines)] + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: FnKind<'tcx>, + _: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + _: HirId, + ) { + let def_id = cx.tcx.hir().body_owner_def_id(body.id()); + + // Building MIR for `fn`s with unsatisfiable preds results in ICE. + if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { + return; + } + + let mir = cx.tcx.optimized_mir(def_id.to_def_id()); + + let maybe_storage_live_result = MaybeStorageLive + .into_engine(cx.tcx, mir) + .pass_name("redundant_clone") + .iterate_to_fixpoint() + .into_results_cursor(mir); + let mut possible_borrower = { + let mut vis = PossibleBorrowerVisitor::new(cx, mir); + vis.visit_body(&mir); + vis.into_map(cx, maybe_storage_live_result) + }; + + for (bb, bbdata) in mir.basic_blocks().iter_enumerated() { + let terminator = bbdata.terminator(); + + if terminator.source_info.span.from_expansion() { + continue; + } + + // Give up on loops + if terminator.successors().any(|s| *s == bb) { + continue; + } + + let (fn_def_id, arg, arg_ty, clone_ret) = + unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind)); + + let from_borrow = match_def_path(cx, fn_def_id, &paths::CLONE_TRAIT_METHOD) + || match_def_path(cx, fn_def_id, &paths::TO_OWNED_METHOD) + || (match_def_path(cx, fn_def_id, &paths::TO_STRING_METHOD) + && is_type_diagnostic_item(cx, arg_ty, sym::string_type)); + + let from_deref = !from_borrow + && (match_def_path(cx, fn_def_id, &paths::PATH_TO_PATH_BUF) + || match_def_path(cx, fn_def_id, &paths::OS_STR_TO_OS_STRING)); + + if !from_borrow && !from_deref { + continue; + } + + if let ty::Adt(ref def, _) = arg_ty.kind() { + if match_def_path(cx, def.did, &paths::MEM_MANUALLY_DROP) { + continue; + } + } + + // `{ cloned = &arg; clone(move cloned); }` or `{ cloned = &arg; to_path_buf(cloned); }` + let (cloned, cannot_move_out) = unwrap_or_continue!(find_stmt_assigns_to(cx, mir, arg, from_borrow, bb)); + + let loc = mir::Location { + block: bb, + statement_index: bbdata.statements.len(), + }; + + // `Local` to be cloned, and a local of `clone` call's destination + let (local, ret_local) = if from_borrow { + // `res = clone(arg)` can be turned into `res = move arg;` + // if `arg` is the only borrow of `cloned` at this point. + + if cannot_move_out || !possible_borrower.only_borrowers(&[arg], cloned, loc) { + continue; + } + + (cloned, clone_ret) + } else { + // `arg` is a reference as it is `.deref()`ed in the previous block. + // Look into the predecessor block and find out the source of deref. + + let ps = &mir.predecessors()[bb]; + if ps.len() != 1 { + continue; + } + let pred_terminator = mir[ps[0]].terminator(); + + // receiver of the `deref()` call + let (pred_arg, deref_clone_ret) = if_chain! { + if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, res)) = + is_call_with_ref_arg(cx, mir, &pred_terminator.kind); + if res == cloned; + if cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id); + if is_type_diagnostic_item(cx, pred_arg_ty, sym::PathBuf) + || is_type_diagnostic_item(cx, pred_arg_ty, sym::OsString); + then { + (pred_arg, res) + } else { + continue; + } + }; + + let (local, cannot_move_out) = + unwrap_or_continue!(find_stmt_assigns_to(cx, mir, pred_arg, true, ps[0])); + let loc = mir::Location { + block: bb, + statement_index: mir.basic_blocks()[bb].statements.len(), + }; + + // This can be turned into `res = move local` if `arg` and `cloned` are not borrowed + // at the last statement: + // + // ``` + // pred_arg = &local; + // cloned = deref(pred_arg); + // arg = &cloned; + // StorageDead(pred_arg); + // res = to_path_buf(cloned); + // ``` + if cannot_move_out || !possible_borrower.only_borrowers(&[arg, cloned], local, loc) { + continue; + } + + (local, deref_clone_ret) + }; + + let is_temp = mir.local_kind(ret_local) == mir::LocalKind::Temp; + + // 1. `local` can be moved out if it is not used later. + // 2. If `ret_local` is a temporary and is neither consumed nor mutated, we can remove this `clone` + // call anyway. + let (used, consumed_or_mutated) = traversal::ReversePostorder::new(&mir, bb).skip(1).fold( + (false, !is_temp), + |(used, consumed), (tbb, tdata)| { + // Short-circuit + if (used && consumed) || + // Give up on loops + tdata.terminator().successors().any(|s| *s == bb) + { + return (true, true); + } + + let mut vis = LocalUseVisitor { + used: (local, false), + consumed_or_mutated: (ret_local, false), + }; + vis.visit_basic_block_data(tbb, tdata); + (used || vis.used.1, consumed || vis.consumed_or_mutated.1) + }, + ); + + if !used || !consumed_or_mutated { + let span = terminator.source_info.span; + let scope = terminator.source_info.scope; + let node = mir.source_scopes[scope] + .local_data + .as_ref() + .assert_crate_local() + .lint_root; + + if_chain! { + if let Some(snip) = snippet_opt(cx, span); + if let Some(dot) = snip.rfind('.'); + then { + let sugg_span = span.with_lo( + span.lo() + BytePos(u32::try_from(dot).unwrap()) + ); + let mut app = Applicability::MaybeIncorrect; + + let call_snip = &snip[dot + 1..]; + // Machine applicable when `call_snip` looks like `foobar()` + if let Some(call_snip) = call_snip.strip_suffix("()").map(str::trim) { + if call_snip.as_bytes().iter().all(|b| b.is_ascii_alphabetic() || *b == b'_') { + app = Applicability::MachineApplicable; + } + } + + span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| { + diag.span_suggestion( + sugg_span, + "remove this", + String::new(), + app, + ); + if used { + diag.span_note( + span, + "cloned value is neither consumed nor mutated", + ); + } else { + diag.span_note( + span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())), + "this value is dropped without further use", + ); + } + }); + } else { + span_lint_hir(cx, REDUNDANT_CLONE, node, span, "redundant clone"); + } + } + } + } + } +} + +/// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`. +fn is_call_with_ref_arg<'tcx>( + cx: &LateContext<'tcx>, + mir: &'tcx mir::Body<'tcx>, + kind: &'tcx mir::TerminatorKind<'tcx>, +) -> Option<(def_id::DefId, mir::Local, Ty<'tcx>, mir::Local)> { + if_chain! { + if let mir::TerminatorKind::Call { func, args, destination, .. } = kind; + if args.len() == 1; + if let mir::Operand::Move(mir::Place { local, .. }) = &args[0]; + if let ty::FnDef(def_id, _) = *func.ty(&*mir, cx.tcx).kind(); + if let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].ty(&*mir, cx.tcx)); + if !is_copy(cx, inner_ty); + then { + Some((def_id, *local, inner_ty, destination.as_ref().map(|(dest, _)| dest)?.as_local()?)) + } else { + None + } + } +} + +type CannotMoveOut = bool; + +/// Finds the first `to = (&)from`, and returns +/// ``Some((from, whether `from` cannot be moved out))``. +fn find_stmt_assigns_to<'tcx>( + cx: &LateContext<'tcx>, + mir: &mir::Body<'tcx>, + to_local: mir::Local, + by_ref: bool, + bb: mir::BasicBlock, +) -> Option<(mir::Local, CannotMoveOut)> { + let rvalue = mir.basic_blocks()[bb].statements.iter().rev().find_map(|stmt| { + if let mir::StatementKind::Assign(box (mir::Place { local, .. }, v)) = &stmt.kind { + return if *local == to_local { Some(v) } else { None }; + } + + None + })?; + + match (by_ref, &*rvalue) { + (true, mir::Rvalue::Ref(_, _, place)) | (false, mir::Rvalue::Use(mir::Operand::Copy(place))) => { + Some(base_local_and_movability(cx, mir, *place)) + }, + (false, mir::Rvalue::Ref(_, _, place)) => { + if let [mir::ProjectionElem::Deref] = place.as_ref().projection { + Some(base_local_and_movability(cx, mir, *place)) + } else { + None + } + }, + _ => None, + } +} + +/// Extracts and returns the undermost base `Local` of given `place`. Returns `place` itself +/// if it is already a `Local`. +/// +/// Also reports whether given `place` cannot be moved out. +fn base_local_and_movability<'tcx>( + cx: &LateContext<'tcx>, + mir: &mir::Body<'tcx>, + place: mir::Place<'tcx>, +) -> (mir::Local, CannotMoveOut) { + use rustc_middle::mir::PlaceRef; + + // Dereference. You cannot move things out from a borrowed value. + let mut deref = false; + // Accessing a field of an ADT that has `Drop`. Moving the field out will cause E0509. + let mut field = false; + // If projection is a slice index then clone can be removed only if the + // underlying type implements Copy + let mut slice = false; + + let PlaceRef { local, mut projection } = place.as_ref(); + while let [base @ .., elem] = projection { + projection = base; + deref |= matches!(elem, mir::ProjectionElem::Deref); + field |= matches!(elem, mir::ProjectionElem::Field(..)) + && has_drop(cx, mir::Place::ty_from(local, projection, &mir.local_decls, cx.tcx).ty); + slice |= matches!(elem, mir::ProjectionElem::Index(..)) + && !is_copy(cx, mir::Place::ty_from(local, projection, &mir.local_decls, cx.tcx).ty); + } + + (local, deref || field || slice) +} + +struct LocalUseVisitor { + used: (mir::Local, bool), + consumed_or_mutated: (mir::Local, bool), +} + +impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor { + fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) { + let statements = &data.statements; + for (statement_index, statement) in statements.iter().enumerate() { + self.visit_statement(statement, mir::Location { block, statement_index }); + } + + self.visit_terminator( + data.terminator(), + mir::Location { + block, + statement_index: statements.len(), + }, + ); + } + + fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, _: mir::Location) { + let local = place.local; + + if local == self.used.0 + && !matches!( + ctx, + PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_) + ) + { + self.used.1 = true; + } + + if local == self.consumed_or_mutated.0 { + match ctx { + PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) + | PlaceContext::MutatingUse(MutatingUseContext::Borrow) => { + self.consumed_or_mutated.1 = true; + }, + _ => {}, + } + } + } +} + +/// Determines liveness of each local purely based on `StorageLive`/`Dead`. +#[derive(Copy, Clone)] +struct MaybeStorageLive; + +impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive { + type Domain = BitSet; + const NAME: &'static str = "maybe_storage_live"; + + fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { + // bottom = dead + BitSet::new_empty(body.local_decls.len()) + } + + fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) { + for arg in body.args_iter() { + state.insert(arg); + } + } +} + +impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive { + type Idx = mir::Local; + + fn statement_effect(&self, trans: &mut impl GenKill, stmt: &mir::Statement<'tcx>, _: mir::Location) { + match stmt.kind { + mir::StatementKind::StorageLive(l) => trans.gen(l), + mir::StatementKind::StorageDead(l) => trans.kill(l), + _ => (), + } + } + + fn terminator_effect( + &self, + _trans: &mut impl GenKill, + _terminator: &mir::Terminator<'tcx>, + _loc: mir::Location, + ) { + } + + fn call_return_effect( + &self, + _in_out: &mut impl GenKill, + _block: mir::BasicBlock, + _func: &mir::Operand<'tcx>, + _args: &[mir::Operand<'tcx>], + _return_place: mir::Place<'tcx>, + ) { + // Nothing to do when a call returns successfully + } +} + +/// Collects the possible borrowers of each local. +/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c` +/// possible borrowers of `a`. +struct PossibleBorrowerVisitor<'a, 'tcx> { + possible_borrower: TransitiveRelation, + body: &'a mir::Body<'tcx>, + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx> PossibleBorrowerVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>, body: &'a mir::Body<'tcx>) -> Self { + Self { + possible_borrower: TransitiveRelation::default(), + cx, + body, + } + } + + fn into_map( + self, + cx: &LateContext<'tcx>, + maybe_live: ResultsCursor<'tcx, 'tcx, MaybeStorageLive>, + ) -> PossibleBorrowerMap<'a, 'tcx> { + let mut map = FxHashMap::default(); + for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) { + if is_copy(cx, self.body.local_decls[row].ty) { + continue; + } + + let borrowers = self.possible_borrower.reachable_from(&row); + if !borrowers.is_empty() { + let mut bs = HybridBitSet::new_empty(self.body.local_decls.len()); + for &c in borrowers { + if c != mir::Local::from_usize(0) { + bs.insert(c); + } + } + + if !bs.is_empty() { + map.insert(row, bs); + } + } + } + + let bs = BitSet::new_empty(self.body.local_decls.len()); + PossibleBorrowerMap { + map, + maybe_live, + bitset: (bs.clone(), bs), + } + } +} + +impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'tcx> { + fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) { + let lhs = place.local; + match rvalue { + mir::Rvalue::Ref(_, _, borrowed) => { + self.possible_borrower.add(borrowed.local, lhs); + }, + other => { + if ContainsRegion + .visit_ty(place.ty(&self.body.local_decls, self.cx.tcx).ty) + .is_continue() + { + return; + } + rvalue_locals(other, |rhs| { + if lhs != rhs { + self.possible_borrower.add(rhs, lhs); + } + }); + }, + } + } + + fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) { + if let mir::TerminatorKind::Call { + args, + destination: Some((mir::Place { local: dest, .. }, _)), + .. + } = &terminator.kind + { + // If the call returns something with lifetimes, + // let's conservatively assume the returned value contains lifetime of all the arguments. + // For example, given `let y: Foo<'a> = foo(x)`, `y` is considered to be a possible borrower of `x`. + if ContainsRegion.visit_ty(&self.body.local_decls[*dest].ty).is_continue() { + return; + } + + for op in args { + match op { + mir::Operand::Copy(p) | mir::Operand::Move(p) => { + self.possible_borrower.add(p.local, *dest); + }, + _ => (), + } + } + } + } +} + +struct ContainsRegion; + +impl TypeVisitor<'_> for ContainsRegion { + type BreakTy = (); + + fn visit_region(&mut self, _: ty::Region<'_>) -> ControlFlow { + ControlFlow::BREAK + } +} + +fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) { + use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, CheckedBinaryOp, Repeat, UnaryOp, Use}; + + let mut visit_op = |op: &mir::Operand<'_>| match op { + mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local), + _ => (), + }; + + match rvalue { + Use(op) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op), + Aggregate(_, ops) => ops.iter().for_each(visit_op), + BinaryOp(_, box (lhs, rhs)) | CheckedBinaryOp(_, box (lhs, rhs)) => { + visit_op(lhs); + visit_op(rhs); + } + _ => (), + } +} + +/// Result of `PossibleBorrowerVisitor`. +struct PossibleBorrowerMap<'a, 'tcx> { + /// Mapping `Local -> its possible borrowers` + map: FxHashMap>, + maybe_live: ResultsCursor<'a, 'tcx, MaybeStorageLive>, + // Caches to avoid allocation of `BitSet` on every query + bitset: (BitSet, BitSet), +} + +impl PossibleBorrowerMap<'_, '_> { + /// Returns true if the set of borrowers of `borrowed` living at `at` matches with `borrowers`. + fn only_borrowers(&mut self, borrowers: &[mir::Local], borrowed: mir::Local, at: mir::Location) -> bool { + self.maybe_live.seek_after_primary_effect(at); + + self.bitset.0.clear(); + let maybe_live = &mut self.maybe_live; + if let Some(bitset) = self.map.get(&borrowed) { + for b in bitset.iter().filter(move |b| maybe_live.contains(*b)) { + self.bitset.0.insert(b); + } + } else { + return false; + } + + self.bitset.1.clear(); + for b in borrowers { + self.bitset.1.insert(*b); + } + + self.bitset.0 == self.bitset.1 + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs b/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs new file mode 100644 index 0000000000..f398b3fff2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs @@ -0,0 +1,156 @@ +use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_then}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_ast::visit as ast_visit; +use rustc_ast::visit::Visitor as AstVisitor; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit as hir_visit; +use rustc_hir::intravisit::Visitor as HirVisitor; +use rustc_lint::{EarlyContext, EarlyLintPass, 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:** Detects closures called in the same expression where they + /// are defined. + /// + /// **Why is this bad?** It is unnecessarily adding to the expression's + /// complexity. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// let a = (|| 42)() + /// + /// // Good + /// let a = 42 + /// ``` + pub REDUNDANT_CLOSURE_CALL, + complexity, + "throwaway closures called in the expression they are defined" +} + +declare_lint_pass!(RedundantClosureCall => [REDUNDANT_CLOSURE_CALL]); + +// Used to find `return` statements or equivalents e.g., `?` +struct ReturnVisitor { + found_return: bool, +} + +impl ReturnVisitor { + #[must_use] + fn new() -> Self { + Self { found_return: false } + } +} + +impl<'ast> ast_visit::Visitor<'ast> for ReturnVisitor { + fn visit_expr(&mut self, ex: &'ast ast::Expr) { + if let ast::ExprKind::Ret(_) = ex.kind { + self.found_return = true; + } else if let ast::ExprKind::Try(_) = ex.kind { + self.found_return = true; + } + + ast_visit::walk_expr(self, ex) + } +} + +impl EarlyLintPass for RedundantClosureCall { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + if_chain! { + if let ast::ExprKind::Call(ref paren, _) = expr.kind; + if let ast::ExprKind::Paren(ref closure) = paren.kind; + if let ast::ExprKind::Closure(_, _, _, ref decl, ref block, _) = closure.kind; + then { + let mut visitor = ReturnVisitor::new(); + visitor.visit_expr(block); + if !visitor.found_return { + span_lint_and_then( + cx, + REDUNDANT_CLOSURE_CALL, + expr.span, + "try not to call a closure in the expression where it is declared", + |diag| { + if decl.inputs.is_empty() { + let mut app = Applicability::MachineApplicable; + let hint = + snippet_with_applicability(cx, block.span, "..", &mut app).into_owned(); + diag.span_suggestion(expr.span, "try doing something like", hint, app); + } + }, + ); + } + } + } + } +} + +impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { + fn count_closure_usage<'a, 'tcx>( + cx: &'a LateContext<'tcx>, + block: &'tcx hir::Block<'_>, + path: &'tcx hir::Path<'tcx>, + ) -> usize { + struct ClosureUsageCount<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + path: &'tcx hir::Path<'tcx>, + count: usize, + } + impl<'a, 'tcx> hir_visit::Visitor<'tcx> for ClosureUsageCount<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + if_chain! { + if let hir::ExprKind::Call(ref closure, _) = expr.kind; + if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = closure.kind; + if self.path.segments[0].ident == path.segments[0].ident + && self.path.res == path.res; + then { + self.count += 1; + } + } + hir_visit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> hir_visit::NestedVisitorMap { + hir_visit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } + } + let mut closure_usage_count = ClosureUsageCount { cx, path, count: 0 }; + closure_usage_count.visit_block(block); + closure_usage_count.count + } + + for w in block.stmts.windows(2) { + if_chain! { + if let hir::StmtKind::Local(ref local) = w[0].kind; + if let Option::Some(ref t) = local.init; + if let hir::ExprKind::Closure(..) = t.kind; + if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind; + if let hir::StmtKind::Semi(ref second) = w[1].kind; + if let hir::ExprKind::Assign(_, ref call, _) = second.kind; + if let hir::ExprKind::Call(ref closure, _) = call.kind; + if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = closure.kind; + if ident == path.segments[0].ident; + if count_closure_usage(cx, block, path) == 1; + then { + span_lint( + cx, + REDUNDANT_CLOSURE_CALL, + second.span, + "closure called just once immediately after it was declared", + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_else.rs b/src/tools/clippy/clippy_lints/src/redundant_else.rs new file mode 100644 index 0000000000..3d585cd27a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_else.rs @@ -0,0 +1,135 @@ +use crate::utils::span_lint_and_help; +use rustc_ast::ast::{Block, Expr, ExprKind, Stmt, StmtKind}; +use rustc_ast::visit::{walk_expr, Visitor}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `else` blocks that can be removed without changing semantics. + /// + /// **Why is this bad?** The `else` block adds unnecessary indentation and verbosity. + /// + /// **Known problems:** Some may prefer to keep the `else` block for clarity. + /// + /// **Example:** + /// + /// ```rust + /// fn my_func(count: u32) { + /// if count == 0 { + /// print!("Nothing to do"); + /// return; + /// } else { + /// print!("Moving on..."); + /// } + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn my_func(count: u32) { + /// if count == 0 { + /// print!("Nothing to do"); + /// return; + /// } + /// print!("Moving on..."); + /// } + /// ``` + pub REDUNDANT_ELSE, + pedantic, + "`else` branch that can be removed without changing semantics" +} + +declare_lint_pass!(RedundantElse => [REDUNDANT_ELSE]); + +impl EarlyLintPass for RedundantElse { + fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &Stmt) { + if in_external_macro(cx.sess, stmt.span) { + return; + } + // Only look at expressions that are a whole statement + let expr: &Expr = match &stmt.kind { + StmtKind::Expr(expr) | StmtKind::Semi(expr) => expr, + _ => return, + }; + // if else + let (mut then, mut els): (&Block, &Expr) = match &expr.kind { + ExprKind::If(_, then, Some(els)) => (then, els), + _ => return, + }; + loop { + if !BreakVisitor::default().check_block(then) { + // then block does not always break + return; + } + match &els.kind { + // else if else + ExprKind::If(_, next_then, Some(next_els)) => { + then = next_then; + els = next_els; + continue; + }, + // else if without else + ExprKind::If(..) => return, + // done + _ => break, + } + } + span_lint_and_help( + cx, + REDUNDANT_ELSE, + els.span, + "redundant else block", + None, + "remove the `else` block and move the contents out", + ); + } +} + +/// Call `check` functions to check if an expression always breaks control flow +#[derive(Default)] +struct BreakVisitor { + is_break: bool, +} + +impl<'ast> Visitor<'ast> for BreakVisitor { + fn visit_block(&mut self, block: &'ast Block) { + self.is_break = match block.stmts.as_slice() { + [.., last] => self.check_stmt(last), + _ => false, + }; + } + + fn visit_expr(&mut self, expr: &'ast Expr) { + self.is_break = match expr.kind { + ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) => true, + ExprKind::Match(_, ref arms) => arms.iter().all(|arm| self.check_expr(&arm.body)), + ExprKind::If(_, ref then, Some(ref els)) => self.check_block(then) && self.check_expr(els), + ExprKind::If(_, _, None) + // ignore loops for simplicity + | ExprKind::While(..) | ExprKind::ForLoop(..) | ExprKind::Loop(..) => false, + _ => { + walk_expr(self, expr); + return; + }, + }; + } +} + +impl BreakVisitor { + fn check(&mut self, item: T, visit: fn(&mut Self, T)) -> bool { + visit(self, item); + std::mem::replace(&mut self.is_break, false) + } + + fn check_block(&mut self, block: &Block) -> bool { + self.check(block, Self::visit_block) + } + + fn check_expr(&mut self, expr: &Expr) -> bool { + self.check(expr, Self::visit_expr) + } + + fn check_stmt(&mut self, stmt: &Stmt) -> bool { + self.check(stmt, Self::visit_stmt) + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_field_names.rs b/src/tools/clippy/clippy_lints/src/redundant_field_names.rs new file mode 100644 index 0000000000..9688ef3933 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_field_names.rs @@ -0,0 +1,86 @@ +use crate::utils::{meets_msrv, span_lint_and_sugg}; +use rustc_ast::ast::{Expr, ExprKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +const REDUNDANT_FIELD_NAMES_MSRV: RustcVersion = RustcVersion::new(1, 17, 0); + +declare_clippy_lint! { + /// **What it does:** Checks for fields in struct literals where shorthands + /// could be used. + /// + /// **Why is this bad?** If the field and variable names are the same, + /// the field name is redundant. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let bar: u8 = 123; + /// + /// struct Foo { + /// bar: u8, + /// } + /// + /// let foo = Foo { bar: bar }; + /// ``` + /// the last line can be simplified to + /// ```ignore + /// let foo = Foo { bar }; + /// ``` + pub REDUNDANT_FIELD_NAMES, + style, + "checks for fields in struct literals where shorthands could be used" +} + +pub struct RedundantFieldNames { + msrv: Option, +} + +impl RedundantFieldNames { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(RedundantFieldNames => [REDUNDANT_FIELD_NAMES]); + +impl EarlyLintPass for RedundantFieldNames { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if !meets_msrv(self.msrv.as_ref(), &REDUNDANT_FIELD_NAMES_MSRV) { + return; + } + + if in_external_macro(cx.sess, expr.span) { + return; + } + if let ExprKind::Struct(ref se) = expr.kind { + for field in &se.fields { + if field.is_shorthand { + continue; + } + if let ExprKind::Path(None, path) = &field.expr.kind { + if path.segments.len() == 1 + && path.segments[0].ident == field.ident + && path.segments[0].args.is_none() + { + span_lint_and_sugg( + cx, + REDUNDANT_FIELD_NAMES, + field.span, + "redundant field names in struct initialization", + "replace it with", + field.ident.to_string(), + Applicability::MachineApplicable, + ); + } + } + } + } + } + extract_msrv_attr!(EarlyContext); +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs b/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs new file mode 100644 index 0000000000..c876bae230 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs @@ -0,0 +1,77 @@ +use crate::utils::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind, VisibilityKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +declare_clippy_lint! { + /// **What it does:** Checks for items declared `pub(crate)` that are not crate visible because they + /// are inside a private module. + /// + /// **Why is this bad?** Writing `pub(crate)` is misleading when it's redundant due to the parent + /// module's visibility. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// mod internal { + /// pub(crate) fn internal_fn() { } + /// } + /// ``` + /// This function is not visible outside the module and it can be declared with `pub` or + /// private visibility + /// ```rust + /// mod internal { + /// pub fn internal_fn() { } + /// } + /// ``` + pub REDUNDANT_PUB_CRATE, + nursery, + "Using `pub(crate)` visibility on items that are not crate visible due to the visibility of the module that contains them." +} + +#[derive(Default)] +pub struct RedundantPubCrate { + is_exported: Vec, +} + +impl_lint_pass!(RedundantPubCrate => [REDUNDANT_PUB_CRATE]); + +impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let VisibilityKind::Crate { .. } = item.vis.node { + if !cx.access_levels.is_exported(item.hir_id()) { + if let Some(false) = self.is_exported.last() { + let span = item.span.with_hi(item.ident.span.hi()); + let descr = cx.tcx.def_kind(item.def_id).descr(item.def_id.to_def_id()); + span_lint_and_then( + cx, + REDUNDANT_PUB_CRATE, + span, + &format!("pub(crate) {} inside private module", descr), + |diag| { + diag.span_suggestion( + item.vis.span, + "consider using", + "pub".to_string(), + Applicability::MachineApplicable, + ); + }, + ) + } + } + } + + if let ItemKind::Mod { .. } = item.kind { + self.is_exported.push(cx.access_levels.is_exported(item.hir_id())); + } + } + + fn check_item_post(&mut self, _cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Mod { .. } = item.kind { + self.is_exported.pop().expect("unbalanced check_item/check_item_post"); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_slicing.rs b/src/tools/clippy/clippy_lints/src/redundant_slicing.rs new file mode 100644 index 0000000000..e5ced13514 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_slicing.rs @@ -0,0 +1,67 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, LangItem}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::{lint::in_external_macro, ty::TyS}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{is_type_lang_item, snippet_with_applicability, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for redundant slicing expressions which use the full range, and + /// do not change the type. + /// + /// **Why is this bad?** It unnecessarily adds complexity to the expression. + /// + /// **Known problems:** If the type being sliced has an implementation of `Index` + /// that actually changes anything then it can't be removed. However, this would be surprising + /// to people reading the code and should have a note with it. + /// + /// **Example:** + /// + /// ```ignore + /// fn get_slice(x: &[u32]) -> &[u32] { + /// &x[..] + /// } + /// ``` + /// Use instead: + /// ```ignore + /// fn get_slice(x: &[u32]) -> &[u32] { + /// x + /// } + /// ``` + pub REDUNDANT_SLICING, + complexity, + "redundant slicing of the whole range of a type" +} + +declare_lint_pass!(RedundantSlicing => [REDUNDANT_SLICING]); + +impl LateLintPass<'_> for RedundantSlicing { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if_chain! { + if let ExprKind::AddrOf(_, _, addressee) = expr.kind; + if let ExprKind::Index(indexed, range) = addressee.kind; + if is_type_lang_item(cx, cx.typeck_results().expr_ty_adjusted(range), LangItem::RangeFull); + if TyS::same_type(cx.typeck_results().expr_ty(expr), cx.typeck_results().expr_ty(indexed)); + then { + let mut app = Applicability::MachineApplicable; + let hint = snippet_with_applicability(cx, indexed.span, "..", &mut app).into_owned(); + + span_lint_and_sugg( + cx, + REDUNDANT_SLICING, + expr.span, + "redundant slicing of the whole range", + "use the original slice instead", + hint, + app, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs new file mode 100644 index 0000000000..fcfa3c1275 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs @@ -0,0 +1,119 @@ +use crate::utils::{meets_msrv, snippet, span_lint_and_then}; +use rustc_ast::ast::{Item, ItemKind, Ty, TyKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +const REDUNDANT_STATIC_LIFETIMES_MSRV: RustcVersion = RustcVersion::new(1, 17, 0); + +declare_clippy_lint! { + /// **What it does:** Checks for constants and statics with an explicit `'static` lifetime. + /// + /// **Why is this bad?** Adding `'static` to every reference can create very + /// complicated types. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// const FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] = + /// &[...] + /// static FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] = + /// &[...] + /// ``` + /// This code can be rewritten as + /// ```ignore + /// const FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...] + /// static FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...] + /// ``` + pub REDUNDANT_STATIC_LIFETIMES, + style, + "Using explicit `'static` lifetime for constants or statics when elision rules would allow omitting them." +} + +pub struct RedundantStaticLifetimes { + msrv: Option, +} + +impl RedundantStaticLifetimes { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(RedundantStaticLifetimes => [REDUNDANT_STATIC_LIFETIMES]); + +impl RedundantStaticLifetimes { + // Recursively visit types + fn visit_type(&mut self, ty: &Ty, cx: &EarlyContext<'_>, reason: &str) { + match ty.kind { + // Be careful of nested structures (arrays and tuples) + TyKind::Array(ref ty, _) => { + self.visit_type(&*ty, cx, reason); + }, + TyKind::Tup(ref tup) => { + for tup_ty in tup { + self.visit_type(&*tup_ty, cx, reason); + } + }, + // This is what we are looking for ! + TyKind::Rptr(ref optional_lifetime, ref borrow_type) => { + // Match the 'static lifetime + if let Some(lifetime) = *optional_lifetime { + match borrow_type.ty.kind { + TyKind::Path(..) | TyKind::Slice(..) | TyKind::Array(..) | TyKind::Tup(..) => { + if lifetime.ident.name == rustc_span::symbol::kw::StaticLifetime { + let snip = snippet(cx, borrow_type.ty.span, ""); + let sugg = format!("&{}", snip); + span_lint_and_then( + cx, + REDUNDANT_STATIC_LIFETIMES, + lifetime.ident.span, + reason, + |diag| { + diag.span_suggestion( + ty.span, + "consider removing `'static`", + sugg, + Applicability::MachineApplicable, //snippet + ); + }, + ); + } + }, + _ => {}, + } + } + self.visit_type(&*borrow_type.ty, cx, reason); + }, + TyKind::Slice(ref ty) => { + self.visit_type(ty, cx, reason); + }, + _ => {}, + } + } +} + +impl EarlyLintPass for RedundantStaticLifetimes { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if !meets_msrv(self.msrv.as_ref(), &REDUNDANT_STATIC_LIFETIMES_MSRV) { + return; + } + + if !item.span.from_expansion() { + if let ItemKind::Const(_, ref var_type, _) = item.kind { + self.visit_type(var_type, cx, "constants have by default a `'static` lifetime"); + // Don't check associated consts because `'static` cannot be elided on those (issue + // #2438) + } + + if let ItemKind::Static(ref var_type, _, _) = item.kind { + self.visit_type(var_type, cx, "statics have by default a `'static` lifetime"); + } + } + } + + extract_msrv_attr!(EarlyContext); +} diff --git a/src/tools/clippy/clippy_lints/src/ref_option_ref.rs b/src/tools/clippy/clippy_lints/src/ref_option_ref.rs new file mode 100644 index 0000000000..8cd6692ce0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ref_option_ref.rs @@ -0,0 +1,67 @@ +use crate::utils::{last_path_segment, snippet, span_lint_and_sugg}; +use rustc_hir::{GenericArg, Mutability, Ty, TyKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; + +use if_chain::if_chain; +use rustc_errors::Applicability; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `&Option<&T>`. + /// + /// **Why is this bad?** Since `&` is Copy, it's useless to have a + /// reference on `Option<&T>`. + /// + /// **Known problems:** It may be irrelevant to use this lint on + /// public API code as it will make a breaking change to apply it. + /// + /// **Example:** + /// + /// ```rust,ignore + /// let x: &Option<&u32> = &Some(&0u32); + /// ``` + /// Use instead: + /// ```rust,ignore + /// let x: Option<&u32> = Some(&0u32); + /// ``` + pub REF_OPTION_REF, + pedantic, + "use `Option<&T>` instead of `&Option<&T>`" +} + +declare_lint_pass!(RefOptionRef => [REF_OPTION_REF]); + +impl<'tcx> LateLintPass<'tcx> for RefOptionRef { + fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) { + if_chain! { + if let TyKind::Rptr(_, ref mut_ty) = ty.kind; + if mut_ty.mutbl == Mutability::Not; + if let TyKind::Path(ref qpath) = &mut_ty.ty.kind; + let last = last_path_segment(qpath); + if let Some(res) = last.res; + if let Some(def_id) = res.opt_def_id(); + + if cx.tcx.is_diagnostic_item(sym::option_type, def_id); + if let Some(ref params) = last_path_segment(qpath).args ; + if !params.parenthesized; + if let Some(inner_ty) = params.args.iter().find_map(|arg| match arg { + GenericArg::Type(inner_ty) => Some(inner_ty), + _ => None, + }); + if let TyKind::Rptr(_, _) = inner_ty.kind; + + then { + span_lint_and_sugg( + cx, + REF_OPTION_REF, + ty.span, + "since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>`", + "try", + format!("Option<{}>", &snippet(cx, inner_ty.span, "..")), + Applicability::MaybeIncorrect, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/reference.rs b/src/tools/clippy/clippy_lints/src/reference.rs new file mode 100644 index 0000000000..e1450466a7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/reference.rs @@ -0,0 +1,152 @@ +use crate::utils::sugg::Sugg; +use crate::utils::{in_macro, snippet_opt, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::BytePos; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `*&` and `*&mut` in expressions. + /// + /// **Why is this bad?** Immediately dereferencing a reference is no-op and + /// makes the code less clear. + /// + /// **Known problems:** Multiple dereference/addrof pairs are not handled so + /// the suggested fix for `x = **&&y` is `x = *&y`, which is still incorrect. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// let a = f(*&mut b); + /// let c = *&d; + /// + /// // Good + /// let a = f(b); + /// let c = d; + /// ``` + pub DEREF_ADDROF, + complexity, + "use of `*&` or `*&mut` in an expression" +} + +declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]); + +fn without_parens(mut e: &Expr) -> &Expr { + while let ExprKind::Paren(ref child_e) = e.kind { + e = child_e; + } + e +} + +impl EarlyLintPass for DerefAddrOf { + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { + if_chain! { + if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind; + if let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind; + if !in_macro(addrof_target.span); + then { + let mut applicability = Applicability::MachineApplicable; + let sugg = if e.span.from_expansion() { + if let Ok(macro_source) = cx.sess.source_map().span_to_snippet(e.span) { + // Remove leading whitespace from the given span + // e.g: ` $visitor` turns into `$visitor` + let trim_leading_whitespaces = |span| { + snippet_opt(cx, span).and_then(|snip| { + #[allow(clippy::cast_possible_truncation)] + snip.find(|c: char| !c.is_whitespace()).map(|pos| { + span.lo() + BytePos(pos as u32) + }) + }).map_or(span, |start_no_whitespace| e.span.with_lo(start_no_whitespace)) + }; + + let mut generate_snippet = |pattern: &str| { + #[allow(clippy::cast_possible_truncation)] + macro_source.rfind(pattern).map(|pattern_pos| { + let rpos = pattern_pos + pattern.len(); + let span_after_ref = e.span.with_lo(BytePos(e.span.lo().0 + rpos as u32)); + let span = trim_leading_whitespaces(span_after_ref); + snippet_with_applicability(cx, span, "_", &mut applicability) + }) + }; + + if *mutability == Mutability::Mut { + generate_snippet("mut") + } else { + generate_snippet("&") + } + } else { + Some(snippet_with_applicability(cx, e.span, "_", &mut applicability)) + } + } else { + Some(snippet_with_applicability(cx, addrof_target.span, "_", &mut applicability)) + }; + if let Some(sugg) = sugg { + span_lint_and_sugg( + cx, + DEREF_ADDROF, + e.span, + "immediately dereferencing a reference", + "try this", + sugg.to_string(), + applicability, + ); + } + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for references in expressions that use + /// auto dereference. + /// + /// **Why is this bad?** The reference is a no-op and is automatically + /// dereferenced by the compiler and makes the code less clear. + /// + /// **Example:** + /// ```rust + /// struct Point(u32, u32); + /// let point = Point(30, 20); + /// let x = (&point).0; + /// ``` + /// Use instead: + /// ```rust + /// # struct Point(u32, u32); + /// # let point = Point(30, 20); + /// let x = point.0; + /// ``` + pub REF_IN_DEREF, + complexity, + "Use of reference in auto dereference expression." +} + +declare_lint_pass!(RefInDeref => [REF_IN_DEREF]); + +impl EarlyLintPass for RefInDeref { + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { + if_chain! { + if let ExprKind::Field(ref object, _) = e.kind; + if let ExprKind::Paren(ref parened) = object.kind; + if let ExprKind::AddrOf(_, _, ref inner) = parened.kind; + then { + let applicability = if inner.span.from_expansion() { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; + let sugg = Sugg::ast(cx, inner, "_").maybe_par(); + span_lint_and_sugg( + cx, + REF_IN_DEREF, + object.span, + "creating a reference that is immediately dereferenced", + "try this", + sugg.to_string(), + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/regex.rs b/src/tools/clippy/clippy_lints/src/regex.rs new file mode 100644 index 0000000000..1edea61314 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/regex.rs @@ -0,0 +1,211 @@ +use crate::consts::{constant, Constant}; +use crate::utils::{match_def_path, paths, span_lint, span_lint_and_help}; +use if_chain::if_chain; +use rustc_ast::ast::{LitKind, StrStyle}; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::{BorrowKind, Expr, ExprKind, HirId}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::{BytePos, Span}; +use std::convert::TryFrom; + +declare_clippy_lint! { + /// **What it does:** Checks [regex](https://crates.io/crates/regex) creation + /// (with `Regex::new`, `RegexBuilder::new`, or `RegexSet::new`) for correct + /// regex syntax. + /// + /// **Why is this bad?** This will lead to a runtime panic. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// Regex::new("|") + /// ``` + pub INVALID_REGEX, + correctness, + "invalid regular expressions" +} + +declare_clippy_lint! { + /// **What it does:** Checks for trivial [regex](https://crates.io/crates/regex) + /// creation (with `Regex::new`, `RegexBuilder::new`, or `RegexSet::new`). + /// + /// **Why is this bad?** Matching the regex can likely be replaced by `==` or + /// `str::starts_with`, `str::ends_with` or `std::contains` or other `str` + /// methods. + /// + /// **Known problems:** If the same regex is going to be applied to multiple + /// inputs, the precomputations done by `Regex` construction can give + /// significantly better performance than any of the `str`-based methods. + /// + /// **Example:** + /// ```ignore + /// Regex::new("^foobar") + /// ``` + pub TRIVIAL_REGEX, + nursery, + "trivial regular expressions" +} + +#[derive(Clone, Default)] +pub struct Regex { + spans: FxHashSet, + last: Option, +} + +impl_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX]); + +impl<'tcx> LateLintPass<'tcx> for Regex { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref fun, ref args) = expr.kind; + if let ExprKind::Path(ref qpath) = fun.kind; + if args.len() == 1; + if let Some(def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); + then { + if match_def_path(cx, def_id, &paths::REGEX_NEW) || + match_def_path(cx, def_id, &paths::REGEX_BUILDER_NEW) { + check_regex(cx, &args[0], true); + } else if match_def_path(cx, def_id, &paths::REGEX_BYTES_NEW) || + match_def_path(cx, def_id, &paths::REGEX_BYTES_BUILDER_NEW) { + check_regex(cx, &args[0], false); + } else if match_def_path(cx, def_id, &paths::REGEX_SET_NEW) { + check_set(cx, &args[0], true); + } else if match_def_path(cx, def_id, &paths::REGEX_BYTES_SET_NEW) { + check_set(cx, &args[0], false); + } + } + } + } +} + +#[allow(clippy::cast_possible_truncation)] // truncation very unlikely here +#[must_use] +fn str_span(base: Span, c: regex_syntax::ast::Span, offset: u16) -> Span { + let offset = u32::from(offset); + let end = base.lo() + BytePos(u32::try_from(c.end.offset).expect("offset too large") + offset); + let start = base.lo() + BytePos(u32::try_from(c.start.offset).expect("offset too large") + offset); + assert!(start <= end); + Span::new(start, end, base.ctxt()) +} + +fn const_str<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option { + constant(cx, cx.typeck_results(), e).and_then(|(c, _)| match c { + Constant::Str(s) => Some(s), + _ => None, + }) +} + +fn is_trivial_regex(s: ®ex_syntax::hir::Hir) -> Option<&'static str> { + use regex_syntax::hir::Anchor::{EndText, StartText}; + use regex_syntax::hir::HirKind::{Alternation, Anchor, Concat, Empty, Literal}; + + let is_literal = |e: &[regex_syntax::hir::Hir]| e.iter().all(|e| matches!(*e.kind(), Literal(_))); + + match *s.kind() { + Empty | Anchor(_) => Some("the regex is unlikely to be useful as it is"), + Literal(_) => Some("consider using `str::contains`"), + Alternation(ref exprs) => { + if exprs.iter().all(|e| e.kind().is_empty()) { + Some("the regex is unlikely to be useful as it is") + } else { + None + } + }, + Concat(ref exprs) => match (exprs[0].kind(), exprs[exprs.len() - 1].kind()) { + (&Anchor(StartText), &Anchor(EndText)) if exprs[1..(exprs.len() - 1)].is_empty() => { + Some("consider using `str::is_empty`") + }, + (&Anchor(StartText), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => { + Some("consider using `==` on `str`s") + }, + (&Anchor(StartText), &Literal(_)) if is_literal(&exprs[1..]) => Some("consider using `str::starts_with`"), + (&Literal(_), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => { + Some("consider using `str::ends_with`") + }, + _ if is_literal(exprs) => Some("consider using `str::contains`"), + _ => None, + }, + _ => None, + } +} + +fn check_set<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) { + if_chain! { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) = expr.kind; + if let ExprKind::Array(exprs) = expr.kind; + then { + for expr in exprs { + check_regex(cx, expr, utf8); + } + } + } +} + +fn check_regex<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) { + let mut parser = regex_syntax::ParserBuilder::new() + .unicode(true) + .allow_invalid_utf8(!utf8) + .build(); + + if let ExprKind::Lit(ref lit) = expr.kind { + if let LitKind::Str(ref r, style) = lit.node { + let r = &r.as_str(); + let offset = if let StrStyle::Raw(n) = style { 2 + n } else { 1 }; + match parser.parse(r) { + Ok(r) => { + if let Some(repl) = is_trivial_regex(&r) { + span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl); + } + }, + Err(regex_syntax::Error::Parse(e)) => { + span_lint( + cx, + INVALID_REGEX, + str_span(expr.span, *e.span(), offset), + &format!("regex syntax error: {}", e.kind()), + ); + }, + Err(regex_syntax::Error::Translate(e)) => { + span_lint( + cx, + INVALID_REGEX, + str_span(expr.span, *e.span(), offset), + &format!("regex syntax error: {}", e.kind()), + ); + }, + Err(e) => { + span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e)); + }, + } + } + } else if let Some(r) = const_str(cx, expr) { + match parser.parse(&r) { + Ok(r) => { + if let Some(repl) = is_trivial_regex(&r) { + span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl); + } + }, + Err(regex_syntax::Error::Parse(e)) => { + span_lint( + cx, + INVALID_REGEX, + expr.span, + &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()), + ); + }, + Err(regex_syntax::Error::Translate(e)) => { + span_lint( + cx, + INVALID_REGEX, + expr.span, + &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()), + ); + }, + Err(e) => { + span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e)); + }, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/repeat_once.rs b/src/tools/clippy/clippy_lints/src/repeat_once.rs new file mode 100644 index 0000000000..d34e744eb9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/repeat_once.rs @@ -0,0 +1,83 @@ +use crate::consts::{constant_context, Constant}; +use crate::utils::{in_macro, is_type_diagnostic_item, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +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::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.repeat(1)` and suggest the following method for each types. + /// - `.to_string()` for `str` + /// - `.clone()` for `String` + /// - `.to_vec()` for `slice` + /// + /// **Why is this bad?** For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning the string is the intention behind this, `clone()` should be used. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// fn main() { + /// let x = String::from("hello world").repeat(1); + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn main() { + /// let x = String::from("hello world").clone(); + /// } + /// ``` + pub REPEAT_ONCE, + complexity, + "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` " +} + +declare_lint_pass!(RepeatOnce => [REPEAT_ONCE]); + +impl<'tcx> LateLintPass<'tcx> for RepeatOnce { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::MethodCall(path, _, [receiver, count], _) = &expr.kind; + if path.ident.name == sym!(repeat); + if let Some(Constant::Int(1)) = constant_context(cx, cx.typeck_results()).expr(&count); + if !in_macro(receiver.span); + then { + let ty = cx.typeck_results().expr_ty(&receiver).peel_refs(); + if ty.is_str() { + span_lint_and_sugg( + cx, + REPEAT_ONCE, + expr.span, + "calling `repeat(1)` on str", + "consider using `.to_string()` instead", + format!("{}.to_string()", snippet(cx, receiver.span, r#""...""#)), + Applicability::MachineApplicable, + ); + } else if ty.builtin_index().is_some() { + span_lint_and_sugg( + cx, + REPEAT_ONCE, + expr.span, + "calling `repeat(1)` on slice", + "consider using `.to_vec()` instead", + format!("{}.to_vec()", snippet(cx, receiver.span, r#""...""#)), + Applicability::MachineApplicable, + ); + } else if is_type_diagnostic_item(cx, ty, sym::string_type) { + span_lint_and_sugg( + cx, + REPEAT_ONCE, + expr.span, + "calling `repeat(1)` on a string literal", + "consider using `.clone()` instead", + format!("{}.clone()", snippet(cx, receiver.span, r#""...""#)), + Applicability::MachineApplicable, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/returns.rs b/src/tools/clippy/clippy_lints/src/returns.rs new file mode 100644 index 0000000000..40c0f1f458 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/returns.rs @@ -0,0 +1,308 @@ +use if_chain::if_chain; +use rustc_ast::ast::Attribute; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, PatKind, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::sym; + +use crate::utils::{fn_def_id, in_macro, match_qpath, snippet_opt, span_lint_and_sugg, span_lint_and_then}; + +declare_clippy_lint! { + /// **What it does:** Checks for `let`-bindings, which are subsequently + /// returned. + /// + /// **Why is this bad?** It is just extraneous code. Remove it to make your code + /// more rusty. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo() -> String { + /// let x = String::new(); + /// x + /// } + /// ``` + /// instead, use + /// ``` + /// fn foo() -> String { + /// String::new() + /// } + /// ``` + pub LET_AND_RETURN, + style, + "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" +} + +declare_clippy_lint! { + /// **What it does:** Checks for return statements at the end of a block. + /// + /// **Why is this bad?** Removing the `return` and semicolon will make the code + /// more rusty. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo(x: usize) -> usize { + /// return x; + /// } + /// ``` + /// simplify to + /// ```rust + /// fn foo(x: usize) -> usize { + /// x + /// } + /// ``` + pub NEEDLESS_RETURN, + style, + "using a return statement like `return expr;` where an expression would suffice" +} + +#[derive(PartialEq, Eq, Copy, Clone)] +enum RetReplacement { + Empty, + Block, +} + +declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN]); + +impl<'tcx> LateLintPass<'tcx> for Return { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { + // we need both a let-binding stmt and an expr + if_chain! { + if let Some(retexpr) = block.expr; + if let Some(stmt) = block.stmts.iter().last(); + if let StmtKind::Local(local) = &stmt.kind; + if local.ty.is_none(); + if cx.tcx.hir().attrs(local.hir_id).is_empty(); + if let Some(initexpr) = &local.init; + if let PatKind::Binding(.., ident, _) = local.pat.kind; + if let ExprKind::Path(qpath) = &retexpr.kind; + if match_qpath(qpath, &[&*ident.name.as_str()]); + if !last_statement_borrows(cx, initexpr); + if !in_external_macro(cx.sess(), initexpr.span); + if !in_external_macro(cx.sess(), retexpr.span); + if !in_external_macro(cx.sess(), local.span); + if !in_macro(local.span); + then { + span_lint_and_then( + cx, + LET_AND_RETURN, + retexpr.span, + "returning the result of a `let` binding from a block", + |err| { + err.span_label(local.span, "unnecessary `let` binding"); + + if let Some(mut snippet) = snippet_opt(cx, initexpr.span) { + if !cx.typeck_results().expr_adjustments(&retexpr).is_empty() { + snippet.push_str(" as _"); + } + err.multipart_suggestion( + "return the expression directly", + vec![ + (local.span, String::new()), + (retexpr.span, snippet), + ], + Applicability::MachineApplicable, + ); + } else { + err.span_help(initexpr.span, "this expression can be directly returned"); + } + }, + ); + } + } + } + + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + _: &'tcx FnDecl<'tcx>, + body: &'tcx Body<'tcx>, + _: Span, + _: HirId, + ) { + match kind { + FnKind::Closure => { + // when returning without value in closure, replace this `return` + // with an empty block to prevent invalid suggestion (see #6501) + let replacement = if let ExprKind::Ret(None) = &body.value.kind { + RetReplacement::Block + } else { + RetReplacement::Empty + }; + check_final_expr(cx, &body.value, Some(body.value.span), replacement) + }, + FnKind::ItemFn(..) | FnKind::Method(..) => { + if let ExprKind::Block(ref block, _) = body.value.kind { + check_block_return(cx, block); + } + }, + } + } +} + +fn attr_is_cfg(attr: &Attribute) -> bool { + attr.meta_item_list().is_some() && attr.has_name(sym::cfg) +} + +fn check_block_return<'tcx>(cx: &LateContext<'tcx>, block: &Block<'tcx>) { + if let Some(expr) = block.expr { + check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty); + } else if let Some(stmt) = block.stmts.iter().last() { + match stmt.kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { + check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty); + }, + _ => (), + } + } +} + +fn check_final_expr<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + span: Option, + replacement: RetReplacement, +) { + match expr.kind { + // simple return is always "bad" + ExprKind::Ret(ref inner) => { + // allow `#[cfg(a)] return a; #[cfg(b)] return b;` + let attrs = cx.tcx.hir().attrs(expr.hir_id); + if !attrs.iter().any(attr_is_cfg) { + let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner)); + if !borrows { + emit_return_lint( + cx, + span.expect("`else return` is not possible"), + inner.as_ref().map(|i| i.span), + replacement, + ); + } + } + }, + // a whole block? check it! + ExprKind::Block(ref block, _) => { + check_block_return(cx, block); + }, + ExprKind::If(_, then, else_clause_opt) => { + if let ExprKind::Block(ref ifblock, _) = then.kind { + check_block_return(cx, ifblock); + } + if let Some(else_clause) = else_clause_opt { + check_final_expr(cx, else_clause, None, RetReplacement::Empty); + } + }, + // a match expr, check all arms + // an if/if let expr, check both exprs + // note, if without else is going to be a type checking error anyways + // (except for unit type functions) so we don't match it + ExprKind::Match(_, ref arms, source) => match source { + MatchSource::Normal => { + for arm in arms.iter() { + check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block); + } + }, + MatchSource::IfLetDesugar { + contains_else_clause: true, + } => { + if let ExprKind::Block(ref ifblock, _) = arms[0].body.kind { + check_block_return(cx, ifblock); + } + check_final_expr(cx, arms[1].body, None, RetReplacement::Empty); + }, + _ => (), + }, + _ => (), + } +} + +fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option, replacement: RetReplacement) { + if ret_span.from_expansion() { + return; + } + match inner_span { + Some(inner_span) => { + if in_external_macro(cx.tcx.sess, inner_span) || inner_span.from_expansion() { + return; + } + + span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| { + if let Some(snippet) = snippet_opt(cx, inner_span) { + diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable); + } + }) + }, + None => match replacement { + RetReplacement::Empty => { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN, + ret_span, + "unneeded `return` statement", + "remove `return`", + String::new(), + Applicability::MachineApplicable, + ); + }, + RetReplacement::Block => { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN, + ret_span, + "unneeded `return` statement", + "replace `return` with an empty block", + "{}".to_string(), + Applicability::MachineApplicable, + ); + }, + }, + } +} + +fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + let mut visitor = BorrowVisitor { cx, borrows: false }; + walk_expr(&mut visitor, expr); + visitor.borrows +} + +struct BorrowVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + borrows: bool, +} + +impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.borrows { + return; + } + + if let Some(def_id) = fn_def_id(self.cx, expr) { + self.borrows = self + .cx + .tcx + .fn_sig(def_id) + .output() + .skip_binder() + .walk() + .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_))); + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/self_assignment.rs b/src/tools/clippy/clippy_lints/src/self_assignment.rs new file mode 100644 index 0000000000..e096c9aebc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/self_assignment.rs @@ -0,0 +1,51 @@ +use crate::utils::{eq_expr_value, snippet, span_lint}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for explicit self-assignments. + /// + /// **Why is this bad?** Self-assignments are redundant and unlikely to be + /// intentional. + /// + /// **Known problems:** If expression contains any deref coercions or + /// indexing operations they are assumed not to have any side effects. + /// + /// **Example:** + /// + /// ```rust + /// struct Event { + /// id: usize, + /// x: i32, + /// y: i32, + /// } + /// + /// fn copy_position(a: &mut Event, b: &Event) { + /// a.x = b.x; + /// a.y = a.y; + /// } + /// ``` + pub SELF_ASSIGNMENT, + correctness, + "explicit self-assignment" +} + +declare_lint_pass!(SelfAssignment => [SELF_ASSIGNMENT]); + +impl<'tcx> LateLintPass<'tcx> for SelfAssignment { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Assign(lhs, rhs, _) = &expr.kind { + if eq_expr_value(cx, lhs, rhs) { + let lhs = snippet(cx, lhs.span, ""); + let rhs = snippet(cx, rhs.span, ""); + span_lint( + cx, + SELF_ASSIGNMENT, + expr.span, + &format!("self-assignment of `{}` to `{}`", rhs, lhs), + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs b/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs new file mode 100644 index 0000000000..839c995e52 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs @@ -0,0 +1,66 @@ +use crate::utils::{in_macro, snippet_with_macro_callsite, span_lint_and_sugg, sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Block, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Looks for blocks of expressions and fires if the last expression returns `()` + /// but is not followed by a semicolon. + /// + /// **Why is this bad?** The semicolon might be optional but when + /// extending the block with new code, it doesn't require a change in previous last line. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// fn main() { + /// println!("Hello world") + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn main() { + /// println!("Hello world"); + /// } + /// ``` + pub SEMICOLON_IF_NOTHING_RETURNED, + restriction, + "add a semicolon if nothing is returned" +} + +declare_lint_pass!(SemicolonIfNothingReturned => [SEMICOLON_IF_NOTHING_RETURNED]); + +impl LateLintPass<'_> for SemicolonIfNothingReturned { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) { + if_chain! { + if !in_macro(block.span); + if let Some(expr) = block.expr; + let t_expr = cx.typeck_results().expr_ty(expr); + if t_expr.is_unit(); + if let snippet = snippet_with_macro_callsite(cx, expr.span, "}"); + if !snippet.ends_with('}'); + then { + // filter out the desugared `for` loop + if let ExprKind::DropTemps(..) = &expr.kind { + return; + } + + let sugg = sugg::Sugg::hir_with_macro_callsite(cx, &expr, ".."); + let suggestion = format!("{0};", sugg); + span_lint_and_sugg( + cx, + SEMICOLON_IF_NOTHING_RETURNED, + expr.span.source_callsite(), + "consider adding a `;` to the last statement for consistent formatting", + "add a `;` here", + suggestion, + Applicability::MaybeIncorrect, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/serde_api.rs b/src/tools/clippy/clippy_lints/src/serde_api.rs new file mode 100644 index 0000000000..90cf1b6c86 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/serde_api.rs @@ -0,0 +1,57 @@ +use crate::utils::{get_trait_def_id, paths, span_lint}; +use rustc_hir::{Impl, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for mis-uses of the serde API. + /// + /// **Why is this bad?** Serde is very finnicky about how its API should be + /// used, but the type system can't be used to enforce it (yet?). + /// + /// **Known problems:** None. + /// + /// **Example:** Implementing `Visitor::visit_string` but not + /// `Visitor::visit_str`. + pub SERDE_API_MISUSE, + correctness, + "various things that will negatively affect your serde experience" +} + +declare_lint_pass!(SerdeApi => [SERDE_API_MISUSE]); + +impl<'tcx> LateLintPass<'tcx> for SerdeApi { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Impl(Impl { + of_trait: Some(ref trait_ref), + items, + .. + }) = item.kind + { + let did = trait_ref.path.res.def_id(); + if let Some(visit_did) = get_trait_def_id(cx, &paths::SERDE_DE_VISITOR) { + if did == visit_did { + let mut seen_str = None; + let mut seen_string = None; + for item in items { + match &*item.ident.as_str() { + "visit_str" => seen_str = Some(item.span), + "visit_string" => seen_string = Some(item.span), + _ => {}, + } + } + if let Some(span) = seen_string { + if seen_str.is_none() { + span_lint( + cx, + SERDE_API_MISUSE, + span, + "you should not implement `visit_string` without also implementing `visit_str`", + ); + } + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/shadow.rs b/src/tools/clippy/clippy_lints/src/shadow.rs new file mode 100644 index 0000000000..32f6bc7464 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/shadow.rs @@ -0,0 +1,400 @@ +use crate::utils::{contains_name, higher, iter_input_pats, snippet, span_lint_and_then}; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{ + Block, Body, Expr, ExprKind, FnDecl, Guard, HirId, Local, MutTy, Pat, PatKind, Path, QPath, StmtKind, Ty, TyKind, + UnOp, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::symbol::Symbol; + +declare_clippy_lint! { + /// **What it does:** Checks for bindings that shadow other bindings already in + /// scope, while just changing reference level or mutability. + /// + /// **Why is this bad?** Not much, in fact it's a very common pattern in Rust + /// code. Still, some may opt to avoid it in their code base, they can set this + /// lint to `Warn`. + /// + /// **Known problems:** This lint, as the other shadowing related lints, + /// currently only catches very simple patterns. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// // Bad + /// let x = &x; + /// + /// // Good + /// let y = &x; // use different variable name + /// ``` + pub SHADOW_SAME, + restriction, + "rebinding a name to itself, e.g., `let mut x = &mut x`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for bindings that shadow other bindings already in + /// scope, while reusing the original value. + /// + /// **Why is this bad?** Not too much, in fact it's a common pattern in Rust + /// code. Still, some argue that name shadowing like this hurts readability, + /// because a value may be bound to different things depending on position in + /// the code. + /// + /// **Known problems:** This lint, as the other shadowing related lints, + /// currently only catches very simple patterns. + /// + /// **Example:** + /// ```rust + /// let x = 2; + /// let x = x + 1; + /// ``` + /// use different variable name: + /// ```rust + /// let x = 2; + /// let y = x + 1; + /// ``` + pub SHADOW_REUSE, + restriction, + "rebinding a name to an expression that re-uses the original value, e.g., `let x = x + 1`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for bindings that shadow other bindings already in + /// scope, either without a initialization or with one that does not even use + /// the original value. + /// + /// **Why is this bad?** Name shadowing can hurt readability, especially in + /// large code bases, because it is easy to lose track of the active binding at + /// any place in the code. This can be alleviated by either giving more specific + /// names to bindings or introducing more scopes to contain the bindings. + /// + /// **Known problems:** This lint, as the other shadowing related lints, + /// currently only catches very simple patterns. Note that + /// `allow`/`warn`/`deny`/`forbid` attributes only work on the function level + /// for this lint. + /// + /// **Example:** + /// ```rust + /// # let y = 1; + /// # let z = 2; + /// let x = y; + /// + /// // Bad + /// let x = z; // shadows the earlier binding + /// + /// // Good + /// let w = z; // use different variable name + /// ``` + pub SHADOW_UNRELATED, + pedantic, + "rebinding a name without even using the original value" +} + +declare_lint_pass!(Shadow => [SHADOW_SAME, SHADOW_REUSE, SHADOW_UNRELATED]); + +impl<'tcx> LateLintPass<'tcx> for Shadow { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + _: HirId, + ) { + if in_external_macro(cx.sess(), body.value.span) { + return; + } + check_fn(cx, decl, body); + } +} + +fn check_fn<'tcx>(cx: &LateContext<'tcx>, decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>) { + let mut bindings = Vec::with_capacity(decl.inputs.len()); + for arg in iter_input_pats(decl, body) { + if let PatKind::Binding(.., ident, _) = arg.pat.kind { + bindings.push((ident.name, ident.span)) + } + } + check_expr(cx, &body.value, &mut bindings); +} + +fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>, bindings: &mut Vec<(Symbol, Span)>) { + let len = bindings.len(); + for stmt in block.stmts { + match stmt.kind { + StmtKind::Local(ref local) => check_local(cx, local, bindings), + StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => check_expr(cx, e, bindings), + StmtKind::Item(..) => {}, + } + } + if let Some(ref o) = block.expr { + check_expr(cx, o, bindings); + } + bindings.truncate(len); +} + +fn check_local<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>, bindings: &mut Vec<(Symbol, Span)>) { + if in_external_macro(cx.sess(), local.span) { + return; + } + if higher::is_from_for_desugar(local) { + return; + } + let Local { + ref pat, + ref ty, + ref init, + span, + .. + } = *local; + if let Some(ref t) = *ty { + check_ty(cx, t, bindings) + } + if let Some(ref o) = *init { + check_expr(cx, o, bindings); + check_pat(cx, pat, Some(o), span, bindings); + } else { + check_pat(cx, pat, None, span, bindings); + } +} + +fn is_binding(cx: &LateContext<'_>, pat_id: HirId) -> bool { + let var_ty = cx.typeck_results().node_type_opt(pat_id); + var_ty.map_or(false, |var_ty| !matches!(var_ty.kind(), ty::Adt(..))) +} + +fn check_pat<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + init: Option<&'tcx Expr<'_>>, + span: Span, + bindings: &mut Vec<(Symbol, Span)>, +) { + // TODO: match more stuff / destructuring + match pat.kind { + PatKind::Binding(.., ident, ref inner) => { + let name = ident.name; + if is_binding(cx, pat.hir_id) { + let mut new_binding = true; + for tup in bindings.iter_mut() { + if tup.0 == name { + lint_shadow(cx, name, span, pat.span, init, tup.1); + tup.1 = ident.span; + new_binding = false; + break; + } + } + if new_binding { + bindings.push((name, ident.span)); + } + } + if let Some(ref p) = *inner { + check_pat(cx, p, init, span, bindings); + } + }, + PatKind::Struct(_, pfields, _) => { + if let Some(init_struct) = init { + if let ExprKind::Struct(_, ref efields, _) = init_struct.kind { + for field in pfields { + let name = field.ident.name; + let efield = efields + .iter() + .find_map(|f| if f.ident.name == name { Some(&*f.expr) } else { None }); + check_pat(cx, &field.pat, efield, span, bindings); + } + } else { + for field in pfields { + check_pat(cx, &field.pat, init, span, bindings); + } + } + } else { + for field in pfields { + check_pat(cx, &field.pat, None, span, bindings); + } + } + }, + PatKind::Tuple(inner, _) => { + if let Some(init_tup) = init { + if let ExprKind::Tup(ref tup) = init_tup.kind { + for (i, p) in inner.iter().enumerate() { + check_pat(cx, p, Some(&tup[i]), p.span, bindings); + } + } else { + for p in inner { + check_pat(cx, p, init, span, bindings); + } + } + } else { + for p in inner { + check_pat(cx, p, None, span, bindings); + } + } + }, + PatKind::Box(ref inner) => { + if let Some(initp) = init { + if let ExprKind::Box(ref inner_init) = initp.kind { + check_pat(cx, inner, Some(&**inner_init), span, bindings); + } else { + check_pat(cx, inner, init, span, bindings); + } + } else { + check_pat(cx, inner, init, span, bindings); + } + }, + PatKind::Ref(ref inner, _) => check_pat(cx, inner, init, span, bindings), + // PatVec(Vec>, Option>, Vec>), + _ => (), + } +} + +fn lint_shadow<'tcx>( + cx: &LateContext<'tcx>, + name: Symbol, + span: Span, + pattern_span: Span, + init: Option<&'tcx Expr<'_>>, + prev_span: Span, +) { + if let Some(expr) = init { + if is_self_shadow(name, expr) { + span_lint_and_then( + cx, + SHADOW_SAME, + span, + &format!( + "`{}` is shadowed by itself in `{}`", + snippet(cx, pattern_span, "_"), + snippet(cx, expr.span, "..") + ), + |diag| { + diag.span_note(prev_span, "previous binding is here"); + }, + ); + } else if contains_name(name, expr) { + span_lint_and_then( + cx, + SHADOW_REUSE, + pattern_span, + &format!( + "`{}` is shadowed by `{}` which reuses the original value", + snippet(cx, pattern_span, "_"), + snippet(cx, expr.span, "..") + ), + |diag| { + diag.span_note(expr.span, "initialization happens here"); + diag.span_note(prev_span, "previous binding is here"); + }, + ); + } else { + span_lint_and_then( + cx, + SHADOW_UNRELATED, + pattern_span, + &format!("`{}` is being shadowed", snippet(cx, pattern_span, "_")), + |diag| { + diag.span_note(expr.span, "initialization happens here"); + diag.span_note(prev_span, "previous binding is here"); + }, + ); + } + } else { + span_lint_and_then( + cx, + SHADOW_UNRELATED, + span, + &format!("`{}` shadows a previous declaration", snippet(cx, pattern_span, "_")), + |diag| { + diag.span_note(prev_span, "previous binding is here"); + }, + ); + } +} + +fn check_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, bindings: &mut Vec<(Symbol, Span)>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + match expr.kind { + ExprKind::Unary(_, ref e) + | ExprKind::Field(ref e, _) + | ExprKind::AddrOf(_, _, ref e) + | ExprKind::Box(ref e) => check_expr(cx, e, bindings), + ExprKind::Block(ref block, _) | ExprKind::Loop(ref block, ..) => check_block(cx, block, bindings), + // ExprKind::Call + // ExprKind::MethodCall + ExprKind::Array(v) | ExprKind::Tup(v) => { + for e in v { + check_expr(cx, e, bindings) + } + }, + ExprKind::If(ref cond, ref then, ref otherwise) => { + check_expr(cx, cond, bindings); + check_expr(cx, &**then, bindings); + if let Some(ref o) = *otherwise { + check_expr(cx, o, bindings); + } + }, + ExprKind::Match(ref init, arms, _) => { + check_expr(cx, init, bindings); + let len = bindings.len(); + for arm in arms { + check_pat(cx, &arm.pat, Some(&**init), arm.pat.span, bindings); + // This is ugly, but needed to get the right type + if let Some(ref guard) = arm.guard { + match guard { + Guard::If(if_expr) => check_expr(cx, if_expr, bindings), + Guard::IfLet(guard_pat, guard_expr) => { + check_pat(cx, guard_pat, Some(*guard_expr), guard_pat.span, bindings); + check_expr(cx, guard_expr, bindings); + }, + } + } + check_expr(cx, &arm.body, bindings); + bindings.truncate(len); + } + }, + _ => (), + } +} + +fn check_ty<'tcx>(cx: &LateContext<'tcx>, ty: &'tcx Ty<'_>, bindings: &mut Vec<(Symbol, Span)>) { + match ty.kind { + TyKind::Slice(ref sty) => check_ty(cx, sty, bindings), + TyKind::Array(ref fty, ref anon_const) => { + check_ty(cx, fty, bindings); + check_expr(cx, &cx.tcx.hir().body(anon_const.body).value, bindings); + }, + TyKind::Ptr(MutTy { ty: ref mty, .. }) | TyKind::Rptr(_, MutTy { ty: ref mty, .. }) => { + check_ty(cx, mty, bindings) + }, + TyKind::Tup(tup) => { + for t in tup { + check_ty(cx, t, bindings) + } + }, + TyKind::Typeof(ref anon_const) => check_expr(cx, &cx.tcx.hir().body(anon_const.body).value, bindings), + _ => (), + } +} + +fn is_self_shadow(name: Symbol, expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Box(ref inner) | ExprKind::AddrOf(_, _, ref inner) => is_self_shadow(name, inner), + ExprKind::Block(ref block, _) => { + block.stmts.is_empty() && block.expr.as_ref().map_or(false, |e| is_self_shadow(name, e)) + }, + ExprKind::Unary(op, ref inner) => (UnOp::Deref == op) && is_self_shadow(name, inner), + ExprKind::Path(QPath::Resolved(_, ref path)) => path_eq_name(name, path), + _ => false, + } +} + +fn path_eq_name(name: Symbol, path: &Path<'_>) -> bool { + !path.is_global() && path.segments.len() == 1 && path.segments[0].ident.name == name +} diff --git a/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs b/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs new file mode 100644 index 0000000000..1fc4ff5c2e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs @@ -0,0 +1,62 @@ +use crate::utils::{in_macro, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::{Item, ItemKind, UseTreeKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::edition::Edition; + +declare_clippy_lint! { + /// **What it does:** Checking for imports with single component use path. + /// + /// **Why is this bad?** Import with single component use path such as `use cratename;` + /// is not necessary, and thus should be removed. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,ignore + /// use regex; + /// + /// fn main() { + /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); + /// } + /// ``` + /// Better as + /// ```rust,ignore + /// fn main() { + /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); + /// } + /// ``` + pub SINGLE_COMPONENT_PATH_IMPORTS, + style, + "imports with single component path are redundant" +} + +declare_lint_pass!(SingleComponentPathImports => [SINGLE_COMPONENT_PATH_IMPORTS]); + +impl EarlyLintPass for SingleComponentPathImports { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if_chain! { + if !in_macro(item.span); + if cx.sess.opts.edition >= Edition::Edition2018; + if !item.vis.kind.is_pub(); + if let ItemKind::Use(use_tree) = &item.kind; + if let segments = &use_tree.prefix.segments; + if segments.len() == 1; + if let UseTreeKind::Simple(None, _, _) = use_tree.kind; + then { + span_lint_and_sugg( + cx, + SINGLE_COMPONENT_PATH_IMPORTS, + item.span, + "this import is redundant", + "remove it entirely", + String::new(), + Applicability::MachineApplicable + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs b/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs new file mode 100644 index 0000000000..87e386baad --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs @@ -0,0 +1,149 @@ +//! Lint on use of `size_of` or `size_of_val` of T in an expression +//! expecting a count of T + +use crate::utils::{match_def_path, paths, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::BinOpKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty, TyS, TypeAndMut}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Detects expressions where + /// `size_of::` or `size_of_val::` is used as a + /// count of elements of type `T` + /// + /// **Why is this bad?** These functions expect a count + /// of `T` and not a number of bytes + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,no_run + /// # use std::ptr::copy_nonoverlapping; + /// # use std::mem::size_of; + /// const SIZE: usize = 128; + /// let x = [2u8; SIZE]; + /// let mut y = [2u8; SIZE]; + /// unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::() * SIZE) }; + /// ``` + pub SIZE_OF_IN_ELEMENT_COUNT, + correctness, + "using `size_of::` or `size_of_val::` where a count of elements of `T` is expected" +} + +declare_lint_pass!(SizeOfInElementCount => [SIZE_OF_IN_ELEMENT_COUNT]); + +fn get_size_of_ty(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, inverted: bool) -> Option> { + match expr.kind { + ExprKind::Call(count_func, _func_args) => { + if_chain! { + if !inverted; + if let ExprKind::Path(ref count_func_qpath) = count_func.kind; + if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::MEM_SIZE_OF) + || match_def_path(cx, def_id, &paths::MEM_SIZE_OF_VAL); + then { + cx.typeck_results().node_substs(count_func.hir_id).types().next() + } else { + None + } + } + }, + ExprKind::Binary(op, left, right) if BinOpKind::Mul == op.node => { + get_size_of_ty(cx, left, inverted).or_else(|| get_size_of_ty(cx, right, inverted)) + }, + ExprKind::Binary(op, left, right) if BinOpKind::Div == op.node => { + get_size_of_ty(cx, left, inverted).or_else(|| get_size_of_ty(cx, right, !inverted)) + }, + ExprKind::Cast(expr, _) => get_size_of_ty(cx, expr, inverted), + _ => None, + } +} + +fn get_pointee_ty_and_count_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(Ty<'tcx>, &'tcx Expr<'tcx>)> { + const FUNCTIONS: [&[&str]; 8] = [ + &paths::COPY_NONOVERLAPPING, + &paths::COPY, + &paths::WRITE_BYTES, + &paths::PTR_SWAP_NONOVERLAPPING, + &paths::PTR_SLICE_FROM_RAW_PARTS, + &paths::PTR_SLICE_FROM_RAW_PARTS_MUT, + &paths::SLICE_FROM_RAW_PARTS, + &paths::SLICE_FROM_RAW_PARTS_MUT, + ]; + const METHODS: [&str; 11] = [ + "write_bytes", + "copy_to", + "copy_from", + "copy_to_nonoverlapping", + "copy_from_nonoverlapping", + "add", + "wrapping_add", + "sub", + "wrapping_sub", + "offset", + "wrapping_offset", + ]; + + if_chain! { + // Find calls to ptr::{copy, copy_nonoverlapping} + // and ptr::{swap_nonoverlapping, write_bytes}, + if let ExprKind::Call(func, [.., count]) = expr.kind; + if let ExprKind::Path(ref func_qpath) = func.kind; + if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); + if FUNCTIONS.iter().any(|func_path| match_def_path(cx, def_id, func_path)); + + // Get the pointee type + if let Some(pointee_ty) = cx.typeck_results().node_substs(func.hir_id).types().next(); + then { + return Some((pointee_ty, count)); + } + }; + if_chain! { + // Find calls to copy_{from,to}{,_nonoverlapping} and write_bytes methods + if let ExprKind::MethodCall(method_path, _, [ptr_self, .., count], _) = expr.kind; + let method_ident = method_path.ident.as_str(); + if METHODS.iter().any(|m| *m == &*method_ident); + + // Get the pointee type + if let ty::RawPtr(TypeAndMut { ty: pointee_ty, .. }) = + cx.typeck_results().expr_ty(ptr_self).kind(); + then { + return Some((pointee_ty, count)); + } + }; + None +} + +impl<'tcx> LateLintPass<'tcx> for SizeOfInElementCount { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + const HELP_MSG: &str = "use a count of elements instead of a count of bytes\ + , it already gets multiplied by the size of the type"; + + const LINT_MSG: &str = "found a count of bytes \ + instead of a count of elements of `T`"; + + if_chain! { + // Find calls to functions with an element count parameter and get + // the pointee type and count parameter expression + if let Some((pointee_ty, count_expr)) = get_pointee_ty_and_count_expr(cx, expr); + + // Find a size_of call in the count parameter expression and + // check that it's the same type + if let Some(ty_used_for_size_of) = get_size_of_ty(cx, count_expr, false); + if TyS::same_type(pointee_ty, ty_used_for_size_of); + then { + span_lint_and_help( + cx, + SIZE_OF_IN_ELEMENT_COUNT, + count_expr.span, + LINT_MSG, + None, + HELP_MSG + ); + } + }; + } +} diff --git a/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs new file mode 100644 index 0000000000..96f6881556 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs @@ -0,0 +1,330 @@ +use crate::utils::sugg::Sugg; +use crate::utils::{get_enclosing_block, match_qpath, span_lint_and_then, SpanlessEq}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, NestedVisitorMap, Visitor}; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, Lint}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Symbol; + +declare_clippy_lint! { + /// **What it does:** Checks slow zero-filled vector initialization + /// + /// **Why is this bad?** These structures are non-idiomatic and less efficient than simply using + /// `vec![0; len]`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use core::iter::repeat; + /// # let len = 4; + /// + /// // Bad + /// let mut vec1 = Vec::with_capacity(len); + /// vec1.resize(len, 0); + /// + /// let mut vec2 = Vec::with_capacity(len); + /// vec2.extend(repeat(0).take(len)); + /// + /// // Good + /// let mut vec1 = vec![0; len]; + /// let mut vec2 = vec![0; len]; + /// ``` + pub SLOW_VECTOR_INITIALIZATION, + perf, + "slow vector initialization" +} + +declare_lint_pass!(SlowVectorInit => [SLOW_VECTOR_INITIALIZATION]); + +/// `VecAllocation` contains data regarding a vector allocated with `with_capacity` and then +/// assigned to a variable. For example, `let mut vec = Vec::with_capacity(0)` or +/// `vec = Vec::with_capacity(0)` +struct VecAllocation<'tcx> { + /// Symbol of the local variable name + variable_name: Symbol, + + /// Reference to the expression which allocates the vector + allocation_expr: &'tcx Expr<'tcx>, + + /// Reference to the expression used as argument on `with_capacity` call. This is used + /// to only match slow zero-filling idioms of the same length than vector initialization. + len_expr: &'tcx Expr<'tcx>, +} + +/// Type of slow initialization +enum InitializationType<'tcx> { + /// Extend is a slow initialization with the form `vec.extend(repeat(0).take(..))` + Extend(&'tcx Expr<'tcx>), + + /// Resize is a slow initialization with the form `vec.resize(.., 0)` + Resize(&'tcx Expr<'tcx>), +} + +impl<'tcx> LateLintPass<'tcx> for SlowVectorInit { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // Matches initialization on reassignements. For example: `vec = Vec::with_capacity(100)` + if_chain! { + if let ExprKind::Assign(ref left, ref right, _) = expr.kind; + + // Extract variable name + if let ExprKind::Path(QPath::Resolved(_, ref path)) = left.kind; + if let Some(variable_name) = path.segments.get(0); + + // Extract len argument + if let Some(ref len_arg) = Self::is_vec_with_capacity(right); + + then { + let vi = VecAllocation { + variable_name: variable_name.ident.name, + allocation_expr: right, + len_expr: len_arg, + }; + + Self::search_initialization(cx, vi, expr.hir_id); + } + } + } + + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)` + if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let PatKind::Binding(BindingAnnotation::Mutable, .., variable_name, None) = local.pat.kind; + if let Some(ref init) = local.init; + if let Some(ref len_arg) = Self::is_vec_with_capacity(init); + + then { + let vi = VecAllocation { + variable_name: variable_name.name, + allocation_expr: init, + len_expr: len_arg, + }; + + Self::search_initialization(cx, vi, stmt.hir_id); + } + } + } +} + +impl SlowVectorInit { + /// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression + /// of the first argument of `with_capacity` call if it matches or `None` if it does not. + fn is_vec_with_capacity<'tcx>(expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if_chain! { + if let ExprKind::Call(ref func, ref args) = expr.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &["Vec", "with_capacity"]); + if args.len() == 1; + + then { + return Some(&args[0]); + } + } + + None + } + + /// Search initialization for the given vector + fn search_initialization<'tcx>(cx: &LateContext<'tcx>, vec_alloc: VecAllocation<'tcx>, parent_node: HirId) { + let enclosing_body = get_enclosing_block(cx, parent_node); + + if enclosing_body.is_none() { + return; + } + + let mut v = VectorInitializationVisitor { + cx, + vec_alloc, + slow_expression: None, + initialization_found: false, + }; + + v.visit_block(enclosing_body.unwrap()); + + if let Some(ref allocation_expr) = v.slow_expression { + Self::lint_initialization(cx, allocation_expr, &v.vec_alloc); + } + } + + fn lint_initialization<'tcx>( + cx: &LateContext<'tcx>, + initialization: &InitializationType<'tcx>, + vec_alloc: &VecAllocation<'_>, + ) { + match initialization { + InitializationType::Extend(e) | InitializationType::Resize(e) => Self::emit_lint( + cx, + e, + vec_alloc, + "slow zero-filling initialization", + SLOW_VECTOR_INITIALIZATION, + ), + }; + } + + fn emit_lint<'tcx>( + cx: &LateContext<'tcx>, + slow_fill: &Expr<'_>, + vec_alloc: &VecAllocation<'_>, + msg: &str, + lint: &'static Lint, + ) { + let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len"); + + span_lint_and_then(cx, lint, slow_fill.span, msg, |diag| { + diag.span_suggestion( + vec_alloc.allocation_expr.span, + "consider replace allocation with", + format!("vec![0; {}]", len_expr), + Applicability::Unspecified, + ); + }); + } +} + +/// `VectorInitializationVisitor` searches for unsafe or slow vector initializations for the given +/// vector. +struct VectorInitializationVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + + /// Contains the information. + vec_alloc: VecAllocation<'tcx>, + + /// Contains the slow initialization expression, if one was found. + slow_expression: Option>, + + /// `true` if the initialization of the vector has been found on the visited block. + initialization_found: bool, +} + +impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> { + /// Checks if the given expression is extending a vector with `repeat(0).take(..)` + fn search_slow_extend_filling(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + if self.initialization_found; + if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind; + if let ExprKind::Path(ref qpath_subj) = args[0].kind; + if match_qpath(&qpath_subj, &[&*self.vec_alloc.variable_name.as_str()]); + if path.ident.name == sym!(extend); + if let Some(ref extend_arg) = args.get(1); + if self.is_repeat_take(extend_arg); + + then { + self.slow_expression = Some(InitializationType::Extend(expr)); + } + } + } + + /// Checks if the given expression is resizing a vector with 0 + fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + if self.initialization_found; + if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind; + if let ExprKind::Path(ref qpath_subj) = args[0].kind; + if match_qpath(&qpath_subj, &[&*self.vec_alloc.variable_name.as_str()]); + if path.ident.name == sym!(resize); + if let (Some(ref len_arg), Some(fill_arg)) = (args.get(1), args.get(2)); + + // Check that is filled with 0 + if let ExprKind::Lit(ref lit) = fill_arg.kind; + if let LitKind::Int(0, _) = lit.node; + + // Check that len expression is equals to `with_capacity` expression + if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr); + + then { + self.slow_expression = Some(InitializationType::Resize(expr)); + } + } + } + + /// Returns `true` if give expression is `repeat(0).take(...)` + fn is_repeat_take(&self, expr: &Expr<'_>) -> bool { + if_chain! { + if let ExprKind::MethodCall(ref take_path, _, ref take_args, _) = expr.kind; + if take_path.ident.name == sym!(take); + + // Check that take is applied to `repeat(0)` + if let Some(ref repeat_expr) = take_args.get(0); + if Self::is_repeat_zero(repeat_expr); + + // Check that len expression is equals to `with_capacity` expression + if let Some(ref len_arg) = take_args.get(1); + if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr); + + then { + return true; + } + } + + false + } + + /// Returns `true` if given expression is `repeat(0)` + fn is_repeat_zero(expr: &Expr<'_>) -> bool { + if_chain! { + if let ExprKind::Call(ref fn_expr, ref repeat_args) = expr.kind; + if let ExprKind::Path(ref qpath_repeat) = fn_expr.kind; + if match_qpath(&qpath_repeat, &["repeat"]); + if let Some(ref repeat_arg) = repeat_args.get(0); + if let ExprKind::Lit(ref lit) = repeat_arg.kind; + if let LitKind::Int(0, _) = lit.node; + + then { + return true + } + } + + false + } +} + +impl<'a, 'tcx> Visitor<'tcx> for VectorInitializationVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + if self.initialization_found { + match stmt.kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { + self.search_slow_extend_filling(expr); + self.search_slow_resize_filling(expr); + }, + _ => (), + } + + self.initialization_found = false; + } else { + walk_stmt(self, stmt); + } + } + + fn visit_block(&mut self, block: &'tcx Block<'_>) { + if self.initialization_found { + if let Some(ref s) = block.stmts.get(0) { + self.visit_stmt(s) + } + + self.initialization_found = false; + } else { + walk_block(self, block); + } + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + // Skip all the expressions previous to the vector initialization + if self.vec_alloc.allocation_expr.hir_id == expr.hir_id { + self.initialization_found = true; + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/stable_sort_primitive.rs b/src/tools/clippy/clippy_lints/src/stable_sort_primitive.rs new file mode 100644 index 0000000000..276a933881 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/stable_sort_primitive.rs @@ -0,0 +1,139 @@ +use crate::utils::{is_slice_of_primitives, span_lint_and_then, sugg::Sugg}; + +use if_chain::if_chain; + +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** + /// When sorting primitive values (integers, bools, chars, as well + /// as arrays, slices, and tuples of such items), it is better to + /// use an unstable sort than a stable sort. + /// + /// **Why is this bad?** + /// Using a stable sort consumes more memory and cpu cycles. Because + /// values which compare equal are identical, preserving their + /// relative order (the guarantee that a stable sort provides) means + /// nothing, while the extra costs still apply. + /// + /// **Known problems:** + /// None + /// + /// **Example:** + /// + /// ```rust + /// let mut vec = vec![2, 1, 3]; + /// vec.sort(); + /// ``` + /// Use instead: + /// ```rust + /// let mut vec = vec![2, 1, 3]; + /// vec.sort_unstable(); + /// ``` + pub STABLE_SORT_PRIMITIVE, + perf, + "use of sort() when sort_unstable() is equivalent" +} + +declare_lint_pass!(StableSortPrimitive => [STABLE_SORT_PRIMITIVE]); + +/// The three "kinds" of sorts +enum SortingKind { + Vanilla, + /* The other kinds of lint are currently commented out because they + * can map distinct values to equal ones. If the key function is + * provably one-to-one, or if the Cmp function conserves equality, + * then they could be linted on, but I don't know if we can check + * for that. */ + + /* ByKey, + * ByCmp, */ +} +impl SortingKind { + /// The name of the stable version of this kind of sort + fn stable_name(&self) -> &str { + match self { + SortingKind::Vanilla => "sort", + /* SortingKind::ByKey => "sort_by_key", + * SortingKind::ByCmp => "sort_by", */ + } + } + /// The name of the unstable version of this kind of sort + fn unstable_name(&self) -> &str { + match self { + SortingKind::Vanilla => "sort_unstable", + /* SortingKind::ByKey => "sort_unstable_by_key", + * SortingKind::ByCmp => "sort_unstable_by", */ + } + } + /// Takes the name of a function call and returns the kind of sort + /// that corresponds to that function name (or None if it isn't) + fn from_stable_name(name: &str) -> Option { + match name { + "sort" => Some(SortingKind::Vanilla), + // "sort_by" => Some(SortingKind::ByCmp), + // "sort_by_key" => Some(SortingKind::ByKey), + _ => None, + } + } +} + +/// A detected instance of this lint +struct LintDetection { + slice_name: String, + method: SortingKind, + method_args: String, + slice_type: String, +} + +fn detect_stable_sort_primitive(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { + if_chain! { + if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind; + if let Some(slice) = &args.get(0); + if let Some(method) = SortingKind::from_stable_name(&method_name.ident.name.as_str()); + if let Some(slice_type) = is_slice_of_primitives(cx, slice); + then { + let args_str = args.iter().skip(1).map(|arg| Sugg::hir(cx, arg, "..").to_string()).collect::>().join(", "); + Some(LintDetection { slice_name: Sugg::hir(cx, slice, "..").to_string(), method, method_args: args_str, slice_type }) + } else { + None + } + } +} + +impl LateLintPass<'_> for StableSortPrimitive { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if let Some(detection) = detect_stable_sort_primitive(cx, expr) { + span_lint_and_then( + cx, + STABLE_SORT_PRIMITIVE, + expr.span, + format!( + "used `{}` on primitive type `{}`", + detection.method.stable_name(), + detection.slice_type, + ) + .as_str(), + |diag| { + diag.span_suggestion( + expr.span, + "try", + format!( + "{}.{}({})", + detection.slice_name, + detection.method.unstable_name(), + detection.method_args, + ), + Applicability::MachineApplicable, + ); + diag.note( + "an unstable sort would perform faster without any observable difference for this data type", + ); + }, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/strings.rs b/src/tools/clippy/clippy_lints/src/strings.rs new file mode 100644 index 0000000000..31dd596547 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/strings.rs @@ -0,0 +1,388 @@ +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; +use rustc_span::sym; + +use if_chain::if_chain; + +use crate::utils::SpanlessEq; +use crate::utils::{ + get_parent_expr, is_allowed, is_type_diagnostic_item, match_function_call, method_calls, paths, span_lint, + span_lint_and_help, span_lint_and_sugg, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for string appends of the form `x = x + y` (without + /// `let`!). + /// + /// **Why is this bad?** It's not really bad, but some people think that the + /// `.push_str(_)` method is more readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let mut x = "Hello".to_owned(); + /// x = x + ", World"; + /// + /// // More readable + /// x += ", World"; + /// x.push_str(", World"); + /// ``` + pub STRING_ADD_ASSIGN, + pedantic, + "using `x = x + ..` where x is a `String` instead of `push_str()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for all instances of `x + _` where `x` is of type + /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not* + /// match. + /// + /// **Why is this bad?** It's not bad in and of itself. However, this particular + /// `Add` implementation is asymmetric (the other operand need not be `String`, + /// but `x` does), while addition as mathematically defined is symmetric, also + /// the `String::push_str(_)` function is a perfectly good replacement. + /// Therefore, some dislike it and wish not to have it in their code. + /// + /// That said, other people think that string addition, having a long tradition + /// in other languages is actually fine, which is why we decided to make this + /// particular lint `allow` by default. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x = "Hello".to_owned(); + /// x + ", World"; + /// ``` + pub STRING_ADD, + restriction, + "using `x + ..` where x is a `String` instead of `push_str()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the `as_bytes` method called on string literals + /// that contain only ASCII characters. + /// + /// **Why is this bad?** Byte string literals (e.g., `b"foo"`) can be used + /// instead. They are shorter but less discoverable than `as_bytes()`. + /// + /// **Known Problems:** + /// `"str".as_bytes()` and the suggested replacement of `b"str"` are not + /// equivalent because they have different types. The former is `&[u8]` + /// while the latter is `&[u8; 3]`. That means in general they will have a + /// different set of methods and different trait implementations. + /// + /// ```compile_fail + /// fn f(v: Vec) {} + /// + /// f("...".as_bytes().to_owned()); // works + /// f(b"...".to_owned()); // does not work, because arg is [u8; 3] not Vec + /// + /// fn g(r: impl std::io::Read) {} + /// + /// g("...".as_bytes()); // works + /// g(b"..."); // does not work + /// ``` + /// + /// The actual equivalent of `"str".as_bytes()` with the same type is not + /// `b"str"` but `&b"str"[..]`, which is a great deal of punctuation and not + /// more readable than a function call. + /// + /// **Example:** + /// ```rust + /// // Bad + /// let bs = "a byte string".as_bytes(); + /// + /// // Good + /// let bs = b"a byte string"; + /// ``` + pub STRING_LIT_AS_BYTES, + nursery, + "calling `as_bytes` on a string literal instead of using a byte string literal" +} + +declare_lint_pass!(StringAdd => [STRING_ADD, STRING_ADD_ASSIGN]); + +impl<'tcx> LateLintPass<'tcx> for StringAdd { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), e.span) { + return; + } + + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + ref left, + _, + ) = e.kind + { + if is_string(cx, left) { + if !is_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) { + let parent = get_parent_expr(cx, e); + if let Some(p) = parent { + if let ExprKind::Assign(ref target, _, _) = p.kind { + // avoid duplicate matches + if SpanlessEq::new(cx).eq_expr(target, left) { + return; + } + } + } + } + span_lint( + cx, + STRING_ADD, + e.span, + "you added something to a string. Consider using `String::push_str()` instead", + ); + } + } else if let ExprKind::Assign(ref target, ref src, _) = e.kind { + if is_string(cx, target) && is_add(cx, src, target) { + span_lint( + cx, + STRING_ADD_ASSIGN, + e.span, + "you assigned the result of adding something to this string. Consider using \ + `String::push_str()` instead", + ); + } + } + } +} + +fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), sym::string_type) +} + +fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool { + match src.kind { + ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + ref left, + _, + ) => SpanlessEq::new(cx).eq_expr(target, left), + ExprKind::Block(ref block, _) => { + block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| is_add(cx, expr, target)) + }, + _ => false, + } +} + +declare_clippy_lint! { + /// **What it does:** Check if the string is transformed to byte array and casted back to string. + /// + /// **Why is this bad?** It's unnecessary, the string can be used directly. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap(); + /// ``` + /// could be written as + /// ```rust + /// let _ = &"Hello World!"[6..11]; + /// ``` + pub STRING_FROM_UTF8_AS_BYTES, + complexity, + "casting string slices to byte slices and back" +} + +// Max length a b"foo" string can take +const MAX_LENGTH_BYTE_STRING_LIT: usize = 32; + +declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES, STRING_FROM_UTF8_AS_BYTES]); + +impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + use crate::utils::{snippet, snippet_with_applicability}; + use rustc_ast::LitKind; + + if_chain! { + // Find std::str::converts::from_utf8 + if let Some(args) = match_function_call(cx, e, &paths::STR_FROM_UTF8); + + // Find string::as_bytes + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref args) = args[0].kind; + if let ExprKind::Index(ref left, ref right) = args.kind; + let (method_names, expressions, _) = method_calls(left, 1); + if method_names.len() == 1; + if expressions.len() == 1; + if expressions[0].len() == 1; + if method_names[0] == sym!(as_bytes); + + // Check for slicer + if let ExprKind::Struct(QPath::LangItem(LangItem::Range, _), _, _) = right.kind; + + then { + let mut applicability = Applicability::MachineApplicable; + let string_expression = &expressions[0][0]; + + let snippet_app = snippet_with_applicability( + cx, + string_expression.span, "..", + &mut applicability, + ); + + span_lint_and_sugg( + cx, + STRING_FROM_UTF8_AS_BYTES, + e.span, + "calling a slice of `as_bytes()` with `from_utf8` should be not necessary", + "try", + format!("Some(&{}[{}])", snippet_app, snippet(cx, right.span, "..")), + applicability + ) + } + } + + if_chain! { + if let ExprKind::MethodCall(path, _, args, _) = &e.kind; + if path.ident.name == sym!(as_bytes); + if let ExprKind::Lit(lit) = &args[0].kind; + if let LitKind::Str(lit_content, _) = &lit.node; + then { + let callsite = snippet(cx, args[0].span.source_callsite(), r#""foo""#); + let mut applicability = Applicability::MachineApplicable; + if callsite.starts_with("include_str!") { + span_lint_and_sugg( + cx, + STRING_LIT_AS_BYTES, + e.span, + "calling `as_bytes()` on `include_str!(..)`", + "consider using `include_bytes!(..)` instead", + snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability).replacen( + "include_str", + "include_bytes", + 1, + ), + applicability, + ); + } else if lit_content.as_str().is_ascii() + && lit_content.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT + && !args[0].span.from_expansion() + { + span_lint_and_sugg( + cx, + STRING_LIT_AS_BYTES, + e.span, + "calling `as_bytes()` on a string literal", + "consider using a byte string literal instead", + format!( + "b{}", + snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability) + ), + applicability, + ); + } + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** This lint checks for `.to_string()` method calls on values of type `&str`. + /// + /// **Why is this bad?** The `to_string` method is also used on other types to convert them to a string. + /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be better + /// expressed with `.to_owned()`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // example code where clippy issues a warning + /// let _ = "str".to_string(); + /// ``` + /// Use instead: + /// ```rust + /// // example code which does not raise clippy warning + /// let _ = "str".to_owned(); + /// ``` + pub STR_TO_STRING, + restriction, + "using `to_string()` on a `&str`, which should be `to_owned()`" +} + +declare_lint_pass!(StrToString => [STR_TO_STRING]); + +impl LateLintPass<'_> for StrToString { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::MethodCall(path, _, args, _) = &expr.kind; + if path.ident.name == sym!(to_string); + let ty = cx.typeck_results().expr_ty(&args[0]); + if let ty::Ref(_, ty, ..) = ty.kind(); + if *ty.kind() == ty::Str; + then { + span_lint_and_help( + cx, + STR_TO_STRING, + expr.span, + "`to_string()` called on a `&str`", + None, + "consider using `.to_owned()`", + ); + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** This lint checks for `.to_string()` method calls on values of type `String`. + /// + /// **Why is this bad?** The `to_string` method is also used on other types to convert them to a string. + /// When called on a `String` it only clones the `String`, which can be better expressed with `.clone()`. + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // example code where clippy issues a warning + /// let msg = String::from("Hello World"); + /// let _ = msg.to_string(); + /// ``` + /// Use instead: + /// ```rust + /// // example code which does not raise clippy warning + /// let msg = String::from("Hello World"); + /// let _ = msg.clone(); + /// ``` + pub STRING_TO_STRING, + restriction, + "using `to_string()` on a `String`, which should be `clone()`" +} + +declare_lint_pass!(StringToString => [STRING_TO_STRING]); + +impl LateLintPass<'_> for StringToString { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::MethodCall(path, _, args, _) = &expr.kind; + if path.ident.name == sym!(to_string); + let ty = cx.typeck_results().expr_ty(&args[0]); + if is_type_diagnostic_item(cx, ty, sym::string_type); + then { + span_lint_and_help( + cx, + STRING_TO_STRING, + expr.span, + "`to_string()` called on a `String`", + None, + "consider using `.clone()`", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs b/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs new file mode 100644 index 0000000000..9acc47deb0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs @@ -0,0 +1,693 @@ +use crate::utils::ast_utils::{eq_id, is_useless_with_eq_exprs, IdentIter}; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use core::ops::{Add, AddAssign}; +use if_chain::if_chain; +use rustc_ast::ast::{BinOpKind, Expr, ExprKind, StmtKind}; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; +use rustc_span::symbol::Ident; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** + /// Checks for unlikely usages of binary operators that are almost + /// certainly typos and/or copy/paste errors, given the other usages + /// of binary operators nearby. + /// **Why is this bad?** + /// They are probably bugs and if they aren't then they look like bugs + /// and you should add a comment explaining why you are doing such an + /// odd set of operations. + /// **Known problems:** + /// There may be some false positives if you are trying to do something + /// unusual that happens to look like a typo. + /// + /// **Example:** + /// + /// ```rust + /// struct Vec3 { + /// x: f64, + /// y: f64, + /// z: f64, + /// } + /// + /// impl Eq for Vec3 {} + /// + /// impl PartialEq for Vec3 { + /// fn eq(&self, other: &Self) -> bool { + /// // This should trigger the lint because `self.x` is compared to `other.y` + /// self.x == other.y && self.y == other.y && self.z == other.z + /// } + /// } + /// ``` + /// Use instead: + /// ```rust + /// # struct Vec3 { + /// # x: f64, + /// # y: f64, + /// # z: f64, + /// # } + /// // same as above except: + /// impl PartialEq for Vec3 { + /// fn eq(&self, other: &Self) -> bool { + /// // Note we now compare other.x to self.x + /// self.x == other.x && self.y == other.y && self.z == other.z + /// } + /// } + /// ``` + pub SUSPICIOUS_OPERATION_GROUPINGS, + style, + "groupings of binary operations that look suspiciously like typos" +} + +declare_lint_pass!(SuspiciousOperationGroupings => [SUSPICIOUS_OPERATION_GROUPINGS]); + +impl EarlyLintPass for SuspiciousOperationGroupings { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if expr.span.from_expansion() { + return; + } + + if let Some(binops) = extract_related_binops(&expr.kind) { + check_binops(cx, &binops.iter().collect::>()); + + let mut op_types = Vec::with_capacity(binops.len()); + // We could use a hashmap, etc. to avoid being O(n*m) here, but + // we want the lints to be emitted in a consistent order. Besides, + // m, (the number of distinct `BinOpKind`s in `binops`) + // will often be small, and does have an upper limit. + binops.iter().map(|b| b.op).for_each(|op| { + if !op_types.contains(&op) { + op_types.push(op); + } + }); + + for op_type in op_types { + let ops: Vec<_> = binops.iter().filter(|b| b.op == op_type).collect(); + + check_binops(cx, &ops); + } + } + } +} + +fn check_binops(cx: &EarlyContext<'_>, binops: &[&BinaryOp<'_>]) { + let binop_count = binops.len(); + if binop_count < 2 { + // Single binary operation expressions would likely be false + // positives. + return; + } + + let mut one_ident_difference_count = 0; + let mut no_difference_info = None; + let mut double_difference_info = None; + let mut expected_ident_loc = None; + + let mut paired_identifiers = FxHashSet::default(); + + for (i, BinaryOp { left, right, op, .. }) in binops.iter().enumerate() { + match ident_difference_expr(left, right) { + IdentDifference::NoDifference => { + if is_useless_with_eq_exprs(*op) { + // The `eq_op` lint should catch this in this case. + return; + } + + no_difference_info = Some(i); + }, + IdentDifference::Single(ident_loc) => { + one_ident_difference_count += 1; + if let Some(previous_expected) = expected_ident_loc { + if previous_expected != ident_loc { + // This expression doesn't match the form we're + // looking for. + return; + } + } else { + expected_ident_loc = Some(ident_loc); + } + + // If there was only a single difference, all other idents + // must have been the same, and thus were paired. + for id in skip_index(IdentIter::from(*left), ident_loc.index) { + paired_identifiers.insert(id); + } + }, + IdentDifference::Double(ident_loc1, ident_loc2) => { + double_difference_info = Some((i, ident_loc1, ident_loc2)); + }, + IdentDifference::Multiple | IdentDifference::NonIdent => { + // It's too hard to know whether this is a bug or not. + return; + }, + } + } + + let mut applicability = Applicability::MachineApplicable; + + if let Some(expected_loc) = expected_ident_loc { + match (no_difference_info, double_difference_info) { + (Some(i), None) => attempt_to_emit_no_difference_lint(cx, binops, i, expected_loc), + (None, Some((double_difference_index, ident_loc1, ident_loc2))) => { + if_chain! { + if one_ident_difference_count == binop_count - 1; + if let Some(binop) = binops.get(double_difference_index); + then { + let changed_loc = if ident_loc1 == expected_loc { + ident_loc2 + } else if ident_loc2 == expected_loc { + ident_loc1 + } else { + // This expression doesn't match the form we're + // looking for. + return; + }; + + if let Some(sugg) = ident_swap_sugg( + cx, + &paired_identifiers, + binop, + changed_loc, + &mut applicability, + ) { + emit_suggestion( + cx, + binop.span, + sugg, + applicability, + ); + } + } + } + }, + _ => {}, + } + } +} + +fn attempt_to_emit_no_difference_lint( + cx: &EarlyContext<'_>, + binops: &[&BinaryOp<'_>], + i: usize, + expected_loc: IdentLocation, +) { + if let Some(binop) = binops.get(i).cloned() { + // We need to try and figure out which identifier we should + // suggest using instead. Since there could be multiple + // replacement candidates in a given expression, and we're + // just taking the first one, we may get some bad lint + // messages. + let mut applicability = Applicability::MaybeIncorrect; + + // We assume that the correct ident is one used elsewhere in + // the other binops, in a place that there was a single + // difference between idents before. + let old_left_ident = get_ident(binop.left, expected_loc); + let old_right_ident = get_ident(binop.right, expected_loc); + + for b in skip_index(binops.iter(), i) { + if_chain! { + if let (Some(old_ident), Some(new_ident)) = + (old_left_ident, get_ident(b.left, expected_loc)); + if old_ident != new_ident; + if let Some(sugg) = suggestion_with_swapped_ident( + cx, + binop.left, + expected_loc, + new_ident, + &mut applicability, + ); + then { + emit_suggestion( + cx, + binop.span, + replace_left_sugg(cx, &binop, &sugg, &mut applicability), + applicability, + ); + return; + } + } + + if_chain! { + if let (Some(old_ident), Some(new_ident)) = + (old_right_ident, get_ident(b.right, expected_loc)); + if old_ident != new_ident; + if let Some(sugg) = suggestion_with_swapped_ident( + cx, + binop.right, + expected_loc, + new_ident, + &mut applicability, + ); + then { + emit_suggestion( + cx, + binop.span, + replace_right_sugg(cx, &binop, &sugg, &mut applicability), + applicability, + ); + return; + } + } + } + } +} + +fn emit_suggestion(cx: &EarlyContext<'_>, span: Span, sugg: String, applicability: Applicability) { + span_lint_and_sugg( + cx, + SUSPICIOUS_OPERATION_GROUPINGS, + span, + "this sequence of operators looks suspiciously like a bug", + "did you mean", + sugg, + applicability, + ) +} + +fn ident_swap_sugg( + cx: &EarlyContext<'_>, + paired_identifiers: &FxHashSet, + binop: &BinaryOp<'_>, + location: IdentLocation, + applicability: &mut Applicability, +) -> Option { + let left_ident = get_ident(&binop.left, location)?; + let right_ident = get_ident(&binop.right, location)?; + + let sugg = match ( + paired_identifiers.contains(&left_ident), + paired_identifiers.contains(&right_ident), + ) { + (true, true) | (false, false) => { + // We don't have a good guess of what ident should be + // used instead, in these cases. + *applicability = Applicability::MaybeIncorrect; + + // We arbitraily choose one side to suggest changing, + // since we don't have a better guess. If the user + // ends up duplicating a clause, the `logic_bug` lint + // should catch it. + + let right_suggestion = + suggestion_with_swapped_ident(cx, &binop.right, location, left_ident, applicability)?; + + replace_right_sugg(cx, binop, &right_suggestion, applicability) + }, + (false, true) => { + // We haven't seen a pair involving the left one, so + // it's probably what is wanted. + + let right_suggestion = + suggestion_with_swapped_ident(cx, &binop.right, location, left_ident, applicability)?; + + replace_right_sugg(cx, binop, &right_suggestion, applicability) + }, + (true, false) => { + // We haven't seen a pair involving the right one, so + // it's probably what is wanted. + let left_suggestion = suggestion_with_swapped_ident(cx, &binop.left, location, right_ident, applicability)?; + + replace_left_sugg(cx, binop, &left_suggestion, applicability) + }, + }; + + Some(sugg) +} + +fn replace_left_sugg( + cx: &EarlyContext<'_>, + binop: &BinaryOp<'_>, + left_suggestion: &str, + applicability: &mut Applicability, +) -> String { + format!( + "{} {} {}", + left_suggestion, + binop.op.to_string(), + snippet_with_applicability(cx, binop.right.span, "..", applicability), + ) +} + +fn replace_right_sugg( + cx: &EarlyContext<'_>, + binop: &BinaryOp<'_>, + right_suggestion: &str, + applicability: &mut Applicability, +) -> String { + format!( + "{} {} {}", + snippet_with_applicability(cx, binop.left.span, "..", applicability), + binop.op.to_string(), + right_suggestion, + ) +} + +#[derive(Clone, Debug)] +struct BinaryOp<'exprs> { + op: BinOpKind, + span: Span, + left: &'exprs Expr, + right: &'exprs Expr, +} + +impl BinaryOp<'exprs> { + fn new(op: BinOpKind, span: Span, (left, right): (&'exprs Expr, &'exprs Expr)) -> Self { + Self { op, span, left, right } + } +} + +fn strip_non_ident_wrappers(expr: &Expr) -> &Expr { + let mut output = expr; + loop { + output = match &output.kind { + ExprKind::Paren(ref inner) | ExprKind::Unary(_, ref inner) => inner, + _ => { + return output; + }, + }; + } +} + +fn extract_related_binops(kind: &ExprKind) -> Option>> { + append_opt_vecs(chained_binops(kind), if_statment_binops(kind)) +} + +fn if_statment_binops(kind: &ExprKind) -> Option>> { + match kind { + ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind), + ExprKind::Paren(ref e) => if_statment_binops(&e.kind), + ExprKind::Block(ref block, _) => { + let mut output = None; + for stmt in &block.stmts { + match stmt.kind { + StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => { + output = append_opt_vecs(output, if_statment_binops(&e.kind)); + }, + _ => {}, + } + } + output + }, + _ => None, + } +} + +fn append_opt_vecs(target_opt: Option>, source_opt: Option>) -> Option> { + match (target_opt, source_opt) { + (Some(mut target), Some(mut source)) => { + target.reserve(source.len()); + for op in source.drain(..) { + target.push(op); + } + Some(target) + }, + (Some(v), None) | (None, Some(v)) => Some(v), + (None, None) => None, + } +} + +fn chained_binops(kind: &ExprKind) -> Option>> { + match kind { + ExprKind::Binary(_, left_outer, right_outer) => chained_binops_helper(left_outer, right_outer), + ExprKind::Paren(ref e) | ExprKind::Unary(_, ref e) => chained_binops(&e.kind), + _ => None, + } +} + +fn chained_binops_helper(left_outer: &'expr Expr, right_outer: &'expr Expr) -> Option>> { + match (&left_outer.kind, &right_outer.kind) { + ( + ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), + ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e), + ) => chained_binops_helper(left_e, right_e), + (ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), _) => chained_binops_helper(left_e, right_outer), + (_, ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e)) => { + chained_binops_helper(left_outer, right_e) + }, + ( + ExprKind::Binary(Spanned { node: left_op, .. }, ref left_left, ref left_right), + ExprKind::Binary(Spanned { node: right_op, .. }, ref right_left, ref right_right), + ) => match ( + chained_binops_helper(left_left, left_right), + chained_binops_helper(right_left, right_right), + ) { + (Some(mut left_ops), Some(mut right_ops)) => { + left_ops.reserve(right_ops.len()); + for op in right_ops.drain(..) { + left_ops.push(op); + } + Some(left_ops) + }, + (Some(mut left_ops), _) => { + left_ops.push(BinaryOp::new(*right_op, right_outer.span, (right_left, right_right))); + Some(left_ops) + }, + (_, Some(mut right_ops)) => { + right_ops.insert(0, BinaryOp::new(*left_op, left_outer.span, (left_left, left_right))); + Some(right_ops) + }, + (None, None) => Some(vec![ + BinaryOp::new(*left_op, left_outer.span, (left_left, left_right)), + BinaryOp::new(*right_op, right_outer.span, (right_left, right_right)), + ]), + }, + _ => None, + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +struct IdentLocation { + index: usize, +} + +impl Add for IdentLocation { + type Output = IdentLocation; + + fn add(self, other: Self) -> Self::Output { + Self { + index: self.index + other.index, + } + } +} + +impl AddAssign for IdentLocation { + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +#[derive(Clone, Copy, Debug)] +enum IdentDifference { + NoDifference, + Single(IdentLocation), + Double(IdentLocation, IdentLocation), + Multiple, + NonIdent, +} + +impl Add for IdentDifference { + type Output = IdentDifference; + + fn add(self, other: Self) -> Self::Output { + match (self, other) { + (Self::NoDifference, output) | (output, Self::NoDifference) => output, + (Self::Multiple, _) + | (_, Self::Multiple) + | (Self::Double(_, _), Self::Single(_)) + | (Self::Single(_) | Self::Double(_, _), Self::Double(_, _)) => Self::Multiple, + (Self::NonIdent, _) | (_, Self::NonIdent) => Self::NonIdent, + (Self::Single(il1), Self::Single(il2)) => Self::Double(il1, il2), + } + } +} + +impl AddAssign for IdentDifference { + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +impl IdentDifference { + /// Returns true if learning about more differences will not change the value + /// of this `IdentDifference`, and false otherwise. + fn is_complete(&self) -> bool { + match self { + Self::NoDifference | Self::Single(_) | Self::Double(_, _) => false, + Self::Multiple | Self::NonIdent => true, + } + } +} + +fn ident_difference_expr(left: &Expr, right: &Expr) -> IdentDifference { + ident_difference_expr_with_base_location(left, right, IdentLocation::default()).0 +} + +fn ident_difference_expr_with_base_location( + left: &Expr, + right: &Expr, + mut base: IdentLocation, +) -> (IdentDifference, IdentLocation) { + // Ideally, this function should not use IdentIter because it should return + // early if the expressions have any non-ident differences. We want that early + // return because if without that restriction the lint would lead to false + // positives. + // + // But, we cannot (easily?) use a `rustc_ast::visit::Visitor`, since we need + // the two expressions to be walked in lockstep. And without a `Visitor`, we'd + // have to do all the AST traversal ourselves, which is a lot of work, since to + // do it properly we'd need to be able to handle more or less every possible + // AST node since `Item`s can be written inside `Expr`s. + // + // In practice, it seems likely that expressions, above a certain size, that + // happen to use the exact same idents in the exact same order, and which are + // not structured the same, would be rare. Therefore it seems likely that if + // we do only the first layer of matching ourselves and eventually fallback on + // IdentIter, then the output of this function will be almost always be correct + // in practice. + // + // If it turns out that problematic cases are more prelavent than we assume, + // then we should be able to change this function to do the correct traversal, + // without needing to change the rest of the code. + + #![allow(clippy::enum_glob_use)] + use ExprKind::*; + + match ( + &strip_non_ident_wrappers(left).kind, + &strip_non_ident_wrappers(right).kind, + ) { + (Yield(_), Yield(_)) + | (Try(_), Try(_)) + | (Paren(_), Paren(_)) + | (Repeat(_, _), Repeat(_, _)) + | (Struct(_), Struct(_)) + | (MacCall(_), MacCall(_)) + | (LlvmInlineAsm(_), LlvmInlineAsm(_)) + | (InlineAsm(_), InlineAsm(_)) + | (Ret(_), Ret(_)) + | (Continue(_), Continue(_)) + | (Break(_, _), Break(_, _)) + | (AddrOf(_, _, _), AddrOf(_, _, _)) + | (Path(_, _), Path(_, _)) + | (Range(_, _, _), Range(_, _, _)) + | (Index(_, _), Index(_, _)) + | (Field(_, _), Field(_, _)) + | (AssignOp(_, _, _), AssignOp(_, _, _)) + | (Assign(_, _, _), Assign(_, _, _)) + | (TryBlock(_), TryBlock(_)) + | (Await(_), Await(_)) + | (Async(_, _, _), Async(_, _, _)) + | (Block(_, _), Block(_, _)) + | (Closure(_, _, _, _, _, _), Closure(_, _, _, _, _, _)) + | (Match(_, _), Match(_, _)) + | (Loop(_, _), Loop(_, _)) + | (ForLoop(_, _, _, _), ForLoop(_, _, _, _)) + | (While(_, _, _), While(_, _, _)) + | (If(_, _, _), If(_, _, _)) + | (Let(_, _), Let(_, _)) + | (Type(_, _), Type(_, _)) + | (Cast(_, _), Cast(_, _)) + | (Lit(_), Lit(_)) + | (Unary(_, _), Unary(_, _)) + | (Binary(_, _, _), Binary(_, _, _)) + | (Tup(_), Tup(_)) + | (MethodCall(_, _, _), MethodCall(_, _, _)) + | (Call(_, _), Call(_, _)) + | (ConstBlock(_), ConstBlock(_)) + | (Array(_), Array(_)) + | (Box(_), Box(_)) => { + // keep going + }, + _ => { + return (IdentDifference::NonIdent, base); + }, + } + + let mut difference = IdentDifference::NoDifference; + + for (left_attr, right_attr) in left.attrs.iter().zip(right.attrs.iter()) { + let (new_difference, new_base) = + ident_difference_via_ident_iter_with_base_location(left_attr, right_attr, base); + base = new_base; + difference += new_difference; + if difference.is_complete() { + return (difference, base); + } + } + + let (new_difference, new_base) = ident_difference_via_ident_iter_with_base_location(left, right, base); + base = new_base; + difference += new_difference; + + (difference, base) +} + +fn ident_difference_via_ident_iter_with_base_location>( + left: Iterable, + right: Iterable, + mut base: IdentLocation, +) -> (IdentDifference, IdentLocation) { + // See the note in `ident_difference_expr_with_base_location` about `IdentIter` + let mut difference = IdentDifference::NoDifference; + + let mut left_iterator = left.into(); + let mut right_iterator = right.into(); + + loop { + match (left_iterator.next(), right_iterator.next()) { + (Some(left_ident), Some(right_ident)) => { + if !eq_id(left_ident, right_ident) { + difference += IdentDifference::Single(base); + if difference.is_complete() { + return (difference, base); + } + } + }, + (Some(_), None) | (None, Some(_)) => { + return (IdentDifference::NonIdent, base); + }, + (None, None) => { + return (difference, base); + }, + } + base += IdentLocation { index: 1 }; + } +} + +fn get_ident(expr: &Expr, location: IdentLocation) -> Option { + IdentIter::from(expr).nth(location.index) +} + +fn suggestion_with_swapped_ident( + cx: &EarlyContext<'_>, + expr: &Expr, + location: IdentLocation, + new_ident: Ident, + applicability: &mut Applicability, +) -> Option { + get_ident(expr, location).and_then(|current_ident| { + if eq_id(current_ident, new_ident) { + // We never want to suggest a non-change + return None; + } + + Some(format!( + "{}{}{}", + snippet_with_applicability(cx, expr.span.with_hi(current_ident.span.lo()), "..", applicability), + new_ident.to_string(), + snippet_with_applicability(cx, expr.span.with_lo(current_ident.span.hi()), "..", applicability), + )) + }) +} + +fn skip_index(iter: Iter, index: usize) -> impl Iterator +where + Iter: Iterator, +{ + iter.enumerate() + .filter_map(move |(i, a)| if i == index { None } else { Some(a) }) +} diff --git a/src/tools/clippy/clippy_lints/src/suspicious_trait_impl.rs b/src/tools/clippy/clippy_lints/src/suspicious_trait_impl.rs new file mode 100644 index 0000000000..0b7d08cb16 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/suspicious_trait_impl.rs @@ -0,0 +1,208 @@ +use crate::utils::{get_trait_def_id, span_lint, trait_ref_of_method}; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Lints for suspicious operations in impls of arithmetic operators, e.g. + /// subtracting elements in an Add impl. + /// + /// **Why this is bad?** This is probably a typo or copy-and-paste error and not intended. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// impl Add for Foo { + /// type Output = Foo; + /// + /// fn add(self, other: Foo) -> Foo { + /// Foo(self.0 - other.0) + /// } + /// } + /// ``` + pub SUSPICIOUS_ARITHMETIC_IMPL, + correctness, + "suspicious use of operators in impl of arithmetic trait" +} + +declare_clippy_lint! { + /// **What it does:** Lints for suspicious operations in impls of OpAssign, e.g. + /// subtracting elements in an AddAssign impl. + /// + /// **Why this is bad?** This is probably a typo or copy-and-paste error and not intended. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// impl AddAssign for Foo { + /// fn add_assign(&mut self, other: Foo) { + /// *self = *self - other; + /// } + /// } + /// ``` + pub SUSPICIOUS_OP_ASSIGN_IMPL, + correctness, + "suspicious use of operators in impl of OpAssign trait" +} + +declare_lint_pass!(SuspiciousImpl => [SUSPICIOUS_ARITHMETIC_IMPL, SUSPICIOUS_OP_ASSIGN_IMPL]); + +impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if let hir::ExprKind::Binary(binop, _, _) | hir::ExprKind::AssignOp(binop, ..) = expr.kind { + match binop.node { + hir::BinOpKind::Eq + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ne + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt => return, + _ => {}, + } + + // Check for more than one binary operation in the implemented function + // Linting when multiple operations are involved can result in false positives + if_chain! { + let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id); + if let hir::Node::ImplItem(impl_item) = cx.tcx.hir().get(parent_fn); + if let hir::ImplItemKind::Fn(_, body_id) = impl_item.kind; + let body = cx.tcx.hir().body(body_id); + let mut visitor = BinaryExprVisitor { nb_binops: 0 }; + + then { + walk_expr(&mut visitor, &body.value); + if visitor.nb_binops > 1 { + return; + } + } + } + + if let Some(impl_trait) = check_binop( + cx, + expr, + binop.node, + &[ + "Add", "Sub", "Mul", "Div", "Rem", "BitAnd", "BitOr", "BitXor", "Shl", "Shr", + ], + &[ + hir::BinOpKind::Add, + hir::BinOpKind::Sub, + hir::BinOpKind::Mul, + hir::BinOpKind::Div, + hir::BinOpKind::Rem, + hir::BinOpKind::BitAnd, + hir::BinOpKind::BitOr, + hir::BinOpKind::BitXor, + hir::BinOpKind::Shl, + hir::BinOpKind::Shr, + ], + ) { + span_lint( + cx, + SUSPICIOUS_ARITHMETIC_IMPL, + binop.span, + &format!("suspicious use of binary operator in `{}` impl", impl_trait), + ); + } + + if let Some(impl_trait) = check_binop( + cx, + expr, + binop.node, + &[ + "AddAssign", + "SubAssign", + "MulAssign", + "DivAssign", + "BitAndAssign", + "BitOrAssign", + "BitXorAssign", + "RemAssign", + "ShlAssign", + "ShrAssign", + ], + &[ + hir::BinOpKind::Add, + hir::BinOpKind::Sub, + hir::BinOpKind::Mul, + hir::BinOpKind::Div, + hir::BinOpKind::BitAnd, + hir::BinOpKind::BitOr, + hir::BinOpKind::BitXor, + hir::BinOpKind::Rem, + hir::BinOpKind::Shl, + hir::BinOpKind::Shr, + ], + ) { + span_lint( + cx, + SUSPICIOUS_OP_ASSIGN_IMPL, + binop.span, + &format!("suspicious use of binary operator in `{}` impl", impl_trait), + ); + } + } + } +} + +fn check_binop( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + binop: hir::BinOpKind, + traits: &[&'static str], + expected_ops: &[hir::BinOpKind], +) -> Option<&'static str> { + let mut trait_ids = vec![]; + let [krate, module] = crate::utils::paths::OPS_MODULE; + + for &t in traits { + let path = [krate, module, t]; + if let Some(trait_id) = get_trait_def_id(cx, &path) { + trait_ids.push(trait_id); + } else { + return None; + } + } + + // Get the actually implemented trait + let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id); + + if_chain! { + if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn); + if let Some(idx) = trait_ids.iter().position(|&tid| tid == trait_ref.path.res.def_id()); + if binop != expected_ops[idx]; + then{ + return Some(traits[idx]) + } + } + + None +} + +struct BinaryExprVisitor { + nb_binops: u32, +} + +impl<'tcx> Visitor<'tcx> for BinaryExprVisitor { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + match expr.kind { + hir::ExprKind::Binary(..) + | hir::ExprKind::Unary(hir::UnOp::Not | hir::UnOp::Neg, _) + | hir::ExprKind::AssignOp(..) => self.nb_binops += 1, + _ => {}, + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/swap.rs b/src/tools/clippy/clippy_lints/src/swap.rs new file mode 100644 index 0000000000..9d8a0c2483 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/swap.rs @@ -0,0 +1,263 @@ +use crate::utils::sugg::Sugg; +use crate::utils::{ + differing_macro_contexts, eq_expr_value, is_type_diagnostic_item, snippet_with_applicability, span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for manual swapping. + /// + /// **Why is this bad?** The `std::mem::swap` function exposes the intent better + /// without deinitializing or copying either variable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let mut a = 42; + /// let mut b = 1337; + /// + /// let t = b; + /// b = a; + /// a = t; + /// ``` + /// Use std::mem::swap(): + /// ```rust + /// let mut a = 1; + /// let mut b = 2; + /// std::mem::swap(&mut a, &mut b); + /// ``` + pub MANUAL_SWAP, + complexity, + "manual swap of two variables" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `foo = bar; bar = foo` sequences. + /// + /// **Why is this bad?** This looks like a failed attempt to swap. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let mut a = 1; + /// # let mut b = 2; + /// a = b; + /// b = a; + /// ``` + /// If swapping is intended, use `swap()` instead: + /// ```rust + /// # let mut a = 1; + /// # let mut b = 2; + /// std::mem::swap(&mut a, &mut b); + /// ``` + pub ALMOST_SWAPPED, + correctness, + "`foo = bar; bar = foo` sequence" +} + +declare_lint_pass!(Swap => [MANUAL_SWAP, ALMOST_SWAPPED]); + +impl<'tcx> LateLintPass<'tcx> for Swap { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { + check_manual_swap(cx, block); + check_suspicious_swap(cx, block); + } +} + +/// Implementation of the `MANUAL_SWAP` lint. +fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) { + for w in block.stmts.windows(3) { + if_chain! { + // let t = foo(); + if let StmtKind::Local(ref tmp) = w[0].kind; + if let Some(ref tmp_init) = tmp.init; + if let PatKind::Binding(.., ident, None) = tmp.pat.kind; + + // foo() = bar(); + if let StmtKind::Semi(ref first) = w[1].kind; + if let ExprKind::Assign(ref lhs1, ref rhs1, _) = first.kind; + + // bar() = t; + if let StmtKind::Semi(ref second) = w[2].kind; + if let ExprKind::Assign(ref lhs2, ref rhs2, _) = second.kind; + if let ExprKind::Path(QPath::Resolved(None, ref rhs2)) = rhs2.kind; + if rhs2.segments.len() == 1; + + if ident.name == rhs2.segments[0].ident.name; + if eq_expr_value(cx, tmp_init, lhs1); + if eq_expr_value(cx, rhs1, lhs2); + then { + if let ExprKind::Field(ref lhs1, _) = lhs1.kind { + if let ExprKind::Field(ref lhs2, _) = lhs2.kind { + if lhs1.hir_id.owner == lhs2.hir_id.owner { + return; + } + } + } + + let mut applicability = Applicability::MachineApplicable; + + let slice = check_for_slice(cx, lhs1, lhs2); + let (replace, what, sugg) = if let Slice::NotSwappable = slice { + return; + } else if let Slice::Swappable(slice, idx1, idx2) = slice { + if let Some(slice) = Sugg::hir_opt(cx, slice) { + ( + false, + format!(" elements of `{}`", slice), + format!( + "{}.swap({}, {})", + slice.maybe_par(), + snippet_with_applicability(cx, idx1.span, "..", &mut applicability), + snippet_with_applicability(cx, idx2.span, "..", &mut applicability), + ), + ) + } else { + (false, String::new(), String::new()) + } + } else if let (Some(first), Some(second)) = (Sugg::hir_opt(cx, lhs1), Sugg::hir_opt(cx, rhs1)) { + ( + true, + format!(" `{}` and `{}`", first, second), + format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()), + ) + } else { + (true, String::new(), String::new()) + }; + + let span = w[0].span.to(second.span); + + span_lint_and_then( + cx, + MANUAL_SWAP, + span, + &format!("this looks like you are swapping{} manually", what), + |diag| { + if !sugg.is_empty() { + diag.span_suggestion( + span, + "try", + sugg, + applicability, + ); + + if replace { + diag.note("or maybe you should use `std::mem::replace`?"); + } + } + } + ); + } + } + } +} + +enum Slice<'a> { + /// `slice.swap(idx1, idx2)` can be used + /// + /// ## Example + /// + /// ```rust + /// # let mut a = vec![0, 1]; + /// let t = a[1]; + /// a[1] = a[0]; + /// a[0] = t; + /// // can be written as + /// a.swap(0, 1); + /// ``` + Swappable(&'a Expr<'a>, &'a Expr<'a>, &'a Expr<'a>), + /// The `swap` function cannot be used. + /// + /// ## Example + /// + /// ```rust + /// # let mut a = [vec![1, 2], vec![3, 4]]; + /// let t = a[0][1]; + /// a[0][1] = a[1][0]; + /// a[1][0] = t; + /// ``` + NotSwappable, + /// Not a slice + None, +} + +/// Checks if both expressions are index operations into "slice-like" types. +fn check_for_slice<'a>(cx: &LateContext<'_>, lhs1: &'a Expr<'_>, lhs2: &'a Expr<'_>) -> Slice<'a> { + if let ExprKind::Index(ref lhs1, ref idx1) = lhs1.kind { + if let ExprKind::Index(ref lhs2, ref idx2) = lhs2.kind { + if eq_expr_value(cx, lhs1, lhs2) { + let ty = cx.typeck_results().expr_ty(lhs1).peel_refs(); + + if matches!(ty.kind(), ty::Slice(_)) + || matches!(ty.kind(), ty::Array(_, _)) + || is_type_diagnostic_item(cx, ty, sym::vec_type) + || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) + { + return Slice::Swappable(lhs1, idx1, idx2); + } + } else { + return Slice::NotSwappable; + } + } + } + + Slice::None +} + +/// Implementation of the `ALMOST_SWAPPED` lint. +fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) { + for w in block.stmts.windows(2) { + if_chain! { + if let StmtKind::Semi(ref first) = w[0].kind; + if let StmtKind::Semi(ref second) = w[1].kind; + if !differing_macro_contexts(first.span, second.span); + if let ExprKind::Assign(ref lhs0, ref rhs0, _) = first.kind; + if let ExprKind::Assign(ref lhs1, ref rhs1, _) = second.kind; + if eq_expr_value(cx, lhs0, rhs1); + if eq_expr_value(cx, lhs1, rhs0); + then { + let lhs0 = Sugg::hir_opt(cx, lhs0); + let rhs0 = Sugg::hir_opt(cx, rhs0); + let (what, lhs, rhs) = if let (Some(first), Some(second)) = (lhs0, rhs0) { + ( + format!(" `{}` and `{}`", first, second), + first.mut_addr().to_string(), + second.mut_addr().to_string(), + ) + } else { + (String::new(), String::new(), String::new()) + }; + + let span = first.span.to(second.span); + + span_lint_and_then(cx, + ALMOST_SWAPPED, + span, + &format!("this looks like you are trying to swap{}", what), + |diag| { + if !what.is_empty() { + diag.span_suggestion( + span, + "try", + format!( + "std::mem::swap({}, {})", + lhs, + rhs, + ), + Applicability::MaybeIncorrect, + ); + diag.note("or maybe you should use `std::mem::replace`?"); + } + }); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/tabs_in_doc_comments.rs b/src/tools/clippy/clippy_lints/src/tabs_in_doc_comments.rs new file mode 100644 index 0000000000..74ccd9235d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/tabs_in_doc_comments.rs @@ -0,0 +1,220 @@ +use crate::utils::span_lint_and_sugg; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::{BytePos, Span}; +use std::convert::TryFrom; + +declare_clippy_lint! { + /// **What it does:** Checks doc comments for usage of tab characters. + /// + /// **Why is this bad?** The rust style-guide promotes spaces instead of tabs for indentation. + /// To keep a consistent view on the source, also doc comments should not have tabs. + /// Also, explaining ascii-diagrams containing tabs can get displayed incorrectly when the + /// display settings of the author and reader differ. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// /// + /// /// Struct to hold two strings: + /// /// - first one + /// /// - second one + /// pub struct DoubleString { + /// /// + /// /// - First String: + /// /// - needs to be inside here + /// first_string: String, + /// /// + /// /// - Second String: + /// /// - needs to be inside here + /// second_string: String, + ///} + /// ``` + /// + /// Will be converted to: + /// ```rust + /// /// + /// /// Struct to hold two strings: + /// /// - first one + /// /// - second one + /// pub struct DoubleString { + /// /// + /// /// - First String: + /// /// - needs to be inside here + /// first_string: String, + /// /// + /// /// - Second String: + /// /// - needs to be inside here + /// second_string: String, + ///} + /// ``` + pub TABS_IN_DOC_COMMENTS, + style, + "using tabs in doc comments is not recommended" +} + +declare_lint_pass!(TabsInDocComments => [TABS_IN_DOC_COMMENTS]); + +impl TabsInDocComments { + fn warn_if_tabs_in_doc(cx: &EarlyContext<'_>, attr: &ast::Attribute) { + if let ast::AttrKind::DocComment(_, comment) = attr.kind { + let comment = comment.as_str(); + + for (lo, hi) in get_chunks_of_tabs(&comment) { + // +3 skips the opening delimiter + let new_span = Span::new( + attr.span.lo() + BytePos(3 + lo), + attr.span.lo() + BytePos(3 + hi), + attr.span.ctxt(), + ); + span_lint_and_sugg( + cx, + TABS_IN_DOC_COMMENTS, + new_span, + "using tabs in doc comments is not recommended", + "consider using four spaces per tab", + " ".repeat((hi - lo) as usize), + Applicability::MaybeIncorrect, + ); + } + } + } +} + +impl EarlyLintPass for TabsInDocComments { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attribute: &ast::Attribute) { + Self::warn_if_tabs_in_doc(cx, &attribute); + } +} + +/// +/// scans the string for groups of tabs and returns the start(inclusive) and end positions +/// (exclusive) of all groups +/// e.g. "sd\tasd\t\taa" will be converted to [(2, 3), (6, 8)] as +/// 012 3456 7 89 +/// ^-^ ^---^ +fn get_chunks_of_tabs(the_str: &str) -> Vec<(u32, u32)> { + let line_length_way_to_long = "doc comment longer than 2^32 chars"; + let mut spans: Vec<(u32, u32)> = vec![]; + let mut current_start: u32 = 0; + + // tracker to decide if the last group of tabs is not closed by a non-tab character + let mut is_active = false; + + let chars_array: Vec<_> = the_str.chars().collect(); + + if chars_array == vec!['\t'] { + return vec![(0, 1)]; + } + + for (index, arr) in chars_array.windows(2).enumerate() { + let index = u32::try_from(index).expect(line_length_way_to_long); + match arr { + ['\t', '\t'] => { + // either string starts with double tab, then we have to set it active, + // otherwise is_active is true anyway + is_active = true; + }, + [_, '\t'] => { + // as ['\t', '\t'] is excluded, this has to be a start of a tab group, + // set indices accordingly + is_active = true; + current_start = index + 1; + }, + ['\t', _] => { + // this now has to be an end of the group, hence we have to push a new tuple + is_active = false; + spans.push((current_start, index + 1)); + }, + _ => {}, + } + } + + // only possible when tabs are at the end, insert last group + if is_active { + spans.push(( + current_start, + u32::try_from(the_str.chars().count()).expect(line_length_way_to_long), + )); + } + + spans +} + +#[cfg(test)] +mod tests_for_get_chunks_of_tabs { + use super::get_chunks_of_tabs; + + #[test] + fn test_empty_string() { + let res = get_chunks_of_tabs(""); + + assert_eq!(res, vec![]); + } + + #[test] + fn test_simple() { + let res = get_chunks_of_tabs("sd\t\t\taa"); + + assert_eq!(res, vec![(2, 5)]); + } + + #[test] + fn test_only_t() { + let res = get_chunks_of_tabs("\t\t"); + + assert_eq!(res, vec![(0, 2)]); + } + + #[test] + fn test_only_one_t() { + let res = get_chunks_of_tabs("\t"); + + assert_eq!(res, vec![(0, 1)]); + } + + #[test] + fn test_double() { + let res = get_chunks_of_tabs("sd\tasd\t\taa"); + + assert_eq!(res, vec![(2, 3), (6, 8)]); + } + + #[test] + fn test_start() { + let res = get_chunks_of_tabs("\t\taa"); + + assert_eq!(res, vec![(0, 2)]); + } + + #[test] + fn test_end() { + let res = get_chunks_of_tabs("aa\t\t"); + + assert_eq!(res, vec![(2, 4)]); + } + + #[test] + fn test_start_single() { + let res = get_chunks_of_tabs("\taa"); + + assert_eq!(res, vec![(0, 1)]); + } + + #[test] + fn test_end_single() { + let res = get_chunks_of_tabs("aa\t"); + + assert_eq!(res, vec![(2, 3)]); + } + + #[test] + fn test_no_tabs() { + let res = get_chunks_of_tabs("dsfs"); + + assert_eq!(res, vec![]); + } +} diff --git a/src/tools/clippy/clippy_lints/src/temporary_assignment.rs b/src/tools/clippy/clippy_lints/src/temporary_assignment.rs new file mode 100644 index 0000000000..fb89186636 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/temporary_assignment.rs @@ -0,0 +1,42 @@ +use crate::utils::{is_adjusted, span_lint}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for construction of a structure or tuple just to + /// assign a value in it. + /// + /// **Why is this bad?** Readability. If the structure is only created to be + /// updated, why not write the structure you want in the first place? + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// (0, 0).0 = 1 + /// ``` + pub TEMPORARY_ASSIGNMENT, + complexity, + "assignments to temporaries" +} + +fn is_temporary(expr: &Expr<'_>) -> bool { + matches!(&expr.kind, ExprKind::Struct(..) | ExprKind::Tup(..)) +} + +declare_lint_pass!(TemporaryAssignment => [TEMPORARY_ASSIGNMENT]); + +impl<'tcx> LateLintPass<'tcx> for TemporaryAssignment { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Assign(target, ..) = &expr.kind { + let mut base = target; + while let ExprKind::Field(f, _) | ExprKind::Index(f, _) = &base.kind { + base = f; + } + if is_temporary(base) && !is_adjusted(cx, base) { + span_lint(cx, TEMPORARY_ASSIGNMENT, expr.span, "assignment to temporary"); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/to_digit_is_some.rs b/src/tools/clippy/clippy_lints/src/to_digit_is_some.rs new file mode 100644 index 0000000000..eeda39bfa2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/to_digit_is_some.rs @@ -0,0 +1,94 @@ +use crate::utils::{match_def_path, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `.to_digit(..).is_some()` on `char`s. + /// + /// **Why is this bad?** This is a convoluted way of checking if a `char` is a digit. It's + /// more straight forward to use the dedicated `is_digit` method. + /// + /// **Example:** + /// ```rust + /// # let c = 'c'; + /// # let radix = 10; + /// let is_digit = c.to_digit(radix).is_some(); + /// ``` + /// can be written as: + /// ``` + /// # let c = 'c'; + /// # let radix = 10; + /// let is_digit = c.is_digit(radix); + /// ``` + pub TO_DIGIT_IS_SOME, + style, + "`char.is_digit()` is clearer" +} + +declare_lint_pass!(ToDigitIsSome => [TO_DIGIT_IS_SOME]); + +impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if_chain! { + if let hir::ExprKind::MethodCall(is_some_path, _, is_some_args, _) = &expr.kind; + if is_some_path.ident.name.as_str() == "is_some"; + if let [to_digit_expr] = &**is_some_args; + then { + let match_result = match &to_digit_expr.kind { + hir::ExprKind::MethodCall(to_digits_path, _, to_digit_args, _) => { + if_chain! { + if let [char_arg, radix_arg] = &**to_digit_args; + if to_digits_path.ident.name.as_str() == "to_digit"; + let char_arg_ty = cx.typeck_results().expr_ty_adjusted(char_arg); + if *char_arg_ty.kind() == ty::Char; + then { + Some((true, char_arg, radix_arg)) + } else { + None + } + } + } + hir::ExprKind::Call(to_digits_call, to_digit_args) => { + if_chain! { + if let [char_arg, radix_arg] = &**to_digit_args; + if let hir::ExprKind::Path(to_digits_path) = &to_digits_call.kind; + if let to_digits_call_res = cx.qpath_res(to_digits_path, to_digits_call.hir_id); + if let Some(to_digits_def_id) = to_digits_call_res.opt_def_id(); + if match_def_path(cx, to_digits_def_id, &["core", "char", "methods", "", "to_digit"]); + then { + Some((false, char_arg, radix_arg)) + } else { + None + } + } + } + _ => None + }; + + if let Some((is_method_call, char_arg, radix_arg)) = match_result { + let mut applicability = Applicability::MachineApplicable; + let char_arg_snip = snippet_with_applicability(cx, char_arg.span, "_", &mut applicability); + let radix_snip = snippet_with_applicability(cx, radix_arg.span, "_", &mut applicability); + + span_lint_and_sugg( + cx, + TO_DIGIT_IS_SOME, + expr.span, + "use of `.to_digit(..).is_some()`", + "try this", + if is_method_call { + format!("{}.is_digit({})", char_arg_snip, radix_snip) + } else { + format!("char::is_digit({}, {})", char_arg_snip, radix_snip) + }, + applicability, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/to_string_in_display.rs b/src/tools/clippy/clippy_lints/src/to_string_in_display.rs new file mode 100644 index 0000000000..84ec2aa18a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/to_string_in_display.rs @@ -0,0 +1,121 @@ +use crate::utils::{is_diagnostic_assoc_item, match_def_path, path_to_local_id, paths, span_lint}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for uses of `to_string()` in `Display` traits. + /// + /// **Why is this bad?** Usually `to_string` is implemented indirectly + /// via `Display`. Hence using it while implementing `Display` would + /// lead to infinite recursion. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// use std::fmt; + /// + /// struct Structure(i32); + /// impl fmt::Display for Structure { + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// write!(f, "{}", self.to_string()) + /// } + /// } + /// + /// ``` + /// Use instead: + /// ```rust + /// use std::fmt; + /// + /// struct Structure(i32); + /// impl fmt::Display for Structure { + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// write!(f, "{}", self.0) + /// } + /// } + /// ``` + pub TO_STRING_IN_DISPLAY, + correctness, + "`to_string` method used while implementing `Display` trait" +} + +#[derive(Default)] +pub struct ToStringInDisplay { + in_display_impl: bool, + self_hir_id: Option, +} + +impl ToStringInDisplay { + pub fn new() -> Self { + Self { + in_display_impl: false, + self_hir_id: None, + } + } +} + +impl_lint_pass!(ToStringInDisplay => [TO_STRING_IN_DISPLAY]); + +impl LateLintPass<'_> for ToStringInDisplay { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if is_display_impl(cx, item) { + self.in_display_impl = true; + } + } + + fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if is_display_impl(cx, item) { + self.in_display_impl = false; + self.self_hir_id = None; + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) { + if_chain! { + if self.in_display_impl; + if let ImplItemKind::Fn(.., body_id) = &impl_item.kind; + let body = cx.tcx.hir().body(*body_id); + if !body.params.is_empty(); + then { + let self_param = &body.params[0]; + self.self_hir_id = Some(self_param.pat.hir_id); + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if_chain! { + if self.in_display_impl; + if let Some(self_hir_id) = self.self_hir_id; + if let ExprKind::MethodCall(ref path, _, args, _) = expr.kind; + if path.ident.name == sym!(to_string); + if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if is_diagnostic_assoc_item(cx, expr_def_id, sym::ToString); + if path_to_local_id(&args[0], self_hir_id); + then { + span_lint( + cx, + TO_STRING_IN_DISPLAY, + expr.span, + "using `to_string` in `fmt::Display` implementation might lead to infinite recursion", + ); + } + } + } +} + +fn is_display_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool { + if_chain! { + if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind; + if let Some(did) = trait_ref.trait_def_id(); + then { + match_def_path(cx, did, &paths::DISPLAY_TRAIT) + } else { + false + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/trait_bounds.rs b/src/tools/clippy/clippy_lints/src/trait_bounds.rs new file mode 100644 index 0000000000..daff5f81e8 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/trait_bounds.rs @@ -0,0 +1,192 @@ +use crate::utils::{in_macro, snippet, snippet_with_applicability, span_lint_and_help, SpanlessHash}; +use if_chain::if_chain; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::Applicability; +use rustc_hir::{def::Res, GenericBound, Generics, ParamName, Path, QPath, TyKind, WherePredicate}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** This lint warns about unnecessary type repetitions in trait bounds + /// + /// **Why is this bad?** Repeating the type for every bound makes the code + /// less readable than combining the bounds + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// pub fn foo(t: T) where T: Copy, T: Clone {} + /// ``` + /// + /// Could be written as: + /// + /// ```rust + /// pub fn foo(t: T) where T: Copy + Clone {} + /// ``` + pub TYPE_REPETITION_IN_BOUNDS, + pedantic, + "Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for cases where generics are being used and multiple + /// syntax specifications for trait bounds are used simultaneously. + /// + /// **Why is this bad?** Duplicate bounds makes the code + /// less readable than specifing them only once. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn func(arg: T) where T: Clone + Default {} + /// ``` + /// + /// Could be written as: + /// + /// ```rust + /// fn func(arg: T) {} + /// ``` + /// or + /// + /// ```rust + /// fn func(arg: T) where T: Clone + Default {} + /// ``` + pub TRAIT_DUPLICATION_IN_BOUNDS, + pedantic, + "Check if the same trait bounds are specified twice during a function declaration" +} + +#[derive(Copy, Clone)] +pub struct TraitBounds { + max_trait_bounds: u64, +} + +impl TraitBounds { + #[must_use] + pub fn new(max_trait_bounds: u64) -> Self { + Self { max_trait_bounds } + } +} + +impl_lint_pass!(TraitBounds => [TYPE_REPETITION_IN_BOUNDS, TRAIT_DUPLICATION_IN_BOUNDS]); + +impl<'tcx> LateLintPass<'tcx> for TraitBounds { + fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) { + self.check_type_repetition(cx, gen); + check_trait_bound_duplication(cx, gen); + } +} + +fn get_trait_res_span_from_bound(bound: &GenericBound<'_>) -> Option<(Res, Span)> { + if let GenericBound::Trait(t, _) = bound { + Some((t.trait_ref.path.res, t.span)) + } else { + None + } +} + +impl TraitBounds { + fn check_type_repetition(self, cx: &LateContext<'_>, gen: &'_ Generics<'_>) { + if in_macro(gen.span) { + return; + } + let hash = |ty| -> u64 { + let mut hasher = SpanlessHash::new(cx); + hasher.hash_ty(ty); + hasher.finish() + }; + let mut map = FxHashMap::default(); + let mut applicability = Applicability::MaybeIncorrect; + for bound in gen.where_clause.predicates { + if_chain! { + if let WherePredicate::BoundPredicate(ref p) = bound; + if p.bounds.len() as u64 <= self.max_trait_bounds; + if !in_macro(p.span); + let h = hash(&p.bounded_ty); + if let Some(ref v) = map.insert(h, p.bounds.iter().collect::>()); + + then { + let mut hint_string = format!( + "consider combining the bounds: `{}:", + snippet(cx, p.bounded_ty.span, "_") + ); + for b in v.iter() { + if let GenericBound::Trait(ref poly_trait_ref, _) = b { + let path = &poly_trait_ref.trait_ref.path; + hint_string.push_str(&format!( + " {} +", + snippet_with_applicability(cx, path.span, "..", &mut applicability) + )); + } + } + for b in p.bounds.iter() { + if let GenericBound::Trait(ref poly_trait_ref, _) = b { + let path = &poly_trait_ref.trait_ref.path; + hint_string.push_str(&format!( + " {} +", + snippet_with_applicability(cx, path.span, "..", &mut applicability) + )); + } + } + hint_string.truncate(hint_string.len() - 2); + hint_string.push('`'); + span_lint_and_help( + cx, + TYPE_REPETITION_IN_BOUNDS, + p.span, + "this type has already been used as a bound predicate", + None, + &hint_string, + ); + } + } + } + } +} + +fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { + if in_macro(gen.span) || gen.params.is_empty() || gen.where_clause.predicates.is_empty() { + return; + } + + let mut map = FxHashMap::default(); + for param in gen.params { + if let ParamName::Plain(ref ident) = param.name { + let res = param + .bounds + .iter() + .filter_map(get_trait_res_span_from_bound) + .collect::>(); + map.insert(*ident, res); + } + } + + for predicate in gen.where_clause.predicates { + if_chain! { + if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate; + if !in_macro(bound_predicate.span); + if let TyKind::Path(QPath::Resolved(_, Path { ref segments, .. })) = bound_predicate.bounded_ty.kind; + if let Some(segment) = segments.first(); + if let Some(trait_resolutions_direct) = map.get(&segment.ident); + then { + for (res_where, _) in bound_predicate.bounds.iter().filter_map(get_trait_res_span_from_bound) { + if let Some((_, span_direct)) = trait_resolutions_direct + .iter() + .find(|(res_direct, _)| *res_direct == res_where) { + span_lint_and_help( + cx, + TRAIT_DUPLICATION_IN_BOUNDS, + *span_direct, + "this trait bound is already specified in the where clause", + None, + "consider removing this trait bound", + ); + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/crosspointer_transmute.rs b/src/tools/clippy/clippy_lints/src/transmute/crosspointer_transmute.rs new file mode 100644 index 0000000000..ce87defaa9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/crosspointer_transmute.rs @@ -0,0 +1,37 @@ +use super::CROSSPOINTER_TRANSMUTE; +use crate::utils::span_lint; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `crosspointer_transmute` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::RawPtr(from_ptr), _) if from_ptr.ty == to_ty => { + span_lint( + cx, + CROSSPOINTER_TRANSMUTE, + e.span, + &format!( + "transmute from a type (`{}`) to the type that it points to (`{}`)", + from_ty, to_ty + ), + ); + true + }, + (_, ty::RawPtr(to_ptr)) if to_ptr.ty == from_ty => { + span_lint( + cx, + CROSSPOINTER_TRANSMUTE, + e.span, + &format!( + "transmute from a type (`{}`) to a pointer to that type (`{}`)", + from_ty, to_ty + ), + ); + true + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/mod.rs b/src/tools/clippy/clippy_lints/src/transmute/mod.rs new file mode 100644 index 0000000000..c1870f5208 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/mod.rs @@ -0,0 +1,363 @@ +mod crosspointer_transmute; +mod transmute_float_to_int; +mod transmute_int_to_bool; +mod transmute_int_to_char; +mod transmute_int_to_float; +mod transmute_ptr_to_ptr; +mod transmute_ptr_to_ref; +mod transmute_ref_to_ref; +mod transmutes_expressible_as_ptr_casts; +mod unsound_collection_transmute; +mod useless_transmute; +mod utils; +mod wrong_transmute; + +use crate::utils::{in_constant, match_def_path, paths}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes that can't ever be correct on any + /// architecture. + /// + /// **Why is this bad?** It's basically guaranteed to be undefined behaviour. + /// + /// **Known problems:** When accessing C, users might want to store pointer + /// sized objects in `extradata` arguments to save an allocation. + /// + /// **Example:** + /// ```ignore + /// let ptr: *const T = core::intrinsics::transmute('x') + /// ``` + pub WRONG_TRANSMUTE, + correctness, + "transmutes that are confusing at best, undefined behaviour at worst and always useless" +} + +// FIXME: Move this to `complexity` again, after #5343 is fixed +declare_clippy_lint! { + /// **What it does:** Checks for transmutes to the original type of the object + /// and transmutes that could be a cast. + /// + /// **Why is this bad?** Readability. The code tricks people into thinking that + /// something complex is going on. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// core::intrinsics::transmute(t); // where the result type is the same as `t`'s + /// ``` + pub USELESS_TRANSMUTE, + nursery, + "transmutes that have the same to and from types or could be a cast/coercion" +} + +// FIXME: Merge this lint with USELESS_TRANSMUTE once that is out of the nursery. +declare_clippy_lint! { + /// **What it does:**Checks for transmutes that could be a pointer cast. + /// + /// **Why is this bad?** Readability. The code tricks people into thinking that + /// something complex is going on. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// # let p: *const [i32] = &[]; + /// unsafe { std::mem::transmute::<*const [i32], *const [u16]>(p) }; + /// ``` + /// Use instead: + /// ```rust + /// # let p: *const [i32] = &[]; + /// p as *const [u16]; + /// ``` + pub TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, + complexity, + "transmutes that could be a pointer cast" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes between a type `T` and `*T`. + /// + /// **Why is this bad?** It's easy to mistakenly transmute between a type and a + /// pointer to that type. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// core::intrinsics::transmute(t) // where the result type is the same as + /// // `*t` or `&t`'s + /// ``` + pub CROSSPOINTER_TRANSMUTE, + complexity, + "transmutes that have to or from types that are a pointer to the other" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a pointer to a reference. + /// + /// **Why is this bad?** This can always be rewritten with `&` and `*`. + /// + /// **Known problems:** + /// - `mem::transmute` in statics and constants is stable from Rust 1.46.0, + /// while dereferencing raw pointer is not stable yet. + /// If you need to do this in those places, + /// you would have to use `transmute` instead. + /// + /// **Example:** + /// ```rust,ignore + /// unsafe { + /// let _: &T = std::mem::transmute(p); // where p: *const T + /// } + /// + /// // can be written: + /// let _: &T = &*p; + /// ``` + pub TRANSMUTE_PTR_TO_REF, + complexity, + "transmutes from a pointer to a reference type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from an integer to a `char`. + /// + /// **Why is this bad?** Not every integer is a Unicode scalar value. + /// + /// **Known problems:** + /// - [`from_u32`] which this lint suggests using is slower than `transmute` + /// as it needs to validate the input. + /// If you are certain that the input is always a valid Unicode scalar value, + /// use [`from_u32_unchecked`] which is as fast as `transmute` + /// but has a semantically meaningful name. + /// - You might want to handle `None` returned from [`from_u32`] instead of calling `unwrap`. + /// + /// [`from_u32`]: https://doc.rust-lang.org/std/char/fn.from_u32.html + /// [`from_u32_unchecked`]: https://doc.rust-lang.org/std/char/fn.from_u32_unchecked.html + /// + /// **Example:** + /// ```rust + /// let x = 1_u32; + /// unsafe { + /// let _: char = std::mem::transmute(x); // where x: u32 + /// } + /// + /// // should be: + /// let _ = std::char::from_u32(x).unwrap(); + /// ``` + pub TRANSMUTE_INT_TO_CHAR, + complexity, + "transmutes from an integer to a `char`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a `&[u8]` to a `&str`. + /// + /// **Why is this bad?** Not every byte slice is a valid UTF-8 string. + /// + /// **Known problems:** + /// - [`from_utf8`] which this lint suggests using is slower than `transmute` + /// as it needs to validate the input. + /// If you are certain that the input is always a valid UTF-8, + /// use [`from_utf8_unchecked`] which is as fast as `transmute` + /// but has a semantically meaningful name. + /// - You might want to handle errors returned from [`from_utf8`] instead of calling `unwrap`. + /// + /// [`from_utf8`]: https://doc.rust-lang.org/std/str/fn.from_utf8.html + /// [`from_utf8_unchecked`]: https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html + /// + /// **Example:** + /// ```rust + /// let b: &[u8] = &[1_u8, 2_u8]; + /// unsafe { + /// let _: &str = std::mem::transmute(b); // where b: &[u8] + /// } + /// + /// // should be: + /// let _ = std::str::from_utf8(b).unwrap(); + /// ``` + pub TRANSMUTE_BYTES_TO_STR, + complexity, + "transmutes from a `&[u8]` to a `&str`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from an integer to a `bool`. + /// + /// **Why is this bad?** This might result in an invalid in-memory representation of a `bool`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 1_u8; + /// unsafe { + /// let _: bool = std::mem::transmute(x); // where x: u8 + /// } + /// + /// // should be: + /// let _: bool = x != 0; + /// ``` + pub TRANSMUTE_INT_TO_BOOL, + complexity, + "transmutes from an integer to a `bool`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from an integer to a float. + /// + /// **Why is this bad?** Transmutes are dangerous and error-prone, whereas `from_bits` is intuitive + /// and safe. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// unsafe { + /// let _: f32 = std::mem::transmute(1_u32); // where x: u32 + /// } + /// + /// // should be: + /// let _: f32 = f32::from_bits(1_u32); + /// ``` + pub TRANSMUTE_INT_TO_FLOAT, + complexity, + "transmutes from an integer to a float" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a float to an integer. + /// + /// **Why is this bad?** Transmutes are dangerous and error-prone, whereas `to_bits` is intuitive + /// and safe. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// unsafe { + /// let _: u32 = std::mem::transmute(1f32); + /// } + /// + /// // should be: + /// let _: u32 = 1f32.to_bits(); + /// ``` + pub TRANSMUTE_FLOAT_TO_INT, + complexity, + "transmutes from a float to an integer" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a pointer to a pointer, or + /// from a reference to a reference. + /// + /// **Why is this bad?** Transmutes are dangerous, and these can instead be + /// written as casts. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let ptr = &1u32 as *const u32; + /// unsafe { + /// // pointer-to-pointer transmute + /// let _: *const f32 = std::mem::transmute(ptr); + /// // ref-ref transmute + /// let _: &f32 = std::mem::transmute(&1u32); + /// } + /// // These can be respectively written: + /// let _ = ptr as *const f32; + /// let _ = unsafe{ &*(&1u32 as *const u32 as *const f32) }; + /// ``` + pub TRANSMUTE_PTR_TO_PTR, + complexity, + "transmutes from a pointer to a pointer / a reference to a reference" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes between collections whose + /// types have different ABI, size or alignment. + /// + /// **Why is this bad?** This is undefined behavior. + /// + /// **Known problems:** Currently, we cannot know whether a type is a + /// collection, so we just lint the ones that come with `std`. + /// + /// **Example:** + /// ```rust + /// // different size, therefore likely out-of-bounds memory access + /// // You absolutely do not want this in your code! + /// unsafe { + /// std::mem::transmute::<_, Vec>(vec![2_u16]) + /// }; + /// ``` + /// + /// You must always iterate, map and collect the values: + /// + /// ```rust + /// vec![2_u16].into_iter().map(u32::from).collect::>(); + /// ``` + pub UNSOUND_COLLECTION_TRANSMUTE, + correctness, + "transmute between collections of layout-incompatible types" +} + +declare_lint_pass!(Transmute => [ + CROSSPOINTER_TRANSMUTE, + TRANSMUTE_PTR_TO_REF, + TRANSMUTE_PTR_TO_PTR, + USELESS_TRANSMUTE, + WRONG_TRANSMUTE, + TRANSMUTE_INT_TO_CHAR, + TRANSMUTE_BYTES_TO_STR, + TRANSMUTE_INT_TO_BOOL, + TRANSMUTE_INT_TO_FLOAT, + TRANSMUTE_FLOAT_TO_INT, + UNSOUND_COLLECTION_TRANSMUTE, + TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, +]); + +impl<'tcx> LateLintPass<'tcx> for Transmute { + #[allow(clippy::similar_names, clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref path_expr, ref args) = e.kind; + if let ExprKind::Path(ref qpath) = path_expr.kind; + if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::TRANSMUTE); + then { + // Avoid suggesting from/to bits and dereferencing raw pointers in const contexts. + // See https://github.com/rust-lang/rust/issues/73736 for progress on making them `const fn`. + // And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers. + let const_context = in_constant(cx, e.hir_id); + + let from_ty = cx.typeck_results().expr_ty(&args[0]); + let to_ty = cx.typeck_results().expr_ty(e); + + // If useless_transmute is triggered, the other lints can be skipped. + if useless_transmute::check(cx, e, from_ty, to_ty, args) { + return; + } + + let mut linted = wrong_transmute::check(cx, e, from_ty, to_ty); + linted |= crosspointer_transmute::check(cx, e, from_ty, to_ty); + linted |= transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, args, qpath); + linted |= transmute_int_to_char::check(cx, e, from_ty, to_ty, args); + linted |= transmute_ref_to_ref::check(cx, e, from_ty, to_ty, args, const_context); + linted |= transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, args); + linted |= transmute_int_to_bool::check(cx, e, from_ty, to_ty, args); + linted |= transmute_int_to_float::check(cx, e, from_ty, to_ty, args, const_context); + linted |= transmute_float_to_int::check(cx, e, from_ty, to_ty, args, const_context); + linted |= unsound_collection_transmute::check(cx, e, from_ty, to_ty); + + if !linted { + transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, args); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_float_to_int.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_float_to_int.rs new file mode 100644 index 0000000000..562d880e39 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_float_to_int.rs @@ -0,0 +1,65 @@ +use super::TRANSMUTE_FLOAT_TO_INT; +use crate::utils::{span_lint_and_then, sugg}; +use if_chain::if_chain; +use rustc_ast as ast; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_float_to_int` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], + const_context: bool, +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Float(float_ty), ty::Int(_) | ty::Uint(_)) if !const_context => { + span_lint_and_then( + cx, + TRANSMUTE_FLOAT_TO_INT, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + |diag| { + let mut expr = &args[0]; + let mut arg = sugg::Sugg::hir(cx, expr, ".."); + + if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind { + expr = &inner_expr; + } + + if_chain! { + // if the expression is a float literal and it is unsuffixed then + // add a suffix so the suggestion is valid and unambiguous + let op = format!("{}{}", arg, float_ty.name_str()).into(); + if let ExprKind::Lit(lit) = &expr.kind; + if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node; + then { + match arg { + sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op), + _ => arg = sugg::Sugg::NonParen(op) + } + } + } + + arg = sugg::Sugg::NonParen(format!("{}.to_bits()", arg.maybe_par()).into()); + + // cast the result of `to_bits` if `to_ty` is signed + arg = if let ty::Int(int_ty) = to_ty.kind() { + arg.as_ty(int_ty.name_str().to_string()) + } else { + arg + }; + + diag.span_suggestion(e.span, "consider using", arg.to_string(), Applicability::Unspecified); + }, + ); + true + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_bool.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_bool.rs new file mode 100644 index 0000000000..5b609f906a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_bool.rs @@ -0,0 +1,41 @@ +use super::TRANSMUTE_INT_TO_BOOL; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_ast as ast; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use std::borrow::Cow; + +/// Checks for `transmute_int_to_bool` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Int(ty::IntTy::I8) | ty::Uint(ty::UintTy::U8), ty::Bool) => { + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_BOOL, + e.span, + &format!("transmute from a `{}` to a `bool`", from_ty), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let zero = sugg::Sugg::NonParen(Cow::from("0")); + diag.span_suggestion( + e.span, + "consider using", + sugg::make_binop(ast::BinOpKind::Ne, &arg, &zero).to_string(), + Applicability::Unspecified, + ); + }, + ); + true + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_char.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_char.rs new file mode 100644 index 0000000000..29d2450618 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_char.rs @@ -0,0 +1,44 @@ +use super::TRANSMUTE_INT_TO_CHAR; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_ast as ast; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_int_to_char` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) => { + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_CHAR, + e.span, + &format!("transmute from a `{}` to a `char`", from_ty), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = if let ty::Int(_) = from_ty.kind() { + arg.as_ty(ast::UintTy::U32.name_str()) + } else { + arg + }; + diag.span_suggestion( + e.span, + "consider using", + format!("std::char::from_u32({}).unwrap()", arg.to_string()), + Applicability::Unspecified, + ); + }, + ); + true + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_float.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_float.rs new file mode 100644 index 0000000000..f83fba8966 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_float.rs @@ -0,0 +1,47 @@ +use super::TRANSMUTE_INT_TO_FLOAT; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_int_to_float` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], + const_context: bool, +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Int(_) | ty::Uint(_), ty::Float(_)) if !const_context => { + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_FLOAT, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = if let ty::Int(int_ty) = from_ty.kind() { + arg.as_ty(format!( + "u{}", + int_ty.bit_width().map_or_else(|| "size".to_string(), |v| v.to_string()) + )) + } else { + arg + }; + diag.span_suggestion( + e.span, + "consider using", + format!("{}::from_bits({})", to_ty, arg.to_string()), + Applicability::Unspecified, + ); + }, + ); + true + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs new file mode 100644 index 0000000000..f4e60a3020 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs @@ -0,0 +1,35 @@ +use super::TRANSMUTE_PTR_TO_PTR; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_ptr_to_ptr` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::RawPtr(_), ty::RawPtr(to_ty)) => { + span_lint_and_then( + cx, + TRANSMUTE_PTR_TO_PTR, + e.span, + "transmute from a pointer to a pointer", + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let sugg = arg.as_ty(cx.tcx.mk_ptr(*to_ty)); + diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified); + } + }, + ); + true + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs new file mode 100644 index 0000000000..f5dbbbe33b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -0,0 +1,55 @@ +use super::utils::get_type_snippet; +use super::TRANSMUTE_PTR_TO_REF; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, Mutability, QPath}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_ptr_to_ref` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], + qpath: &'tcx QPath<'_>, +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::RawPtr(from_ptr_ty), ty::Ref(_, to_ref_ty, mutbl)) => { + span_lint_and_then( + cx, + TRANSMUTE_PTR_TO_REF, + e.span, + &format!( + "transmute from a pointer type (`{}`) to a reference type (`{}`)", + from_ty, to_ty + ), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let (deref, cast) = if *mutbl == Mutability::Mut { + ("&mut *", "*mut") + } else { + ("&*", "*const") + }; + + let arg = if from_ptr_ty.ty == *to_ref_ty { + arg + } else { + arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, to_ref_ty))) + }; + + diag.span_suggestion( + e.span, + "try", + sugg::make_unop(deref, arg).to_string(), + Applicability::Unspecified, + ); + }, + ); + true + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_ref_to_ref.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_ref_to_ref.rs new file mode 100644 index 0000000000..01b00bb0a2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_ref_to_ref.rs @@ -0,0 +1,85 @@ +use super::{TRANSMUTE_BYTES_TO_STR, TRANSMUTE_PTR_TO_PTR}; +use crate::utils::{snippet, span_lint_and_sugg, span_lint_and_then, sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, Mutability}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_bytes_to_str` and `transmute_ptr_to_ptr` lints. +/// Returns `true` if either one triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], + const_context: bool, +) -> bool { + let mut triggered = false; + + if let (ty::Ref(_, ty_from, from_mutbl), ty::Ref(_, ty_to, to_mutbl)) = (&from_ty.kind(), &to_ty.kind()) { + if_chain! { + if let (&ty::Slice(slice_ty), &ty::Str) = (&ty_from.kind(), &ty_to.kind()); + if let ty::Uint(ty::UintTy::U8) = slice_ty.kind(); + if from_mutbl == to_mutbl; + then { + let postfix = if *from_mutbl == Mutability::Mut { + "_mut" + } else { + "" + }; + + span_lint_and_sugg( + cx, + TRANSMUTE_BYTES_TO_STR, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + "consider using", + format!( + "std::str::from_utf8{}({}).unwrap()", + postfix, + snippet(cx, args[0].span, ".."), + ), + Applicability::Unspecified, + ); + triggered = true; + } else { + if (cx.tcx.erase_regions(from_ty) != cx.tcx.erase_regions(to_ty)) + && !const_context { + span_lint_and_then( + cx, + TRANSMUTE_PTR_TO_PTR, + e.span, + "transmute from a reference to a reference", + |diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let ty_from_and_mut = ty::TypeAndMut { + ty: ty_from, + mutbl: *from_mutbl + }; + let ty_to_and_mut = ty::TypeAndMut { ty: ty_to, mutbl: *to_mutbl }; + let sugg_paren = arg + .as_ty(cx.tcx.mk_ptr(ty_from_and_mut)) + .as_ty(cx.tcx.mk_ptr(ty_to_and_mut)); + let sugg = if *to_mutbl == Mutability::Mut { + sugg_paren.mut_addr_deref() + } else { + sugg_paren.addr_deref() + }; + diag.span_suggestion( + e.span, + "try", + sugg.to_string(), + Applicability::Unspecified, + ); + }, + ); + + triggered = true; + } + } + } + } + + triggered +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs b/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs new file mode 100644 index 0000000000..dea896622f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs @@ -0,0 +1,38 @@ +use super::utils::can_be_expressed_as_pointer_cast; +use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; + +/// Checks for `transmutes_expressible_as_ptr_casts` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], +) -> bool { + if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) { + span_lint_and_then( + cx, + TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, + e.span, + &format!( + "transmute from `{}` to `{}` which could be expressed as a pointer cast instead", + from_ty, to_ty + ), + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let sugg = arg.as_ty(&to_ty.to_string()).to_string(); + diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable); + } + }, + ); + true + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/unsound_collection_transmute.rs b/src/tools/clippy/clippy_lints/src/transmute/unsound_collection_transmute.rs new file mode 100644 index 0000000000..503c5e0ff3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/unsound_collection_transmute.rs @@ -0,0 +1,48 @@ +use super::utils::is_layout_incompatible; +use super::UNSOUND_COLLECTION_TRANSMUTE; +use crate::utils::{match_def_path, paths, span_lint}; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +// used to check for UNSOUND_COLLECTION_TRANSMUTE +static COLLECTIONS: &[&[&str]] = &[ + &paths::VEC, + &paths::VEC_DEQUE, + &paths::BINARY_HEAP, + &paths::BTREESET, + &paths::BTREEMAP, + &paths::HASHSET, + &paths::HASHMAP, +]; + +/// Checks for `unsound_collection_transmute` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Adt(from_adt, from_substs), ty::Adt(to_adt, to_substs)) => { + if from_adt.did != to_adt.did || !COLLECTIONS.iter().any(|path| match_def_path(cx, to_adt.did, path)) { + return false; + } + if from_substs + .types() + .zip(to_substs.types()) + .any(|(from_ty, to_ty)| is_layout_incompatible(cx, from_ty, to_ty)) + { + span_lint( + cx, + UNSOUND_COLLECTION_TRANSMUTE, + e.span, + &format!( + "transmute from `{}` to `{}` with mismatched layout is unsound", + from_ty, to_ty + ), + ); + true + } else { + false + } + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs b/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs new file mode 100644 index 0000000000..83441514af --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs @@ -0,0 +1,73 @@ +use super::USELESS_TRANSMUTE; +use crate::utils::{span_lint, span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `useless_transmute` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + _ if from_ty == to_ty => { + span_lint( + cx, + USELESS_TRANSMUTE, + e.span, + &format!("transmute from a type (`{}`) to itself", from_ty), + ); + true + }, + (ty::Ref(_, rty, rty_mutbl), ty::RawPtr(ptr_ty)) => { + span_lint_and_then( + cx, + USELESS_TRANSMUTE, + e.span, + "transmute from a reference to a pointer", + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let rty_and_mut = ty::TypeAndMut { + ty: rty, + mutbl: *rty_mutbl, + }; + + let sugg = if *ptr_ty == rty_and_mut { + arg.as_ty(to_ty) + } else { + arg.as_ty(cx.tcx.mk_ptr(rty_and_mut)).as_ty(to_ty) + }; + + diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified); + } + }, + ); + true + }, + (ty::Int(_) | ty::Uint(_), ty::RawPtr(_)) => { + span_lint_and_then( + cx, + USELESS_TRANSMUTE, + e.span, + "transmute from an integer to a pointer", + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + diag.span_suggestion( + e.span, + "try", + arg.as_ty(&to_ty.to_string()).to_string(), + Applicability::Unspecified, + ); + } + }, + ); + true + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/utils.rs b/src/tools/clippy/clippy_lints/src/transmute/utils.rs new file mode 100644 index 0000000000..55008d8ec3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/utils.rs @@ -0,0 +1,104 @@ +use crate::utils::{is_normalizable, last_path_segment, snippet}; +use if_chain::if_chain; +use rustc_hir::{Expr, GenericArg, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, cast::CastKind, Ty}; +use rustc_span::DUMMY_SP; +use rustc_typeck::check::{cast::CastCheck, FnCtxt, Inherited}; + +/// Gets the snippet of `Bar` in `…::transmute`. If that snippet is +/// not available , use +/// the type's `ToString` implementation. In weird cases it could lead to types +/// with invalid `'_` +/// lifetime, but it should be rare. +pub(super) fn get_type_snippet(cx: &LateContext<'_>, path: &QPath<'_>, to_ref_ty: Ty<'_>) -> String { + let seg = last_path_segment(path); + if_chain! { + if let Some(ref params) = seg.args; + if !params.parenthesized; + if let Some(to_ty) = params.args.iter().filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }).nth(1); + if let TyKind::Rptr(_, ref to_ty) = to_ty.kind; + then { + return snippet(cx, to_ty.ty.span, &to_ref_ty.to_string()).to_string(); + } + } + + to_ref_ty.to_string() +} + +// check if the component types of the transmuted collection and the result have different ABI, +// size or alignment +pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool { + let empty_param_env = ty::ParamEnv::empty(); + // check if `from` and `to` are normalizable to avoid ICE (#4968) + if !(is_normalizable(cx, empty_param_env, from) && is_normalizable(cx, empty_param_env, to)) { + return false; + } + let from_ty_layout = cx.tcx.layout_of(empty_param_env.and(from)); + let to_ty_layout = cx.tcx.layout_of(empty_param_env.and(to)); + if let (Ok(from_layout), Ok(to_layout)) = (from_ty_layout, to_ty_layout) { + from_layout.size != to_layout.size || from_layout.align != to_layout.align || from_layout.abi != to_layout.abi + } else { + // no idea about layout, so don't lint + false + } +} + +/// Check if the type conversion can be expressed as a pointer cast, instead of +/// a transmute. In certain cases, including some invalid casts from array +/// references to pointers, this may cause additional errors to be emitted and/or +/// ICE error messages. This function will panic if that occurs. +pub(super) fn can_be_expressed_as_pointer_cast<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, +) -> bool { + use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast}; + matches!( + check_cast(cx, e, from_ty, to_ty), + Some(PtrPtrCast | PtrAddrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast) + ) +} + +/// If a cast from `from_ty` to `to_ty` is valid, returns an Ok containing the kind of +/// the cast. In certain cases, including some invalid casts from array references +/// to pointers, this may cause additional errors to be emitted and/or ICE error +/// messages. This function will panic if that occurs. +fn check_cast<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> Option { + let hir_id = e.hir_id; + let local_def_id = hir_id.owner; + + Inherited::build(cx.tcx, local_def_id).enter(|inherited| { + let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, hir_id); + + // If we already have errors, we can't be sure we can pointer cast. + assert!( + !fn_ctxt.errors_reported_since_creation(), + "Newly created FnCtxt contained errors" + ); + + if let Ok(check) = CastCheck::new( + &fn_ctxt, e, from_ty, to_ty, + // We won't show any error to the user, so we don't care what the span is here. + DUMMY_SP, DUMMY_SP, + ) { + let res = check.do_check(&fn_ctxt); + + // do_check's documentation says that it might return Ok and create + // errors in the fcx instead of returing Err in some cases. Those cases + // should be filtered out before getting here. + assert!( + !fn_ctxt.errors_reported_since_creation(), + "`fn_ctxt` contained errors after cast check!" + ); + + res.ok() + } else { + None + } + }) +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/wrong_transmute.rs b/src/tools/clippy/clippy_lints/src/transmute/wrong_transmute.rs new file mode 100644 index 0000000000..d6d77f2c83 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/wrong_transmute.rs @@ -0,0 +1,22 @@ +use super::WRONG_TRANSMUTE; +use crate::utils::span_lint; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `wrong_transmute` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Float(_) | ty::Char, ty::Ref(..) | ty::RawPtr(_)) => { + span_lint( + cx, + WRONG_TRANSMUTE, + e.span, + &format!("transmute from a `{}` to a pointer", from_ty), + ); + true + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmuting_null.rs b/src/tools/clippy/clippy_lints/src/transmuting_null.rs new file mode 100644 index 0000000000..2ba2b646f0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmuting_null.rs @@ -0,0 +1,88 @@ +use crate::consts::{constant_context, Constant}; +use crate::utils::{match_qpath, paths, span_lint}; +use if_chain::if_chain; +use rustc_ast::LitKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for transmute calls which would receive a null pointer. + /// + /// **Why is this bad?** Transmuting a null pointer is undefined behavior. + /// + /// **Known problems:** Not all cases can be detected at the moment of this writing. + /// For example, variables which hold a null pointer and are then fed to a `transmute` + /// call, aren't detectable yet. + /// + /// **Example:** + /// ```rust + /// let null_ref: &u64 = unsafe { std::mem::transmute(0 as *const u64) }; + /// ``` + pub TRANSMUTING_NULL, + correctness, + "transmutes from a null pointer to a reference, which is undefined behavior" +} + +declare_lint_pass!(TransmutingNull => [TRANSMUTING_NULL]); + +const LINT_MSG: &str = "transmuting a known null pointer into a reference"; + +impl<'tcx> LateLintPass<'tcx> for TransmutingNull { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if_chain! { + if let ExprKind::Call(ref func, ref args) = expr.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &paths::STD_MEM_TRANSMUTE); + if args.len() == 1; + + then { + + // Catching transmute over constants that resolve to `null`. + let mut const_eval_context = constant_context(cx, cx.typeck_results()); + if_chain! { + if let ExprKind::Path(ref _qpath) = args[0].kind; + let x = const_eval_context.expr(&args[0]); + if let Some(Constant::RawPtr(0)) = x; + then { + span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG) + } + } + + // Catching: + // `std::mem::transmute(0 as *const i32)` + if_chain! { + if let ExprKind::Cast(ref inner_expr, ref _cast_ty) = args[0].kind; + if let ExprKind::Lit(ref lit) = inner_expr.kind; + if let LitKind::Int(0, _) = lit.node; + then { + span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG) + } + } + + // Catching: + // `std::mem::transmute(std::ptr::null::())` + if_chain! { + if let ExprKind::Call(ref func1, ref args1) = args[0].kind; + if let ExprKind::Path(ref path1) = func1.kind; + if match_qpath(path1, &paths::STD_PTR_NULL); + if args1.is_empty(); + then { + span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG) + } + } + + // FIXME: + // Also catch transmutations of variables which are known nulls. + // To do this, MIR const propagation seems to be the better tool. + // Whenever MIR const prop routines are more developed, this will + // become available. As of this writing (25/03/19) it is not yet. + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/try_err.rs b/src/tools/clippy/clippy_lints/src/try_err.rs new file mode 100644 index 0000000000..73e3a04aec --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/try_err.rs @@ -0,0 +1,190 @@ +use crate::utils::{ + differing_macro_contexts, in_macro, is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet, + snippet_with_macro_callsite, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for usages of `Err(x)?`. + /// + /// **Why is this bad?** The `?` operator is designed to allow calls that + /// can fail to be easily chained. For example, `foo()?.bar()` or + /// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will + /// always return), it is more clear to write `return Err(x)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo(fail: bool) -> Result { + /// if fail { + /// Err("failed")?; + /// } + /// Ok(0) + /// } + /// ``` + /// Could be written: + /// + /// ```rust + /// fn foo(fail: bool) -> Result { + /// if fail { + /// return Err("failed".into()); + /// } + /// Ok(0) + /// } + /// ``` + pub TRY_ERR, + style, + "return errors explicitly rather than hiding them behind a `?`" +} + +declare_lint_pass!(TryErr => [TRY_ERR]); + +impl<'tcx> LateLintPass<'tcx> for TryErr { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // Looks for a structure like this: + // match ::std::ops::Try::into_result(Err(5)) { + // ::std::result::Result::Err(err) => + // #[allow(unreachable_code)] + // return ::std::ops::Try::from_error(::std::convert::From::from(err)), + // ::std::result::Result::Ok(val) => + // #[allow(unreachable_code)] + // val, + // }; + if_chain! { + if !in_external_macro(cx.tcx.sess, expr.span); + if let ExprKind::Match(ref match_arg, _, MatchSource::TryDesugar) = expr.kind; + if let ExprKind::Call(ref match_fun, ref try_args) = match_arg.kind; + if let ExprKind::Path(ref match_fun_path) = match_fun.kind; + if matches!(match_fun_path, QPath::LangItem(LangItem::TryIntoResult, _)); + if let Some(ref try_arg) = try_args.get(0); + if let ExprKind::Call(ref err_fun, ref err_args) = try_arg.kind; + if let Some(ref err_arg) = err_args.get(0); + if let ExprKind::Path(ref err_fun_path) = err_fun.kind; + if match_qpath(err_fun_path, &paths::RESULT_ERR); + if let Some(return_ty) = find_return_type(cx, &expr.kind); + then { + let prefix; + let suffix; + let err_ty; + + if let Some(ty) = result_error_type(cx, return_ty) { + prefix = "Err("; + suffix = ")"; + err_ty = ty; + } else if let Some(ty) = poll_result_error_type(cx, return_ty) { + prefix = "Poll::Ready(Err("; + suffix = "))"; + err_ty = ty; + } else if let Some(ty) = poll_option_result_error_type(cx, return_ty) { + prefix = "Poll::Ready(Some(Err("; + suffix = ")))"; + err_ty = ty; + } else { + return; + }; + + let expr_err_ty = cx.typeck_results().expr_ty(err_arg); + let differing_contexts = differing_macro_contexts(expr.span, err_arg.span); + + let origin_snippet = if in_macro(expr.span) && in_macro(err_arg.span) && differing_contexts { + snippet(cx, err_arg.span.ctxt().outer_expn_data().call_site, "_") + } else if err_arg.span.from_expansion() && !in_macro(expr.span) { + snippet_with_macro_callsite(cx, err_arg.span, "_") + } else { + snippet(cx, err_arg.span, "_") + }; + let suggestion = if err_ty == expr_err_ty { + format!("return {}{}{}", prefix, origin_snippet, suffix) + } else { + format!("return {}{}.into(){}", prefix, origin_snippet, suffix) + }; + + span_lint_and_sugg( + cx, + TRY_ERR, + expr.span, + "returning an `Err(_)` with the `?` operator", + "try this", + suggestion, + Applicability::MachineApplicable + ); + } + } + } +} + +/// Finds function return type by examining return expressions in match arms. +fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> Option> { + if let ExprKind::Match(_, ref arms, MatchSource::TryDesugar) = expr { + for arm in arms.iter() { + if let ExprKind::Ret(Some(ref ret)) = arm.body.kind { + return Some(cx.typeck_results().expr_ty(ret)); + } + } + } + None +} + +/// Extracts the error type from Result. +fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { + if_chain! { + if let ty::Adt(_, subst) = ty.kind(); + if is_type_diagnostic_item(cx, ty, sym::result_type); + let err_ty = subst.type_at(1); + then { + Some(err_ty) + } else { + None + } + } +} + +/// Extracts the error type from Poll>. +fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { + if_chain! { + if let ty::Adt(def, subst) = ty.kind(); + if match_def_path(cx, def.did, &paths::POLL); + let ready_ty = subst.type_at(0); + + if let ty::Adt(ready_def, ready_subst) = ready_ty.kind(); + if cx.tcx.is_diagnostic_item(sym::result_type, ready_def.did); + let err_ty = ready_subst.type_at(1); + + then { + Some(err_ty) + } else { + None + } + } +} + +/// Extracts the error type from Poll>>. +fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { + if_chain! { + if let ty::Adt(def, subst) = ty.kind(); + if match_def_path(cx, def.did, &paths::POLL); + let ready_ty = subst.type_at(0); + + if let ty::Adt(ready_def, ready_subst) = ready_ty.kind(); + if cx.tcx.is_diagnostic_item(sym::option_type, ready_def.did); + let some_ty = ready_subst.type_at(0); + + if let ty::Adt(some_def, some_subst) = some_ty.kind(); + if cx.tcx.is_diagnostic_item(sym::result_type, some_def.did); + let err_ty = some_subst.type_at(1); + + then { + Some(err_ty) + } else { + None + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/types/borrowed_box.rs b/src/tools/clippy/clippy_lints/src/types/borrowed_box.rs new file mode 100644 index 0000000000..81090040d9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/types/borrowed_box.rs @@ -0,0 +1,114 @@ +use rustc_errors::Applicability; +use rustc_hir::{ + self as hir, GenericArg, GenericBounds, GenericParamKind, HirId, Lifetime, MutTy, Mutability, Node, QPath, + SyntheticTyParamKind, TyKind, +}; +use rustc_lint::LateContext; + +use if_chain::if_chain; + +use crate::utils::{match_path, paths, snippet, span_lint_and_sugg}; + +use super::BORROWED_BOX; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, mut_ty: &MutTy<'_>) -> bool { + match mut_ty.ty.kind { + TyKind::Path(ref qpath) => { + let hir_id = mut_ty.ty.hir_id; + let def = cx.qpath_res(qpath, hir_id); + if_chain! { + if let Some(def_id) = def.opt_def_id(); + if Some(def_id) == cx.tcx.lang_items().owned_box(); + if let QPath::Resolved(None, ref path) = *qpath; + if let [ref bx] = *path.segments; + if let Some(ref params) = bx.args; + if !params.parenthesized; + if let Some(inner) = params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + then { + if is_any_trait(inner) { + // Ignore `Box` types; see issue #1884 for details. + return false; + } + + let ltopt = if lt.is_elided() { + String::new() + } else { + format!("{} ", lt.name.ident().as_str()) + }; + + if mut_ty.mutbl == Mutability::Mut { + // Ignore `&mut Box` types; see issue #2907 for + // details. + return false; + } + + // When trait objects or opaque types have lifetime or auto-trait bounds, + // we need to add parentheses to avoid a syntax error due to its ambiguity. + // Originally reported as the issue #3128. + let inner_snippet = snippet(cx, inner.span, ".."); + let suggestion = match &inner.kind { + TyKind::TraitObject(bounds, lt_bound, _) if bounds.len() > 1 || !lt_bound.is_elided() => { + format!("&{}({})", ltopt, &inner_snippet) + }, + TyKind::Path(qpath) + if get_bounds_if_impl_trait(cx, qpath, inner.hir_id) + .map_or(false, |bounds| bounds.len() > 1) => + { + format!("&{}({})", ltopt, &inner_snippet) + }, + _ => format!("&{}{}", ltopt, &inner_snippet), + }; + span_lint_and_sugg( + cx, + BORROWED_BOX, + hir_ty.span, + "you seem to be trying to use `&Box`. Consider using just `&T`", + "try", + suggestion, + // To make this `MachineApplicable`, at least one needs to check if it isn't a trait item + // because the trait impls of it will break otherwise; + // and there may be other cases that result in invalid code. + // For example, type coercion doesn't work nicely. + Applicability::Unspecified, + ); + return true; + } + }; + false + }, + _ => false, + } +} + +// Returns true if given type is `Any` trait. +fn is_any_trait(t: &hir::Ty<'_>) -> bool { + if_chain! { + if let TyKind::TraitObject(ref traits, ..) = t.kind; + if !traits.is_empty(); + // Only Send/Sync can be used as additional traits, so it is enough to + // check only the first trait. + if match_path(&traits[0].trait_ref.path, &paths::ANY_TRAIT); + then { + return true; + } + } + + false +} + +fn get_bounds_if_impl_trait<'tcx>(cx: &LateContext<'tcx>, qpath: &QPath<'_>, id: HirId) -> Option> { + if_chain! { + if let Some(did) = cx.qpath_res(qpath, id).opt_def_id(); + if let Some(Node::GenericParam(generic_param)) = cx.tcx.hir().get_if_local(did); + if let GenericParamKind::Type { synthetic, .. } = generic_param.kind; + if synthetic == Some(SyntheticTyParamKind::ImplTrait); + then { + Some(generic_param.bounds) + } else { + None + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/types/box_vec.rs b/src/tools/clippy/clippy_lints/src/types/box_vec.rs new file mode 100644 index 0000000000..6aa98e435e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/types/box_vec.rs @@ -0,0 +1,25 @@ +use rustc_hir::{self as hir, def_id::DefId, QPath}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use crate::utils::{is_ty_param_diagnostic_item, span_lint_and_help}; + +use super::BOX_VEC; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { + if Some(def_id) == cx.tcx.lang_items().owned_box() + && is_ty_param_diagnostic_item(cx, qpath, sym::vec_type).is_some() + { + span_lint_and_help( + cx, + BOX_VEC, + hir_ty.span, + "you seem to be trying to use `Box>`. Consider using just `Vec`", + None, + "`Vec` is already on the heap, `Box>` makes an extra allocation", + ); + true + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/types/linked_list.rs b/src/tools/clippy/clippy_lints/src/types/linked_list.rs new file mode 100644 index 0000000000..47eb4ede4e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/types/linked_list.rs @@ -0,0 +1,22 @@ +use rustc_hir::{self as hir, def_id::DefId}; +use rustc_lint::LateContext; + +use crate::utils::{match_def_path, paths, span_lint_and_help}; + +use super::LINKEDLIST; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, def_id: DefId) -> bool { + if match_def_path(cx, def_id, &paths::LINKED_LIST) { + span_lint_and_help( + cx, + LINKEDLIST, + hir_ty.span, + "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?", + None, + "a `VecDeque` might work", + ); + true + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/types/mod.rs b/src/tools/clippy/clippy_lints/src/types/mod.rs new file mode 100644 index 0000000000..4a1a608e8a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/types/mod.rs @@ -0,0 +1,1668 @@ +#![allow(rustc::default_hash_types)] + +mod borrowed_box; +mod box_vec; +mod linked_list; +mod option_option; +mod rc_buffer; +mod redundant_allocation; +mod utils; +mod vec_box; + +use std::borrow::Cow; +use std::cmp::Ordering; +use std::collections::BTreeMap; + +use if_chain::if_chain; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_hir as hir; +use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{ + BinOpKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem, + ImplItemKind, Item, ItemKind, Local, MatchSource, MutTy, Node, QPath, Stmt, StmtKind, TraitFn, TraitItem, + TraitItemKind, TyKind, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, IntTy, Ty, TyS, TypeckResults, UintTy}; +use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; +use rustc_span::hygiene::{ExpnKind, MacroKind}; +use rustc_span::source_map::Span; +use rustc_span::symbol::sym; +use rustc_target::abi::LayoutOf; +use rustc_target::spec::abi::Abi; +use rustc_typeck::hir_ty_to_ty; + +use crate::consts::{constant, Constant}; +use crate::utils::paths; +use crate::utils::{ + clip, comparisons, differing_macro_contexts, higher, indent_of, int_bits, is_isize_or_usize, + is_type_diagnostic_item, match_path, multispan_sugg, reindent_multiline, sext, snippet, snippet_opt, + snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_then, unsext, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for use of `Box>` anywhere in the code. + /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information. + /// + /// **Why is this bad?** `Vec` already keeps its contents in a separate area on + /// the heap. So if you `Box` it, you just add another level of indirection + /// without any benefit whatsoever. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// struct X { + /// values: Box>, + /// } + /// ``` + /// + /// Better: + /// + /// ```rust,ignore + /// struct X { + /// values: Vec, + /// } + /// ``` + pub BOX_VEC, + perf, + "usage of `Box>`, vector elements are already on the heap" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `Vec>` where T: Sized anywhere in the code. + /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information. + /// + /// **Why is this bad?** `Vec` already keeps its contents in a separate area on + /// the heap. So if you `Box` its contents, you just add another level of indirection. + /// + /// **Known problems:** Vec> makes sense if T is a large type (see [#3530](https://github.com/rust-lang/rust-clippy/issues/3530), + /// 1st comment). + /// + /// **Example:** + /// ```rust + /// struct X { + /// values: Vec>, + /// } + /// ``` + /// + /// Better: + /// + /// ```rust + /// struct X { + /// values: Vec, + /// } + /// ``` + pub VEC_BOX, + complexity, + "usage of `Vec>` where T: Sized, vector elements are already on the heap" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `Option>` in function signatures and type + /// definitions + /// + /// **Why is this bad?** `Option<_>` represents an optional value. `Option>` + /// represents an optional optional value which is logically the same thing as an optional + /// value but has an unneeded extra level of wrapping. + /// + /// If you have a case where `Some(Some(_))`, `Some(None)` and `None` are distinct cases, + /// consider a custom `enum` instead, with clear names for each case. + /// + /// **Known problems:** None. + /// + /// **Example** + /// ```rust + /// fn get_data() -> Option> { + /// None + /// } + /// ``` + /// + /// Better: + /// + /// ```rust + /// pub enum Contents { + /// Data(Vec), // Was Some(Some(Vec)) + /// NotYetFetched, // Was Some(None) + /// None, // Was None + /// } + /// + /// fn get_data() -> Contents { + /// Contents::None + /// } + /// ``` + pub OPTION_OPTION, + pedantic, + "usage of `Option>`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of any `LinkedList`, suggesting to use a + /// `Vec` or a `VecDeque` (formerly called `RingBuf`). + /// + /// **Why is this bad?** Gankro says: + /// + /// > The TL;DR of `LinkedList` is that it's built on a massive amount of + /// pointers and indirection. + /// > It wastes memory, it has terrible cache locality, and is all-around slow. + /// `RingBuf`, while + /// > "only" amortized for push/pop, should be faster in the general case for + /// almost every possible + /// > workload, and isn't even amortized at all if you can predict the capacity + /// you need. + /// > + /// > `LinkedList`s are only really good if you're doing a lot of merging or + /// splitting of lists. + /// > This is because they can just mangle some pointers instead of actually + /// copying the data. Even + /// > if you're doing a lot of insertion in the middle of the list, `RingBuf` + /// can still be better + /// > because of how expensive it is to seek to the middle of a `LinkedList`. + /// + /// **Known problems:** False positives – the instances where using a + /// `LinkedList` makes sense are few and far between, but they can still happen. + /// + /// **Example:** + /// ```rust + /// # use std::collections::LinkedList; + /// let x: LinkedList = LinkedList::new(); + /// ``` + pub LINKEDLIST, + pedantic, + "usage of LinkedList, usually a vector is faster, or a more specialized data structure like a `VecDeque`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `&Box` anywhere in the code. + /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information. + /// + /// **Why is this bad?** Any `&Box` can also be a `&T`, which is more + /// general. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// fn foo(bar: &Box) { ... } + /// ``` + /// + /// Better: + /// + /// ```rust,ignore + /// fn foo(bar: &T) { ... } + /// ``` + pub BORROWED_BOX, + complexity, + "a borrow of a boxed type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of redundant allocations anywhere in the code. + /// + /// **Why is this bad?** Expressions such as `Rc<&T>`, `Rc>`, `Rc>`, `Box<&T>` + /// add an unnecessary level of indirection. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::rc::Rc; + /// fn foo(bar: Rc<&usize>) {} + /// ``` + /// + /// Better: + /// + /// ```rust + /// fn foo(bar: &usize) {} + /// ``` + pub REDUNDANT_ALLOCATION, + perf, + "redundant allocation" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `Rc` and `Arc` when `T` is a mutable buffer type such as `String` or `Vec`. + /// + /// **Why is this bad?** Expressions such as `Rc` usually have no advantage over `Rc`, since + /// it is larger and involves an extra level of indirection, and doesn't implement `Borrow`. + /// + /// While mutating a buffer type would still be possible with `Rc::get_mut()`, it only + /// works if there are no additional references yet, which usually defeats the purpose of + /// enclosing it in a shared ownership type. Instead, additionally wrapping the inner + /// type with an interior mutable container (such as `RefCell` or `Mutex`) would normally + /// be used. + /// + /// **Known problems:** This pattern can be desirable to avoid the overhead of a `RefCell` or `Mutex` for + /// cases where mutation only happens before there are any additional references. + /// + /// **Example:** + /// ```rust,ignore + /// # use std::rc::Rc; + /// fn foo(interned: Rc) { ... } + /// ``` + /// + /// Better: + /// + /// ```rust,ignore + /// fn foo(interned: Rc) { ... } + /// ``` + pub RC_BUFFER, + restriction, + "shared ownership of a buffer type" +} + +pub struct Types { + vec_box_size_threshold: u64, +} + +impl_lint_pass!(Types => [BOX_VEC, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION, RC_BUFFER]); + +impl<'tcx> LateLintPass<'tcx> for Types { + fn check_fn(&mut self, cx: &LateContext<'_>, _: FnKind<'_>, decl: &FnDecl<'_>, _: &Body<'_>, _: Span, id: HirId) { + // Skip trait implementations; see issue #605. + if let Some(hir::Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_item(id)) { + if let ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) = item.kind { + return; + } + } + + self.check_fn_decl(cx, decl); + } + + fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) { + self.check_ty(cx, &field.ty, false); + } + + fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { + match item.kind { + TraitItemKind::Const(ref ty, _) | TraitItemKind::Type(_, Some(ref ty)) => self.check_ty(cx, ty, false), + TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, &sig.decl), + _ => (), + } + } + + fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) { + if let Some(ref ty) = local.ty { + self.check_ty(cx, ty, true); + } + } +} + +impl Types { + pub fn new(vec_box_size_threshold: u64) -> Self { + Self { vec_box_size_threshold } + } + + fn check_fn_decl(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>) { + for input in decl.inputs { + self.check_ty(cx, input, false); + } + + if let FnRetTy::Return(ref ty) = decl.output { + self.check_ty(cx, ty, false); + } + } + + /// Recursively check for `TypePass` lints in the given type. Stop at the first + /// lint found. + /// + /// The parameter `is_local` distinguishes the context of the type. + fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, is_local: bool) { + if hir_ty.span.from_expansion() { + return; + } + match hir_ty.kind { + TyKind::Path(ref qpath) if !is_local => { + let hir_id = hir_ty.hir_id; + let res = cx.qpath_res(qpath, hir_id); + if let Some(def_id) = res.opt_def_id() { + let mut triggered = false; + triggered |= box_vec::check(cx, hir_ty, qpath, def_id); + triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id); + triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id); + triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold); + triggered |= option_option::check(cx, hir_ty, qpath, def_id); + triggered |= linked_list::check(cx, hir_ty, def_id); + + if triggered { + return; + } + } + match *qpath { + QPath::Resolved(Some(ref ty), ref p) => { + self.check_ty(cx, ty, is_local); + for ty in p.segments.iter().flat_map(|seg| { + seg.args + .as_ref() + .map_or_else(|| [].iter(), |params| params.args.iter()) + .filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }) + }) { + self.check_ty(cx, ty, is_local); + } + }, + QPath::Resolved(None, ref p) => { + for ty in p.segments.iter().flat_map(|seg| { + seg.args + .as_ref() + .map_or_else(|| [].iter(), |params| params.args.iter()) + .filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }) + }) { + self.check_ty(cx, ty, is_local); + } + }, + QPath::TypeRelative(ref ty, ref seg) => { + self.check_ty(cx, ty, is_local); + if let Some(ref params) = seg.args { + for ty in params.args.iter().filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }) { + self.check_ty(cx, ty, is_local); + } + } + }, + QPath::LangItem(..) => {}, + } + }, + TyKind::Rptr(ref lt, ref mut_ty) => { + if !borrowed_box::check(cx, hir_ty, lt, mut_ty) { + self.check_ty(cx, &mut_ty.ty, is_local); + } + }, + TyKind::Slice(ref ty) | TyKind::Array(ref ty, _) | TyKind::Ptr(MutTy { ref ty, .. }) => { + self.check_ty(cx, ty, is_local) + }, + TyKind::Tup(tys) => { + for ty in tys { + self.check_ty(cx, ty, is_local); + } + }, + _ => {}, + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for binding a unit value. + /// + /// **Why is this bad?** A unit value cannot usefully be used anywhere. So + /// binding one is kind of pointless. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = { + /// 1; + /// }; + /// ``` + pub LET_UNIT_VALUE, + pedantic, + "creating a `let` binding to a value of unit type, which usually can't be used afterwards" +} + +declare_lint_pass!(LetUnitValue => [LET_UNIT_VALUE]); + +impl<'tcx> LateLintPass<'tcx> for LetUnitValue { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if let StmtKind::Local(ref local) = stmt.kind { + if is_unit(cx.typeck_results().pat_ty(&local.pat)) { + if in_external_macro(cx.sess(), stmt.span) || local.pat.span.from_expansion() { + return; + } + if higher::is_from_for_desugar(local) { + return; + } + span_lint_and_then( + cx, + LET_UNIT_VALUE, + stmt.span, + "this let-binding has unit value", + |diag| { + if let Some(expr) = &local.init { + let snip = snippet_with_macro_callsite(cx, expr.span, "()"); + diag.span_suggestion( + stmt.span, + "omit the `let` binding", + format!("{};", snip), + Applicability::MachineApplicable, // snippet + ); + } + }, + ); + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons to unit. This includes all binary + /// comparisons (like `==` and `<`) and asserts. + /// + /// **Why is this bad?** Unit is always equal to itself, and thus is just a + /// clumsily written constant. Mostly this happens when someone accidentally + /// adds semicolons at the end of the operands. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn foo() {}; + /// # fn bar() {}; + /// # fn baz() {}; + /// if { + /// foo(); + /// } == { + /// bar(); + /// } { + /// baz(); + /// } + /// ``` + /// is equal to + /// ```rust + /// # fn foo() {}; + /// # fn bar() {}; + /// # fn baz() {}; + /// { + /// foo(); + /// bar(); + /// baz(); + /// } + /// ``` + /// + /// For asserts: + /// ```rust + /// # fn foo() {}; + /// # fn bar() {}; + /// assert_eq!({ foo(); }, { bar(); }); + /// ``` + /// will always succeed + pub UNIT_CMP, + correctness, + "comparing unit values" +} + +declare_lint_pass!(UnitCmp => [UNIT_CMP]); + +impl<'tcx> LateLintPass<'tcx> for UnitCmp { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if expr.span.from_expansion() { + if let Some(callee) = expr.span.source_callee() { + if let ExpnKind::Macro(MacroKind::Bang, symbol) = callee.kind { + if let ExprKind::Binary(ref cmp, ref left, _) = expr.kind { + let op = cmp.node; + if op.is_comparison() && is_unit(cx.typeck_results().expr_ty(left)) { + let result = match &*symbol.as_str() { + "assert_eq" | "debug_assert_eq" => "succeed", + "assert_ne" | "debug_assert_ne" => "fail", + _ => return, + }; + span_lint( + cx, + UNIT_CMP, + expr.span, + &format!( + "`{}` of unit values detected. This will always {}", + symbol.as_str(), + result + ), + ); + } + } + } + } + return; + } + if let ExprKind::Binary(ref cmp, ref left, _) = expr.kind { + let op = cmp.node; + if op.is_comparison() && is_unit(cx.typeck_results().expr_ty(left)) { + let result = match op { + BinOpKind::Eq | BinOpKind::Le | BinOpKind::Ge => "true", + _ => "false", + }; + span_lint( + cx, + UNIT_CMP, + expr.span, + &format!( + "{}-comparison of unit values detected. This will always be {}", + op.as_str(), + result + ), + ); + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for passing a unit value as an argument to a function without using a + /// unit literal (`()`). + /// + /// **Why is this bad?** This is likely the result of an accidental semicolon. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// foo({ + /// let a = bar(); + /// baz(a); + /// }) + /// ``` + pub UNIT_ARG, + complexity, + "passing unit to a function" +} + +declare_lint_pass!(UnitArg => [UNIT_ARG]); + +impl<'tcx> LateLintPass<'tcx> for UnitArg { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + // apparently stuff in the desugaring of `?` can trigger this + // so check for that here + // only the calls to `Try::from_error` is marked as desugared, + // so we need to check both the current Expr and its parent. + if is_questionmark_desugar_marked_call(expr) { + return; + } + if_chain! { + let map = &cx.tcx.hir(); + let opt_parent_node = map.find(map.get_parent_node(expr.hir_id)); + if let Some(hir::Node::Expr(parent_expr)) = opt_parent_node; + if is_questionmark_desugar_marked_call(parent_expr); + then { + return; + } + } + + match expr.kind { + ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) => { + let args_to_recover = args + .iter() + .filter(|arg| { + if is_unit(cx.typeck_results().expr_ty(arg)) && !is_unit_literal(arg) { + !matches!( + &arg.kind, + ExprKind::Match(.., MatchSource::TryDesugar) | ExprKind::Path(..) + ) + } else { + false + } + }) + .collect::>(); + if !args_to_recover.is_empty() { + lint_unit_args(cx, expr, &args_to_recover); + } + }, + _ => (), + } + } +} + +fn fmt_stmts_and_call( + cx: &LateContext<'_>, + call_expr: &Expr<'_>, + call_snippet: &str, + args_snippets: &[impl AsRef], + non_empty_block_args_snippets: &[impl AsRef], +) -> String { + let call_expr_indent = indent_of(cx, call_expr.span).unwrap_or(0); + let call_snippet_with_replacements = args_snippets + .iter() + .fold(call_snippet.to_owned(), |acc, arg| acc.replacen(arg.as_ref(), "()", 1)); + + let mut stmts_and_call = non_empty_block_args_snippets + .iter() + .map(|it| it.as_ref().to_owned()) + .collect::>(); + stmts_and_call.push(call_snippet_with_replacements); + stmts_and_call = stmts_and_call + .into_iter() + .map(|v| reindent_multiline(v.into(), true, Some(call_expr_indent)).into_owned()) + .collect(); + + let mut stmts_and_call_snippet = stmts_and_call.join(&format!("{}{}", ";\n", " ".repeat(call_expr_indent))); + // expr is not in a block statement or result expression position, wrap in a block + let parent_node = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(call_expr.hir_id)); + if !matches!(parent_node, Some(Node::Block(_))) && !matches!(parent_node, Some(Node::Stmt(_))) { + let block_indent = call_expr_indent + 4; + stmts_and_call_snippet = + reindent_multiline(stmts_and_call_snippet.into(), true, Some(block_indent)).into_owned(); + stmts_and_call_snippet = format!( + "{{\n{}{}\n{}}}", + " ".repeat(block_indent), + &stmts_and_call_snippet, + " ".repeat(call_expr_indent) + ); + } + stmts_and_call_snippet +} + +fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + let (singular, plural) = if args_to_recover.len() > 1 { + ("", "s") + } else { + ("a ", "") + }; + span_lint_and_then( + cx, + UNIT_ARG, + expr.span, + &format!("passing {}unit value{} to a function", singular, plural), + |db| { + let mut or = ""; + args_to_recover + .iter() + .filter_map(|arg| { + if_chain! { + if let ExprKind::Block(block, _) = arg.kind; + if block.expr.is_none(); + if let Some(last_stmt) = block.stmts.iter().last(); + if let StmtKind::Semi(last_expr) = last_stmt.kind; + if let Some(snip) = snippet_opt(cx, last_expr.span); + then { + Some(( + last_stmt.span, + snip, + )) + } + else { + None + } + } + }) + .for_each(|(span, sugg)| { + db.span_suggestion( + span, + "remove the semicolon from the last statement in the block", + sugg, + Applicability::MaybeIncorrect, + ); + or = "or "; + applicability = Applicability::MaybeIncorrect; + }); + + let arg_snippets: Vec = args_to_recover + .iter() + .filter_map(|arg| snippet_opt(cx, arg.span)) + .collect(); + let arg_snippets_without_empty_blocks: Vec = args_to_recover + .iter() + .filter(|arg| !is_empty_block(arg)) + .filter_map(|arg| snippet_opt(cx, arg.span)) + .collect(); + + if let Some(call_snippet) = snippet_opt(cx, expr.span) { + let sugg = fmt_stmts_and_call( + cx, + expr, + &call_snippet, + &arg_snippets, + &arg_snippets_without_empty_blocks, + ); + + if arg_snippets_without_empty_blocks.is_empty() { + db.multipart_suggestion( + &format!("use {}unit literal{} instead", singular, plural), + args_to_recover + .iter() + .map(|arg| (arg.span, "()".to_string())) + .collect::>(), + applicability, + ); + } else { + let plural = arg_snippets_without_empty_blocks.len() > 1; + let empty_or_s = if plural { "s" } else { "" }; + let it_or_them = if plural { "them" } else { "it" }; + db.span_suggestion( + expr.span, + &format!( + "{}move the expression{} in front of the call and replace {} with the unit literal `()`", + or, empty_or_s, it_or_them + ), + sugg, + applicability, + ); + } + } + }, + ); +} + +fn is_empty_block(expr: &Expr<'_>) -> bool { + matches!( + expr.kind, + ExprKind::Block( + Block { + stmts: &[], + expr: None, + .. + }, + _, + ) + ) +} + +fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool { + use rustc_span::hygiene::DesugaringKind; + if let ExprKind::Call(ref callee, _) = expr.kind { + callee.span.is_desugaring(DesugaringKind::QuestionMark) + } else { + false + } +} + +fn is_unit(ty: Ty<'_>) -> bool { + matches!(ty.kind(), ty::Tuple(slice) if slice.is_empty()) +} + +fn is_unit_literal(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Tup(ref slice) if slice.is_empty()) +} + +declare_clippy_lint! { + /// **What it does:** Checks for types used in structs, parameters and `let` + /// declarations above a certain complexity threshold. + /// + /// **Why is this bad?** Too complex types make the code less readable. Consider + /// using a `type` definition to simplify them. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::rc::Rc; + /// struct Foo { + /// inner: Rc>>>, + /// } + /// ``` + pub TYPE_COMPLEXITY, + complexity, + "usage of very complex types that might be better factored into `type` definitions" +} + +pub struct TypeComplexity { + threshold: u64, +} + +impl TypeComplexity { + #[must_use] + pub fn new(threshold: u64) -> Self { + Self { threshold } + } +} + +impl_lint_pass!(TypeComplexity => [TYPE_COMPLEXITY]); + +impl<'tcx> LateLintPass<'tcx> for TypeComplexity { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + _: &'tcx Body<'_>, + _: Span, + _: HirId, + ) { + self.check_fndecl(cx, decl); + } + + fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'_>) { + // enum variants are also struct fields now + self.check_type(cx, &field.ty); + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + match item.kind { + ItemKind::Static(ref ty, _, _) | ItemKind::Const(ref ty, _) => self.check_type(cx, ty), + // functions, enums, structs, impls and traits are covered + _ => (), + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + match item.kind { + TraitItemKind::Const(ref ty, _) | TraitItemKind::Type(_, Some(ref ty)) => self.check_type(cx, ty), + TraitItemKind::Fn(FnSig { ref decl, .. }, TraitFn::Required(_)) => self.check_fndecl(cx, decl), + // methods with default impl are covered by check_fn + _ => (), + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { + match item.kind { + ImplItemKind::Const(ref ty, _) | ImplItemKind::TyAlias(ref ty) => self.check_type(cx, ty), + // methods are covered by check_fn + _ => (), + } + } + + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { + if let Some(ref ty) = local.ty { + self.check_type(cx, ty); + } + } +} + +impl<'tcx> TypeComplexity { + fn check_fndecl(&self, cx: &LateContext<'tcx>, decl: &'tcx FnDecl<'_>) { + for arg in decl.inputs { + self.check_type(cx, arg); + } + if let FnRetTy::Return(ref ty) = decl.output { + self.check_type(cx, ty); + } + } + + fn check_type(&self, cx: &LateContext<'_>, ty: &hir::Ty<'_>) { + if ty.span.from_expansion() { + return; + } + let score = { + let mut visitor = TypeComplexityVisitor { score: 0, nest: 1 }; + visitor.visit_ty(ty); + visitor.score + }; + + if score > self.threshold { + span_lint( + cx, + TYPE_COMPLEXITY, + ty.span, + "very complex type used. Consider factoring parts into `type` definitions", + ); + } + } +} + +/// Walks a type and assigns a complexity score to it. +struct TypeComplexityVisitor { + /// total complexity score of the type + score: u64, + /// current nesting level + nest: u64, +} + +impl<'tcx> Visitor<'tcx> for TypeComplexityVisitor { + type Map = Map<'tcx>; + + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) { + let (add_score, sub_nest) = match ty.kind { + // _, &x and *x have only small overhead; don't mess with nesting level + TyKind::Infer | TyKind::Ptr(..) | TyKind::Rptr(..) => (1, 0), + + // the "normal" components of a type: named types, arrays/tuples + TyKind::Path(..) | TyKind::Slice(..) | TyKind::Tup(..) | TyKind::Array(..) => (10 * self.nest, 1), + + // function types bring a lot of overhead + TyKind::BareFn(ref bare) if bare.abi == Abi::Rust => (50 * self.nest, 1), + + TyKind::TraitObject(ref param_bounds, ..) => { + let has_lifetime_parameters = param_bounds.iter().any(|bound| { + bound + .bound_generic_params + .iter() + .any(|gen| matches!(gen.kind, GenericParamKind::Lifetime { .. })) + }); + if has_lifetime_parameters { + // complex trait bounds like A<'a, 'b> + (50 * self.nest, 1) + } else { + // simple trait bounds like A + B + (20 * self.nest, 0) + } + }, + + _ => (0, 0), + }; + self.score += add_score; + self.nest += sub_nest; + walk_ty(self, ty); + self.nest -= sub_nest; + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons where one side of the relation is + /// either the minimum or maximum value for its type and warns if it involves a + /// case that is always true or always false. Only integer and boolean types are + /// checked. + /// + /// **Why is this bad?** An expression like `min <= x` may misleadingly imply + /// that it is possible for `x` to be less than the minimum. Expressions like + /// `max < x` are probably mistakes. + /// + /// **Known problems:** For `usize` the size of the current compile target will + /// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such + /// a comparison to detect target pointer width will trigger this lint. One can + /// use `mem::sizeof` and compare its value or conditional compilation + /// attributes + /// like `#[cfg(target_pointer_width = "64")] ..` instead. + /// + /// **Example:** + /// + /// ```rust + /// let vec: Vec = Vec::new(); + /// if vec.len() <= 0 {} + /// if 100 > i32::MAX {} + /// ``` + pub ABSURD_EXTREME_COMPARISONS, + correctness, + "a comparison with a maximum or minimum value that is always true or false" +} + +declare_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]); + +enum ExtremeType { + Minimum, + Maximum, +} + +struct ExtremeExpr<'a> { + which: ExtremeType, + expr: &'a Expr<'a>, +} + +enum AbsurdComparisonResult { + AlwaysFalse, + AlwaysTrue, + InequalityImpossible, +} + +fn is_cast_between_fixed_and_target<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + if let ExprKind::Cast(ref cast_exp, _) = expr.kind { + let precast_ty = cx.typeck_results().expr_ty(cast_exp); + let cast_ty = cx.typeck_results().expr_ty(expr); + + return is_isize_or_usize(precast_ty) != is_isize_or_usize(cast_ty); + } + + false +} + +fn detect_absurd_comparison<'tcx>( + cx: &LateContext<'tcx>, + op: BinOpKind, + lhs: &'tcx Expr<'_>, + rhs: &'tcx Expr<'_>, +) -> Option<(ExtremeExpr<'tcx>, AbsurdComparisonResult)> { + use crate::types::AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible}; + use crate::types::ExtremeType::{Maximum, Minimum}; + use crate::utils::comparisons::{normalize_comparison, Rel}; + + // absurd comparison only makes sense on primitive types + // primitive types don't implement comparison operators with each other + if cx.typeck_results().expr_ty(lhs) != cx.typeck_results().expr_ty(rhs) { + return None; + } + + // comparisons between fix sized types and target sized types are considered unanalyzable + if is_cast_between_fixed_and_target(cx, lhs) || is_cast_between_fixed_and_target(cx, rhs) { + return None; + } + + let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?; + + let lx = detect_extreme_expr(cx, normalized_lhs); + let rx = detect_extreme_expr(cx, normalized_rhs); + + Some(match rel { + Rel::Lt => { + match (lx, rx) { + (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, AlwaysFalse), // max < x + (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, AlwaysFalse), // x < min + _ => return None, + } + }, + Rel::Le => { + match (lx, rx) { + (Some(l @ ExtremeExpr { which: Minimum, .. }), _) => (l, AlwaysTrue), // min <= x + (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, InequalityImpossible), // max <= x + (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, InequalityImpossible), // x <= min + (_, Some(r @ ExtremeExpr { which: Maximum, .. })) => (r, AlwaysTrue), // x <= max + _ => return None, + } + }, + Rel::Ne | Rel::Eq => return None, + }) +} + +fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option> { + use crate::types::ExtremeType::{Maximum, Minimum}; + + let ty = cx.typeck_results().expr_ty(expr); + + let cv = constant(cx, cx.typeck_results(), expr)?.0; + + let which = match (ty.kind(), cv) { + (&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => Minimum, + (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MIN >> (128 - int_bits(cx.tcx, ity)), ity) => { + Minimum + }, + + (&ty::Bool, Constant::Bool(true)) => Maximum, + (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MAX >> (128 - int_bits(cx.tcx, ity)), ity) => { + Maximum + }, + (&ty::Uint(uty), Constant::Int(i)) if clip(cx.tcx, u128::MAX, uty) == i => Maximum, + + _ => return None, + }; + Some(ExtremeExpr { which, expr }) +} + +impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + use crate::types::AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible}; + use crate::types::ExtremeType::{Maximum, Minimum}; + + if let ExprKind::Binary(ref cmp, ref lhs, ref rhs) = expr.kind { + if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) { + if !expr.span.from_expansion() { + let msg = "this comparison involving the minimum or maximum element for this \ + type contains a case that is always true or always false"; + + let conclusion = match result { + AlwaysFalse => "this comparison is always false".to_owned(), + AlwaysTrue => "this comparison is always true".to_owned(), + InequalityImpossible => format!( + "the case where the two sides are not equal never occurs, consider using `{} == {}` \ + instead", + snippet(cx, lhs.span, "lhs"), + snippet(cx, rhs.span, "rhs") + ), + }; + + let help = format!( + "because `{}` is the {} value for this type, {}", + snippet(cx, culprit.expr.span, "x"), + match culprit.which { + Minimum => "minimum", + Maximum => "maximum", + }, + conclusion + ); + + span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help); + } + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons where the relation is always either + /// true or false, but where one side has been upcast so that the comparison is + /// necessary. Only integer types are checked. + /// + /// **Why is this bad?** An expression like `let x : u8 = ...; (x as u32) > 300` + /// will mistakenly imply that it is possible for `x` to be outside the range of + /// `u8`. + /// + /// **Known problems:** + /// https://github.com/rust-lang/rust-clippy/issues/886 + /// + /// **Example:** + /// ```rust + /// let x: u8 = 1; + /// (x as u32) > 300; + /// ``` + pub INVALID_UPCAST_COMPARISONS, + pedantic, + "a comparison involving an upcast which is always true or false" +} + +declare_lint_pass!(InvalidUpcastComparisons => [INVALID_UPCAST_COMPARISONS]); + +#[derive(Copy, Clone, Debug, Eq)] +enum FullInt { + S(i128), + U(u128), +} + +impl FullInt { + #[allow(clippy::cast_sign_loss)] + #[must_use] + fn cmp_s_u(s: i128, u: u128) -> Ordering { + if s < 0 { + Ordering::Less + } else if u > (i128::MAX as u128) { + Ordering::Greater + } else { + (s as u128).cmp(&u) + } + } +} + +impl PartialEq for FullInt { + #[must_use] + fn eq(&self, other: &Self) -> bool { + self.partial_cmp(other).expect("`partial_cmp` only returns `Some(_)`") == Ordering::Equal + } +} + +impl PartialOrd for FullInt { + #[must_use] + fn partial_cmp(&self, other: &Self) -> Option { + Some(match (self, other) { + (&Self::S(s), &Self::S(o)) => s.cmp(&o), + (&Self::U(s), &Self::U(o)) => s.cmp(&o), + (&Self::S(s), &Self::U(o)) => Self::cmp_s_u(s, o), + (&Self::U(s), &Self::S(o)) => Self::cmp_s_u(o, s).reverse(), + }) + } +} + +impl Ord for FullInt { + #[must_use] + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other) + .expect("`partial_cmp` for FullInt can never return `None`") + } +} + +fn numeric_cast_precast_bounds<'a>(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<(FullInt, FullInt)> { + if let ExprKind::Cast(ref cast_exp, _) = expr.kind { + let pre_cast_ty = cx.typeck_results().expr_ty(cast_exp); + let cast_ty = cx.typeck_results().expr_ty(expr); + // if it's a cast from i32 to u32 wrapping will invalidate all these checks + if cx.layout_of(pre_cast_ty).ok().map(|l| l.size) == cx.layout_of(cast_ty).ok().map(|l| l.size) { + return None; + } + match pre_cast_ty.kind() { + ty::Int(int_ty) => Some(match int_ty { + IntTy::I8 => (FullInt::S(i128::from(i8::MIN)), FullInt::S(i128::from(i8::MAX))), + IntTy::I16 => (FullInt::S(i128::from(i16::MIN)), FullInt::S(i128::from(i16::MAX))), + IntTy::I32 => (FullInt::S(i128::from(i32::MIN)), FullInt::S(i128::from(i32::MAX))), + IntTy::I64 => (FullInt::S(i128::from(i64::MIN)), FullInt::S(i128::from(i64::MAX))), + IntTy::I128 => (FullInt::S(i128::MIN), FullInt::S(i128::MAX)), + IntTy::Isize => (FullInt::S(isize::MIN as i128), FullInt::S(isize::MAX as i128)), + }), + ty::Uint(uint_ty) => Some(match uint_ty { + UintTy::U8 => (FullInt::U(u128::from(u8::MIN)), FullInt::U(u128::from(u8::MAX))), + UintTy::U16 => (FullInt::U(u128::from(u16::MIN)), FullInt::U(u128::from(u16::MAX))), + UintTy::U32 => (FullInt::U(u128::from(u32::MIN)), FullInt::U(u128::from(u32::MAX))), + UintTy::U64 => (FullInt::U(u128::from(u64::MIN)), FullInt::U(u128::from(u64::MAX))), + UintTy::U128 => (FullInt::U(u128::MIN), FullInt::U(u128::MAX)), + UintTy::Usize => (FullInt::U(usize::MIN as u128), FullInt::U(usize::MAX as u128)), + }), + _ => None, + } + } else { + None + } +} + +fn node_as_const_fullint<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option { + let val = constant(cx, cx.typeck_results(), expr)?.0; + if let Constant::Int(const_int) = val { + match *cx.typeck_results().expr_ty(expr).kind() { + ty::Int(ity) => Some(FullInt::S(sext(cx.tcx, const_int, ity))), + ty::Uint(_) => Some(FullInt::U(const_int)), + _ => None, + } + } else { + None + } +} + +fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) { + if let ExprKind::Cast(ref cast_val, _) = expr.kind { + span_lint( + cx, + INVALID_UPCAST_COMPARISONS, + span, + &format!( + "because of the numeric bounds on `{}` prior to casting, this expression is always {}", + snippet(cx, cast_val.span, "the expression"), + if always { "true" } else { "false" }, + ), + ); + } +} + +fn upcast_comparison_bounds_err<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + rel: comparisons::Rel, + lhs_bounds: Option<(FullInt, FullInt)>, + lhs: &'tcx Expr<'_>, + rhs: &'tcx Expr<'_>, + invert: bool, +) { + use crate::utils::comparisons::Rel; + + if let Some((lb, ub)) = lhs_bounds { + if let Some(norm_rhs_val) = node_as_const_fullint(cx, rhs) { + if rel == Rel::Eq || rel == Rel::Ne { + if norm_rhs_val < lb || norm_rhs_val > ub { + err_upcast_comparison(cx, span, lhs, rel == Rel::Ne); + } + } else if match rel { + Rel::Lt => { + if invert { + norm_rhs_val < lb + } else { + ub < norm_rhs_val + } + }, + Rel::Le => { + if invert { + norm_rhs_val <= lb + } else { + ub <= norm_rhs_val + } + }, + Rel::Eq | Rel::Ne => unreachable!(), + } { + err_upcast_comparison(cx, span, lhs, true) + } else if match rel { + Rel::Lt => { + if invert { + norm_rhs_val >= ub + } else { + lb >= norm_rhs_val + } + }, + Rel::Le => { + if invert { + norm_rhs_val > ub + } else { + lb > norm_rhs_val + } + }, + Rel::Eq | Rel::Ne => unreachable!(), + } { + err_upcast_comparison(cx, span, lhs, false) + } + } + } +} + +impl<'tcx> LateLintPass<'tcx> for InvalidUpcastComparisons { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(ref cmp, ref lhs, ref rhs) = expr.kind { + let normalized = comparisons::normalize_comparison(cmp.node, lhs, rhs); + let (rel, normalized_lhs, normalized_rhs) = if let Some(val) = normalized { + val + } else { + return; + }; + + let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs); + let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs); + + upcast_comparison_bounds_err(cx, expr.span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false); + upcast_comparison_bounds_err(cx, expr.span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true); + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for public `impl` or `fn` missing generalization + /// over different hashers and implicitly defaulting to the default hashing + /// algorithm (`SipHash`). + /// + /// **Why is this bad?** `HashMap` or `HashSet` with custom hashers cannot be + /// used with them. + /// + /// **Known problems:** Suggestions for replacing constructors can contain + /// false-positives. Also applying suggestions can require modification of other + /// pieces of code, possibly including external crates. + /// + /// **Example:** + /// ```rust + /// # use std::collections::HashMap; + /// # use std::hash::{Hash, BuildHasher}; + /// # trait Serialize {}; + /// impl Serialize for HashMap { } + /// + /// pub fn foo(map: &mut HashMap) { } + /// ``` + /// could be rewritten as + /// ```rust + /// # use std::collections::HashMap; + /// # use std::hash::{Hash, BuildHasher}; + /// # trait Serialize {}; + /// impl Serialize for HashMap { } + /// + /// pub fn foo(map: &mut HashMap) { } + /// ``` + pub IMPLICIT_HASHER, + pedantic, + "missing generalization over different hashers" +} + +declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]); + +impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { + #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)] + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + use rustc_span::BytePos; + + fn suggestion<'tcx>( + cx: &LateContext<'tcx>, + diag: &mut DiagnosticBuilder<'_>, + generics_span: Span, + generics_suggestion_span: Span, + target: &ImplicitHasherType<'_>, + vis: ImplicitHasherConstructorVisitor<'_, '_, '_>, + ) { + let generics_snip = snippet(cx, generics_span, ""); + // trim `<` `>` + let generics_snip = if generics_snip.is_empty() { + "" + } else { + &generics_snip[1..generics_snip.len() - 1] + }; + + multispan_sugg( + diag, + "consider adding a type parameter", + vec![ + ( + generics_suggestion_span, + format!( + "<{}{}S: ::std::hash::BuildHasher{}>", + generics_snip, + if generics_snip.is_empty() { "" } else { ", " }, + if vis.suggestions.is_empty() { + "" + } else { + // request users to add `Default` bound so that generic constructors can be used + " + Default" + }, + ), + ), + ( + target.span(), + format!("{}<{}, S>", target.type_name(), target.type_arguments(),), + ), + ], + ); + + if !vis.suggestions.is_empty() { + multispan_sugg(diag, "...and use generic constructor", vis.suggestions); + } + } + + if !cx.access_levels.is_exported(item.hir_id()) { + return; + } + + match item.kind { + ItemKind::Impl(ref impl_) => { + let mut vis = ImplicitHasherTypeVisitor::new(cx); + vis.visit_ty(impl_.self_ty); + + for target in &vis.found { + if differing_macro_contexts(item.span, target.span()) { + return; + } + + let generics_suggestion_span = impl_.generics.span.substitute_dummy({ + let pos = snippet_opt(cx, item.span.until(target.span())) + .and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4))); + if let Some(pos) = pos { + Span::new(pos, pos, item.span.data().ctxt) + } else { + return; + } + }); + + let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target); + for item in impl_.items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) { + ctr_vis.visit_impl_item(item); + } + + span_lint_and_then( + cx, + IMPLICIT_HASHER, + target.span(), + &format!( + "impl for `{}` should be generalized over different hashers", + target.type_name() + ), + move |diag| { + suggestion(cx, diag, impl_.generics.span, generics_suggestion_span, target, ctr_vis); + }, + ); + } + }, + ItemKind::Fn(ref sig, ref generics, body_id) => { + let body = cx.tcx.hir().body(body_id); + + for ty in sig.decl.inputs { + let mut vis = ImplicitHasherTypeVisitor::new(cx); + vis.visit_ty(ty); + + for target in &vis.found { + if in_external_macro(cx.sess(), generics.span) { + continue; + } + let generics_suggestion_span = generics.span.substitute_dummy({ + let pos = snippet_opt(cx, item.span.until(body.params[0].pat.span)) + .and_then(|snip| { + let i = snip.find("fn")?; + Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32)) + }) + .expect("failed to create span for type parameters"); + Span::new(pos, pos, item.span.data().ctxt) + }); + + let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target); + ctr_vis.visit_body(body); + + span_lint_and_then( + cx, + IMPLICIT_HASHER, + target.span(), + &format!( + "parameter of type `{}` should be generalized over different hashers", + target.type_name() + ), + move |diag| { + suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis); + }, + ); + } + } + }, + _ => {}, + } + } +} + +enum ImplicitHasherType<'tcx> { + HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>), + HashSet(Span, Ty<'tcx>, Cow<'static, str>), +} + +impl<'tcx> ImplicitHasherType<'tcx> { + /// Checks that `ty` is a target type without a `BuildHasher`. + fn new(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Option { + if let TyKind::Path(QPath::Resolved(None, ref path)) = hir_ty.kind { + let params: Vec<_> = path + .segments + .last() + .as_ref()? + .args + .as_ref()? + .args + .iter() + .filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }) + .collect(); + let params_len = params.len(); + + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + + if is_type_diagnostic_item(cx, ty, sym::hashmap_type) && params_len == 2 { + Some(ImplicitHasherType::HashMap( + hir_ty.span, + ty, + snippet(cx, params[0].span, "K"), + snippet(cx, params[1].span, "V"), + )) + } else if is_type_diagnostic_item(cx, ty, sym::hashset_type) && params_len == 1 { + Some(ImplicitHasherType::HashSet( + hir_ty.span, + ty, + snippet(cx, params[0].span, "T"), + )) + } else { + None + } + } else { + None + } + } + + fn type_name(&self) -> &'static str { + match *self { + ImplicitHasherType::HashMap(..) => "HashMap", + ImplicitHasherType::HashSet(..) => "HashSet", + } + } + + fn type_arguments(&self) -> String { + match *self { + ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{}, {}", k, v), + ImplicitHasherType::HashSet(.., ref t) => format!("{}", t), + } + } + + fn ty(&self) -> Ty<'tcx> { + match *self { + ImplicitHasherType::HashMap(_, ty, ..) | ImplicitHasherType::HashSet(_, ty, ..) => ty, + } + } + + fn span(&self) -> Span { + match *self { + ImplicitHasherType::HashMap(span, ..) | ImplicitHasherType::HashSet(span, ..) => span, + } + } +} + +struct ImplicitHasherTypeVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + found: Vec>, +} + +impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { cx, found: vec![] } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) { + if let Some(target) = ImplicitHasherType::new(self.cx, t) { + self.found.push(target); + } + + walk_ty(self, t); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Looks for default-hasher-dependent constructors like `HashMap::new`. +struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> { + cx: &'a LateContext<'tcx>, + maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>, + target: &'b ImplicitHasherType<'tcx>, + suggestions: BTreeMap, +} + +impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> { + fn new(cx: &'a LateContext<'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self { + Self { + cx, + maybe_typeck_results: cx.maybe_typeck_results(), + target, + suggestions: BTreeMap::new(), + } + } +} + +impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> { + type Map = Map<'tcx>; + + fn visit_body(&mut self, body: &'tcx Body<'_>) { + let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id())); + walk_body(self, body); + self.maybe_typeck_results = old_maybe_typeck_results; + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref fun, ref args) = e.kind; + if let ExprKind::Path(QPath::TypeRelative(ref ty, ref method)) = fun.kind; + if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind; + then { + if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) { + return; + } + + if match_path(ty_path, &paths::HASHMAP) { + if method.ident.name == sym::new { + self.suggestions + .insert(e.span, "HashMap::default()".to_string()); + } else if method.ident.name == sym!(with_capacity) { + self.suggestions.insert( + e.span, + format!( + "HashMap::with_capacity_and_hasher({}, Default::default())", + snippet(self.cx, args[0].span, "capacity"), + ), + ); + } + } else if match_path(ty_path, &paths::HASHSET) { + if method.ident.name == sym::new { + self.suggestions + .insert(e.span, "HashSet::default()".to_string()); + } else if method.ident.name == sym!(with_capacity) { + self.suggestions.insert( + e.span, + format!( + "HashSet::with_capacity_and_hasher({}, Default::default())", + snippet(self.cx, args[0].span, "capacity"), + ), + ); + } + } + } + } + + walk_expr(self, e); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} diff --git a/src/tools/clippy/clippy_lints/src/types/option_option.rs b/src/tools/clippy/clippy_lints/src/types/option_option.rs new file mode 100644 index 0000000000..dc5db963b4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/types/option_option.rs @@ -0,0 +1,24 @@ +use rustc_hir::{self as hir, def_id::DefId, QPath}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use crate::utils::{is_ty_param_diagnostic_item, span_lint}; + +use super::OPTION_OPTION; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { + if cx.tcx.is_diagnostic_item(sym::option_type, def_id) + && is_ty_param_diagnostic_item(cx, qpath, sym::option_type).is_some() + { + span_lint( + cx, + OPTION_OPTION, + hir_ty.span, + "consider using `Option` instead of `Option>` or a custom \ + enum if you need to distinguish all 3 cases", + ); + true + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs b/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs new file mode 100644 index 0000000000..e34b95147e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs @@ -0,0 +1,98 @@ +use rustc_errors::Applicability; +use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use crate::utils::{ + get_qpath_generic_tys, is_ty_param_diagnostic_item, snippet_with_applicability, span_lint_and_sugg, +}; + +use super::RC_BUFFER; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { + if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { + if let Some(alternate) = match_buffer_type(cx, qpath) { + span_lint_and_sugg( + cx, + RC_BUFFER, + hir_ty.span, + "usage of `Rc` when T is a buffer type", + "try", + format!("Rc<{}>", alternate), + Applicability::MachineApplicable, + ); + } else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::vec_type) { + let qpath = match &ty.kind { + TyKind::Path(qpath) => qpath, + _ => return false, + }; + let inner_span = match get_qpath_generic_tys(qpath).next() { + Some(ty) => ty.span, + None => return false, + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + RC_BUFFER, + hir_ty.span, + "usage of `Rc` when T is a buffer type", + "try", + format!( + "Rc<[{}]>", + snippet_with_applicability(cx, inner_span, "..", &mut applicability) + ), + Applicability::MachineApplicable, + ); + return true; + } + } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { + if let Some(alternate) = match_buffer_type(cx, qpath) { + span_lint_and_sugg( + cx, + RC_BUFFER, + hir_ty.span, + "usage of `Arc` when T is a buffer type", + "try", + format!("Arc<{}>", alternate), + Applicability::MachineApplicable, + ); + } else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::vec_type) { + let qpath = match &ty.kind { + TyKind::Path(qpath) => qpath, + _ => return false, + }; + let inner_span = match get_qpath_generic_tys(qpath).next() { + Some(ty) => ty.span, + None => return false, + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + RC_BUFFER, + hir_ty.span, + "usage of `Arc` when T is a buffer type", + "try", + format!( + "Arc<[{}]>", + snippet_with_applicability(cx, inner_span, "..", &mut applicability) + ), + Applicability::MachineApplicable, + ); + return true; + } + } + + false +} + +fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> { + if is_ty_param_diagnostic_item(cx, qpath, sym::string_type).is_some() { + Some("str") + } else if is_ty_param_diagnostic_item(cx, qpath, sym::OsString).is_some() { + Some("std::ffi::OsStr") + } else if is_ty_param_diagnostic_item(cx, qpath, sym::PathBuf).is_some() { + Some("std::path::Path") + } else { + None + } +} diff --git a/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs b/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs new file mode 100644 index 0000000000..5da6db179c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs @@ -0,0 +1,84 @@ +use rustc_errors::Applicability; +use rustc_hir::{self as hir, def_id::DefId, LangItem, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use crate::utils::{ + get_qpath_generic_tys, is_ty_param_diagnostic_item, is_ty_param_lang_item, snippet_with_applicability, + span_lint_and_sugg, +}; + +use super::{utils, REDUNDANT_ALLOCATION}; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { + if Some(def_id) == cx.tcx.lang_items().owned_box() { + if let Some(span) = utils::match_borrows_parameter(cx, qpath) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Box<&T>`", + "try", + snippet_with_applicability(cx, span, "..", &mut applicability).to_string(), + applicability, + ); + return true; + } + } + + if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { + if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Rc) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Rc>`", + "try", + snippet_with_applicability(cx, ty.span, "..", &mut applicability).to_string(), + applicability, + ); + true + } else if let Some(ty) = is_ty_param_lang_item(cx, qpath, LangItem::OwnedBox) { + let qpath = match &ty.kind { + TyKind::Path(qpath) => qpath, + _ => return false, + }; + let inner_span = match get_qpath_generic_tys(qpath).next() { + Some(ty) => ty.span, + None => return false, + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Rc>`", + "try", + format!( + "Rc<{}>", + snippet_with_applicability(cx, inner_span, "..", &mut applicability) + ), + applicability, + ); + true + } else { + utils::match_borrows_parameter(cx, qpath).map_or(false, |span| { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Rc<&T>`", + "try", + snippet_with_applicability(cx, span, "..", &mut applicability).to_string(), + applicability, + ); + true + }) + } + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/types/utils.rs b/src/tools/clippy/clippy_lints/src/types/utils.rs new file mode 100644 index 0000000000..4d64748f99 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/types/utils.rs @@ -0,0 +1,24 @@ +use rustc_hir::{GenericArg, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use crate::utils::last_path_segment; + +use if_chain::if_chain; + +pub(super) fn match_borrows_parameter(_cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option { + let last = last_path_segment(qpath); + if_chain! { + if let Some(ref params) = last.args; + if !params.parenthesized; + if let Some(ty) = params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + if let TyKind::Rptr(..) = ty.kind; + then { + return Some(ty.span); + } + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/types/vec_box.rs b/src/tools/clippy/clippy_lints/src/types/vec_box.rs new file mode 100644 index 0000000000..2530cc133c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/types/vec_box.rs @@ -0,0 +1,64 @@ +use rustc_errors::Applicability; +use rustc_hir::{self as hir, def_id::DefId, GenericArg, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::TypeFoldable; +use rustc_span::symbol::sym; +use rustc_target::abi::LayoutOf; +use rustc_typeck::hir_ty_to_ty; + +use if_chain::if_chain; + +use crate::utils::{last_path_segment, snippet, span_lint_and_sugg}; + +use super::VEC_BOX; + +pub(super) fn check( + cx: &LateContext<'_>, + hir_ty: &hir::Ty<'_>, + qpath: &QPath<'_>, + def_id: DefId, + box_size_threshold: u64, +) -> bool { + if cx.tcx.is_diagnostic_item(sym::vec_type, def_id) { + if_chain! { + // Get the _ part of Vec<_> + if let Some(ref last) = last_path_segment(qpath).args; + if let Some(ty) = last.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + // ty is now _ at this point + if let TyKind::Path(ref ty_qpath) = ty.kind; + let res = cx.qpath_res(ty_qpath, ty.hir_id); + if let Some(def_id) = res.opt_def_id(); + if Some(def_id) == cx.tcx.lang_items().owned_box(); + // At this point, we know ty is Box, now get T + if let Some(ref last) = last_path_segment(ty_qpath).args; + if let Some(boxed_ty) = last.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + let ty_ty = hir_ty_to_ty(cx.tcx, boxed_ty); + if !ty_ty.has_escaping_bound_vars(); + if ty_ty.is_sized(cx.tcx.at(ty.span), cx.param_env); + if let Ok(ty_ty_size) = cx.layout_of(ty_ty).map(|l| l.size.bytes()); + if ty_ty_size <= box_size_threshold; + then { + span_lint_and_sugg( + cx, + VEC_BOX, + hir_ty.span, + "`Vec` is already on the heap, the boxing is unnecessary", + "try", + format!("Vec<{}>", snippet(cx, boxed_ty.span, "..")), + Applicability::MachineApplicable, + ); + true + } else { + false + } + } + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/undropped_manually_drops.rs b/src/tools/clippy/clippy_lints/src/undropped_manually_drops.rs new file mode 100644 index 0000000000..5443f1601f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/undropped_manually_drops.rs @@ -0,0 +1,50 @@ +use crate::utils::{is_type_lang_item, match_function_call, paths, span_lint_and_help}; +use rustc_hir::{lang_items, Expr}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`. + /// + /// **Why is this bad?** The safe `drop` function does not drop the inner value of a `ManuallyDrop`. + /// + /// **Known problems:** Does not catch cases if the user binds `std::mem::drop` + /// to a different name and calls it that way. + /// + /// **Example:** + /// + /// ```rust + /// struct S; + /// drop(std::mem::ManuallyDrop::new(S)); + /// ``` + /// Use instead: + /// ```rust + /// struct S; + /// unsafe { + /// std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S)); + /// } + /// ``` + pub UNDROPPED_MANUALLY_DROPS, + correctness, + "use of safe `std::mem::drop` function to drop a std::mem::ManuallyDrop, which will not drop the inner value" +} + +declare_lint_pass!(UndroppedManuallyDrops => [UNDROPPED_MANUALLY_DROPS]); + +impl LateLintPass<'tcx> for UndroppedManuallyDrops { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(ref args) = match_function_call(cx, expr, &paths::DROP) { + let ty = cx.typeck_results().expr_ty(&args[0]); + if is_type_lang_item(cx, ty, lang_items::LangItem::ManuallyDrop) { + span_lint_and_help( + cx, + UNDROPPED_MANUALLY_DROPS, + expr.span, + "the inner value of this ManuallyDrop will not be dropped", + None, + "to drop a `ManuallyDrop`, use std::mem::ManuallyDrop::drop", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/unicode.rs b/src/tools/clippy/clippy_lints/src/unicode.rs new file mode 100644 index 0000000000..93d59cc7fc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unicode.rs @@ -0,0 +1,134 @@ +use crate::utils::{is_allowed, snippet, span_lint_and_sugg}; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, HirId}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use unicode_normalization::UnicodeNormalization; + +declare_clippy_lint! { + /// **What it does:** Checks for invisible Unicode characters in the code. + /// + /// **Why is this bad?** Having an invisible character in the code makes for all + /// sorts of April fools, but otherwise is very much frowned upon. + /// + /// **Known problems:** None. + /// + /// **Example:** You don't see it, but there may be a zero-width space or soft hyphen + /// some­where in this text. + pub INVISIBLE_CHARACTERS, + correctness, + "using an invisible character in a string literal, which is confusing" +} + +declare_clippy_lint! { + /// **What it does:** Checks for non-ASCII characters in string literals. + /// + /// **Why is this bad?** Yeah, we know, the 90's called and wanted their charset + /// back. Even so, there still are editors and other programs out there that + /// don't work well with Unicode. So if the code is meant to be used + /// internationally, on multiple operating systems, or has other portability + /// requirements, activating this lint could be useful. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = String::from("€"); + /// ``` + /// Could be written as: + /// ```rust + /// let x = String::from("\u{20ac}"); + /// ``` + pub NON_ASCII_LITERAL, + pedantic, + "using any literal non-ASCII chars in a string literal instead of using the `\\u` escape" +} + +declare_clippy_lint! { + /// **What it does:** Checks for string literals that contain Unicode in a form + /// that is not equal to its + /// [NFC-recomposition](http://www.unicode.org/reports/tr15/#Norm_Forms). + /// + /// **Why is this bad?** If such a string is compared to another, the results + /// may be surprising. + /// + /// **Known problems** None. + /// + /// **Example:** You may not see it, but "à"" and "à"" aren't the same string. The + /// former when escaped is actually `"a\u{300}"` while the latter is `"\u{e0}"`. + pub UNICODE_NOT_NFC, + pedantic, + "using a Unicode literal not in NFC normal form (see [Unicode tr15](http://www.unicode.org/reports/tr15/) for further information)" +} + +declare_lint_pass!(Unicode => [INVISIBLE_CHARACTERS, NON_ASCII_LITERAL, UNICODE_NOT_NFC]); + +impl LateLintPass<'_> for Unicode { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { + if let ExprKind::Lit(ref lit) = expr.kind { + if let LitKind::Str(_, _) = lit.node { + check_str(cx, lit.span, expr.hir_id) + } + } + } +} + +fn escape>(s: T) -> String { + let mut result = String::new(); + for c in s { + if c as u32 > 0x7F { + for d in c.escape_unicode() { + result.push(d) + } + } else { + result.push(c); + } + } + result +} + +fn check_str(cx: &LateContext<'_>, span: Span, id: HirId) { + let string = snippet(cx, span, ""); + if string.chars().any(|c| ['\u{200B}', '\u{ad}', '\u{2060}'].contains(&c)) { + span_lint_and_sugg( + cx, + INVISIBLE_CHARACTERS, + span, + "invisible character detected", + "consider replacing the string with", + string + .replace("\u{200B}", "\\u{200B}") + .replace("\u{ad}", "\\u{AD}") + .replace("\u{2060}", "\\u{2060}"), + Applicability::MachineApplicable, + ); + } + if string.chars().any(|c| c as u32 > 0x7F) { + span_lint_and_sugg( + cx, + NON_ASCII_LITERAL, + span, + "literal non-ASCII character detected", + "consider replacing the string with", + if is_allowed(cx, UNICODE_NOT_NFC, id) { + escape(string.chars()) + } else { + escape(string.nfc()) + }, + Applicability::MachineApplicable, + ); + } + if is_allowed(cx, NON_ASCII_LITERAL, id) && string.chars().zip(string.nfc()).any(|(a, b)| a != b) { + span_lint_and_sugg( + cx, + UNICODE_NOT_NFC, + span, + "non-NFC Unicode sequence detected", + "consider replacing the string with", + string.nfc().collect::(), + Applicability::MachineApplicable, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs b/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs new file mode 100644 index 0000000000..c6ae8b9b59 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs @@ -0,0 +1,177 @@ +use crate::utils::{get_trait_def_id, paths, span_lint, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::def_id::DefId; +use rustc_hir::{Expr, ExprKind, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_middle::ty::{GenericPredicates, PredicateKind, ProjectionPredicate, TraitPredicate}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{BytePos, Span}; + +declare_clippy_lint! { + /// **What it does:** Checks for functions that expect closures of type + /// Fn(...) -> Ord where the implemented closure returns the unit type. + /// The lint also suggests to remove the semi-colon at the end of the statement if present. + /// + /// **Why is this bad?** Likely, returning the unit type is unintentional, and + /// could simply be caused by an extra semi-colon. Since () implements Ord + /// it doesn't cause a compilation error. + /// This is the same reasoning behind the unit_cmp lint. + /// + /// **Known problems:** If returning unit is intentional, then there is no + /// way of specifying this without triggering needless_return lint + /// + /// **Example:** + /// + /// ```rust + /// let mut twins = vec!((1, 1), (2, 2)); + /// twins.sort_by_key(|x| { x.1; }); + /// ``` + pub UNIT_RETURN_EXPECTING_ORD, + correctness, + "fn arguments of type Fn(...) -> Ord returning the unit type ()." +} + +declare_lint_pass!(UnitReturnExpectingOrd => [UNIT_RETURN_EXPECTING_ORD]); + +fn get_trait_predicates_for_trait_id<'tcx>( + cx: &LateContext<'tcx>, + generics: GenericPredicates<'tcx>, + trait_id: Option, +) -> Vec> { + let mut preds = Vec::new(); + for (pred, _) in generics.predicates { + if_chain! { + if let PredicateKind::Trait(poly_trait_pred, _) = pred.kind().skip_binder(); + let trait_pred = cx.tcx.erase_late_bound_regions(ty::Binder::bind(poly_trait_pred)); + if let Some(trait_def_id) = trait_id; + if trait_def_id == trait_pred.trait_ref.def_id; + then { + preds.push(trait_pred); + } + } + } + preds +} + +fn get_projection_pred<'tcx>( + cx: &LateContext<'tcx>, + generics: GenericPredicates<'tcx>, + pred: TraitPredicate<'tcx>, +) -> Option> { + generics.predicates.iter().find_map(|(proj_pred, _)| { + if let ty::PredicateKind::Projection(proj_pred) = proj_pred.kind().skip_binder() { + let projection_pred = cx.tcx.erase_late_bound_regions(ty::Binder::bind(proj_pred)); + if projection_pred.projection_ty.substs == pred.trait_ref.substs { + return Some(projection_pred); + } + } + None + }) +} + +fn get_args_to_check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Vec<(usize, String)> { + let mut args_to_check = Vec::new(); + if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { + let fn_sig = cx.tcx.fn_sig(def_id); + let generics = cx.tcx.predicates_of(def_id); + let fn_mut_preds = get_trait_predicates_for_trait_id(cx, generics, cx.tcx.lang_items().fn_mut_trait()); + let ord_preds = get_trait_predicates_for_trait_id(cx, generics, get_trait_def_id(cx, &paths::ORD)); + let partial_ord_preds = + get_trait_predicates_for_trait_id(cx, generics, cx.tcx.lang_items().partial_ord_trait()); + // Trying to call erase_late_bound_regions on fn_sig.inputs() gives the following error + // The trait `rustc::ty::TypeFoldable<'_>` is not implemented for `&[&rustc::ty::TyS<'_>]` + let inputs_output = cx.tcx.erase_late_bound_regions(fn_sig.inputs_and_output()); + inputs_output + .iter() + .rev() + .skip(1) + .rev() + .enumerate() + .for_each(|(i, inp)| { + for trait_pred in &fn_mut_preds { + if_chain! { + if trait_pred.self_ty() == inp; + if let Some(return_ty_pred) = get_projection_pred(cx, generics, *trait_pred); + then { + if ord_preds.iter().any(|ord| ord.self_ty() == return_ty_pred.ty) { + args_to_check.push((i, "Ord".to_string())); + } else if partial_ord_preds.iter().any(|pord| pord.self_ty() == return_ty_pred.ty) { + args_to_check.push((i, "PartialOrd".to_string())); + } + } + } + } + }); + } + args_to_check +} + +fn check_arg<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>) -> Option<(Span, Option)> { + if_chain! { + if let ExprKind::Closure(_, _fn_decl, body_id, span, _) = arg.kind; + if let ty::Closure(_def_id, substs) = &cx.typeck_results().node_type(arg.hir_id).kind(); + let ret_ty = substs.as_closure().sig().output(); + let ty = cx.tcx.erase_late_bound_regions(ret_ty); + if ty.is_unit(); + then { + if_chain! { + let body = cx.tcx.hir().body(body_id); + if let ExprKind::Block(block, _) = body.value.kind; + if block.expr.is_none(); + if let Some(stmt) = block.stmts.last(); + if let StmtKind::Semi(_) = stmt.kind; + then { + let data = stmt.span.data(); + // Make a span out of the semicolon for the help message + Some((span, Some(Span::new(data.hi-BytePos(1), data.hi, data.ctxt)))) + } else { + Some((span, None)) + } + } + } else { + None + } + } +} + +impl<'tcx> LateLintPass<'tcx> for UnitReturnExpectingOrd { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let ExprKind::MethodCall(_, _, ref args, _) = expr.kind { + let arg_indices = get_args_to_check(cx, expr); + for (i, trait_name) in arg_indices { + if i < args.len() { + match check_arg(cx, &args[i]) { + Some((span, None)) => { + span_lint( + cx, + UNIT_RETURN_EXPECTING_ORD, + span, + &format!( + "this closure returns \ + the unit type which also implements {}", + trait_name + ), + ); + }, + Some((span, Some(last_semi))) => { + span_lint_and_help( + cx, + UNIT_RETURN_EXPECTING_ORD, + span, + &format!( + "this closure returns \ + the unit type which also implements {}", + trait_name + ), + Some(last_semi), + &"probably caused by this trailing semicolon".to_string(), + ); + }, + None => {}, + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/unnamed_address.rs b/src/tools/clippy/clippy_lints/src/unnamed_address.rs new file mode 100644 index 0000000000..9582c162e7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unnamed_address.rs @@ -0,0 +1,131 @@ +use crate::utils::{match_def_path, paths, span_lint, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons with an address of a function item. + /// + /// **Why is this bad?** Function item address is not guaranteed to be unique and could vary + /// between different code generation units. Furthermore different function items could have + /// the same address after being merged together. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// type F = fn(); + /// fn a() {} + /// let f: F = a; + /// if f == a { + /// // ... + /// } + /// ``` + pub FN_ADDRESS_COMPARISONS, + correctness, + "comparison with an address of a function item" +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons with an address of a trait vtable. + /// + /// **Why is this bad?** Comparing trait objects pointers compares an vtable addresses which + /// are not guaranteed to be unique and could vary between different code generation units. + /// Furthermore vtables for different types could have the same address after being merged + /// together. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,ignore + /// let a: Rc = ... + /// let b: Rc = ... + /// if Rc::ptr_eq(&a, &b) { + /// ... + /// } + /// ``` + pub VTABLE_ADDRESS_COMPARISONS, + correctness, + "comparison with an address of a trait vtable" +} + +declare_lint_pass!(UnnamedAddress => [FN_ADDRESS_COMPARISONS, VTABLE_ADDRESS_COMPARISONS]); + +impl LateLintPass<'_> for UnnamedAddress { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + fn is_comparison(binop: BinOpKind) -> bool { + matches!( + binop, + BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Ge | BinOpKind::Gt + ) + } + + fn is_trait_ptr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + match cx.typeck_results().expr_ty_adjusted(expr).kind() { + ty::RawPtr(ty::TypeAndMut { ty, .. }) => ty.is_trait(), + _ => false, + } + } + + fn is_fn_def(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + matches!(cx.typeck_results().expr_ty(expr).kind(), ty::FnDef(..)) + } + + if_chain! { + if let ExprKind::Binary(binop, ref left, ref right) = expr.kind; + if is_comparison(binop.node); + if is_trait_ptr(cx, left) && is_trait_ptr(cx, right); + then { + span_lint_and_help( + cx, + VTABLE_ADDRESS_COMPARISONS, + expr.span, + "comparing trait object pointers compares a non-unique vtable address", + None, + "consider extracting and comparing data pointers only", + ); + } + } + + if_chain! { + if let ExprKind::Call(ref func, [ref _left, ref _right]) = expr.kind; + if let ExprKind::Path(ref func_qpath) = func.kind; + if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::PTR_EQ) || + match_def_path(cx, def_id, &paths::RC_PTR_EQ) || + match_def_path(cx, def_id, &paths::ARC_PTR_EQ); + let ty_param = cx.typeck_results().node_substs(func.hir_id).type_at(0); + if ty_param.is_trait(); + then { + span_lint_and_help( + cx, + VTABLE_ADDRESS_COMPARISONS, + expr.span, + "comparing trait object pointers compares a non-unique vtable address", + None, + "consider extracting and comparing data pointers only", + ); + } + } + + if_chain! { + if let ExprKind::Binary(binop, ref left, ref right) = expr.kind; + if is_comparison(binop.node); + if cx.typeck_results().expr_ty_adjusted(left).is_fn_ptr() && + cx.typeck_results().expr_ty_adjusted(right).is_fn_ptr(); + if is_fn_def(cx, left) || is_fn_def(cx, right); + then { + span_lint( + cx, + FN_ADDRESS_COMPARISONS, + expr.span, + "comparing with a non-unique address of a function item", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs b/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs new file mode 100644 index 0000000000..00a707107b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs @@ -0,0 +1,274 @@ +use crate::utils; +use crate::utils::sugg::Sugg; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, subst::GenericArgKind}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; +use rustc_span::symbol::Ident; + +declare_clippy_lint! { + /// **What it does:** + /// Detects uses of `Vec::sort_by` passing in a closure + /// which compares the two arguments, either directly or indirectly. + /// + /// **Why is this bad?** + /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if + /// possible) than to use `Vec::sort_by` and a more complicated + /// closure. + /// + /// **Known problems:** + /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already + /// imported by a use statement, then it will need to be added manually. + /// + /// **Example:** + /// + /// ```rust + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec = Vec::new(); + /// vec.sort_by(|a, b| a.foo().cmp(&b.foo())); + /// ``` + /// Use instead: + /// ```rust + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec = Vec::new(); + /// vec.sort_by_key(|a| a.foo()); + /// ``` + pub UNNECESSARY_SORT_BY, + complexity, + "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer" +} + +declare_lint_pass!(UnnecessarySortBy => [UNNECESSARY_SORT_BY]); + +enum LintTrigger { + Sort(SortDetection), + SortByKey(SortByKeyDetection), +} + +struct SortDetection { + vec_name: String, + unstable: bool, +} + +struct SortByKeyDetection { + vec_name: String, + closure_arg: String, + closure_body: String, + reverse: bool, + unstable: bool, +} + +/// Detect if the two expressions are mirrored (identical, except one +/// contains a and the other replaces it with b) +fn mirrored_exprs( + cx: &LateContext<'_>, + a_expr: &Expr<'_>, + a_ident: &Ident, + b_expr: &Expr<'_>, + b_ident: &Ident, +) -> bool { + match (&a_expr.kind, &b_expr.kind) { + // Two boxes with mirrored contents + (ExprKind::Box(left_expr), ExprKind::Box(right_expr)) => { + mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) + }, + // Two arrays with mirrored contents + (ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => left_exprs + .iter() + .zip(right_exprs.iter()) + .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + // The two exprs are function calls. + // Check to see that the function itself and its arguments are mirrored + (ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) => { + mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) + && left_args + .iter() + .zip(right_args.iter()) + .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)) + }, + // The two exprs are method calls. + // Check to see that the function is the same and the arguments are mirrored + // This is enough because the receiver of the method is listed in the arguments + ( + ExprKind::MethodCall(left_segment, _, left_args, _), + ExprKind::MethodCall(right_segment, _, right_args, _), + ) => { + left_segment.ident == right_segment.ident + && left_args + .iter() + .zip(right_args.iter()) + .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)) + }, + // Two tuples with mirrored contents + (ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) => left_exprs + .iter() + .zip(right_exprs.iter()) + .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + // Two binary ops, which are the same operation and which have mirrored arguments + (ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) => { + left_op.node == right_op.node + && mirrored_exprs(cx, left_left, a_ident, right_left, b_ident) + && mirrored_exprs(cx, left_right, a_ident, right_right, b_ident) + }, + // Two unary ops, which are the same operation and which have the same argument + (ExprKind::Unary(left_op, left_expr), ExprKind::Unary(right_op, right_expr)) => { + left_op == right_op && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) + }, + // The two exprs are literals of some kind + (ExprKind::Lit(left_lit), ExprKind::Lit(right_lit)) => left_lit.node == right_lit.node, + (ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(cx, left, a_ident, right, b_ident), + (ExprKind::DropTemps(left_block), ExprKind::DropTemps(right_block)) => { + mirrored_exprs(cx, left_block, a_ident, right_block, b_ident) + }, + (ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) => { + left_ident.name == right_ident.name && mirrored_exprs(cx, left_expr, a_ident, right_expr, right_ident) + }, + // Two paths: either one is a and the other is b, or they're identical to each other + ( + ExprKind::Path(QPath::Resolved( + _, + Path { + segments: left_segments, + .. + }, + )), + ExprKind::Path(QPath::Resolved( + _, + Path { + segments: right_segments, + .. + }, + )), + ) => { + (left_segments + .iter() + .zip(right_segments.iter()) + .all(|(left, right)| left.ident == right.ident) + && left_segments + .iter() + .all(|seg| &seg.ident != a_ident && &seg.ident != b_ident)) + || (left_segments.len() == 1 + && &left_segments[0].ident == a_ident + && right_segments.len() == 1 + && &right_segments[0].ident == b_ident) + }, + // Matching expressions, but one or both is borrowed + ( + ExprKind::AddrOf(left_kind, Mutability::Not, left_expr), + ExprKind::AddrOf(right_kind, Mutability::Not, right_expr), + ) => left_kind == right_kind && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => { + mirrored_exprs(cx, a_expr, a_ident, right_expr, b_ident) + }, + (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(cx, left_expr, a_ident, b_expr, b_ident), + _ => false, + } +} + +fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { + if_chain! { + if let ExprKind::MethodCall(name_ident, _, args, _) = &expr.kind; + if let name = name_ident.ident.name.to_ident_string(); + if name == "sort_by" || name == "sort_unstable_by"; + if let [vec, Expr { kind: ExprKind::Closure(_, _, closure_body_id, _, _), .. }] = args; + if utils::is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(vec), sym::vec_type); + if let closure_body = cx.tcx.hir().body(*closure_body_id); + if let &[ + Param { pat: Pat { kind: PatKind::Binding(_, _, left_ident, _), .. }, ..}, + Param { pat: Pat { kind: PatKind::Binding(_, _, right_ident, _), .. }, .. } + ] = &closure_body.params; + if let ExprKind::MethodCall(method_path, _, [ref left_expr, ref right_expr], _) = &closure_body.value.kind; + if method_path.ident.name == sym::cmp; + then { + let (closure_body, closure_arg, reverse) = if mirrored_exprs( + &cx, + &left_expr, + &left_ident, + &right_expr, + &right_ident + ) { + (Sugg::hir(cx, &left_expr, "..").to_string(), left_ident.name.to_string(), false) + } else if mirrored_exprs(&cx, &left_expr, &right_ident, &right_expr, &left_ident) { + (Sugg::hir(cx, &left_expr, "..").to_string(), right_ident.name.to_string(), true) + } else { + return None; + }; + let vec_name = Sugg::hir(cx, &args[0], "..").to_string(); + let unstable = name == "sort_unstable_by"; + + if let ExprKind::Path(QPath::Resolved(_, Path { + segments: [PathSegment { ident: left_name, .. }], .. + })) = &left_expr.kind { + if left_name == left_ident { + return Some(LintTrigger::Sort(SortDetection { vec_name, unstable })); + } + } + + if !expr_borrows(cx, left_expr) { + return Some(LintTrigger::SortByKey(SortByKeyDetection { + vec_name, + closure_arg, + closure_body, + reverse, + unstable, + })); + } + } + } + + None +} + +fn expr_borrows(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let ty = cx.typeck_results().expr_ty(expr); + matches!(ty.kind(), ty::Ref(..)) || ty.walk().any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_))) +} + +impl LateLintPass<'_> for UnnecessarySortBy { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + match detect_lint(cx, expr) { + Some(LintTrigger::SortByKey(trigger)) => utils::span_lint_and_sugg( + cx, + UNNECESSARY_SORT_BY, + expr.span, + "use Vec::sort_by_key here instead", + "try", + format!( + "{}.sort{}_by_key(|{}| {})", + trigger.vec_name, + if trigger.unstable { "_unstable" } else { "" }, + trigger.closure_arg, + if trigger.reverse { + format!("Reverse({})", trigger.closure_body) + } else { + trigger.closure_body.to_string() + }, + ), + if trigger.reverse { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }, + ), + Some(LintTrigger::Sort(trigger)) => utils::span_lint_and_sugg( + cx, + UNNECESSARY_SORT_BY, + expr.span, + "use Vec::sort here instead", + "try", + format!( + "{}.sort{}()", + trigger.vec_name, + if trigger.unstable { "_unstable" } else { "" }, + ), + Applicability::MachineApplicable, + ), + None => {}, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs b/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs new file mode 100644 index 0000000000..8e076397c1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs @@ -0,0 +1,164 @@ +use crate::utils::{ + contains_return, in_macro, match_qpath, paths, return_ty, snippet, span_lint_and_then, + visitors::find_all_ret_expressions, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for private functions that only return `Ok` or `Some`. + /// + /// **Why is this bad?** It is not meaningful to wrap values when no `None` or `Err` is returned. + /// + /// **Known problems:** There can be false positives if the function signature is designed to + /// fit some external requirement. + /// + /// **Example:** + /// + /// ```rust + /// fn get_cool_number(a: bool, b: bool) -> Option { + /// if a && b { + /// return Some(50); + /// } + /// if a { + /// Some(0) + /// } else { + /// Some(10) + /// } + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn get_cool_number(a: bool, b: bool) -> i32 { + /// if a && b { + /// return 50; + /// } + /// if a { + /// 0 + /// } else { + /// 10 + /// } + /// } + /// ``` + pub UNNECESSARY_WRAPS, + pedantic, + "functions that only return `Ok` or `Some`" +} + +declare_lint_pass!(UnnecessaryWraps => [UNNECESSARY_WRAPS]); + +impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + fn_kind: FnKind<'tcx>, + fn_decl: &FnDecl<'tcx>, + body: &Body<'tcx>, + span: Span, + hir_id: HirId, + ) { + // Abort if public function/method or closure. + match fn_kind { + FnKind::ItemFn(.., visibility) | FnKind::Method(.., Some(visibility)) => { + if visibility.node.is_pub() { + return; + } + }, + FnKind::Closure => return, + _ => (), + } + + // Abort if the method is implementing a trait or of it a trait method. + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { + if matches!( + item.kind, + ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) + ) { + return; + } + } + + // Get the wrapper and inner types, if can't, abort. + let (return_type_label, path, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() { + if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did) { + ("Option", &paths::OPTION_SOME, subst.type_at(0)) + } else if cx.tcx.is_diagnostic_item(sym::result_type, adt_def.did) { + ("Result", &paths::RESULT_OK, subst.type_at(0)) + } else { + return; + } + } else { + return; + }; + + // Check if all return expression respect the following condition and collect them. + let mut suggs = Vec::new(); + let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| { + if_chain! { + if !in_macro(ret_expr.span); + // Check if a function call. + if let ExprKind::Call(ref func, ref args) = ret_expr.kind; + // Get the Path of the function call. + if let ExprKind::Path(ref qpath) = func.kind; + // Check if OPTION_SOME or RESULT_OK, depending on return type. + if match_qpath(qpath, path); + if args.len() == 1; + // Make sure the function argument does not contain a return expression. + if !contains_return(&args[0]); + then { + suggs.push( + ( + ret_expr.span, + if inner_type.is_unit() { + "".to_string() + } else { + snippet(cx, args[0].span.source_callsite(), "..").to_string() + } + ) + ); + true + } else { + false + } + } + }); + + if can_sugg && !suggs.is_empty() { + let (lint_msg, return_type_sugg_msg, return_type_sugg, body_sugg_msg) = if inner_type.is_unit() { + ( + "this function's return value is unnecessary".to_string(), + "remove the return type...".to_string(), + snippet(cx, fn_decl.output.span(), "..").to_string(), + "...and then remove returned values", + ) + } else { + ( + format!( + "this function's return value is unnecessarily wrapped by `{}`", + return_type_label + ), + format!("remove `{}` from the return type...", return_type_label), + inner_type.to_string(), + "...and then change returning expressions", + ) + }; + + span_lint_and_then(cx, UNNECESSARY_WRAPS, span, lint_msg.as_str(), |diag| { + diag.span_suggestion( + fn_decl.output.span(), + return_type_sugg_msg.as_str(), + return_type_sugg, + Applicability::MaybeIncorrect, + ); + diag.multipart_suggestion(body_sugg_msg, suggs, Applicability::MaybeIncorrect); + }); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs new file mode 100644 index 0000000000..fa613bb7da --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs @@ -0,0 +1,408 @@ +#![allow(clippy::wildcard_imports, clippy::enum_glob_use)] + +use crate::utils::ast_utils::{eq_field_pat, eq_id, eq_pat, eq_path}; +use crate::utils::{over, span_lint_and_then}; +use rustc_ast::mut_visit::*; +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, Pat, PatKind, PatKind::*, DUMMY_NODE_ID}; +use rustc_ast_pretty::pprust; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::DUMMY_SP; + +use std::cell::Cell; +use std::mem; + +declare_clippy_lint! { + /// **What it does:** + /// + /// Checks for unnested or-patterns, e.g., `Some(0) | Some(2)` and + /// suggests replacing the pattern with a nested one, `Some(0 | 2)`. + /// + /// Another way to think of this is that it rewrites patterns in + /// *disjunctive normal form (DNF)* into *conjunctive normal form (CNF)*. + /// + /// **Why is this bad?** + /// + /// In the example above, `Some` is repeated, which unncessarily complicates the pattern. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// fn main() { + /// if let Some(0) | Some(2) = Some(0) {} + /// } + /// ``` + /// Use instead: + /// ```rust + /// #![feature(or_patterns)] + /// + /// fn main() { + /// if let Some(0 | 2) = Some(0) {} + /// } + /// ``` + pub UNNESTED_OR_PATTERNS, + pedantic, + "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`" +} + +declare_lint_pass!(UnnestedOrPatterns => [UNNESTED_OR_PATTERNS]); + +impl EarlyLintPass for UnnestedOrPatterns { + fn check_arm(&mut self, cx: &EarlyContext<'_>, a: &ast::Arm) { + lint_unnested_or_patterns(cx, &a.pat); + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + if let ast::ExprKind::Let(pat, _) = &e.kind { + lint_unnested_or_patterns(cx, pat); + } + } + + fn check_param(&mut self, cx: &EarlyContext<'_>, p: &ast::Param) { + lint_unnested_or_patterns(cx, &p.pat); + } + + fn check_local(&mut self, cx: &EarlyContext<'_>, l: &ast::Local) { + lint_unnested_or_patterns(cx, &l.pat); + } +} + +fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { + if !cx.sess.features_untracked().or_patterns { + // Do not suggest nesting the patterns if the feature `or_patterns` is not enabled. + return; + } + + if let Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_) = pat.kind { + // This is a leaf pattern, so cloning is unprofitable. + return; + } + + let mut pat = P(pat.clone()); + + // Nix all the paren patterns everywhere so that they aren't in our way. + remove_all_parens(&mut pat); + + // Transform all unnested or-patterns into nested ones, and if there were none, quit. + if !unnest_or_patterns(&mut pat) { + return; + } + + span_lint_and_then(cx, UNNESTED_OR_PATTERNS, pat.span, "unnested or-patterns", |db| { + insert_necessary_parens(&mut pat); + db.span_suggestion_verbose( + pat.span, + "nest the patterns", + pprust::pat_to_string(&pat), + Applicability::MachineApplicable, + ); + }); +} + +/// Remove all `(p)` patterns in `pat`. +fn remove_all_parens(pat: &mut P) { + struct Visitor; + impl MutVisitor for Visitor { + fn visit_pat(&mut self, pat: &mut P) { + noop_visit_pat(pat, self); + let inner = match &mut pat.kind { + Paren(i) => mem::replace(&mut i.kind, Wild), + _ => return, + }; + pat.kind = inner; + } + } + Visitor.visit_pat(pat); +} + +/// Insert parens where necessary according to Rust's precedence rules for patterns. +fn insert_necessary_parens(pat: &mut P) { + struct Visitor; + impl MutVisitor for Visitor { + fn visit_pat(&mut self, pat: &mut P) { + use ast::{BindingMode::*, Mutability::*}; + noop_visit_pat(pat, self); + let target = match &mut pat.kind { + // `i @ a | b`, `box a | b`, and `& mut? a | b`. + Ident(.., Some(p)) | Box(p) | Ref(p, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p, + Ref(p, Not) if matches!(p.kind, Ident(ByValue(Mut), ..)) => p, // `&(mut x)` + _ => return, + }; + target.kind = Paren(P(take_pat(target))); + } + } + Visitor.visit_pat(pat); +} + +/// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`. +/// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`. +fn unnest_or_patterns(pat: &mut P) -> bool { + struct Visitor { + changed: bool, + } + impl MutVisitor for Visitor { + fn visit_pat(&mut self, p: &mut P) { + // This is a bottom up transformation, so recurse first. + noop_visit_pat(p, self); + + // Don't have an or-pattern? Just quit early on. + let alternatives = match &mut p.kind { + Or(ps) => ps, + _ => return, + }; + + // Collapse or-patterns directly nested in or-patterns. + let mut idx = 0; + let mut this_level_changed = false; + while idx < alternatives.len() { + let inner = if let Or(ps) = &mut alternatives[idx].kind { + mem::take(ps) + } else { + idx += 1; + continue; + }; + this_level_changed = true; + alternatives.splice(idx..=idx, inner); + } + + // Focus on `p_n` and then try to transform all `p_i` where `i > n`. + let mut focus_idx = 0; + while focus_idx < alternatives.len() { + this_level_changed |= transform_with_focus_on_idx(alternatives, focus_idx); + focus_idx += 1; + } + self.changed |= this_level_changed; + + // Deal with `Some(Some(0)) | Some(Some(1))`. + if this_level_changed { + noop_visit_pat(p, self); + } + } + } + + let mut visitor = Visitor { changed: false }; + visitor.visit_pat(pat); + visitor.changed +} + +/// Match `$scrutinee` against `$pat` and extract `$then` from it. +/// Panics if there is no match. +macro_rules! always_pat { + ($scrutinee:expr, $pat:pat => $then:expr) => { + match $scrutinee { + $pat => $then, + _ => unreachable!(), + } + }; +} + +/// Focus on `focus_idx` in `alternatives`, +/// attempting to extend it with elements of the same constructor `C` +/// in `alternatives[focus_idx + 1..]`. +fn transform_with_focus_on_idx(alternatives: &mut Vec>, focus_idx: usize) -> bool { + // Extract the kind; we'll need to make some changes in it. + let mut focus_kind = mem::replace(&mut alternatives[focus_idx].kind, PatKind::Wild); + // We'll focus on `alternatives[focus_idx]`, + // so we're draining from `alternatives[focus_idx + 1..]`. + let start = focus_idx + 1; + + // We're trying to find whatever kind (~"constructor") we found in `alternatives[start..]`. + let changed = match &mut focus_kind { + // These pattern forms are "leafs" and do not have sub-patterns. + // Therefore they are not some form of constructor `C`, + // with which a pattern `C(p_0)` may be formed, + // which we would want to join with other `C(p_j)`s. + Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_) + // Dealt with elsewhere. + | Or(_) | Paren(_) => false, + // Transform `box x | ... | box y` into `box (x | y)`. + // + // The cases below until `Slice(...)` deal with *singleton* products. + // These patterns have the shape `C(p)`, and not e.g., `C(p0, ..., pn)`. + Box(target) => extend_with_matching( + target, start, alternatives, + |k| matches!(k, Box(_)), + |k| always_pat!(k, Box(p) => p), + ), + // Transform `&m x | ... | &m y` into `&m (x | y)`. + Ref(target, m1) => extend_with_matching( + target, start, alternatives, + |k| matches!(k, Ref(_, m2) if m1 == m2), // Mutabilities must match. + |k| always_pat!(k, Ref(p, _) => p), + ), + // Transform `b @ p0 | ... b @ p1` into `b @ (p0 | p1)`. + Ident(b1, i1, Some(target)) => extend_with_matching( + target, start, alternatives, + // Binding names must match. + |k| matches!(k, Ident(b2, i2, Some(_)) if b1 == b2 && eq_id(*i1, *i2)), + |k| always_pat!(k, Ident(_, _, Some(p)) => p), + ), + // Transform `[pre, x, post] | ... | [pre, y, post]` into `[pre, x | y, post]`. + Slice(ps1) => extend_with_matching_product( + ps1, start, alternatives, + |k, ps1, idx| matches!(k, Slice(ps2) if eq_pre_post(ps1, ps2, idx)), + |k| always_pat!(k, Slice(ps) => ps), + ), + // Transform `(pre, x, post) | ... | (pre, y, post)` into `(pre, x | y, post)`. + Tuple(ps1) => extend_with_matching_product( + ps1, start, alternatives, + |k, ps1, idx| matches!(k, Tuple(ps2) if eq_pre_post(ps1, ps2, idx)), + |k| always_pat!(k, Tuple(ps) => ps), + ), + // Transform `S(pre, x, post) | ... | S(pre, y, post)` into `S(pre, x | y, post)`. + TupleStruct(path1, ps1) => extend_with_matching_product( + ps1, start, alternatives, + |k, ps1, idx| matches!( + k, + TupleStruct(path2, ps2) if eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx) + ), + |k| always_pat!(k, TupleStruct(_, ps) => ps), + ), + // Transform a record pattern `S { fp_0, ..., fp_n }`. + Struct(path1, fps1, rest1) => extend_with_struct_pat(path1, fps1, *rest1, start, alternatives), + }; + + alternatives[focus_idx].kind = focus_kind; + changed +} + +/// Here we focusing on a record pattern `S { fp_0, ..., fp_n }`. +/// In particular, for a record pattern, the order in which the field patterns is irrelevant. +/// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern +/// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal. +fn extend_with_struct_pat( + path1: &ast::Path, + fps1: &mut Vec, + rest1: bool, + start: usize, + alternatives: &mut Vec>, +) -> bool { + (0..fps1.len()).any(|idx| { + let pos_in_2 = Cell::new(None); // The element `k`. + let tail_or = drain_matching( + start, + alternatives, + |k| { + matches!(k, Struct(path2, fps2, rest2) + if rest1 == *rest2 // If one struct pattern has `..` so must the other. + && eq_path(path1, path2) + && fps1.len() == fps2.len() + && fps1.iter().enumerate().all(|(idx_1, fp1)| { + if idx_1 == idx { + // In the case of `k`, we merely require identical field names + // so that we will transform into `ident_k: p1_k | p2_k`. + let pos = fps2.iter().position(|fp2| eq_id(fp1.ident, fp2.ident)); + pos_in_2.set(pos); + pos.is_some() + } else { + fps2.iter().any(|fp2| eq_field_pat(fp1, fp2)) + } + })) + }, + // Extract `p2_k`. + |k| always_pat!(k, Struct(_, mut fps, _) => fps.swap_remove(pos_in_2.take().unwrap()).pat), + ); + extend_with_tail_or(&mut fps1[idx].pat, tail_or) + }) +} + +/// Like `extend_with_matching` but for products with > 1 factor, e.g., `C(p_0, ..., p_n)`. +/// Here, the idea is that we fixate on some `p_k` in `C`, +/// allowing it to vary between two `targets` and `ps2` (returned by `extract`), +/// while also requiring `ps1[..n] ~ ps2[..n]` (pre) and `ps1[n + 1..] ~ ps2[n + 1..]` (post), +/// where `~` denotes semantic equality. +fn extend_with_matching_product( + targets: &mut Vec>, + start: usize, + alternatives: &mut Vec>, + predicate: impl Fn(&PatKind, &[P], usize) -> bool, + extract: impl Fn(PatKind) -> Vec>, +) -> bool { + (0..targets.len()).any(|idx| { + let tail_or = drain_matching( + start, + alternatives, + |k| predicate(k, targets, idx), + |k| extract(k).swap_remove(idx), + ); + extend_with_tail_or(&mut targets[idx], tail_or) + }) +} + +/// Extract the pattern from the given one and replace it with `Wild`. +/// This is meant for temporarily swapping out the pattern for manipulation. +fn take_pat(from: &mut Pat) -> Pat { + let dummy = Pat { + id: DUMMY_NODE_ID, + kind: Wild, + span: DUMMY_SP, + tokens: None, + }; + mem::replace(from, dummy) +} + +/// Extend `target` as an or-pattern with the alternatives +/// in `tail_or` if there are any and return if there were. +fn extend_with_tail_or(target: &mut Pat, tail_or: Vec>) -> bool { + fn extend(target: &mut Pat, mut tail_or: Vec>) { + match target { + // On an existing or-pattern in the target, append to it. + Pat { kind: Or(ps), .. } => ps.append(&mut tail_or), + // Otherwise convert the target to an or-pattern. + target => { + let mut init_or = vec![P(take_pat(target))]; + init_or.append(&mut tail_or); + target.kind = Or(init_or); + }, + } + } + + let changed = !tail_or.is_empty(); + if changed { + // Extend the target. + extend(target, tail_or); + } + changed +} + +// Extract all inner patterns in `alternatives` matching our `predicate`. +// Only elements beginning with `start` are considered for extraction. +fn drain_matching( + start: usize, + alternatives: &mut Vec>, + predicate: impl Fn(&PatKind) -> bool, + extract: impl Fn(PatKind) -> P, +) -> Vec> { + let mut tail_or = vec![]; + let mut idx = 0; + for pat in alternatives.drain_filter(|p| { + // Check if we should extract, but only if `idx >= start`. + idx += 1; + idx > start && predicate(&p.kind) + }) { + tail_or.push(extract(pat.into_inner().kind)); + } + tail_or +} + +fn extend_with_matching( + target: &mut Pat, + start: usize, + alternatives: &mut Vec>, + predicate: impl Fn(&PatKind) -> bool, + extract: impl Fn(PatKind) -> P, +) -> bool { + extend_with_tail_or(target, drain_matching(start, alternatives, predicate, extract)) +} + +/// Are the patterns in `ps1` and `ps2` equal save for `ps1[idx]` compared to `ps2[idx]`? +fn eq_pre_post(ps1: &[P], ps2: &[P], idx: usize) -> bool { + ps1.len() == ps2.len() + && ps1[idx].is_rest() == ps2[idx].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`. + && over(&ps1[..idx], &ps2[..idx], |l, r| eq_pat(l, r)) + && over(&ps1[idx + 1..], &ps2[idx + 1..], |l, r| eq_pat(l, r)) +} diff --git a/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs b/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs new file mode 100644 index 0000000000..154082a0fd --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs @@ -0,0 +1,78 @@ +use crate::utils::span_lint; +use rustc_ast::ast::{Item, ItemKind, UseTree, UseTreeKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::symbol::Ident; + +declare_clippy_lint! { + /// **What it does:** Checks for imports that remove "unsafe" from an item's + /// name. + /// + /// **Why is this bad?** Renaming makes it less clear which traits and + /// structures are unsafe. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// use std::cell::{UnsafeCell as TotallySafeCell}; + /// + /// extern crate crossbeam; + /// use crossbeam::{spawn_unsafe as spawn}; + /// ``` + pub UNSAFE_REMOVED_FROM_NAME, + style, + "`unsafe` removed from API names on import" +} + +declare_lint_pass!(UnsafeNameRemoval => [UNSAFE_REMOVED_FROM_NAME]); + +impl EarlyLintPass for UnsafeNameRemoval { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if let ItemKind::Use(ref use_tree) = item.kind { + check_use_tree(use_tree, cx, item.span); + } + } +} + +fn check_use_tree(use_tree: &UseTree, cx: &EarlyContext<'_>, span: Span) { + match use_tree.kind { + UseTreeKind::Simple(Some(new_name), ..) => { + let old_name = use_tree + .prefix + .segments + .last() + .expect("use paths cannot be empty") + .ident; + unsafe_to_safe_check(old_name, new_name, cx, span); + }, + UseTreeKind::Simple(None, ..) | UseTreeKind::Glob => {}, + UseTreeKind::Nested(ref nested_use_tree) => { + for &(ref use_tree, _) in nested_use_tree { + check_use_tree(use_tree, cx, span); + } + }, + } +} + +fn unsafe_to_safe_check(old_name: Ident, new_name: Ident, cx: &EarlyContext<'_>, span: Span) { + let old_str = old_name.name.as_str(); + let new_str = new_name.name.as_str(); + if contains_unsafe(&old_str) && !contains_unsafe(&new_str) { + span_lint( + cx, + UNSAFE_REMOVED_FROM_NAME, + span, + &format!( + "removed `unsafe` from the name of `{}` in use as `{}`", + old_str, new_str + ), + ); + } +} + +#[must_use] +fn contains_unsafe(name: &str) -> bool { + name.contains("Unsafe") || name.contains("unsafe") +} diff --git a/src/tools/clippy/clippy_lints/src/unused_io_amount.rs b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs new file mode 100644 index 0000000000..43166d2678 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs @@ -0,0 +1,92 @@ +use crate::utils::{is_try, match_trait_method, paths, span_lint}; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for unused written/read amount. + /// + /// **Why is this bad?** `io::Write::write(_vectored)` and + /// `io::Read::read(_vectored)` are not guaranteed to + /// process the entire buffer. They return how many bytes were processed, which + /// might be smaller + /// than a given buffer's length. If you don't need to deal with + /// partial-write/read, use + /// `write_all`/`read_exact` instead. + /// + /// **Known problems:** Detects only common patterns. + /// + /// **Example:** + /// ```rust,ignore + /// use std::io; + /// fn foo(w: &mut W) -> io::Result<()> { + /// // must be `w.write_all(b"foo")?;` + /// w.write(b"foo")?; + /// Ok(()) + /// } + /// ``` + pub UNUSED_IO_AMOUNT, + correctness, + "unused written/read amount" +} + +declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]); + +impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { + fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) { + let expr = match s.kind { + hir::StmtKind::Semi(ref expr) | hir::StmtKind::Expr(ref expr) => &**expr, + _ => return, + }; + + match expr.kind { + hir::ExprKind::Match(ref res, _, _) if is_try(expr).is_some() => { + if let hir::ExprKind::Call(ref func, ref args) = res.kind { + if matches!( + func.kind, + hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryIntoResult, _)) + ) { + check_method_call(cx, &args[0], expr); + } + } else { + check_method_call(cx, res, expr); + } + }, + + hir::ExprKind::MethodCall(ref path, _, ref args, _) => match &*path.ident.as_str() { + "expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" => { + check_method_call(cx, &args[0], expr); + }, + _ => (), + }, + + _ => (), + } + } +} + +fn check_method_call(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) { + if let hir::ExprKind::MethodCall(ref path, _, _, _) = call.kind { + let symbol = &*path.ident.as_str(); + let read_trait = match_trait_method(cx, call, &paths::IO_READ); + let write_trait = match_trait_method(cx, call, &paths::IO_WRITE); + + match (read_trait, write_trait, symbol) { + (true, _, "read") => span_lint( + cx, + UNUSED_IO_AMOUNT, + expr.span, + "read amount is not handled. Use `Read::read_exact` instead", + ), + (true, _, "read_vectored") => span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "read amount is not handled"), + (_, true, "write") => span_lint( + cx, + UNUSED_IO_AMOUNT, + expr.span, + "written amount is not handled. Use `Write::write_all` instead", + ), + (_, true, "write_vectored") => span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "written amount is not handled"), + _ => (), + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/unused_self.rs b/src/tools/clippy/clippy_lints/src/unused_self.rs new file mode 100644 index 0000000000..812482cf5c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unused_self.rs @@ -0,0 +1,71 @@ +use if_chain::if_chain; +use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::span_lint_and_help; +use crate::utils::visitors::LocalUsedVisitor; + +declare_clippy_lint! { + /// **What it does:** Checks methods that contain a `self` argument but don't use it + /// + /// **Why is this bad?** It may be clearer to define the method as an associated function instead + /// of an instance method if it doesn't require `self`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// struct A; + /// impl A { + /// fn method(&self) {} + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust,ignore + /// struct A; + /// impl A { + /// fn method() {} + /// } + /// ``` + pub UNUSED_SELF, + pedantic, + "methods that contain a `self` argument but don't use it" +} + +declare_lint_pass!(UnusedSelf => [UNUSED_SELF]); + +impl<'tcx> LateLintPass<'tcx> for UnusedSelf { + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'_>) { + if impl_item.span.from_expansion() { + return; + } + let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id()); + let parent_item = cx.tcx.hir().expect_item(parent); + let assoc_item = cx.tcx.associated_item(impl_item.def_id); + if_chain! { + if let ItemKind::Impl(Impl { of_trait: None, .. }) = parent_item.kind; + if assoc_item.fn_has_self_parameter; + if let ImplItemKind::Fn(.., body_id) = &impl_item.kind; + let body = cx.tcx.hir().body(*body_id); + if !body.params.is_empty(); + then { + let self_param = &body.params[0]; + let self_hir_id = self_param.pat.hir_id; + if !LocalUsedVisitor::new(cx, self_hir_id).check_body(body) { + span_lint_and_help( + cx, + UNUSED_SELF, + self_param.span, + "unused `self` argument", + None, + "consider refactoring to a associated function", + ); + return; + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/unused_unit.rs b/src/tools/clippy/clippy_lints/src/unused_unit.rs new file mode 100644 index 0000000000..a31cd5fda8 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unused_unit.rs @@ -0,0 +1,142 @@ +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_ast::visit::FnKind; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::BytePos; + +use crate::utils::{position_before_rarrow, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for unit (`()`) expressions that can be removed. + /// + /// **Why is this bad?** Such expressions add no value, but can make the code + /// less readable. Depending on formatting they can make a `break` or `return` + /// statement look like a function call. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn return_unit() -> () { + /// () + /// } + /// ``` + pub UNUSED_UNIT, + style, + "needless unit expression" +} + +declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]); + +impl EarlyLintPass for UnusedUnit { + fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) { + if_chain! { + if let ast::FnRetTy::Ty(ref ty) = kind.decl().output; + if let ast::TyKind::Tup(ref vals) = ty.kind; + if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span); + then { + lint_unneeded_unit_return(cx, ty, span); + } + } + } + + fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) { + if_chain! { + if let Some(ref stmt) = block.stmts.last(); + if let ast::StmtKind::Expr(ref expr) = stmt.kind; + if is_unit_expr(expr) && !stmt.span.from_expansion(); + then { + let sp = expr.span; + span_lint_and_sugg( + cx, + UNUSED_UNIT, + sp, + "unneeded unit expression", + "remove the final `()`", + String::new(), + Applicability::MachineApplicable, + ); + } + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + match e.kind { + ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => { + if is_unit_expr(expr) && !expr.span.from_expansion() { + span_lint_and_sugg( + cx, + UNUSED_UNIT, + expr.span, + "unneeded `()`", + "remove the `()`", + String::new(), + Applicability::MachineApplicable, + ); + } + }, + _ => (), + } + } + + fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef, _: &ast::TraitBoundModifier) { + let segments = &poly.trait_ref.path.segments; + + if_chain! { + if segments.len() == 1; + if ["Fn", "FnMut", "FnOnce"].contains(&&*segments[0].ident.name.as_str()); + if let Some(args) = &segments[0].args; + if let ast::GenericArgs::Parenthesized(generic_args) = &**args; + if let ast::FnRetTy::Ty(ty) = &generic_args.output; + if ty.kind.is_unit(); + then { + lint_unneeded_unit_return(cx, ty, generic_args.span); + } + } + } +} + +// get the def site +#[must_use] +fn get_def(span: Span) -> Option { + if span.from_expansion() { + Some(span.ctxt().outer_expn_data().def_site) + } else { + None + } +} + +// is this expr a `()` unit? +fn is_unit_expr(expr: &ast::Expr) -> bool { + if let ast::ExprKind::Tup(ref vals) = expr.kind { + vals.is_empty() + } else { + false + } +} + +fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) { + let (ret_span, appl) = if let Ok(fn_source) = cx.sess().source_map().span_to_snippet(span.with_hi(ty.span.hi())) { + position_before_rarrow(&fn_source).map_or((ty.span, Applicability::MaybeIncorrect), |rpos| { + ( + #[allow(clippy::cast_possible_truncation)] + ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)), + Applicability::MachineApplicable, + ) + }) + } else { + (ty.span, Applicability::MaybeIncorrect) + }; + span_lint_and_sugg( + cx, + UNUSED_UNIT, + ret_span, + "unneeded unit return type", + "remove the `-> ()`", + String::new(), + appl, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/unwrap.rs b/src/tools/clippy/clippy_lints/src/unwrap.rs new file mode 100644 index 0000000000..2fb0463c5a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unwrap.rs @@ -0,0 +1,233 @@ +use crate::utils::{ + differing_macro_contexts, is_type_diagnostic_item, span_lint_and_then, usage::is_potentially_mutated, +}; +use if_chain::if_chain; +use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Path, QPath, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::Ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for calls of `unwrap[_err]()` that cannot fail. + /// + /// **Why is this bad?** Using `if let` or `match` is more idiomatic. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// # let option = Some(0); + /// # fn do_something_with(_x: usize) {} + /// if option.is_some() { + /// do_something_with(option.unwrap()) + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// # let option = Some(0); + /// # fn do_something_with(_x: usize) {} + /// if let Some(value) = option { + /// do_something_with(value) + /// } + /// ``` + pub UNNECESSARY_UNWRAP, + complexity, + "checks for calls of `unwrap[_err]()` that cannot fail" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls of `unwrap[_err]()` that will always fail. + /// + /// **Why is this bad?** If panicking is desired, an explicit `panic!()` should be used. + /// + /// **Known problems:** This lint only checks `if` conditions not assignments. + /// So something like `let x: Option<()> = None; x.unwrap();` will not be recognized. + /// + /// **Example:** + /// ```rust + /// # let option = Some(0); + /// # fn do_something_with(_x: usize) {} + /// if option.is_none() { + /// do_something_with(option.unwrap()) + /// } + /// ``` + /// + /// This code will always panic. The if condition should probably be inverted. + pub PANICKING_UNWRAP, + correctness, + "checks for calls of `unwrap[_err]()` that will always fail" +} + +/// Visitor that keeps track of which variables are unwrappable. +struct UnwrappableVariablesVisitor<'a, 'tcx> { + unwrappables: Vec>, + cx: &'a LateContext<'tcx>, +} +/// Contains information about whether a variable can be unwrapped. +#[derive(Copy, Clone, Debug)] +struct UnwrapInfo<'tcx> { + /// The variable that is checked + ident: &'tcx Path<'tcx>, + /// The check, like `x.is_ok()` + check: &'tcx Expr<'tcx>, + /// The branch where the check takes place, like `if x.is_ok() { .. }` + branch: &'tcx Expr<'tcx>, + /// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`). + safe_to_unwrap: bool, +} + +/// Collects the information about unwrappable variables from an if condition +/// The `invert` argument tells us whether the condition is negated. +fn collect_unwrap_info<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + branch: &'tcx Expr<'_>, + invert: bool, +) -> Vec> { + fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool { + is_type_diagnostic_item(cx, ty, sym::option_type) && ["is_some", "is_none"].contains(&method_name) + } + + fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool { + is_type_diagnostic_item(cx, ty, sym::result_type) && ["is_ok", "is_err"].contains(&method_name) + } + + if let ExprKind::Binary(op, left, right) = &expr.kind { + match (invert, op.node) { + (false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) => { + let mut unwrap_info = collect_unwrap_info(cx, left, branch, invert); + unwrap_info.append(&mut collect_unwrap_info(cx, right, branch, invert)); + return unwrap_info; + }, + _ => (), + } + } else if let ExprKind::Unary(UnOp::Not, expr) = &expr.kind { + return collect_unwrap_info(cx, expr, branch, !invert); + } else { + if_chain! { + if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind; + if let ExprKind::Path(QPath::Resolved(None, path)) = &args[0].kind; + let ty = cx.typeck_results().expr_ty(&args[0]); + let name = method_name.ident.as_str(); + if is_relevant_option_call(cx, ty, &name) || is_relevant_result_call(cx, ty, &name); + then { + assert!(args.len() == 1); + let unwrappable = match name.as_ref() { + "is_some" | "is_ok" => true, + "is_err" | "is_none" => false, + _ => unreachable!(), + }; + let safe_to_unwrap = unwrappable != invert; + return vec![UnwrapInfo { ident: path, check: expr, branch, safe_to_unwrap }]; + } + } + } + Vec::new() +} + +impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> { + fn visit_branch(&mut self, cond: &'tcx Expr<'_>, branch: &'tcx Expr<'_>, else_branch: bool) { + let prev_len = self.unwrappables.len(); + for unwrap_info in collect_unwrap_info(self.cx, cond, branch, else_branch) { + if is_potentially_mutated(unwrap_info.ident, cond, self.cx) + || is_potentially_mutated(unwrap_info.ident, branch, self.cx) + { + // if the variable is mutated, we don't know whether it can be unwrapped: + continue; + } + self.unwrappables.push(unwrap_info); + } + walk_expr(self, branch); + self.unwrappables.truncate(prev_len); + } +} + +impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + // Shouldn't lint when `expr` is in macro. + if in_external_macro(self.cx.tcx.sess, expr.span) { + return; + } + if let ExprKind::If(cond, then, els) = &expr.kind { + walk_expr(self, cond); + self.visit_branch(cond, then, false); + if let Some(els) = els { + self.visit_branch(cond, els, true); + } + } else { + // find `unwrap[_err]()` calls: + if_chain! { + if let ExprKind::MethodCall(ref method_name, _, ref args, _) = expr.kind; + if let ExprKind::Path(QPath::Resolved(None, ref path)) = args[0].kind; + if [sym::unwrap, sym!(unwrap_err)].contains(&method_name.ident.name); + let call_to_unwrap = method_name.ident.name == sym::unwrap; + if let Some(unwrappable) = self.unwrappables.iter() + .find(|u| u.ident.res == path.res); + // Span contexts should not differ with the conditional branch + if !differing_macro_contexts(unwrappable.branch.span, expr.span); + if !differing_macro_contexts(unwrappable.branch.span, unwrappable.check.span); + then { + if call_to_unwrap == unwrappable.safe_to_unwrap { + span_lint_and_then( + self.cx, + UNNECESSARY_UNWRAP, + expr.span, + &format!("you checked before that `{}()` cannot fail, \ + instead of checking and unwrapping, it's better to use `if let` or `match`", + method_name.ident.name), + |diag| { diag.span_label(unwrappable.check.span, "the check is happening here"); }, + ); + } else { + span_lint_and_then( + self.cx, + PANICKING_UNWRAP, + expr.span, + &format!("this call to `{}()` will always panic", + method_name.ident.name), + |diag| { diag.span_label(unwrappable.check.span, "because of this check"); }, + ); + } + } + } + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +declare_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]); + +impl<'tcx> LateLintPass<'tcx> for Unwrap { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + span: Span, + fn_id: HirId, + ) { + if span.from_expansion() { + return; + } + + let mut v = UnwrappableVariablesVisitor { + cx, + unwrappables: Vec::new(), + }; + + walk_fn(&mut v, kind, decl, body.id(), span, fn_id); + } +} diff --git a/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs b/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs new file mode 100644 index 0000000000..8cb7429849 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs @@ -0,0 +1,139 @@ +use crate::utils::{is_type_diagnostic_item, method_chain_args, return_ty, span_lint_and_then}; +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::{sym, Span}; + +declare_clippy_lint! { + /// **What it does:** Checks for functions of type Result that contain `expect()` or `unwrap()` + /// + /// **Why is this bad?** These functions promote recoverable errors to non-recoverable errors which may be undesirable in code bases which wish to avoid panics. + /// + /// **Known problems:** This can cause false positives in functions that handle both recoverable and non recoverable errors. + /// + /// **Example:** + /// Before: + /// ```rust + /// fn divisible_by_3(i_str: String) -> Result<(), String> { + /// let i = i_str + /// .parse::() + /// .expect("cannot divide the input by three"); + /// + /// if i % 3 != 0 { + /// Err("Number is not divisible by 3")? + /// } + /// + /// Ok(()) + /// } + /// ``` + /// + /// After: + /// ```rust + /// fn divisible_by_3(i_str: String) -> Result<(), String> { + /// let i = i_str + /// .parse::() + /// .map_err(|e| format!("cannot divide the input by three: {}", e))?; + /// + /// if i % 3 != 0 { + /// Err("Number is not divisible by 3")? + /// } + /// + /// Ok(()) + /// } + /// ``` + pub UNWRAP_IN_RESULT, + restriction, + "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`" +} + +declare_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]); + +impl<'tcx> LateLintPass<'tcx> for UnwrapInResult { + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { + if_chain! { + // first check if it's a method or function + if let hir::ImplItemKind::Fn(ref _signature, _) = impl_item.kind; + // checking if its return type is `result` or `option` + if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::result_type) + || is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::option_type); + then { + lint_impl_body(cx, impl_item.span, impl_item); + } + } + } +} + +use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; +use rustc_hir::{Expr, ImplItemKind}; + +struct FindExpectUnwrap<'a, 'tcx> { + lcx: &'a LateContext<'tcx>, + typeck_results: &'tcx ty::TypeckResults<'tcx>, + result: Vec, +} + +impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + // check for `expect` + if let Some(arglists) = method_chain_args(expr, &["expect"]) { + let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); + 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); + } + } + + // check for `unwrap` + if let Some(arglists) = method_chain_args(expr, &["unwrap"]) { + let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); + if is_type_diagnostic_item(self.lcx, reciever_ty, sym::option_type) + || is_type_diagnostic_item(self.lcx, reciever_ty, sym::result_type) + { + self.result.push(expr.span); + } + } + + // and check sub-expressions + intravisit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) { + if_chain! { + + if let ImplItemKind::Fn(_, body_id) = impl_item.kind; + then { + let body = cx.tcx.hir().body(body_id); + let mut fpu = FindExpectUnwrap { + lcx: cx, + typeck_results: cx.tcx.typeck(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, + UNWRAP_IN_RESULT, + impl_span, + "used unwrap or expect in a function that returns result or option", + move |diag| { + diag.help( + "unwrap and expect should not be used in a function that returns result or option" ); + diag.span_note(fpu.result, "potential non-recoverable error(s)"); + }); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs b/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs new file mode 100644 index 0000000000..0470e1dbbb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs @@ -0,0 +1,117 @@ +use crate::utils::span_lint_and_sugg; +use if_chain::if_chain; +use itertools::Itertools; +use rustc_ast::ast::{Item, ItemKind, Variant}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::Ident; + +declare_clippy_lint! { + /// **What it does:** Checks for fully capitalized names and optionally names containing a capitalized acronym. + /// + /// **Why is this bad?** In CamelCase, acronyms count as one word. + /// See [naming conventions](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case) + /// for more. + /// + /// By default, the lint only triggers on fully-capitalized names. + /// You can use the `upper-case-acronyms-aggressive: true` config option to enable linting + /// on all camel case names + /// + /// **Known problems:** When two acronyms are contiguous, the lint can't tell where + /// the first acronym ends and the second starts, so it suggests to lowercase all of + /// the letters in the second acronym. + /// + /// **Example:** + /// + /// ```rust + /// struct HTTPResponse; + /// ``` + /// Use instead: + /// ```rust + /// struct HttpResponse; + /// ``` + pub UPPER_CASE_ACRONYMS, + style, + "capitalized acronyms are against the naming convention" +} + +#[derive(Default)] +pub struct UpperCaseAcronyms { + upper_case_acronyms_aggressive: bool, +} + +impl UpperCaseAcronyms { + pub fn new(aggressive: bool) -> Self { + Self { + upper_case_acronyms_aggressive: aggressive, + } + } +} + +impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]); + +fn correct_ident(ident: &str) -> String { + let ident = ident.chars().rev().collect::(); + let fragments = ident + .split_inclusive(|x: char| !x.is_ascii_lowercase()) + .rev() + .map(|x| x.chars().rev().collect::()); + + let mut ident = fragments.clone().next().unwrap(); + for (ref prev, ref curr) in fragments.tuple_windows() { + if [prev, curr] + .iter() + .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase()) + { + ident.push_str(&curr.to_ascii_lowercase()); + } else { + ident.push_str(curr); + } + } + ident +} + +fn check_ident(cx: &EarlyContext<'_>, ident: &Ident, be_aggressive: bool) { + let span = ident.span; + let ident = &ident.as_str(); + let corrected = correct_ident(ident); + // warn if we have pure-uppercase idents + // assume that two-letter words are some kind of valid abbreviation like FP for false positive + // (and don't warn) + if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2) + // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive + // upper-case-acronyms-aggressive config option enabled + || (be_aggressive && ident != &corrected) + { + span_lint_and_sugg( + cx, + UPPER_CASE_ACRONYMS, + span, + &format!("name `{}` contains a capitalized acronym", ident), + "consider making the acronym lowercase, except the initial letter", + corrected, + Applicability::MaybeIncorrect, + ) + } +} + +impl EarlyLintPass for UpperCaseAcronyms { + fn check_item(&mut self, cx: &EarlyContext<'_>, it: &Item) { + if_chain! { + if !in_external_macro(cx.sess(), it.span); + if matches!( + it.kind, + ItemKind::TyAlias(..) | ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Trait(..) + ); + then { + check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive); + } + } + } + + fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &Variant) { + check_ident(cx, &v.ident, self.upper_case_acronyms_aggressive); + } +} diff --git a/src/tools/clippy/clippy_lints/src/use_self.rs b/src/tools/clippy/clippy_lints/src/use_self.rs new file mode 100644 index 0000000000..f0523cec62 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/use_self.rs @@ -0,0 +1,468 @@ +use crate::utils::{in_macro, meets_msrv, snippet_opt, span_lint_and_sugg}; +use if_chain::if_chain; + +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_hir::{ + def, + def_id::LocalDefId, + intravisit::{walk_ty, NestedVisitorMap, Visitor}, + Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Node, Path, PathSegment, + QPath, TyKind, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::ty::{AssocKind, Ty, TyS}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{BytePos, Span}; +use rustc_typeck::hir_ty_to_ty; + +declare_clippy_lint! { + /// **What it does:** Checks for unnecessary repetition of structure name when a + /// replacement with `Self` is applicable. + /// + /// **Why is this bad?** Unnecessary repetition. Mixed use of `Self` and struct + /// name + /// feels inconsistent. + /// + /// **Known problems:** + /// - Unaddressed false negative in fn bodies of trait implementations + /// - False positive with assotiated types in traits (#4140) + /// + /// **Example:** + /// + /// ```rust + /// struct Foo {} + /// impl Foo { + /// fn new() -> Foo { + /// Foo {} + /// } + /// } + /// ``` + /// could be + /// ```rust + /// struct Foo {} + /// impl Foo { + /// fn new() -> Self { + /// Self {} + /// } + /// } + /// ``` + pub USE_SELF, + nursery, + "unnecessary structure name repetition whereas `Self` is applicable" +} + +#[derive(Default)] +pub struct UseSelf { + msrv: Option, + stack: Vec, +} + +const USE_SELF_MSRV: RustcVersion = RustcVersion::new(1, 37, 0); + +impl UseSelf { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { + msrv, + ..Self::default() + } + } +} + +#[derive(Debug)] +enum StackItem { + Check { + hir_id: HirId, + impl_trait_ref_def_id: Option, + types_to_skip: Vec, + types_to_lint: Vec, + }, + NoCheck, +} + +impl_lint_pass!(UseSelf => [USE_SELF]); + +const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element"; + +impl<'tcx> LateLintPass<'tcx> for UseSelf { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + // We push the self types of `impl`s on a stack here. Only the top type on the stack is + // relevant for linting, since this is the self type of the `impl` we're currently in. To + // avoid linting on nested items, we push `StackItem::NoCheck` on the stack to signal, that + // we're in an `impl` or nested item, that we don't want to lint + // + // NB: If you push something on the stack in this method, remember to also pop it in the + // `check_item_post` method. + match &item.kind { + ItemKind::Impl(Impl { + self_ty: hir_self_ty, + of_trait, + .. + }) => { + let should_check = if let TyKind::Path(QPath::Resolved(_, ref item_path)) = hir_self_ty.kind { + let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args; + parameters.as_ref().map_or(true, |params| { + !params.parenthesized && !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_))) + }) + } else { + false + }; + let impl_trait_ref_def_id = of_trait.as_ref().map(|_| cx.tcx.hir().local_def_id(item.hir_id())); + if should_check { + self.stack.push(StackItem::Check { + hir_id: hir_self_ty.hir_id, + impl_trait_ref_def_id, + types_to_lint: Vec::new(), + types_to_skip: Vec::new(), + }); + } else { + self.stack.push(StackItem::NoCheck); + } + }, + ItemKind::Static(..) + | ItemKind::Const(..) + | ItemKind::Fn(..) + | ItemKind::Enum(..) + | ItemKind::Struct(..) + | ItemKind::Union(..) + | ItemKind::Trait(..) => { + self.stack.push(StackItem::NoCheck); + }, + _ => (), + } + } + + fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) { + use ItemKind::{Const, Enum, Fn, Impl, Static, Struct, Trait, Union}; + match item.kind { + Impl { .. } | Static(..) | Const(..) | Fn(..) | Enum(..) | Struct(..) | Union(..) | Trait(..) => { + self.stack.pop(); + }, + _ => (), + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) { + // We want to skip types in trait `impl`s that aren't declared as `Self` in the trait + // declaration. The collection of those types is all this method implementation does. + if_chain! { + if let ImplItemKind::Fn(FnSig { decl, .. }, ..) = impl_item.kind; + if let Some(&mut StackItem::Check { + impl_trait_ref_def_id: Some(def_id), + ref mut types_to_skip, + .. + }) = self.stack.last_mut(); + if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(def_id); + then { + // `self_ty` is the semantic self type of `impl for `. This cannot be + // `Self`. + let self_ty = impl_trait_ref.self_ty(); + + // `trait_method_sig` is the signature of the function, how it is declared in the + // trait, not in the impl of the trait. + let trait_method = cx + .tcx + .associated_items(impl_trait_ref.def_id) + .find_by_name_and_kind(cx.tcx, impl_item.ident, AssocKind::Fn, impl_trait_ref.def_id) + .expect("impl method matches a trait method"); + let trait_method_sig = cx.tcx.fn_sig(trait_method.def_id); + let trait_method_sig = cx.tcx.erase_late_bound_regions(trait_method_sig); + + // `impl_inputs_outputs` is an iterator over the types (`hir::Ty`) declared in the + // implementation of the trait. + let output_hir_ty = if let FnRetTy::Return(ty) = &decl.output { + Some(&**ty) + } else { + None + }; + let impl_inputs_outputs = decl.inputs.iter().chain(output_hir_ty); + + // `impl_hir_ty` (of type `hir::Ty`) represents the type written in the signature. + // + // `trait_sem_ty` (of type `ty::Ty`) is the semantic type for the signature in the + // trait declaration. This is used to check if `Self` was used in the trait + // declaration. + // + // If `any`where in the `trait_sem_ty` the `self_ty` was used verbatim (as opposed + // to `Self`), we want to skip linting that type and all subtypes of it. This + // avoids suggestions to e.g. replace `Vec` with `Vec`, in an `impl Trait + // for u8`, when the trait always uses `Vec`. + // + // See also https://github.com/rust-lang/rust-clippy/issues/2894. + for (impl_hir_ty, trait_sem_ty) in impl_inputs_outputs.zip(trait_method_sig.inputs_and_output) { + if trait_sem_ty.walk().any(|inner| inner == self_ty.into()) { + let mut visitor = SkipTyCollector::default(); + visitor.visit_ty(&impl_hir_ty); + types_to_skip.extend(visitor.types_to_skip); + } + } + } + } + } + + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) { + // `hir_ty_to_ty` cannot be called in `Body`s or it will panic (sometimes). But in bodies + // we can use `cx.typeck_results.node_type(..)` to get the `ty::Ty` from a `hir::Ty`. + // However the `node_type()` method can *only* be called in bodies. + // + // This method implementation determines which types should get linted in a `Body` and + // which shouldn't, with a visitor. We could directly lint in the visitor, but then we + // could only allow this lint on item scope. And we would have to check if those types are + // already dealt with in `check_ty` anyway. + if let Some(StackItem::Check { + hir_id, + types_to_lint, + types_to_skip, + .. + }) = self.stack.last_mut() + { + let self_ty = ty_from_hir_id(cx, *hir_id); + + let mut visitor = LintTyCollector { + cx, + self_ty, + types_to_lint: vec![], + types_to_skip: vec![], + }; + visitor.visit_expr(&body.value); + types_to_lint.extend(visitor.types_to_lint); + types_to_skip.extend(visitor.types_to_skip); + } + } + + fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) { + if in_macro(hir_ty.span) | in_impl(cx, hir_ty) | !meets_msrv(self.msrv.as_ref(), &USE_SELF_MSRV) { + return; + } + + let lint_dependend_on_expr_kind = if let Some(StackItem::Check { + hir_id, + types_to_lint, + types_to_skip, + .. + }) = self.stack.last() + { + if types_to_skip.contains(&hir_ty.hir_id) { + false + } else if types_to_lint.contains(&hir_ty.hir_id) { + true + } else { + let self_ty = ty_from_hir_id(cx, *hir_id); + should_lint_ty(hir_ty, hir_ty_to_ty(cx.tcx, hir_ty), self_ty) + } + } else { + false + }; + + if lint_dependend_on_expr_kind { + // FIXME: this span manipulation should not be necessary + // @flip1995 found an ast lowering issue in + // https://github.com/rust-lang/rust/blob/master/src/librustc_ast_lowering/path.rs#l142-l162 + let hir = cx.tcx.hir(); + let id = hir.get_parent_node(hir_ty.hir_id); + + if !hir.opt_span(id).map_or(false, in_macro) { + match hir.find(id) { + Some(Node::Expr(Expr { + kind: ExprKind::Path(QPath::TypeRelative(_, segment)), + .. + })) => span_lint_until_last_segment(cx, hir_ty.span, segment), + _ => span_lint(cx, hir_ty.span), + } + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + fn expr_ty_matches(cx: &LateContext<'_>, expr: &Expr<'_>, self_ty: Ty<'_>) -> bool { + let def_id = expr.hir_id.owner; + if cx.tcx.has_typeck_results(def_id) { + cx.tcx.typeck(def_id).expr_ty_opt(expr) == Some(self_ty) + } else { + false + } + } + + if in_macro(expr.span) | !meets_msrv(self.msrv.as_ref(), &USE_SELF_MSRV) { + return; + } + + if let Some(StackItem::Check { hir_id, .. }) = self.stack.last() { + let self_ty = ty_from_hir_id(cx, *hir_id); + + match &expr.kind { + ExprKind::Struct(QPath::Resolved(_, path), ..) => { + if expr_ty_matches(cx, expr, self_ty) { + match path.res { + def::Res::SelfTy(..) => (), + def::Res::Def(DefKind::Variant, _) => span_lint_on_path_until_last_segment(cx, path), + _ => { + span_lint(cx, path.span); + }, + } + } + }, + // tuple struct instantiation (`Foo(arg)` or `Enum::Foo(arg)`) + ExprKind::Call(fun, _) => { + if let Expr { + kind: ExprKind::Path(ref qpath), + .. + } = fun + { + if expr_ty_matches(cx, expr, self_ty) { + let res = cx.qpath_res(qpath, fun.hir_id); + + if let def::Res::Def(DefKind::Ctor(ctor_of, _), ..) = res { + match ctor_of { + def::CtorOf::Variant => { + span_lint_on_qpath_resolved(cx, qpath, true); + }, + def::CtorOf::Struct => { + span_lint_on_qpath_resolved(cx, qpath, false); + }, + } + } + } + } + }, + // unit enum variants (`Enum::A`) + ExprKind::Path(qpath) => { + if expr_ty_matches(cx, expr, self_ty) { + span_lint_on_qpath_resolved(cx, &qpath, true); + } + }, + _ => (), + } + } + } + + extract_msrv_attr!(LateContext); +} + +#[derive(Default)] +struct SkipTyCollector { + types_to_skip: Vec, +} + +impl<'tcx> Visitor<'tcx> for SkipTyCollector { + type Map = Map<'tcx>; + + fn visit_ty(&mut self, hir_ty: &hir::Ty<'_>) { + self.types_to_skip.push(hir_ty.hir_id); + + walk_ty(self, hir_ty) + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +struct LintTyCollector<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + self_ty: Ty<'tcx>, + types_to_lint: Vec, + types_to_skip: Vec, +} + +impl<'a, 'tcx> Visitor<'tcx> for LintTyCollector<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_ty(&mut self, hir_ty: &'tcx hir::Ty<'_>) { + if_chain! { + if let Some(ty) = self.cx.typeck_results().node_type_opt(hir_ty.hir_id); + if should_lint_ty(hir_ty, ty, self.self_ty); + then { + self.types_to_lint.push(hir_ty.hir_id); + } else { + self.types_to_skip.push(hir_ty.hir_id); + } + } + + walk_ty(self, hir_ty) + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn span_lint(cx: &LateContext<'_>, span: Span) { + span_lint_and_sugg( + cx, + USE_SELF, + span, + "unnecessary structure name repetition", + "use the applicable keyword", + "Self".to_owned(), + Applicability::MachineApplicable, + ); +} + +#[allow(clippy::cast_possible_truncation)] +fn span_lint_until_last_segment(cx: &LateContext<'_>, span: Span, segment: &PathSegment<'_>) { + let sp = span.with_hi(segment.ident.span.lo()); + // remove the trailing :: + let span_without_last_segment = match snippet_opt(cx, sp) { + Some(snippet) => match snippet.rfind("::") { + Some(bidx) => sp.with_hi(sp.lo() + BytePos(bidx as u32)), + None => sp, + }, + None => sp, + }; + span_lint(cx, span_without_last_segment); +} + +fn span_lint_on_path_until_last_segment(cx: &LateContext<'_>, path: &Path<'_>) { + if path.segments.len() > 1 { + span_lint_until_last_segment(cx, path.span, path.segments.last().unwrap()); + } +} + +fn span_lint_on_qpath_resolved(cx: &LateContext<'_>, qpath: &QPath<'_>, until_last_segment: bool) { + if let QPath::Resolved(_, path) = qpath { + if until_last_segment { + span_lint_on_path_until_last_segment(cx, path); + } else { + span_lint(cx, path.span); + } + } +} + +fn ty_from_hir_id<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Ty<'tcx> { + if let Some(Node::Ty(hir_ty)) = cx.tcx.hir().find(hir_id) { + hir_ty_to_ty(cx.tcx, hir_ty) + } else { + unreachable!("This function should only be called with `HirId`s that are for sure `Node::Ty`") + } +} + +fn in_impl(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> bool { + let map = cx.tcx.hir(); + let parent = map.get_parent_node(hir_ty.hir_id); + if_chain! { + if let Some(Node::Item(item)) = map.find(parent); + if let ItemKind::Impl { .. } = item.kind; + then { + true + } else { + false + } + } +} + +fn should_lint_ty(hir_ty: &hir::Ty<'_>, ty: Ty<'_>, self_ty: Ty<'_>) -> bool { + if_chain! { + if TyS::same_type(ty, self_ty); + if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind; + then { + !matches!(path.res, def::Res::SelfTy(..)) + } else { + false + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/useless_conversion.rs b/src/tools/clippy/clippy_lints/src/useless_conversion.rs new file mode 100644 index 0000000000..c533485398 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/useless_conversion.rs @@ -0,0 +1,190 @@ +use crate::utils::sugg::Sugg; +use crate::utils::{ + get_parent_expr, is_type_diagnostic_item, match_def_path, match_trait_method, paths, snippet, + snippet_with_macro_callsite, span_lint_and_help, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, HirId, MatchSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, TyS}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` calls + /// which uselessly convert to the same type. + /// + /// **Why is this bad?** Redundant code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// // format!() returns a `String` + /// let s: String = format!("hello").into(); + /// + /// // Good + /// let s: String = format!("hello"); + /// ``` + pub USELESS_CONVERSION, + complexity, + "calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` which perform useless conversions to the same type" +} + +#[derive(Default)] +pub struct UselessConversion { + try_desugar_arm: Vec, +} + +impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]); + +#[allow(clippy::too_many_lines)] +impl<'tcx> LateLintPass<'tcx> for UselessConversion { + fn check_expr(&mut self, cx: &LateContext<'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.typeck_results().expr_ty(e); + let b = cx.typeck_results().expr_ty(&args[0]); + if TyS::same_type(a, b) { + let sugg = snippet_with_macro_callsite(cx, args[0].span, "").to_string(); + span_lint_and_sugg( + cx, + USELESS_CONVERSION, + e.span, + &format!("useless conversion to the same type: `{}`", b), + "consider removing `.into()`", + sugg, + Applicability::MachineApplicable, // snippet + ); + } + } + if match_trait_method(cx, e, &paths::INTO_ITERATOR) && name.ident.name == sym::into_iter { + if let Some(parent_expr) = get_parent_expr(cx, e) { + if let ExprKind::MethodCall(ref parent_name, ..) = parent_expr.kind { + if parent_name.ident.name != sym::into_iter { + return; + } + } + } + let a = cx.typeck_results().expr_ty(e); + let b = cx.typeck_results().expr_ty(&args[0]); + if TyS::same_type(a, b) { + let sugg = snippet(cx, args[0].span, "").into_owned(); + span_lint_and_sugg( + cx, + USELESS_CONVERSION, + e.span, + &format!("useless conversion to the same type: `{}`", b), + "consider removing `.into_iter()`", + sugg, + Applicability::MachineApplicable, // snippet + ); + } + } + if match_trait_method(cx, e, &paths::TRY_INTO_TRAIT) && &*name.ident.as_str() == "try_into" { + if_chain! { + let a = cx.typeck_results().expr_ty(e); + let b = cx.typeck_results().expr_ty(&args[0]); + if is_type_diagnostic_item(cx, a, sym::result_type); + if let ty::Adt(_, substs) = a.kind(); + if let Some(a_type) = substs.types().next(); + if TyS::same_type(a_type, b); + + then { + span_lint_and_help( + cx, + USELESS_CONVERSION, + e.span, + &format!("useless conversion to the same type: `{}`", b), + None, + "consider removing `.try_into()`", + ); + } + } + } + }, + + ExprKind::Call(ref path, ref args) => { + if_chain! { + if args.len() == 1; + if let ExprKind::Path(ref qpath) = path.kind; + if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id(); + let a = cx.typeck_results().expr_ty(e); + let b = cx.typeck_results().expr_ty(&args[0]); + + then { + if_chain! { + if match_def_path(cx, def_id, &paths::TRY_FROM); + if is_type_diagnostic_item(cx, a, sym::result_type); + if let ty::Adt(_, substs) = a.kind(); + if let Some(a_type) = substs.types().next(); + if TyS::same_type(a_type, b); + + then { + let hint = format!("consider removing `{}()`", snippet(cx, path.span, "TryFrom::try_from")); + span_lint_and_help( + cx, + USELESS_CONVERSION, + e.span, + &format!("useless conversion to the same type: `{}`", b), + None, + &hint, + ); + } + } + + if_chain! { + if match_def_path(cx, def_id, &paths::FROM_FROM); + if TyS::same_type(a, b); + + then { + let sugg = Sugg::hir_with_macro_callsite(cx, &args[0], "").maybe_par(); + let sugg_msg = + format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); + span_lint_and_sugg( + cx, + USELESS_CONVERSION, + e.span, + &format!("useless conversion to the same type: `{}`", b), + &sugg_msg, + sugg.to_string(), + Applicability::MachineApplicable, // snippet + ); + } + } + } + } + }, + + _ => {}, + } + } + + fn check_expr_post(&mut self, _: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if Some(&e.hir_id) == self.try_desugar_arm.last() { + self.try_desugar_arm.pop(); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs new file mode 100644 index 0000000000..c576148008 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -0,0 +1,779 @@ +//! A group of attributes that can be attached to Rust code in order +//! to generate a clippy lint detecting said code automatically. + +use crate::utils::get_attr; +use rustc_ast::ast::{LitFloatType, LitKind}; +use rustc_ast::walk_list; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir as hir; +use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Pat, PatKind, QPath, Stmt, StmtKind, TyKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Generates clippy code that detects the offending pattern + /// + /// **Example:** + /// ```rust,ignore + /// // ./tests/ui/my_lint.rs + /// fn foo() { + /// // detect the following pattern + /// #[clippy::author] + /// if x == 42 { + /// // but ignore everything from here on + /// #![clippy::author = "ignore"] + /// } + /// () + /// } + /// ``` + /// + /// Running `TESTNAME=ui/my_lint cargo uitest` will produce + /// a `./tests/ui/new_lint.stdout` file with the generated code: + /// + /// ```rust,ignore + /// // ./tests/ui/new_lint.stdout + /// if_chain! { + /// if let ExprKind::If(ref cond, ref then, None) = item.kind, + /// if let ExprKind::Binary(BinOp::Eq, ref left, ref right) = cond.kind, + /// if let ExprKind::Path(ref path) = left.kind, + /// if let ExprKind::Lit(ref lit) = right.kind, + /// if let LitKind::Int(42, _) = lit.node, + /// then { + /// // report your lint here + /// } + /// } + /// ``` + pub LINT_AUTHOR, + internal_warn, + "helper for writing lints" +} + +declare_lint_pass!(Author => [LINT_AUTHOR]); + +fn prelude() { + println!("if_chain! {{"); +} + +fn done() { + println!(" then {{"); + println!(" // report your lint here"); + println!(" }}"); + println!("}}"); +} + +impl<'tcx> LateLintPass<'tcx> for Author { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { + if !has_attr(cx, item.hir_id()) { + return; + } + prelude(); + PrintVisitor::new("item").visit_item(item); + done(); + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { + if !has_attr(cx, item.hir_id()) { + return; + } + prelude(); + PrintVisitor::new("item").visit_impl_item(item); + done(); + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { + if !has_attr(cx, item.hir_id()) { + return; + } + prelude(); + PrintVisitor::new("item").visit_trait_item(item); + done(); + } + + fn check_variant(&mut self, cx: &LateContext<'tcx>, var: &'tcx hir::Variant<'_>) { + if !has_attr(cx, var.id) { + return; + } + prelude(); + let parent_hir_id = cx.tcx.hir().get_parent_node(var.id); + PrintVisitor::new("var").visit_variant(var, &hir::Generics::empty(), parent_hir_id); + done(); + } + + fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'_>) { + if !has_attr(cx, field.hir_id) { + return; + } + prelude(); + PrintVisitor::new("field").visit_field_def(field); + done(); + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if !has_attr(cx, expr.hir_id) { + return; + } + prelude(); + PrintVisitor::new("expr").visit_expr(expr); + done(); + } + + fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) { + if !has_attr(cx, arm.hir_id) { + return; + } + prelude(); + PrintVisitor::new("arm").visit_arm(arm); + done(); + } + + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) { + if !has_attr(cx, stmt.hir_id) { + return; + } + prelude(); + PrintVisitor::new("stmt").visit_stmt(stmt); + done(); + } + + fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ForeignItem<'_>) { + if !has_attr(cx, item.hir_id()) { + return; + } + prelude(); + PrintVisitor::new("item").visit_foreign_item(item); + done(); + } +} + +impl PrintVisitor { + #[must_use] + fn new(s: &'static str) -> Self { + Self { + ids: FxHashMap::default(), + current: s.to_owned(), + } + } + + fn next(&mut self, s: &'static str) -> String { + use std::collections::hash_map::Entry::{Occupied, Vacant}; + match self.ids.entry(s) { + // already there: start numbering from `1` + Occupied(mut occ) => { + let val = occ.get_mut(); + *val += 1; + format!("{}{}", s, *val) + }, + // not there: insert and return name as given + Vacant(vac) => { + vac.insert(0); + s.to_owned() + }, + } + } + + fn print_qpath(&mut self, path: &QPath<'_>) { + if let QPath::LangItem(lang_item, _) = *path { + println!( + " if matches!({}, QPath::LangItem(LangItem::{:?}, _));", + self.current, lang_item, + ); + } else { + print!(" if match_qpath({}, &[", self.current); + print_path(path, &mut true); + println!("]);"); + } + } +} + +struct PrintVisitor { + /// Fields are the current index that needs to be appended to pattern + /// binding names + ids: FxHashMap<&'static str, usize>, + /// the name that needs to be destructured + current: String, +} + +impl<'tcx> Visitor<'tcx> for PrintVisitor { + type Map = Map<'tcx>; + + #[allow(clippy::too_many_lines)] + fn visit_expr(&mut self, expr: &Expr<'_>) { + print!(" if let ExprKind::"); + let current = format!("{}.kind", self.current); + match expr.kind { + ExprKind::Box(ref inner) => { + let inner_pat = self.next("inner"); + println!("Box(ref {}) = {};", inner_pat, current); + self.current = inner_pat; + self.visit_expr(inner); + }, + ExprKind::Array(ref elements) => { + let elements_pat = self.next("elements"); + println!("Array(ref {}) = {};", elements_pat, current); + println!(" if {}.len() == {};", elements_pat, elements.len()); + for (i, element) in elements.iter().enumerate() { + self.current = format!("{}[{}]", elements_pat, i); + self.visit_expr(element); + } + }, + ExprKind::Call(ref func, ref args) => { + let func_pat = self.next("func"); + let args_pat = self.next("args"); + println!("Call(ref {}, ref {}) = {};", func_pat, args_pat, current); + self.current = func_pat; + self.visit_expr(func); + println!(" if {}.len() == {};", args_pat, args.len()); + for (i, arg) in args.iter().enumerate() { + self.current = format!("{}[{}]", args_pat, i); + self.visit_expr(arg); + } + }, + ExprKind::MethodCall(ref _method_name, ref _generics, ref _args, ref _fn_span) => { + println!( + "MethodCall(ref method_name, ref generics, ref args, ref fn_span) = {};", + current + ); + println!(" // unimplemented: `ExprKind::MethodCall` is not further destructured at the moment"); + }, + ExprKind::Tup(ref elements) => { + let elements_pat = self.next("elements"); + println!("Tup(ref {}) = {};", elements_pat, current); + println!(" if {}.len() == {};", elements_pat, elements.len()); + for (i, element) in elements.iter().enumerate() { + self.current = format!("{}[{}]", elements_pat, i); + self.visit_expr(element); + } + }, + ExprKind::Binary(ref op, ref left, ref right) => { + let op_pat = self.next("op"); + let left_pat = self.next("left"); + let right_pat = self.next("right"); + println!( + "Binary(ref {}, ref {}, ref {}) = {};", + op_pat, left_pat, right_pat, current + ); + println!(" if BinOpKind::{:?} == {}.node;", op.node, op_pat); + self.current = left_pat; + self.visit_expr(left); + self.current = right_pat; + self.visit_expr(right); + }, + ExprKind::Unary(ref op, ref inner) => { + let inner_pat = self.next("inner"); + println!("Unary(UnOp::{:?}, ref {}) = {};", op, inner_pat, current); + self.current = inner_pat; + self.visit_expr(inner); + }, + ExprKind::Lit(ref lit) => { + let lit_pat = self.next("lit"); + println!("Lit(ref {}) = {};", lit_pat, current); + match lit.node { + LitKind::Bool(val) => println!(" if let LitKind::Bool({:?}) = {}.node;", val, lit_pat), + LitKind::Char(c) => println!(" if let LitKind::Char({:?}) = {}.node;", c, lit_pat), + LitKind::Err(val) => println!(" if let LitKind::Err({}) = {}.node;", val, lit_pat), + LitKind::Byte(b) => println!(" if let LitKind::Byte({}) = {}.node;", b, lit_pat), + // FIXME: also check int type + LitKind::Int(i, _) => println!(" if let LitKind::Int({}, _) = {}.node;", i, lit_pat), + LitKind::Float(_, LitFloatType::Suffixed(_)) => println!( + " if let LitKind::Float(_, LitFloatType::Suffixed(_)) = {}.node;", + lit_pat + ), + LitKind::Float(_, LitFloatType::Unsuffixed) => println!( + " if let LitKind::Float(_, LitFloatType::Unsuffixed) = {}.node;", + lit_pat + ), + LitKind::ByteStr(ref vec) => { + let vec_pat = self.next("vec"); + println!(" if let LitKind::ByteStr(ref {}) = {}.node;", vec_pat, lit_pat); + println!(" if let [{:?}] = **{};", vec, vec_pat); + }, + LitKind::Str(ref text, _) => { + let str_pat = self.next("s"); + println!(" if let LitKind::Str(ref {}, _) = {}.node;", str_pat, lit_pat); + println!(" if {}.as_str() == {:?}", str_pat, &*text.as_str()) + }, + } + }, + ExprKind::Cast(ref expr, ref ty) => { + let cast_pat = self.next("expr"); + let cast_ty = self.next("cast_ty"); + let qp_label = self.next("qp"); + + println!("Cast(ref {}, ref {}) = {};", cast_pat, cast_ty, current); + if let TyKind::Path(ref qp) = ty.kind { + println!(" if let TyKind::Path(ref {}) = {}.kind;", qp_label, cast_ty); + self.current = qp_label; + self.print_qpath(qp); + } + self.current = cast_pat; + self.visit_expr(expr); + }, + ExprKind::Type(ref expr, ref _ty) => { + let cast_pat = self.next("expr"); + println!("Type(ref {}, _) = {};", cast_pat, current); + self.current = cast_pat; + self.visit_expr(expr); + }, + ExprKind::Loop(ref body, _, desugaring, _) => { + let body_pat = self.next("body"); + let des = loop_desugaring_name(desugaring); + let label_pat = self.next("label"); + println!("Loop(ref {}, ref {}, {}) = {};", body_pat, label_pat, des, current); + self.current = body_pat; + self.visit_block(body); + }, + ExprKind::If(ref cond, ref then, ref opt_else) => { + let cond_pat = self.next("cond"); + let then_pat = self.next("then"); + if let Some(ref else_) = *opt_else { + let else_pat = self.next("else_"); + println!( + "If(ref {}, ref {}, Some(ref {})) = {};", + cond_pat, then_pat, else_pat, current + ); + self.current = else_pat; + self.visit_expr(else_); + } else { + println!("If(ref {}, ref {}, None) = {};", cond_pat, then_pat, current); + } + self.current = cond_pat; + self.visit_expr(cond); + self.current = then_pat; + self.visit_expr(then); + }, + ExprKind::Match(ref expr, ref arms, desugaring) => { + let des = desugaring_name(desugaring); + let expr_pat = self.next("expr"); + let arms_pat = self.next("arms"); + println!("Match(ref {}, ref {}, {}) = {};", expr_pat, arms_pat, des, current); + self.current = expr_pat; + self.visit_expr(expr); + println!(" if {}.len() == {};", arms_pat, arms.len()); + for (i, arm) in arms.iter().enumerate() { + self.current = format!("{}[{}].body", arms_pat, i); + self.visit_expr(&arm.body); + if let Some(ref guard) = arm.guard { + let guard_pat = self.next("guard"); + println!(" if let Some(ref {}) = {}[{}].guard;", guard_pat, arms_pat, i); + match guard { + hir::Guard::If(ref if_expr) => { + let if_expr_pat = self.next("expr"); + println!(" if let Guard::If(ref {}) = {};", if_expr_pat, guard_pat); + self.current = if_expr_pat; + self.visit_expr(if_expr); + }, + hir::Guard::IfLet(ref if_let_pat, ref if_let_expr) => { + let if_let_pat_pat = self.next("pat"); + let if_let_expr_pat = self.next("expr"); + println!( + " if let Guard::IfLet(ref {}, ref {}) = {};", + if_let_pat_pat, if_let_expr_pat, guard_pat + ); + self.current = if_let_expr_pat; + self.visit_expr(if_let_expr); + self.current = if_let_pat_pat; + self.visit_pat(if_let_pat); + }, + } + } + self.current = format!("{}[{}].pat", arms_pat, i); + self.visit_pat(&arm.pat); + } + }, + ExprKind::Closure(ref _capture_clause, ref _func, _, _, _) => { + println!("Closure(ref capture_clause, ref func, _, _, _) = {};", current); + println!(" // unimplemented: `ExprKind::Closure` is not further destructured at the moment"); + }, + ExprKind::Yield(ref sub, _) => { + let sub_pat = self.next("sub"); + println!("Yield(ref sub) = {};", current); + self.current = sub_pat; + self.visit_expr(sub); + }, + ExprKind::Block(ref block, _) => { + let block_pat = self.next("block"); + println!("Block(ref {}) = {};", block_pat, current); + self.current = block_pat; + self.visit_block(block); + }, + ExprKind::Assign(ref target, ref value, _) => { + let target_pat = self.next("target"); + let value_pat = self.next("value"); + println!( + "Assign(ref {}, ref {}, ref _span) = {};", + target_pat, value_pat, current + ); + self.current = target_pat; + self.visit_expr(target); + self.current = value_pat; + self.visit_expr(value); + }, + ExprKind::AssignOp(ref op, ref target, ref value) => { + let op_pat = self.next("op"); + let target_pat = self.next("target"); + let value_pat = self.next("value"); + println!( + "AssignOp(ref {}, ref {}, ref {}) = {};", + op_pat, target_pat, value_pat, current + ); + println!(" if BinOpKind::{:?} == {}.node;", op.node, op_pat); + self.current = target_pat; + self.visit_expr(target); + self.current = value_pat; + self.visit_expr(value); + }, + ExprKind::Field(ref object, ref field_ident) => { + let obj_pat = self.next("object"); + let field_name_pat = self.next("field_name"); + println!("Field(ref {}, ref {}) = {};", obj_pat, field_name_pat, current); + println!(" if {}.as_str() == {:?}", field_name_pat, field_ident.as_str()); + self.current = obj_pat; + self.visit_expr(object); + }, + ExprKind::Index(ref object, ref index) => { + let object_pat = self.next("object"); + let index_pat = self.next("index"); + println!("Index(ref {}, ref {}) = {};", object_pat, index_pat, current); + self.current = object_pat; + self.visit_expr(object); + self.current = index_pat; + self.visit_expr(index); + }, + ExprKind::Path(ref path) => { + let path_pat = self.next("path"); + println!("Path(ref {}) = {};", path_pat, current); + self.current = path_pat; + self.print_qpath(path); + }, + ExprKind::AddrOf(kind, mutability, ref inner) => { + let inner_pat = self.next("inner"); + println!( + "AddrOf(BorrowKind::{:?}, Mutability::{:?}, ref {}) = {};", + kind, mutability, inner_pat, current + ); + self.current = inner_pat; + self.visit_expr(inner); + }, + ExprKind::Break(ref _destination, ref opt_value) => { + let destination_pat = self.next("destination"); + if let Some(ref value) = *opt_value { + let value_pat = self.next("value"); + println!("Break(ref {}, Some(ref {})) = {};", destination_pat, value_pat, current); + self.current = value_pat; + self.visit_expr(value); + } else { + println!("Break(ref {}, None) = {};", destination_pat, current); + } + // FIXME: implement label printing + }, + ExprKind::Continue(ref _destination) => { + let destination_pat = self.next("destination"); + println!("Again(ref {}) = {};", destination_pat, current); + // FIXME: implement label printing + }, + ExprKind::Ret(ref opt_value) => { + if let Some(ref value) = *opt_value { + let value_pat = self.next("value"); + println!("Ret(Some(ref {})) = {};", value_pat, current); + self.current = value_pat; + self.visit_expr(value); + } else { + println!("Ret(None) = {};", current); + } + }, + ExprKind::InlineAsm(_) => { + println!("InlineAsm(_) = {};", current); + println!(" // unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment"); + }, + ExprKind::LlvmInlineAsm(_) => { + println!("LlvmInlineAsm(_) = {};", current); + println!(" // unimplemented: `ExprKind::LlvmInlineAsm` is not further destructured at the moment"); + }, + ExprKind::Struct(ref path, ref fields, ref opt_base) => { + let path_pat = self.next("path"); + let fields_pat = self.next("fields"); + if let Some(ref base) = *opt_base { + let base_pat = self.next("base"); + println!( + "Struct(ref {}, ref {}, Some(ref {})) = {};", + path_pat, fields_pat, base_pat, current + ); + self.current = base_pat; + self.visit_expr(base); + } else { + println!("Struct(ref {}, ref {}, None) = {};", path_pat, fields_pat, current); + } + self.current = path_pat; + self.print_qpath(path); + println!(" if {}.len() == {};", fields_pat, fields.len()); + println!(" // unimplemented: field checks"); + }, + ExprKind::ConstBlock(_) => { + let value_pat = self.next("value"); + println!("Const({})", value_pat); + self.current = value_pat; + }, + // FIXME: compute length (needs type info) + ExprKind::Repeat(ref value, _) => { + let value_pat = self.next("value"); + println!("Repeat(ref {}, _) = {};", value_pat, current); + println!("// unimplemented: repeat count check"); + self.current = value_pat; + self.visit_expr(value); + }, + ExprKind::Err => { + println!("Err = {}", current); + }, + ExprKind::DropTemps(ref expr) => { + let expr_pat = self.next("expr"); + println!("DropTemps(ref {}) = {};", expr_pat, current); + self.current = expr_pat; + self.visit_expr(expr); + }, + } + } + + fn visit_block(&mut self, block: &Block<'_>) { + let trailing_pat = self.next("trailing_expr"); + println!(" if let Some({}) = &{}.expr;", trailing_pat, self.current); + println!(" if {}.stmts.len() == {};", self.current, block.stmts.len()); + let current = self.current.clone(); + for (i, stmt) in block.stmts.iter().enumerate() { + self.current = format!("{}.stmts[{}]", current, i); + self.visit_stmt(stmt); + } + } + + #[allow(clippy::too_many_lines)] + fn visit_pat(&mut self, pat: &Pat<'_>) { + print!(" if let PatKind::"); + let current = format!("{}.kind", self.current); + match pat.kind { + PatKind::Wild => println!("Wild = {};", current), + PatKind::Binding(anno, .., ident, ref sub) => { + let anno_pat = match anno { + BindingAnnotation::Unannotated => "BindingAnnotation::Unannotated", + BindingAnnotation::Mutable => "BindingAnnotation::Mutable", + BindingAnnotation::Ref => "BindingAnnotation::Ref", + BindingAnnotation::RefMut => "BindingAnnotation::RefMut", + }; + let name_pat = self.next("name"); + if let Some(ref sub) = *sub { + let sub_pat = self.next("sub"); + println!( + "Binding({}, _, {}, Some(ref {})) = {};", + anno_pat, name_pat, sub_pat, current + ); + self.current = sub_pat; + self.visit_pat(sub); + } else { + println!("Binding({}, _, {}, None) = {};", anno_pat, name_pat, current); + } + println!(" if {}.as_str() == \"{}\";", name_pat, ident.as_str()); + }, + PatKind::Struct(ref path, ref fields, ignore) => { + let path_pat = self.next("path"); + let fields_pat = self.next("fields"); + println!( + "Struct(ref {}, ref {}, {}) = {};", + path_pat, fields_pat, ignore, current + ); + self.current = path_pat; + self.print_qpath(path); + println!(" if {}.len() == {};", fields_pat, fields.len()); + println!(" // unimplemented: field checks"); + }, + PatKind::Or(ref fields) => { + let fields_pat = self.next("fields"); + println!("Or(ref {}) = {};", fields_pat, current); + println!(" if {}.len() == {};", fields_pat, fields.len()); + println!(" // unimplemented: field checks"); + }, + PatKind::TupleStruct(ref path, ref fields, skip_pos) => { + let path_pat = self.next("path"); + let fields_pat = self.next("fields"); + println!( + "TupleStruct(ref {}, ref {}, {:?}) = {};", + path_pat, fields_pat, skip_pos, current + ); + self.current = path_pat; + self.print_qpath(path); + println!(" if {}.len() == {};", fields_pat, fields.len()); + println!(" // unimplemented: field checks"); + }, + PatKind::Path(ref path) => { + let path_pat = self.next("path"); + println!("Path(ref {}) = {};", path_pat, current); + self.current = path_pat; + self.print_qpath(path); + }, + PatKind::Tuple(ref fields, skip_pos) => { + let fields_pat = self.next("fields"); + println!("Tuple(ref {}, {:?}) = {};", fields_pat, skip_pos, current); + println!(" if {}.len() == {};", fields_pat, fields.len()); + println!(" // unimplemented: field checks"); + }, + PatKind::Box(ref pat) => { + let pat_pat = self.next("pat"); + println!("Box(ref {}) = {};", pat_pat, current); + self.current = pat_pat; + self.visit_pat(pat); + }, + PatKind::Ref(ref pat, muta) => { + let pat_pat = self.next("pat"); + println!("Ref(ref {}, Mutability::{:?}) = {};", pat_pat, muta, current); + self.current = pat_pat; + self.visit_pat(pat); + }, + PatKind::Lit(ref lit_expr) => { + let lit_expr_pat = self.next("lit_expr"); + println!("Lit(ref {}) = {}", lit_expr_pat, current); + self.current = lit_expr_pat; + self.visit_expr(lit_expr); + }, + PatKind::Range(ref start, ref end, end_kind) => { + let start_pat = self.next("start"); + let end_pat = self.next("end"); + println!( + "Range(ref {}, ref {}, RangeEnd::{:?}) = {};", + start_pat, end_pat, end_kind, current + ); + self.current = start_pat; + walk_list!(self, visit_expr, start); + self.current = end_pat; + walk_list!(self, visit_expr, end); + }, + PatKind::Slice(ref start, ref middle, ref end) => { + let start_pat = self.next("start"); + let end_pat = self.next("end"); + if let Some(ref middle) = middle { + let middle_pat = self.next("middle"); + println!( + "Slice(ref {}, Some(ref {}), ref {}) = {};", + start_pat, middle_pat, end_pat, current + ); + self.current = middle_pat; + self.visit_pat(middle); + } else { + println!("Slice(ref {}, None, ref {}) = {};", start_pat, end_pat, current); + } + println!(" if {}.len() == {};", start_pat, start.len()); + for (i, pat) in start.iter().enumerate() { + self.current = format!("{}[{}]", start_pat, i); + self.visit_pat(pat); + } + println!(" if {}.len() == {};", end_pat, end.len()); + for (i, pat) in end.iter().enumerate() { + self.current = format!("{}[{}]", end_pat, i); + self.visit_pat(pat); + } + }, + } + } + + fn visit_stmt(&mut self, s: &Stmt<'_>) { + print!(" if let StmtKind::"); + let current = format!("{}.kind", self.current); + match s.kind { + // A local (let) binding: + StmtKind::Local(ref local) => { + let local_pat = self.next("local"); + println!("Local(ref {}) = {};", local_pat, current); + if let Some(ref init) = local.init { + let init_pat = self.next("init"); + println!(" if let Some(ref {}) = {}.init;", init_pat, local_pat); + self.current = init_pat; + self.visit_expr(init); + } + self.current = format!("{}.pat", local_pat); + self.visit_pat(&local.pat); + }, + // An item binding: + StmtKind::Item(_) => { + println!("Item(item_id) = {};", current); + }, + + // Expr without trailing semi-colon (must have unit type): + StmtKind::Expr(ref e) => { + let e_pat = self.next("e"); + println!("Expr(ref {}, _) = {}", e_pat, current); + self.current = e_pat; + self.visit_expr(e); + }, + + // Expr with trailing semi-colon (may have any type): + StmtKind::Semi(ref e) => { + let e_pat = self.next("e"); + println!("Semi(ref {}, _) = {}", e_pat, current); + self.current = e_pat; + self.visit_expr(e); + }, + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool { + let attrs = cx.tcx.hir().attrs(hir_id); + get_attr(cx.sess(), attrs, "author").count() > 0 +} + +#[must_use] +fn desugaring_name(des: hir::MatchSource) -> String { + match des { + hir::MatchSource::ForLoopDesugar => "MatchSource::ForLoopDesugar".to_string(), + hir::MatchSource::TryDesugar => "MatchSource::TryDesugar".to_string(), + hir::MatchSource::WhileDesugar => "MatchSource::WhileDesugar".to_string(), + hir::MatchSource::WhileLetDesugar => "MatchSource::WhileLetDesugar".to_string(), + hir::MatchSource::Normal => "MatchSource::Normal".to_string(), + hir::MatchSource::IfLetDesugar { contains_else_clause } => format!( + "MatchSource::IfLetDesugar {{ contains_else_clause: {} }}", + contains_else_clause + ), + hir::MatchSource::IfLetGuardDesugar => "MatchSource::IfLetGuardDesugar".to_string(), + hir::MatchSource::AwaitDesugar => "MatchSource::AwaitDesugar".to_string(), + } +} + +#[must_use] +fn loop_desugaring_name(des: hir::LoopSource) -> &'static str { + match des { + hir::LoopSource::ForLoop => "LoopSource::ForLoop", + hir::LoopSource::Loop => "LoopSource::Loop", + hir::LoopSource::While => "LoopSource::While", + hir::LoopSource::WhileLet => "LoopSource::WhileLet", + } +} + +fn print_path(path: &QPath<'_>, first: &mut bool) { + match *path { + QPath::Resolved(_, ref path) => { + for segment in path.segments { + if *first { + *first = false; + } else { + print!(", "); + } + print!("{:?}", segment.ident.as_str()); + } + }, + QPath::TypeRelative(ref ty, ref segment) => match ty.kind { + hir::TyKind::Path(ref inner_path) => { + print_path(inner_path, first); + if *first { + *first = false; + } else { + print!(", "); + } + print!("{:?}", segment.ident.as_str()); + }, + ref other => print!("/* unimplemented: {:?}*/", other), + }, + QPath::LangItem(..) => panic!("print_path: called for lang item qpath"), + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/conf.rs b/src/tools/clippy/clippy_lints/src/utils/conf.rs new file mode 100644 index 0000000000..9139a0966c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/conf.rs @@ -0,0 +1,259 @@ +//! Read configurations files. + +#![deny(clippy::missing_docs_in_private_items)] + +use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem}; +use rustc_span::source_map; +use source_map::Span; +use std::lazy::SyncLazy; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; +use std::{env, fmt, fs, io}; + +/// Gets the configuration file from arguments. +pub fn file_from_args(args: &[NestedMetaItem]) -> Result, (&'static str, Span)> { + for arg in args.iter().filter_map(NestedMetaItem::meta_item) { + if arg.has_name(sym!(conf_file)) { + return match arg.kind { + MetaItemKind::Word | MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)), + MetaItemKind::NameValue(ref value) => { + if let LitKind::Str(ref file, _) = value.kind { + Ok(Some(file.to_string().into())) + } else { + Err(("`conf_file` value must be a string", value.span)) + } + }, + }; + } + } + + Ok(None) +} + +/// Error from reading a configuration file. +#[derive(Debug)] +pub enum Error { + /// An I/O error. + Io(io::Error), + /// Not valid toml or doesn't fit the expected config format + Toml(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Io(err) => err.fmt(f), + Self::Toml(err) => err.fmt(f), + } + } +} + +impl From for Error { + fn from(e: io::Error) -> Self { + Self::Io(e) + } +} + +/// Vec of errors that might be collected during config toml parsing +static ERRORS: SyncLazy>> = SyncLazy::new(|| Mutex::new(Vec::new())); + +macro_rules! define_Conf { + ($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => { + mod helpers { + use serde::Deserialize; + /// Type used to store lint configuration. + #[derive(Deserialize)] + #[serde(rename_all = "kebab-case", deny_unknown_fields)] + pub struct Conf { + $( + #[$doc] + #[serde(default = $config_str)] + #[serde(with = $config_str)] + pub $config: $Ty, + )+ + #[allow(dead_code)] + #[serde(default)] + third_party: Option<::toml::Value>, + } + + $( + mod $config { + use serde::Deserialize; + pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> { + use super::super::{ERRORS, Error}; + + Ok( + <$Ty>::deserialize(deserializer).unwrap_or_else(|e| { + ERRORS + .lock() + .expect("no threading here") + .push(Error::Toml(e.to_string())); + super::$config() + }) + ) + } + } + + #[must_use] + fn $config() -> $Ty { + let x = $default; + x + } + )+ + } + }; +} + +pub use self::helpers::Conf; +define_Conf! { + /// Lint: REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN. The minimum rust version that the project supports + (msrv, "msrv": Option, None), + /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses + (blacklisted_names, "blacklisted_names": Vec, ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), + /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have + (cognitive_complexity_threshold, "cognitive_complexity_threshold": u64, 25), + /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. + (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold": Option, None), + /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks + (doc_valid_idents, "doc_valid_idents": Vec, [ + "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", + "DirectX", + "ECMAScript", + "GPLv2", "GPLv3", + "GitHub", "GitLab", + "IPv4", "IPv6", + "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", + "NaN", "NaNs", + "OAuth", "GraphQL", + "OCaml", + "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", + "WebGL", + "TensorFlow", + "TrueType", + "iOS", "macOS", + "TeX", "LaTeX", "BibTeX", "BibLaTeX", + "MinGW", + "CamelCase", + ].iter().map(ToString::to_string).collect()), + /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have + (too_many_arguments_threshold, "too_many_arguments_threshold": u64, 7), + /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have + (type_complexity_threshold, "type_complexity_threshold": u64, 250), + /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have + (single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4), + /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap + (too_large_for_stack, "too_large_for_stack": u64, 200), + /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger + (enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3), + /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion + (enum_variant_size_threshold, "enum_variant_size_threshold": u64, 200), + /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' + (verbose_bit_mask_threshold, "verbose_bit_mask_threshold": u64, 1), + /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals + (literal_representation_threshold, "literal_representation_threshold": u64, 16384), + /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. + (trivial_copy_size_limit, "trivial_copy_size_limit": Option, None), + /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value. + (pass_by_value_size_limit, "pass_by_value_size_limit": u64, 256), + /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have + (too_many_lines_threshold, "too_many_lines_threshold": u64, 100), + /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack + (array_size_threshold, "array_size_threshold": u64, 512_000), + /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed + (vec_box_size_threshold, "vec_box_size_threshold": u64, 4096), + /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted + (max_trait_bounds, "max_trait_bounds": u64, 3), + /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have + (max_struct_bools, "max_struct_bools": u64, 3), + /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have + (max_fn_params_bools, "max_fn_params_bools": u64, 3), + /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests). + (warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports": bool, false), + /// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths. + (disallowed_methods, "disallowed_methods": Vec, Vec::::new()), + /// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators. + (unreadable_literal_lint_fractions, "unreadable_literal_lint_fractions": bool, true), + /// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other + (upper_case_acronyms_aggressive, "upper_case_acronyms_aggressive": bool, false), + /// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest. + (cargo_ignore_publish, "cargo_ignore_publish": bool, false), +} + +impl Default for Conf { + #[must_use] + fn default() -> Self { + toml::from_str("").expect("we never error on empty config files") + } +} + +/// Search for the configuration file. +pub fn lookup_conf_file() -> io::Result> { + /// Possible filename to search for. + const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"]; + + // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR. + // If neither of those exist, use ".". + let mut current = env::var_os("CLIPPY_CONF_DIR") + .or_else(|| env::var_os("CARGO_MANIFEST_DIR")) + .map_or_else(|| PathBuf::from("."), PathBuf::from); + loop { + for config_file_name in &CONFIG_FILE_NAMES { + let config_file = current.join(config_file_name); + match fs::metadata(&config_file) { + // Only return if it's a file to handle the unlikely situation of a directory named + // `clippy.toml`. + Ok(ref md) if !md.is_dir() => return Ok(Some(config_file)), + // Return the error if it's something other than `NotFound`; otherwise we didn't + // find the project file yet, and continue searching. + Err(e) if e.kind() != io::ErrorKind::NotFound => return Err(e), + _ => {}, + } + } + + // If the current directory has no parent, we're done searching. + if !current.pop() { + return Ok(None); + } + } +} + +/// Produces a `Conf` filled with the default values and forwards the errors +/// +/// Used internally for convenience +fn default(errors: Vec) -> (Conf, Vec) { + (Conf::default(), errors) +} + +/// Read the `toml` configuration file. +/// +/// In case of error, the function tries to continue as much as possible. +pub fn read(path: &Path) -> (Conf, Vec) { + let content = match fs::read_to_string(path) { + Ok(content) => content, + Err(err) => return default(vec![err.into()]), + }; + + assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty()); + match toml::from_str(&content) { + Ok(toml) => { + let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0); + + let toml_ref: &Conf = &toml; + + let cyc_field: Option = toml_ref.cyclomatic_complexity_threshold; + + if cyc_field.is_some() { + let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string(); + errors.push(Error::Toml(cyc_err)); + } + + (toml, errors) + }, + Err(e) => { + let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0); + errors.push(Error::Toml(e.to_string())); + + default(errors) + }, + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/inspector.rs b/src/tools/clippy/clippy_lints/src/utils/inspector.rs new file mode 100644 index 0000000000..64ee9e65bb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/inspector.rs @@ -0,0 +1,577 @@ +//! checks for attributes + +use crate::utils::get_attr; +use rustc_ast::ast::{Attribute, InlineAsmTemplatePiece}; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::Session; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Dumps every ast/hir node which has the `#[clippy::dump]` + /// attribute + /// + /// **Example:** + /// ```rust,ignore + /// #[clippy::dump] + /// extern crate foo; + /// ``` + /// + /// prints + /// + /// ```text + /// item `foo` + /// visibility inherited from outer item + /// extern crate dylib source: "/path/to/foo.so" + /// ``` + pub DEEP_CODE_INSPECTION, + internal_warn, + "helper to dump info about code" +} + +declare_lint_pass!(DeepCodeInspector => [DEEP_CODE_INSPECTION]); + +impl<'tcx> LateLintPass<'tcx> for DeepCodeInspector { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { + if !has_attr(cx.sess(), cx.tcx.hir().attrs(item.hir_id())) { + return; + } + print_item(cx, item); + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { + if !has_attr(cx.sess(), cx.tcx.hir().attrs(item.hir_id())) { + return; + } + println!("impl item `{}`", item.ident.name); + match item.vis.node { + hir::VisibilityKind::Public => println!("public"), + hir::VisibilityKind::Crate(_) => println!("visible crate wide"), + hir::VisibilityKind::Restricted { ref path, .. } => println!( + "visible in module `{}`", + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(path, false)) + ), + hir::VisibilityKind::Inherited => println!("visibility inherited from outer item"), + } + if item.defaultness.is_default() { + println!("default"); + } + match item.kind { + hir::ImplItemKind::Const(_, body_id) => { + println!("associated constant"); + print_expr(cx, &cx.tcx.hir().body(body_id).value, 1); + }, + hir::ImplItemKind::Fn(..) => println!("method"), + hir::ImplItemKind::TyAlias(_) => println!("associated type"), + } + } + // fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx + // hir::TraitItem) { + // if !has_attr(&item.attrs) { + // return; + // } + // } + // + // fn check_variant(&mut self, cx: &LateContext<'tcx>, var: &'tcx + // hir::Variant, _: + // &hir::Generics) { + // if !has_attr(&var.node.attrs) { + // return; + // } + // } + // + // fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx + // hir::FieldDef) { + // if !has_attr(&field.attrs) { + // return; + // } + // } + // + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if !has_attr(cx.sess(), cx.tcx.hir().attrs(expr.hir_id)) { + return; + } + print_expr(cx, expr, 0); + } + + fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) { + if !has_attr(cx.sess(), cx.tcx.hir().attrs(arm.hir_id)) { + return; + } + print_pat(cx, &arm.pat, 1); + if let Some(ref guard) = arm.guard { + println!("guard:"); + print_guard(cx, guard, 1); + } + println!("body:"); + print_expr(cx, &arm.body, 1); + } + + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) { + if !has_attr(cx.sess(), cx.tcx.hir().attrs(stmt.hir_id)) { + return; + } + match stmt.kind { + hir::StmtKind::Local(ref local) => { + println!("local variable of type {}", cx.typeck_results().node_type(local.hir_id)); + println!("pattern:"); + print_pat(cx, &local.pat, 0); + if let Some(ref e) = local.init { + println!("init expression:"); + print_expr(cx, e, 0); + } + }, + hir::StmtKind::Item(_) => println!("item decl"), + hir::StmtKind::Expr(ref e) | hir::StmtKind::Semi(ref e) => print_expr(cx, e, 0), + } + } + // fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx + // hir::ForeignItem) { + // if !has_attr(&item.attrs) { + // return; + // } + // } + // +} + +fn has_attr(sess: &Session, attrs: &[Attribute]) -> bool { + get_attr(sess, attrs, "dump").count() > 0 +} + +#[allow(clippy::similar_names)] +#[allow(clippy::too_many_lines)] +fn print_expr(cx: &LateContext<'_>, expr: &hir::Expr<'_>, indent: usize) { + let ind = " ".repeat(indent); + println!("{}+", ind); + println!("{}ty: {}", ind, cx.typeck_results().expr_ty(expr)); + println!( + "{}adjustments: {:?}", + ind, + cx.typeck_results().adjustments().get(expr.hir_id) + ); + match expr.kind { + hir::ExprKind::Box(ref e) => { + println!("{}Box", ind); + print_expr(cx, e, indent + 1); + }, + hir::ExprKind::Array(v) => { + println!("{}Array", ind); + for e in v { + print_expr(cx, e, indent + 1); + } + }, + hir::ExprKind::Call(ref func, args) => { + println!("{}Call", ind); + println!("{}function:", ind); + print_expr(cx, func, indent + 1); + println!("{}arguments:", ind); + for arg in args { + print_expr(cx, arg, indent + 1); + } + }, + hir::ExprKind::MethodCall(ref path, _, args, _) => { + println!("{}MethodCall", ind); + println!("{}method name: {}", ind, path.ident.name); + for arg in args { + print_expr(cx, arg, indent + 1); + } + }, + hir::ExprKind::Tup(v) => { + println!("{}Tup", ind); + for e in v { + print_expr(cx, e, indent + 1); + } + }, + hir::ExprKind::Binary(op, ref lhs, ref rhs) => { + println!("{}Binary", ind); + println!("{}op: {:?}", ind, op.node); + println!("{}lhs:", ind); + print_expr(cx, lhs, indent + 1); + println!("{}rhs:", ind); + print_expr(cx, rhs, indent + 1); + }, + hir::ExprKind::Unary(op, ref inner) => { + println!("{}Unary", ind); + println!("{}op: {:?}", ind, op); + print_expr(cx, inner, indent + 1); + }, + hir::ExprKind::Lit(ref lit) => { + println!("{}Lit", ind); + println!("{}{:?}", ind, lit); + }, + hir::ExprKind::Cast(ref e, ref target) => { + println!("{}Cast", ind); + print_expr(cx, e, indent + 1); + println!("{}target type: {:?}", ind, target); + }, + hir::ExprKind::Type(ref e, ref target) => { + println!("{}Type", ind); + print_expr(cx, e, indent + 1); + println!("{}target type: {:?}", ind, target); + }, + hir::ExprKind::Loop(..) => { + println!("{}Loop", ind); + }, + hir::ExprKind::If(ref cond, _, ref else_opt) => { + println!("{}If", ind); + println!("{}condition:", ind); + print_expr(cx, cond, indent + 1); + if let Some(ref els) = *else_opt { + println!("{}else:", ind); + print_expr(cx, els, indent + 1); + } + }, + hir::ExprKind::Match(ref cond, _, ref source) => { + println!("{}Match", ind); + println!("{}condition:", ind); + print_expr(cx, cond, indent + 1); + println!("{}source: {:?}", ind, source); + }, + hir::ExprKind::Closure(ref clause, _, _, _, _) => { + println!("{}Closure", ind); + println!("{}clause: {:?}", ind, clause); + }, + hir::ExprKind::Yield(ref sub, _) => { + println!("{}Yield", ind); + print_expr(cx, sub, indent + 1); + }, + hir::ExprKind::Block(_, _) => { + println!("{}Block", ind); + }, + hir::ExprKind::Assign(ref lhs, ref rhs, _) => { + println!("{}Assign", ind); + println!("{}lhs:", ind); + print_expr(cx, lhs, indent + 1); + println!("{}rhs:", ind); + print_expr(cx, rhs, indent + 1); + }, + hir::ExprKind::AssignOp(ref binop, ref lhs, ref rhs) => { + println!("{}AssignOp", ind); + println!("{}op: {:?}", ind, binop.node); + println!("{}lhs:", ind); + print_expr(cx, lhs, indent + 1); + println!("{}rhs:", ind); + print_expr(cx, rhs, indent + 1); + }, + hir::ExprKind::Field(ref e, ident) => { + println!("{}Field", ind); + println!("{}field name: {}", ind, ident.name); + println!("{}struct expr:", ind); + print_expr(cx, e, indent + 1); + }, + hir::ExprKind::Index(ref arr, ref idx) => { + println!("{}Index", ind); + println!("{}array expr:", ind); + print_expr(cx, arr, indent + 1); + println!("{}index expr:", ind); + print_expr(cx, idx, indent + 1); + }, + hir::ExprKind::Path(hir::QPath::Resolved(ref ty, ref path)) => { + println!("{}Resolved Path, {:?}", ind, ty); + println!("{}path: {:?}", ind, path); + }, + hir::ExprKind::Path(hir::QPath::TypeRelative(ref ty, ref seg)) => { + println!("{}Relative Path, {:?}", ind, ty); + println!("{}seg: {:?}", ind, seg); + }, + hir::ExprKind::Path(hir::QPath::LangItem(lang_item, ..)) => { + println!("{}Lang Item Path, {:?}", ind, lang_item.name()); + }, + hir::ExprKind::AddrOf(kind, ref muta, ref e) => { + println!("{}AddrOf", ind); + println!("kind: {:?}", kind); + println!("mutability: {:?}", muta); + print_expr(cx, e, indent + 1); + }, + hir::ExprKind::Break(_, ref e) => { + println!("{}Break", ind); + if let Some(ref e) = *e { + print_expr(cx, e, indent + 1); + } + }, + hir::ExprKind::Continue(_) => println!("{}Again", ind), + hir::ExprKind::Ret(ref e) => { + println!("{}Ret", ind); + if let Some(ref e) = *e { + print_expr(cx, e, indent + 1); + } + }, + hir::ExprKind::InlineAsm(ref asm) => { + println!("{}InlineAsm", ind); + println!("{}template: {}", ind, InlineAsmTemplatePiece::to_string(asm.template)); + println!("{}options: {:?}", ind, asm.options); + println!("{}operands:", ind); + for (op, _op_sp) in asm.operands { + match op { + hir::InlineAsmOperand::In { expr, .. } + | hir::InlineAsmOperand::InOut { expr, .. } + | hir::InlineAsmOperand::Const { expr } + | hir::InlineAsmOperand::Sym { expr } => print_expr(cx, expr, indent + 1), + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + print_expr(cx, expr, indent + 1); + } + }, + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + print_expr(cx, in_expr, indent + 1); + if let Some(out_expr) = out_expr { + print_expr(cx, out_expr, indent + 1); + } + }, + } + } + }, + hir::ExprKind::LlvmInlineAsm(ref asm) => { + let inputs = &asm.inputs_exprs; + let outputs = &asm.outputs_exprs; + println!("{}LlvmInlineAsm", ind); + println!("{}inputs:", ind); + for e in inputs.iter() { + print_expr(cx, e, indent + 1); + } + println!("{}outputs:", ind); + for e in outputs.iter() { + print_expr(cx, e, indent + 1); + } + }, + hir::ExprKind::Struct(ref path, fields, ref base) => { + println!("{}Struct", ind); + println!("{}path: {:?}", ind, path); + for field in fields { + println!("{}field \"{}\":", ind, field.ident.name); + print_expr(cx, &field.expr, indent + 1); + } + if let Some(ref base) = *base { + println!("{}base:", ind); + print_expr(cx, base, indent + 1); + } + }, + hir::ExprKind::ConstBlock(ref anon_const) => { + println!("{}ConstBlock", ind); + println!("{}anon_const:", ind); + print_expr(cx, &cx.tcx.hir().body(anon_const.body).value, indent + 1); + }, + hir::ExprKind::Repeat(ref val, ref anon_const) => { + println!("{}Repeat", ind); + println!("{}value:", ind); + print_expr(cx, val, indent + 1); + println!("{}repeat count:", ind); + print_expr(cx, &cx.tcx.hir().body(anon_const.body).value, indent + 1); + }, + hir::ExprKind::Err => { + println!("{}Err", ind); + }, + hir::ExprKind::DropTemps(ref e) => { + println!("{}DropTemps", ind); + print_expr(cx, e, indent + 1); + }, + } +} + +fn print_item(cx: &LateContext<'_>, item: &hir::Item<'_>) { + let did = item.def_id; + println!("item `{}`", item.ident.name); + match item.vis.node { + hir::VisibilityKind::Public => println!("public"), + hir::VisibilityKind::Crate(_) => println!("visible crate wide"), + hir::VisibilityKind::Restricted { ref path, .. } => println!( + "visible in module `{}`", + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(path, false)) + ), + hir::VisibilityKind::Inherited => println!("visibility inherited from outer item"), + } + match item.kind { + hir::ItemKind::ExternCrate(ref _renamed_from) => { + if let Some(crate_id) = cx.tcx.extern_mod_stmt_cnum(did) { + let source = cx.tcx.used_crate_source(crate_id); + if let Some(ref src) = source.dylib { + println!("extern crate dylib source: {:?}", src.0); + } + if let Some(ref src) = source.rlib { + println!("extern crate rlib source: {:?}", src.0); + } + } else { + println!("weird extern crate without a crate id"); + } + }, + hir::ItemKind::Use(ref path, ref kind) => println!("{:?}, {:?}", path, kind), + hir::ItemKind::Static(..) => println!("static item of type {:#?}", cx.tcx.type_of(did)), + hir::ItemKind::Const(..) => println!("const item of type {:#?}", cx.tcx.type_of(did)), + hir::ItemKind::Fn(..) => { + let item_ty = cx.tcx.type_of(did); + println!("function of type {:#?}", item_ty); + }, + hir::ItemKind::Mod(..) => println!("module"), + hir::ItemKind::ForeignMod { abi, .. } => println!("foreign module with abi: {}", abi), + hir::ItemKind::GlobalAsm(ref asm) => println!("global asm: {:?}", asm), + hir::ItemKind::TyAlias(..) => { + println!("type alias for {:?}", cx.tcx.type_of(did)); + }, + hir::ItemKind::OpaqueTy(..) => { + println!("existential type with real type {:?}", cx.tcx.type_of(did)); + }, + hir::ItemKind::Enum(..) => { + println!("enum definition of type {:?}", cx.tcx.type_of(did)); + }, + hir::ItemKind::Struct(..) => { + println!("struct definition of type {:?}", cx.tcx.type_of(did)); + }, + hir::ItemKind::Union(..) => { + println!("union definition of type {:?}", cx.tcx.type_of(did)); + }, + hir::ItemKind::Trait(..) => { + println!("trait decl"); + if cx.tcx.trait_is_auto(did.to_def_id()) { + println!("trait is auto"); + } else { + println!("trait is not auto"); + } + }, + hir::ItemKind::TraitAlias(..) => { + println!("trait alias"); + }, + hir::ItemKind::Impl(hir::Impl { + of_trait: Some(ref _trait_ref), + .. + }) => { + println!("trait impl"); + }, + hir::ItemKind::Impl(hir::Impl { of_trait: None, .. }) => { + println!("impl"); + }, + } +} + +#[allow(clippy::similar_names)] +#[allow(clippy::too_many_lines)] +fn print_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, indent: usize) { + let ind = " ".repeat(indent); + println!("{}+", ind); + match pat.kind { + hir::PatKind::Wild => println!("{}Wild", ind), + hir::PatKind::Binding(ref mode, .., ident, ref inner) => { + println!("{}Binding", ind); + println!("{}mode: {:?}", ind, mode); + println!("{}name: {}", ind, ident.name); + if let Some(ref inner) = *inner { + println!("{}inner:", ind); + print_pat(cx, inner, indent + 1); + } + }, + hir::PatKind::Or(fields) => { + println!("{}Or", ind); + for field in fields { + print_pat(cx, field, indent + 1); + } + }, + hir::PatKind::Struct(ref path, fields, ignore) => { + println!("{}Struct", ind); + println!( + "{}name: {}", + ind, + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) + ); + println!("{}ignore leftover fields: {}", ind, ignore); + println!("{}fields:", ind); + for field in fields { + println!("{} field name: {}", ind, field.ident.name); + if field.is_shorthand { + println!("{} in shorthand notation", ind); + } + print_pat(cx, &field.pat, indent + 1); + } + }, + hir::PatKind::TupleStruct(ref path, fields, opt_dots_position) => { + println!("{}TupleStruct", ind); + println!( + "{}path: {}", + ind, + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) + ); + if let Some(dot_position) = opt_dots_position { + println!("{}dot position: {}", ind, dot_position); + } + for field in fields { + print_pat(cx, field, indent + 1); + } + }, + hir::PatKind::Path(hir::QPath::Resolved(ref ty, ref path)) => { + println!("{}Resolved Path, {:?}", ind, ty); + println!("{}path: {:?}", ind, path); + }, + hir::PatKind::Path(hir::QPath::TypeRelative(ref ty, ref seg)) => { + println!("{}Relative Path, {:?}", ind, ty); + println!("{}seg: {:?}", ind, seg); + }, + hir::PatKind::Path(hir::QPath::LangItem(lang_item, ..)) => { + println!("{}Lang Item Path, {:?}", ind, lang_item.name()); + }, + hir::PatKind::Tuple(pats, opt_dots_position) => { + println!("{}Tuple", ind); + if let Some(dot_position) = opt_dots_position { + println!("{}dot position: {}", ind, dot_position); + } + for field in pats { + print_pat(cx, field, indent + 1); + } + }, + hir::PatKind::Box(ref inner) => { + println!("{}Box", ind); + print_pat(cx, inner, indent + 1); + }, + hir::PatKind::Ref(ref inner, ref muta) => { + println!("{}Ref", ind); + println!("{}mutability: {:?}", ind, muta); + print_pat(cx, inner, indent + 1); + }, + hir::PatKind::Lit(ref e) => { + println!("{}Lit", ind); + print_expr(cx, e, indent + 1); + }, + hir::PatKind::Range(ref l, ref r, ref range_end) => { + println!("{}Range", ind); + if let Some(expr) = l { + print_expr(cx, expr, indent + 1); + } + if let Some(expr) = r { + print_expr(cx, expr, indent + 1); + } + match *range_end { + hir::RangeEnd::Included => println!("{} end included", ind), + hir::RangeEnd::Excluded => println!("{} end excluded", ind), + } + }, + hir::PatKind::Slice(first_pats, ref range, last_pats) => { + println!("{}Slice [a, b, ..i, y, z]", ind); + println!("[a, b]:"); + for pat in first_pats { + print_pat(cx, pat, indent + 1); + } + println!("i:"); + if let Some(ref pat) = *range { + print_pat(cx, pat, indent + 1); + } + println!("[y, z]:"); + for pat in last_pats { + print_pat(cx, pat, indent + 1); + } + }, + } +} + +fn print_guard(cx: &LateContext<'_>, guard: &hir::Guard<'_>, indent: usize) { + let ind = " ".repeat(indent); + println!("{}+", ind); + match guard { + hir::Guard::If(expr) => { + println!("{}If", ind); + print_expr(cx, expr, indent + 1); + }, + hir::Guard::IfLet(pat, expr) => { + println!("{}IfLet", ind); + print_pat(cx, pat, indent + 1); + print_expr(cx, expr, indent + 1); + }, + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs new file mode 100644 index 0000000000..0a347516c3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs @@ -0,0 +1,1065 @@ +use crate::consts::{constant_simple, Constant}; +use crate::utils::{ + is_expn_of, match_def_path, match_qpath, match_type, method_calls, path_to_res, paths, run_lints, snippet, + span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq, +}; +use if_chain::if_chain; +use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, ModKind, NodeId}; +use rustc_ast::visit::FnKind; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::hir_id::CRATE_HIR_ID; +use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; +use rustc_hir::{ + BinOpKind, Crate, Expr, ExprKind, HirId, Item, MutTy, Mutability, Node, Path, StmtKind, Ty, TyKind, UnOp, +}; +use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::mir::interpret::ConstValue; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::{Span, Spanned}; +use rustc_span::symbol::{Symbol, SymbolStr}; +use rustc_typeck::hir_ty_to_ty; + +use std::borrow::{Borrow, Cow}; + +declare_clippy_lint! { + /// **What it does:** Checks for various things we like to keep tidy in clippy. + /// + /// **Why is this bad?** We like to pretend we're an example of tidy code. + /// + /// **Known problems:** None. + /// + /// **Example:** Wrong ordering of the util::paths constants. + pub CLIPPY_LINTS_INTERNAL, + internal, + "various things that will negatively affect your clippy experience" +} + +declare_clippy_lint! { + /// **What it does:** Ensures every lint is associated to a `LintPass`. + /// + /// **Why is this bad?** The compiler only knows lints via a `LintPass`. Without + /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not + /// know the name of the lint. + /// + /// **Known problems:** Only checks for lints associated using the + /// `declare_lint_pass!`, `impl_lint_pass!`, and `lint_array!` macros. + /// + /// **Example:** + /// ```rust,ignore + /// declare_lint! { pub LINT_1, ... } + /// declare_lint! { pub LINT_2, ... } + /// declare_lint! { pub FORGOTTEN_LINT, ... } + /// // ... + /// declare_lint_pass!(Pass => [LINT_1, LINT_2]); + /// // missing FORGOTTEN_LINT + /// ``` + pub LINT_WITHOUT_LINT_PASS, + internal, + "declaring a lint without associating it in a LintPass" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `cx.span_lint*` and suggests to use the `utils::*` + /// variant of the function. + /// + /// **Why is this bad?** The `utils::*` variants also add a link to the Clippy documentation to the + /// warning/error messages. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// cx.span_lint(LINT_NAME, "message"); + /// ``` + /// + /// Good: + /// ```rust,ignore + /// utils::span_lint(cx, LINT_NAME, "message"); + /// ``` + pub COMPILER_LINT_FUNCTIONS, + internal, + "usage of the lint functions of the compiler instead of the utils::* variant" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `cx.outer().expn_data()` and suggests to use + /// the `cx.outer_expn_data()` + /// + /// **Why is this bad?** `cx.outer_expn_data()` is faster and more concise. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// expr.span.ctxt().outer().expn_data() + /// ``` + /// + /// Good: + /// ```rust,ignore + /// expr.span.ctxt().outer_expn_data() + /// ``` + pub OUTER_EXPN_EXPN_DATA, + internal, + "using `cx.outer_expn().expn_data()` instead of `cx.outer_expn_data()`" +} + +declare_clippy_lint! { + /// **What it does:** Not an actual lint. This lint is only meant for testing our customized internal compiler + /// error message by calling `panic`. + /// + /// **Why is this bad?** ICE in large quantities can damage your teeth + /// + /// **Known problems:** None + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// 🍦🍦🍦🍦🍦 + /// ``` + pub PRODUCE_ICE, + internal, + "this message should not appear anywhere as we ICE before and don't emit the lint" +} + +declare_clippy_lint! { + /// **What it does:** Checks for cases of an auto-generated lint without an updated description, + /// i.e. `default lint description`. + /// + /// **Why is this bad?** Indicates that the lint is not finished. + /// + /// **Known problems:** None + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// declare_lint! { pub COOL_LINT, nursery, "default lint description" } + /// ``` + /// + /// Good: + /// ```rust,ignore + /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" } + /// ``` + pub DEFAULT_LINT, + internal, + "found 'default lint description' in a lint declaration" +} + +declare_clippy_lint! { + /// **What it does:** Lints `span_lint_and_then` function calls, where the + /// closure argument has only one statement and that statement is a method + /// call to `span_suggestion`, `span_help`, `span_note` (using the same + /// span), `help` or `note`. + /// + /// These usages of `span_lint_and_then` should be replaced with one of the + /// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or + /// `span_lint_and_note`. + /// + /// **Why is this bad?** Using the wrapper `span_lint_and_*` functions, is more + /// convenient, readable and less error prone. + /// + /// **Known problems:** None + /// + /// *Example:** + /// Bad: + /// ```rust,ignore + /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { + /// diag.span_suggestion( + /// expr.span, + /// help_msg, + /// sugg.to_string(), + /// Applicability::MachineApplicable, + /// ); + /// }); + /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { + /// diag.span_help(expr.span, help_msg); + /// }); + /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { + /// diag.help(help_msg); + /// }); + /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { + /// diag.span_note(expr.span, note_msg); + /// }); + /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { + /// diag.note(note_msg); + /// }); + /// ``` + /// + /// Good: + /// ```rust,ignore + /// span_lint_and_sugg( + /// cx, + /// TEST_LINT, + /// expr.span, + /// lint_msg, + /// help_msg, + /// sugg.to_string(), + /// Applicability::MachineApplicable, + /// ); + /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg); + /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg); + /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg); + /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg); + /// ``` + pub COLLAPSIBLE_SPAN_LINT_CALLS, + internal, + "found collapsible `span_lint_and_then` calls" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `utils::match_type()` on a type diagnostic item + /// and suggests to use `utils::is_type_diagnostic_item()` instead. + /// + /// **Why is this bad?** `utils::is_type_diagnostic_item()` does not require hardcoded paths. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// utils::match_type(cx, ty, &paths::VEC) + /// ``` + /// + /// Good: + /// ```rust,ignore + /// utils::is_type_diagnostic_item(cx, ty, sym::vec_type) + /// ``` + pub MATCH_TYPE_ON_DIAGNOSTIC_ITEM, + internal, + "using `utils::match_type()` instead of `utils::is_type_diagnostic_item()`" +} + +declare_clippy_lint! { + /// **What it does:** + /// Checks the paths module for invalid paths. + /// + /// **Why is this bad?** + /// It indicates a bug in the code. + /// + /// **Known problems:** None. + /// + /// **Example:** None. + pub INVALID_PATHS, + internal, + "invalid path" +} + +declare_clippy_lint! { + /// **What it does:** + /// Checks for interning symbols that have already been pre-interned and defined as constants. + /// + /// **Why is this bad?** + /// It's faster and easier to use the symbol constant. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// let _ = sym!(f32); + /// ``` + /// + /// Good: + /// ```rust,ignore + /// let _ = sym::f32; + /// ``` + pub INTERNING_DEFINED_SYMBOL, + internal, + "interning a symbol that is pre-interned and defined as a constant" +} + +declare_clippy_lint! { + /// **What it does:** Checks for unnecessary conversion from Symbol to a string. + /// + /// **Why is this bad?** It's faster use symbols directly intead of strings. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// symbol.as_str() == "clippy"; + /// ``` + /// + /// Good: + /// ```rust,ignore + /// symbol == sym::clippy; + /// ``` + pub UNNECESSARY_SYMBOL_STR, + internal, + "unnecessary conversion between Symbol and string" +} + +declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]); + +impl EarlyLintPass for ClippyLintsInternal { + fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &AstCrate) { + if let Some(utils) = krate.items.iter().find(|item| item.ident.name.as_str() == "utils") { + if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = utils.kind { + if let Some(paths) = items.iter().find(|item| item.ident.name.as_str() == "paths") { + if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = paths.kind { + let mut last_name: Option = None; + for item in items { + let name = item.ident.as_str(); + if let Some(ref last_name) = last_name { + if **last_name > *name { + span_lint( + cx, + CLIPPY_LINTS_INTERNAL, + item.span, + "this constant should be before the previous constant due to lexical \ + ordering", + ); + } + } + last_name = Some(name); + } + } + } + } + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct LintWithoutLintPass { + declared_lints: FxHashMap, + registered_lints: FxHashSet, +} + +impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS]); + +impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if !run_lints(cx, &[DEFAULT_LINT], item.hir_id()) { + return; + } + + if let hir::ItemKind::Static(ref ty, Mutability::Not, body_id) = item.kind { + if is_lint_ref_type(cx, ty) { + let expr = &cx.tcx.hir().body(body_id).value; + if_chain! { + if let ExprKind::AddrOf(_, _, ref inner_exp) = expr.kind; + if let ExprKind::Struct(_, ref fields, _) = inner_exp.kind; + let field = fields + .iter() + .find(|f| f.ident.as_str() == "desc") + .expect("lints must have a description field"); + if let ExprKind::Lit(Spanned { + node: LitKind::Str(ref sym, _), + .. + }) = field.expr.kind; + if sym.as_str() == "default lint description"; + + then { + span_lint( + cx, + DEFAULT_LINT, + item.span, + &format!("the lint `{}` has the default lint description", item.ident.name), + ); + } + } + self.declared_lints.insert(item.ident.name, item.span); + } + } else if is_expn_of(item.span, "impl_lint_pass").is_some() + || is_expn_of(item.span, "declare_lint_pass").is_some() + { + if let hir::ItemKind::Impl(hir::Impl { + of_trait: None, + items: ref impl_item_refs, + .. + }) = item.kind + { + let mut collector = LintCollector { + output: &mut self.registered_lints, + cx, + }; + let body_id = cx.tcx.hir().body_owned_by( + impl_item_refs + .iter() + .find(|iiref| iiref.ident.as_str() == "get_lints") + .expect("LintPass needs to implement get_lints") + .id + .hir_id(), + ); + collector.visit_expr(&cx.tcx.hir().body(body_id).value); + } + } + } + + fn check_crate_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Crate<'_>) { + if !run_lints(cx, &[LINT_WITHOUT_LINT_PASS], CRATE_HIR_ID) { + return; + } + + for (lint_name, &lint_span) in &self.declared_lints { + // When using the `declare_tool_lint!` macro, the original `lint_span`'s + // file points to "". + // `compiletest-rs` thinks that's an error in a different file and + // just ignores it. This causes the test in compile-fail/lint_pass + // not able to capture the error. + // Therefore, we need to climb the macro expansion tree and find the + // actual span that invoked `declare_tool_lint!`: + let lint_span = lint_span.ctxt().outer_expn_data().call_site; + + if !self.registered_lints.contains(lint_name) { + span_lint( + cx, + LINT_WITHOUT_LINT_PASS, + lint_span, + &format!("the lint `{}` is not added to any `LintPass`", lint_name), + ); + } + } + } +} + +fn is_lint_ref_type<'tcx>(cx: &LateContext<'tcx>, ty: &Ty<'_>) -> bool { + if let TyKind::Rptr( + _, + MutTy { + ty: ref inner, + mutbl: Mutability::Not, + }, + ) = ty.kind + { + if let TyKind::Path(ref path) = inner.kind { + if let Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, inner.hir_id) { + return match_def_path(cx, def_id, &paths::LINT); + } + } + } + + false +} + +struct LintCollector<'a, 'tcx> { + output: &'a mut FxHashSet, + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for LintCollector<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_path(&mut self, path: &'tcx Path<'_>, _: HirId) { + if path.segments.len() == 1 { + self.output.insert(path.segments[0].ident.name); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} + +#[derive(Clone, Default)] +pub struct CompilerLintFunctions { + map: FxHashMap<&'static str, &'static str>, +} + +impl CompilerLintFunctions { + #[must_use] + pub fn new() -> Self { + let mut map = FxHashMap::default(); + map.insert("span_lint", "utils::span_lint"); + map.insert("struct_span_lint", "utils::span_lint"); + map.insert("lint", "utils::span_lint"); + map.insert("span_lint_note", "utils::span_lint_and_note"); + map.insert("span_lint_help", "utils::span_lint_and_help"); + Self { map } + } +} + +impl_lint_pass!(CompilerLintFunctions => [COMPILER_LINT_FUNCTIONS]); + +impl<'tcx> LateLintPass<'tcx> for CompilerLintFunctions { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if !run_lints(cx, &[COMPILER_LINT_FUNCTIONS], expr.hir_id) { + return; + } + + if_chain! { + if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind; + let fn_name = path.ident; + if let Some(sugg) = self.map.get(&*fn_name.as_str()); + let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); + if match_type(cx, ty, &paths::EARLY_CONTEXT) + || match_type(cx, ty, &paths::LATE_CONTEXT); + then { + span_lint_and_help( + cx, + COMPILER_LINT_FUNCTIONS, + path.ident.span, + "usage of a compiler lint function", + None, + &format!("please use the Clippy variant of this function: `{}`", sugg), + ); + } + } + } +} + +declare_lint_pass!(OuterExpnDataPass => [OUTER_EXPN_EXPN_DATA]); + +impl<'tcx> LateLintPass<'tcx> for OuterExpnDataPass { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if !run_lints(cx, &[OUTER_EXPN_EXPN_DATA], expr.hir_id) { + return; + } + + let (method_names, arg_lists, spans) = method_calls(expr, 2); + let method_names: Vec = method_names.iter().map(|s| s.as_str()).collect(); + let method_names: Vec<&str> = method_names.iter().map(|s| &**s).collect(); + if_chain! { + if let ["expn_data", "outer_expn"] = method_names.as_slice(); + let args = arg_lists[1]; + if args.len() == 1; + let self_arg = &args[0]; + let self_ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); + if match_type(cx, self_ty, &paths::SYNTAX_CONTEXT); + then { + span_lint_and_sugg( + cx, + OUTER_EXPN_EXPN_DATA, + spans[1].with_hi(expr.span.hi()), + "usage of `outer_expn().expn_data()`", + "try", + "outer_expn_data()".to_string(), + Applicability::MachineApplicable, + ); + } + } + } +} + +declare_lint_pass!(ProduceIce => [PRODUCE_ICE]); + +impl EarlyLintPass for ProduceIce { + fn check_fn(&mut self, _: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) { + if is_trigger_fn(fn_kind) { + panic!("Would you like some help with that?"); + } + } +} + +fn is_trigger_fn(fn_kind: FnKind<'_>) -> bool { + match fn_kind { + FnKind::Fn(_, ident, ..) => ident.name.as_str() == "it_looks_like_you_are_trying_to_kill_clippy", + FnKind::Closure(..) => false, + } +} + +declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]); + +impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if !run_lints(cx, &[COLLAPSIBLE_SPAN_LINT_CALLS], expr.hir_id) { + return; + } + + if_chain! { + if let ExprKind::Call(ref func, ref and_then_args) = expr.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &["span_lint_and_then"]); + if and_then_args.len() == 5; + if let ExprKind::Closure(_, _, body_id, _, _) = &and_then_args[4].kind; + let body = cx.tcx.hir().body(*body_id); + if let ExprKind::Block(block, _) = &body.value.kind; + let stmts = &block.stmts; + if stmts.len() == 1 && block.expr.is_none(); + if let StmtKind::Semi(only_expr) = &stmts[0].kind; + if let ExprKind::MethodCall(ref ps, _, ref span_call_args, _) = &only_expr.kind; + let and_then_snippets = get_and_then_snippets(cx, and_then_args); + let mut sle = SpanlessEq::new(cx).deny_side_effects(); + then { + match &*ps.ident.as_str() { + "span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => { + suggest_suggestion(cx, expr, &and_then_snippets, &span_suggestion_snippets(cx, span_call_args)); + }, + "span_help" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => { + let help_snippet = snippet(cx, span_call_args[2].span, r#""...""#); + suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true); + }, + "span_note" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => { + let note_snippet = snippet(cx, span_call_args[2].span, r#""...""#); + suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true); + }, + "help" => { + let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#); + suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false); + } + "note" => { + let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#); + suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false); + } + _ => (), + } + } + } + } +} + +struct AndThenSnippets<'a> { + cx: Cow<'a, str>, + lint: Cow<'a, str>, + span: Cow<'a, str>, + msg: Cow<'a, str>, +} + +fn get_and_then_snippets<'a, 'hir>(cx: &LateContext<'_>, and_then_snippets: &'hir [Expr<'hir>]) -> AndThenSnippets<'a> { + let cx_snippet = snippet(cx, and_then_snippets[0].span, "cx"); + let lint_snippet = snippet(cx, and_then_snippets[1].span, ".."); + let span_snippet = snippet(cx, and_then_snippets[2].span, "span"); + let msg_snippet = snippet(cx, and_then_snippets[3].span, r#""...""#); + + AndThenSnippets { + cx: cx_snippet, + lint: lint_snippet, + span: span_snippet, + msg: msg_snippet, + } +} + +struct SpanSuggestionSnippets<'a> { + help: Cow<'a, str>, + sugg: Cow<'a, str>, + applicability: Cow<'a, str>, +} + +fn span_suggestion_snippets<'a, 'hir>( + cx: &LateContext<'_>, + span_call_args: &'hir [Expr<'hir>], +) -> SpanSuggestionSnippets<'a> { + let help_snippet = snippet(cx, span_call_args[2].span, r#""...""#); + let sugg_snippet = snippet(cx, span_call_args[3].span, ".."); + let applicability_snippet = snippet(cx, span_call_args[4].span, "Applicability::MachineApplicable"); + + SpanSuggestionSnippets { + help: help_snippet, + sugg: sugg_snippet, + applicability: applicability_snippet, + } +} + +fn suggest_suggestion( + cx: &LateContext<'_>, + expr: &Expr<'_>, + and_then_snippets: &AndThenSnippets<'_>, + span_suggestion_snippets: &SpanSuggestionSnippets<'_>, +) { + span_lint_and_sugg( + cx, + COLLAPSIBLE_SPAN_LINT_CALLS, + expr.span, + "this call is collapsible", + "collapse into", + format!( + "span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})", + and_then_snippets.cx, + and_then_snippets.lint, + and_then_snippets.span, + and_then_snippets.msg, + span_suggestion_snippets.help, + span_suggestion_snippets.sugg, + span_suggestion_snippets.applicability + ), + Applicability::MachineApplicable, + ); +} + +fn suggest_help( + cx: &LateContext<'_>, + expr: &Expr<'_>, + and_then_snippets: &AndThenSnippets<'_>, + help: &str, + with_span: bool, +) { + let option_span = if with_span { + format!("Some({})", and_then_snippets.span) + } else { + "None".to_string() + }; + + span_lint_and_sugg( + cx, + COLLAPSIBLE_SPAN_LINT_CALLS, + expr.span, + "this call is collapsible", + "collapse into", + format!( + "span_lint_and_help({}, {}, {}, {}, {}, {})", + and_then_snippets.cx, + and_then_snippets.lint, + and_then_snippets.span, + and_then_snippets.msg, + &option_span, + help + ), + Applicability::MachineApplicable, + ); +} + +fn suggest_note( + cx: &LateContext<'_>, + expr: &Expr<'_>, + and_then_snippets: &AndThenSnippets<'_>, + note: &str, + with_span: bool, +) { + let note_span = if with_span { + format!("Some({})", and_then_snippets.span) + } else { + "None".to_string() + }; + + span_lint_and_sugg( + cx, + COLLAPSIBLE_SPAN_LINT_CALLS, + expr.span, + "this call is collspible", + "collapse into", + format!( + "span_lint_and_note({}, {}, {}, {}, {}, {})", + and_then_snippets.cx, + and_then_snippets.lint, + and_then_snippets.span, + and_then_snippets.msg, + note_span, + note + ), + Applicability::MachineApplicable, + ); +} + +declare_lint_pass!(MatchTypeOnDiagItem => [MATCH_TYPE_ON_DIAGNOSTIC_ITEM]); + +impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if !run_lints(cx, &[MATCH_TYPE_ON_DIAGNOSTIC_ITEM], expr.hir_id) { + return; + } + + if_chain! { + // Check if this is a call to utils::match_type() + if let ExprKind::Call(fn_path, [context, ty, ty_path]) = expr.kind; + if let ExprKind::Path(fn_qpath) = &fn_path.kind; + if match_qpath(&fn_qpath, &["utils", "match_type"]); + // Extract the path to the matched type + if let Some(segments) = path_to_matched_type(cx, ty_path); + let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect(); + if let Some(ty_did) = path_to_res(cx, &segments[..]).opt_def_id(); + // Check if the matched type is a diagnostic item + let diag_items = cx.tcx.diagnostic_items(ty_did.krate); + if let Some(item_name) = diag_items.iter().find_map(|(k, v)| if *v == ty_did { Some(k) } else { None }); + then { + let cx_snippet = snippet(cx, context.span, "_"); + let ty_snippet = snippet(cx, ty.span, "_"); + + span_lint_and_sugg( + cx, + MATCH_TYPE_ON_DIAGNOSTIC_ITEM, + expr.span, + "usage of `utils::match_type()` on a type diagnostic item", + "try", + format!("utils::is_type_diagnostic_item({}, {}, sym::{})", cx_snippet, ty_snippet, item_name), + Applicability::MaybeIncorrect, + ); + } + } + } +} + +fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option> { + use rustc_hir::ItemKind; + + match &expr.kind { + ExprKind::AddrOf(.., expr) => return path_to_matched_type(cx, expr), + ExprKind::Path(qpath) => match cx.qpath_res(qpath, expr.hir_id) { + Res::Local(hir_id) => { + let parent_id = cx.tcx.hir().get_parent_node(hir_id); + if let Some(Node::Local(local)) = cx.tcx.hir().find(parent_id) { + if let Some(init) = local.init { + return path_to_matched_type(cx, init); + } + } + }, + Res::Def(DefKind::Const | DefKind::Static, def_id) => { + if let Some(Node::Item(item)) = cx.tcx.hir().get_if_local(def_id) { + if let ItemKind::Const(.., body_id) | ItemKind::Static(.., body_id) = item.kind { + let body = cx.tcx.hir().body(body_id); + return path_to_matched_type(cx, &body.value); + } + } + }, + _ => {}, + }, + ExprKind::Array(exprs) => { + let segments: Vec = exprs + .iter() + .filter_map(|expr| { + if let ExprKind::Lit(lit) = &expr.kind { + if let LitKind::Str(sym, _) = lit.node { + return Some(sym.as_str()); + } + } + + None + }) + .collect(); + + if segments.len() == exprs.len() { + return Some(segments); + } + }, + _ => {}, + } + + None +} + +// This is not a complete resolver for paths. It works on all the paths currently used in the paths +// module. That's all it does and all it needs to do. +pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool { + if path_to_res(cx, path) != Res::Err { + return true; + } + + // Some implementations can't be found by `path_to_res`, particularly inherent + // implementations of native types. Check lang items. + let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect(); + let lang_items = cx.tcx.lang_items(); + for item_def_id in lang_items.items().iter().flatten() { + let lang_item_path = cx.get_def_path(*item_def_id); + if path_syms.starts_with(&lang_item_path) { + if let [item] = &path_syms[lang_item_path.len()..] { + for child in cx.tcx.item_children(*item_def_id) { + if child.ident.name == *item { + return true; + } + } + } + } + } + + false +} + +declare_lint_pass!(InvalidPaths => [INVALID_PATHS]); + +impl<'tcx> LateLintPass<'tcx> for InvalidPaths { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + let local_def_id = &cx.tcx.parent_module(item.hir_id()); + let mod_name = &cx.tcx.item_name(local_def_id.to_def_id()); + if_chain! { + if mod_name.as_str() == "paths"; + if let hir::ItemKind::Const(ty, body_id) = item.kind; + let ty = hir_ty_to_ty(cx.tcx, ty); + if let ty::Array(el_ty, _) = &ty.kind(); + if let ty::Ref(_, el_ty, _) = &el_ty.kind(); + if el_ty.is_str(); + let body = cx.tcx.hir().body(body_id); + let typeck_results = cx.tcx.typeck_body(body_id); + if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, &body.value); + let path: Vec<&str> = path.iter().map(|x| { + if let Constant::Str(s) = x { + s.as_str() + } else { + // We checked the type of the constant above + unreachable!() + } + }).collect(); + if !check_path(cx, &path[..]); + then { + span_lint(cx, CLIPPY_LINTS_INTERNAL, item.span, "invalid path"); + } + } + } +} + +#[derive(Default)] +pub struct InterningDefinedSymbol { + // Maps the symbol value to the constant DefId. + symbol_map: FxHashMap, +} + +impl_lint_pass!(InterningDefinedSymbol => [INTERNING_DEFINED_SYMBOL, UNNECESSARY_SYMBOL_STR]); + +impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol { + fn check_crate(&mut self, cx: &LateContext<'_>, _: &Crate<'_>) { + if !self.symbol_map.is_empty() { + return; + } + + for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] { + if let Some(def_id) = path_to_res(cx, module).opt_def_id() { + for item in cx.tcx.item_children(def_id).iter() { + if_chain! { + if let Res::Def(DefKind::Const, item_def_id) = item.res; + let ty = cx.tcx.type_of(item_def_id); + if match_type(cx, ty, &paths::SYMBOL); + if let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id); + if let Ok(value) = value.to_u32(); + then { + self.symbol_map.insert(value, item_def_id); + } + } + } + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(func, [arg]) = &expr.kind; + if let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(func).kind(); + if match_def_path(cx, *def_id, &paths::SYMBOL_INTERN); + if let Some(Constant::Str(arg)) = constant_simple(cx, cx.typeck_results(), arg); + let value = Symbol::intern(&arg).as_u32(); + if let Some(&def_id) = self.symbol_map.get(&value); + then { + span_lint_and_sugg( + cx, + INTERNING_DEFINED_SYMBOL, + is_expn_of(expr.span, "sym").unwrap_or(expr.span), + "interning a defined symbol", + "try", + cx.tcx.def_path_str(def_id), + Applicability::MachineApplicable, + ); + } + } + if let ExprKind::Binary(op, left, right) = expr.kind { + if matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) { + let data = [ + (left, self.symbol_str_expr(left, cx)), + (right, self.symbol_str_expr(right, cx)), + ]; + match data { + // both operands are a symbol string + [(_, Some(left)), (_, Some(right))] => { + span_lint_and_sugg( + cx, + UNNECESSARY_SYMBOL_STR, + expr.span, + "unnecessary `Symbol` to string conversion", + "try", + format!( + "{} {} {}", + left.as_symbol_snippet(cx), + op.node.as_str(), + right.as_symbol_snippet(cx), + ), + Applicability::MachineApplicable, + ); + }, + // one of the operands is a symbol string + [(expr, Some(symbol)), _] | [_, (expr, Some(symbol))] => { + // creating an owned string for comparison + if matches!(symbol, SymbolStrExpr::Expr { is_to_owned: true, .. }) { + span_lint_and_sugg( + cx, + UNNECESSARY_SYMBOL_STR, + expr.span, + "unnecessary string allocation", + "try", + format!("{}.as_str()", symbol.as_symbol_snippet(cx)), + Applicability::MachineApplicable, + ); + } + }, + // nothing found + [(_, None), (_, None)] => {}, + } + } + } + } +} + +impl InterningDefinedSymbol { + fn symbol_str_expr<'tcx>(&self, expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> Option> { + static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR, &paths::TO_STRING_METHOD]; + static SYMBOL_STR_PATHS: &[&[&str]] = &[ + &paths::SYMBOL_AS_STR, + &paths::SYMBOL_TO_IDENT_STRING, + &paths::TO_STRING_METHOD, + ]; + // SymbolStr might be de-referenced: `&*symbol.as_str()` + let call = if_chain! { + if let ExprKind::AddrOf(_, _, e) = expr.kind; + if let ExprKind::Unary(UnOp::Deref, e) = e.kind; + then { e } else { expr } + }; + if_chain! { + // is a method call + if let ExprKind::MethodCall(_, _, [item], _) = call.kind; + if let Some(did) = cx.typeck_results().type_dependent_def_id(call.hir_id); + let ty = cx.typeck_results().expr_ty(item); + // ...on either an Ident or a Symbol + if let Some(is_ident) = if match_type(cx, ty, &paths::SYMBOL) { + Some(false) + } else if match_type(cx, ty, &paths::IDENT) { + Some(true) + } else { + None + }; + // ...which converts it to a string + let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS }; + if let Some(path) = paths.iter().find(|path| match_def_path(cx, did, path)); + then { + let is_to_owned = path.last().unwrap().ends_with("string"); + return Some(SymbolStrExpr::Expr { + item, + is_ident, + is_to_owned, + }); + } + } + // is a string constant + if let Some(Constant::Str(s)) = constant_simple(cx, cx.typeck_results(), expr) { + let value = Symbol::intern(&s).as_u32(); + // ...which matches a symbol constant + if let Some(&def_id) = self.symbol_map.get(&value) { + return Some(SymbolStrExpr::Const(def_id)); + } + } + None + } +} + +enum SymbolStrExpr<'tcx> { + /// a string constant with a corresponding symbol constant + Const(DefId), + /// a "symbol to string" expression like `symbol.as_str()` + Expr { + /// part that evaluates to `Symbol` or `Ident` + item: &'tcx Expr<'tcx>, + is_ident: bool, + /// whether an owned `String` is created like `to_ident_string()` + is_to_owned: bool, + }, +} + +impl<'tcx> SymbolStrExpr<'tcx> { + /// Returns a snippet that evaluates to a `Symbol` and is const if possible + fn as_symbol_snippet(&self, cx: &LateContext<'_>) -> Cow<'tcx, str> { + match *self { + Self::Const(def_id) => cx.tcx.def_path_str(def_id).into(), + Self::Expr { item, is_ident, .. } => { + let mut snip = snippet(cx, item.span.source_callsite(), ".."); + if is_ident { + // get `Ident.name` + snip.to_mut().push_str(".name"); + } + snip + }, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/mod.rs b/src/tools/clippy/clippy_lints/src/utils/mod.rs new file mode 100644 index 0000000000..be9a07f8d7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/mod.rs @@ -0,0 +1,7 @@ +pub mod author; +pub mod conf; +pub mod inspector; +#[cfg(feature = "internal-lints")] +pub mod internal_lints; + +pub use clippy_utils::*; diff --git a/src/tools/clippy/clippy_lints/src/vec.rs b/src/tools/clippy/clippy_lints/src/vec.rs new file mode 100644 index 0000000000..c132e4de4f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/vec.rs @@ -0,0 +1,135 @@ +use crate::consts::{constant, Constant}; +use crate::rustc_target::abi::LayoutOf; +use crate::utils::{higher, is_copy, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; + +#[allow(clippy::module_name_repetitions)] +#[derive(Copy, Clone)] +pub struct UselessVec { + pub too_large_for_stack: u64, +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `&vec![..]` when using `&[..]` would + /// be possible. + /// + /// **Why is this bad?** This is less efficient. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn foo(my_vec: &[u8]) {} + /// + /// // Bad + /// foo(&vec![1, 2]); + /// + /// // Good + /// foo(&[1, 2]); + /// ``` + pub USELESS_VEC, + perf, + "useless `vec!`" +} + +impl_lint_pass!(UselessVec => [USELESS_VEC]); + +impl<'tcx> LateLintPass<'tcx> for UselessVec { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // search for `&vec![_]` expressions where the adjusted type is `&[_]` + if_chain! { + if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(expr).kind(); + if let ty::Slice(..) = ty.kind(); + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref addressee) = expr.kind; + if let Some(vec_args) = higher::vec_macro(cx, addressee); + then { + self.check_vec_macro(cx, &vec_args, expr.span); + } + } + + // search for `for _ in vec![…]` + if_chain! { + if let Some((_, arg, _, _)) = higher::for_loop(expr); + if let Some(vec_args) = higher::vec_macro(cx, arg); + if is_copy(cx, vec_type(cx.typeck_results().expr_ty_adjusted(arg))); + then { + // report the error around the `vec!` not inside `:` + let span = arg.span + .ctxt() + .outer_expn_data() + .call_site + .ctxt() + .outer_expn_data() + .call_site; + self.check_vec_macro(cx, &vec_args, span); + } + } + } +} + +impl UselessVec { + fn check_vec_macro<'tcx>(self, cx: &LateContext<'tcx>, vec_args: &higher::VecArgs<'tcx>, span: Span) { + let mut applicability = Applicability::MachineApplicable; + let snippet = match *vec_args { + higher::VecArgs::Repeat(elem, len) => { + if let Some((Constant::Int(len_constant), _)) = constant(cx, cx.typeck_results(), len) { + #[allow(clippy::cast_possible_truncation)] + if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack { + return; + } + + format!( + "&[{}; {}]", + snippet_with_applicability(cx, elem.span, "elem", &mut applicability), + snippet_with_applicability(cx, len.span, "len", &mut applicability) + ) + } else { + return; + } + }, + higher::VecArgs::Vec(args) => { + if let Some(last) = args.iter().last() { + #[allow(clippy::cast_possible_truncation)] + if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack { + return; + } + let span = args[0].span.to(last.span); + + format!("&[{}]", snippet_with_applicability(cx, span, "..", &mut applicability)) + } else { + "&[]".into() + } + }, + }; + + span_lint_and_sugg( + cx, + USELESS_VEC, + span, + "useless use of `vec!`", + "you can use a slice directly", + snippet, + applicability, + ); + } +} + +fn size_of(cx: &LateContext<'_>, expr: &Expr<'_>) -> u64 { + let ty = cx.typeck_results().expr_ty_adjusted(expr); + cx.layout_of(ty).map_or(0, |l| l.size.bytes()) +} + +/// Returns the item type of the vector (i.e., the `T` in `Vec`). +fn vec_type(ty: Ty<'_>) -> Ty<'_> { + if let ty::Adt(_, substs) = ty.kind() { + substs.type_at(0) + } else { + panic!("The type of `vec!` is a not a struct?"); + } +} diff --git a/src/tools/clippy/clippy_lints/src/vec_init_then_push.rs b/src/tools/clippy/clippy_lints/src/vec_init_then_push.rs new file mode 100644 index 0000000000..8d111f98ad --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/vec_init_then_push.rs @@ -0,0 +1,189 @@ +use crate::utils::{ + is_type_diagnostic_item, match_def_path, path_to_local, path_to_local_id, paths, snippet, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, QPath, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{symbol::sym, Span}; +use std::convert::TryInto; + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `push` immediately after creating a new `Vec`. + /// + /// **Why is this bad?** The `vec![]` macro is both more performant and easier to read than + /// multiple `push` calls. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let mut v = Vec::new(); + /// v.push(0); + /// ``` + /// Use instead: + /// ```rust + /// let v = vec![0]; + /// ``` + pub VEC_INIT_THEN_PUSH, + perf, + "`push` immediately after `Vec` creation" +} + +impl_lint_pass!(VecInitThenPush => [VEC_INIT_THEN_PUSH]); + +#[derive(Default)] +pub struct VecInitThenPush { + searcher: Option, +} + +#[derive(Clone, Copy)] +enum VecInitKind { + New, + WithCapacity(u64), +} +struct VecPushSearcher { + local_id: HirId, + init: VecInitKind, + lhs_is_local: bool, + lhs_span: Span, + err_span: Span, + found: u64, +} +impl VecPushSearcher { + fn display_err(&self, cx: &LateContext<'_>) { + match self.init { + _ if self.found == 0 => return, + VecInitKind::WithCapacity(x) if x > self.found => return, + _ => (), + }; + + let mut s = if self.lhs_is_local { + String::from("let ") + } else { + String::new() + }; + s.push_str(&snippet(cx, self.lhs_span, "..")); + s.push_str(" = vec![..];"); + + span_lint_and_sugg( + cx, + VEC_INIT_THEN_PUSH, + self.err_span, + "calls to `push` immediately after creation", + "consider using the `vec![]` macro", + s, + Applicability::HasPlaceholders, + ); + } +} + +impl LateLintPass<'_> for VecInitThenPush { + fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx Block<'tcx>) { + self.searcher = None; + } + + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { + if_chain! { + if !in_external_macro(cx.sess(), local.span); + if let Some(init) = local.init; + if let PatKind::Binding(BindingAnnotation::Mutable, id, _, None) = local.pat.kind; + if let Some(init_kind) = get_vec_init_kind(cx, init); + then { + self.searcher = Some(VecPushSearcher { + local_id: id, + init: init_kind, + lhs_is_local: true, + lhs_span: local.ty.map_or(local.pat.span, |t| local.pat.span.to(t.span)), + err_span: local.span, + found: 0, + }); + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if self.searcher.is_none() { + if_chain! { + if !in_external_macro(cx.sess(), expr.span); + if let ExprKind::Assign(left, right, _) = expr.kind; + if let Some(id) = path_to_local(left); + if let Some(init_kind) = get_vec_init_kind(cx, right); + then { + self.searcher = Some(VecPushSearcher { + local_id: id, + init: init_kind, + lhs_is_local: false, + lhs_span: left.span, + err_span: expr.span, + found: 0, + }); + } + } + } + } + + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if let Some(searcher) = self.searcher.take() { + if_chain! { + if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind; + if let ExprKind::MethodCall(path, _, [self_arg, _], _) = expr.kind; + if path_to_local_id(self_arg, searcher.local_id); + if path.ident.name.as_str() == "push"; + then { + self.searcher = Some(VecPushSearcher { + found: searcher.found + 1, + err_span: searcher.err_span.to(stmt.span), + .. searcher + }); + } else { + searcher.display_err(cx); + } + } + } + } + + fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Block<'tcx>) { + if let Some(searcher) = self.searcher.take() { + searcher.display_err(cx); + } + } +} + +fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option { + if let ExprKind::Call(func, args) = expr.kind { + match func.kind { + ExprKind::Path(QPath::TypeRelative(ty, name)) + if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::vec_type) => + { + if name.ident.name == sym::new { + return Some(VecInitKind::New); + } else if name.ident.name.as_str() == "with_capacity" { + return args.get(0).and_then(|arg| { + if_chain! { + if let ExprKind::Lit(lit) = &arg.kind; + if let LitKind::Int(num, _) = lit.node; + then { + Some(VecInitKind::WithCapacity(num.try_into().ok()?)) + } else { + None + } + } + }); + } + } + ExprKind::Path(QPath::Resolved(_, path)) + if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD) + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::vec_type) => + { + return Some(VecInitKind::New); + } + _ => (), + } + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/vec_resize_to_zero.rs b/src/tools/clippy/clippy_lints/src/vec_resize_to_zero.rs new file mode 100644 index 0000000000..d2494b321e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/vec_resize_to_zero.rs @@ -0,0 +1,59 @@ +use crate::utils::span_lint_and_then; +use if_chain::if_chain; +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::Spanned; + +use crate::utils::{match_def_path, paths}; +use rustc_ast::LitKind; +use rustc_hir as hir; + +declare_clippy_lint! { + /// **What it does:** Finds occurrences of `Vec::resize(0, an_int)` + /// + /// **Why is this bad?** This is probably an argument inversion mistake. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// vec!(1, 2, 3, 4, 5).resize(0, 5) + /// ``` + pub VEC_RESIZE_TO_ZERO, + correctness, + "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake" +} + +declare_lint_pass!(VecResizeToZero => [VEC_RESIZE_TO_ZERO]); + +impl<'tcx> LateLintPass<'tcx> for VecResizeToZero { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let hir::ExprKind::MethodCall(path_segment, _, ref args, _) = expr.kind; + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if match_def_path(cx, method_def_id, &paths::VEC_RESIZE) && args.len() == 3; + if let ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = args[1].kind; + if let ExprKind::Lit(Spanned { node: LitKind::Int(..), .. }) = args[2].kind; + then { + let method_call_span = expr.span.with_lo(path_segment.ident.span.lo()); + span_lint_and_then( + cx, + VEC_RESIZE_TO_ZERO, + expr.span, + "emptying a vector with `resize`", + |db| { + db.help("the arguments may be inverted..."); + db.span_suggestion( + method_call_span, + "...or you can empty the vector with", + "clear()".to_string(), + Applicability::MaybeIncorrect, + ); + }, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/verbose_file_reads.rs b/src/tools/clippy/clippy_lints/src/verbose_file_reads.rs new file mode 100644 index 0000000000..32574d9d6c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/verbose_file_reads.rs @@ -0,0 +1,86 @@ +use crate::utils::{match_type, paths, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for use of File::read_to_end and File::read_to_string. + /// + /// **Why is this bad?** `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. + /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,no_run + /// # use std::io::Read; + /// # use std::fs::File; + /// let mut f = File::open("foo.txt").unwrap(); + /// let mut bytes = Vec::new(); + /// f.read_to_end(&mut bytes).unwrap(); + /// ``` + /// Can be written more concisely as + /// ```rust,no_run + /// # use std::fs; + /// let mut bytes = fs::read("foo.txt").unwrap(); + /// ``` + pub VERBOSE_FILE_READS, + restriction, + "use of `File::read_to_end` or `File::read_to_string`" +} + +declare_lint_pass!(VerboseFileReads => [VERBOSE_FILE_READS]); + +impl<'tcx> LateLintPass<'tcx> for VerboseFileReads { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if is_file_read_to_end(cx, expr) { + span_lint_and_help( + cx, + VERBOSE_FILE_READS, + expr.span, + "use of `File::read_to_end`", + None, + "consider using `fs::read` instead", + ); + } else if is_file_read_to_string(cx, expr) { + span_lint_and_help( + cx, + VERBOSE_FILE_READS, + expr.span, + "use of `File::read_to_string`", + None, + "consider using `fs::read_to_string` instead", + ) + } + } +} + +fn is_file_read_to_end<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + if_chain! { + if let ExprKind::MethodCall(method_name, _, exprs, _) = expr.kind; + if method_name.ident.as_str() == "read_to_end"; + if let ExprKind::Path(QPath::Resolved(None, _)) = &exprs[0].kind; + let ty = cx.typeck_results().expr_ty(&exprs[0]); + if match_type(cx, ty, &paths::FILE); + then { + return true + } + } + false +} + +fn is_file_read_to_string<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + if_chain! { + if let ExprKind::MethodCall(method_name, _, exprs, _) = expr.kind; + if method_name.ident.as_str() == "read_to_string"; + if let ExprKind::Path(QPath::Resolved(None, _)) = &exprs[0].kind; + let ty = cx.typeck_results().expr_ty(&exprs[0]); + if match_type(cx, ty, &paths::FILE); + then { + return true + } + } + false +} diff --git a/src/tools/clippy/clippy_lints/src/wildcard_dependencies.rs b/src/tools/clippy/clippy_lints/src/wildcard_dependencies.rs new file mode 100644 index 0000000000..cd1864f461 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/wildcard_dependencies.rs @@ -0,0 +1,57 @@ +use crate::utils::{run_lints, span_lint}; +use rustc_hir::{hir_id::CRATE_HIR_ID, Crate}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::DUMMY_SP; + +use if_chain::if_chain; + +declare_clippy_lint! { + /// **What it does:** Checks for wildcard dependencies in the `Cargo.toml`. + /// + /// **Why is this bad?** [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html), + /// it is highly unlikely that you work with any possible version of your dependency, + /// and wildcard dependencies would cause unnecessary breakage in the ecosystem. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```toml + /// [dependencies] + /// regex = "*" + /// ``` + pub WILDCARD_DEPENDENCIES, + cargo, + "wildcard dependencies being used" +} + +declare_lint_pass!(WildcardDependencies => [WILDCARD_DEPENDENCIES]); + +impl LateLintPass<'_> for WildcardDependencies { + fn check_crate(&mut self, cx: &LateContext<'_>, _: &Crate<'_>) { + if !run_lints(cx, &[WILDCARD_DEPENDENCIES], CRATE_HIR_ID) { + return; + } + + let metadata = unwrap_cargo_metadata!(cx, WILDCARD_DEPENDENCIES, false); + + for dep in &metadata.packages[0].dependencies { + // VersionReq::any() does not work + if_chain! { + if let Ok(wildcard_ver) = semver::VersionReq::parse("*"); + if let Some(ref source) = dep.source; + if !source.starts_with("git"); + if dep.req == wildcard_ver; + then { + span_lint( + cx, + WILDCARD_DEPENDENCIES, + DUMMY_SP, + &format!("wildcard dependency for `{}`", dep.name), + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/wildcard_imports.rs b/src/tools/clippy/clippy_lints/src/wildcard_imports.rs new file mode 100644 index 0000000000..094b1a4234 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/wildcard_imports.rs @@ -0,0 +1,212 @@ +use crate::utils::{in_macro, snippet, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{ + def::{DefKind, Res}, + Item, ItemKind, PathSegment, UseKind, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::kw; +use rustc_span::{sym, BytePos}; + +declare_clippy_lint! { + /// **What it does:** Checks for `use Enum::*`. + /// + /// **Why is this bad?** It is usually better style to use the prefixed name of + /// an enumeration variant, rather than importing variants. + /// + /// **Known problems:** Old-style enumerations that prefix the variants are + /// still around. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// use std::cmp::Ordering::*; + /// foo(Less); + /// + /// // Good + /// use std::cmp::Ordering; + /// foo(Ordering::Less) + /// ``` + pub ENUM_GLOB_USE, + pedantic, + "use items that import all variants of an enum" +} + +declare_clippy_lint! { + /// **What it does:** Checks for wildcard imports `use _::*`. + /// + /// **Why is this bad?** wildcard imports can pollute the namespace. This is especially bad if + /// you try to import something through a wildcard, that already has been imported by name from + /// a different source: + /// + /// ```rust,ignore + /// use crate1::foo; // Imports a function named foo + /// use crate2::*; // Has a function named foo + /// + /// foo(); // Calls crate1::foo + /// ``` + /// + /// This can lead to confusing error messages at best and to unexpected behavior at worst. + /// + /// **Exceptions:** + /// + /// Wildcard imports are allowed from modules named `prelude`. Many crates (including the standard library) + /// provide modules named "prelude" specifically designed for wildcard import. + /// + /// `use super::*` is allowed in test modules. This is defined as any module with "test" in the name. + /// + /// These exceptions can be disabled using the `warn-on-all-wildcard-imports` configuration flag. + /// + /// **Known problems:** If macros are imported through the wildcard, this macro is not included + /// by the suggestion and has to be added by hand. + /// + /// Applying the suggestion when explicit imports of the things imported with a glob import + /// exist, may result in `unused_imports` warnings. + /// + /// **Example:** + /// + /// ```rust,ignore + /// // Bad + /// use crate1::*; + /// + /// foo(); + /// ``` + /// + /// ```rust,ignore + /// // Good + /// use crate1::foo; + /// + /// foo(); + /// ``` + pub WILDCARD_IMPORTS, + pedantic, + "lint `use _::*` statements" +} + +#[derive(Default)] +pub struct WildcardImports { + warn_on_all: bool, + test_modules_deep: u32, +} + +impl WildcardImports { + pub fn new(warn_on_all: bool) -> Self { + Self { + warn_on_all, + test_modules_deep: 0, + } + } +} + +impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]); + +impl LateLintPass<'_> for WildcardImports { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if is_test_module_or_function(item) { + self.test_modules_deep = self.test_modules_deep.saturating_add(1); + } + if item.vis.node.is_pub() || item.vis.node.is_pub_restricted() { + return; + } + if_chain! { + if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind; + if self.warn_on_all || !self.check_exceptions(item, use_path.segments); + let used_imports = cx.tcx.names_imported_by_glob_use(item.def_id); + if !used_imports.is_empty(); // Already handled by `unused_imports` + then { + let mut applicability = Applicability::MachineApplicable; + let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability); + let (span, braced_glob) = if import_source_snippet.is_empty() { + // This is a `_::{_, *}` import + // In this case `use_path.span` is empty and ends directly in front of the `*`, + // so we need to extend it by one byte. + ( + use_path.span.with_hi(use_path.span.hi() + BytePos(1)), + true, + ) + } else { + // In this case, the `use_path.span` ends right before the `::*`, so we need to + // extend it up to the `*`. Since it is hard to find the `*` in weird + // formattings like `use _ :: *;`, we extend it up to, but not including the + // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we + // can just use the end of the item span + let mut span = use_path.span.with_hi(item.span.hi()); + if snippet(cx, span, "").ends_with(';') { + span = use_path.span.with_hi(item.span.hi() - BytePos(1)); + } + ( + span, false, + ) + }; + + let imports_string = if used_imports.len() == 1 { + used_imports.iter().next().unwrap().to_string() + } else { + let mut imports = used_imports + .iter() + .map(ToString::to_string) + .collect::>(); + imports.sort(); + if braced_glob { + imports.join(", ") + } else { + format!("{{{}}}", imports.join(", ")) + } + }; + + let sugg = if braced_glob { + imports_string + } else { + format!("{}::{}", import_source_snippet, imports_string) + }; + + let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res { + (ENUM_GLOB_USE, "usage of wildcard import for enum variants") + } else { + (WILDCARD_IMPORTS, "usage of wildcard import") + }; + + span_lint_and_sugg( + cx, + lint, + span, + message, + "try", + sugg, + applicability, + ); + } + } + } + + fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) { + if is_test_module_or_function(item) { + self.test_modules_deep = self.test_modules_deep.saturating_sub(1); + } + } +} + +impl WildcardImports { + fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool { + in_macro(item.span) + || is_prelude_import(segments) + || (is_super_only_import(segments) && self.test_modules_deep > 0) + } +} + +// Allow "...prelude::..::*" imports. +// Many crates have a prelude, and it is imported as a glob by design. +fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool { + segments.iter().any(|ps| ps.ident.name == sym::prelude) +} + +// Allow "super::*" imports in tests. +fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool { + segments.len() == 1 && segments[0].ident.name == kw::Super +} + +fn is_test_module_or_function(item: &Item<'_>) -> bool { + matches!(item.kind, ItemKind::Mod(..)) && item.ident.name.as_str().contains("test") +} diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs new file mode 100644 index 0000000000..553e6b000e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/write.rs @@ -0,0 +1,576 @@ +use std::borrow::Cow; +use std::ops::Range; + +use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then}; +use if_chain::if_chain; +use rustc_ast::ast::{ + Expr, ExprKind, ImplKind, Item, ItemKind, LitKind, MacCall, StrLit, StrStyle, +}; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_errors::Applicability; +use rustc_lexer::unescape::{self, EscapeError}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_parse::parser; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::kw; +use rustc_span::{sym, BytePos, Span}; + +declare_clippy_lint! { + /// **What it does:** This lint warns when you use `println!("")` to + /// print a newline. + /// + /// **Why is this bad?** You should use `println!()`, which is simpler. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// println!(""); + /// + /// // Good + /// println!(); + /// ``` + pub PRINTLN_EMPTY_STRING, + style, + "using `println!(\"\")` with an empty string" +} + +declare_clippy_lint! { + /// **What it does:** This lint warns when you use `print!()` with a format + /// string that ends in a newline. + /// + /// **Why is this bad?** You should use `println!()` instead, which appends the + /// newline. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let name = "World"; + /// print!("Hello {}!\n", name); + /// ``` + /// use println!() instead + /// ```rust + /// # let name = "World"; + /// println!("Hello {}!", name); + /// ``` + pub PRINT_WITH_NEWLINE, + style, + "using `print!()` with a format string that ends in a single newline" +} + +declare_clippy_lint! { + /// **What it does:** Checks for printing on *stdout*. The purpose of this lint + /// is to catch debugging remnants. + /// + /// **Why is this bad?** People often print on *stdout* while debugging an + /// application and might forget to remove those prints afterward. + /// + /// **Known problems:** Only catches `print!` and `println!` calls. + /// + /// **Example:** + /// ```rust + /// println!("Hello world!"); + /// ``` + pub PRINT_STDOUT, + restriction, + "printing on stdout" +} + +declare_clippy_lint! { + /// **What it does:** Checks for printing on *stderr*. The purpose of this lint + /// is to catch debugging remnants. + /// + /// **Why is this bad?** People often print on *stderr* while debugging an + /// application and might forget to remove those prints afterward. + /// + /// **Known problems:** Only catches `eprint!` and `eprintln!` calls. + /// + /// **Example:** + /// ```rust + /// eprintln!("Hello world!"); + /// ``` + pub PRINT_STDERR, + restriction, + "printing on stderr" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `Debug` formatting. The purpose of this + /// lint is to catch debugging remnants. + /// + /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate + /// debugging Rust code. It should not be used in user-facing output. + /// + /// **Example:** + /// ```rust + /// # let foo = "bar"; + /// println!("{:?}", foo); + /// ``` + pub USE_DEBUG, + restriction, + "use of `Debug`-based formatting" +} + +declare_clippy_lint! { + /// **What it does:** This lint warns about the use of literals as `print!`/`println!` args. + /// + /// **Why is this bad?** Using literals as `println!` args is inefficient + /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary + /// (i.e., just put the literal in the format string) + /// + /// **Known problems:** Will also warn with macro calls as arguments that expand to literals + /// -- e.g., `println!("{}", env!("FOO"))`. + /// + /// **Example:** + /// ```rust + /// println!("{}", "foo"); + /// ``` + /// use the literal without formatting: + /// ```rust + /// println!("foo"); + /// ``` + pub PRINT_LITERAL, + style, + "printing a literal with a format string" +} + +declare_clippy_lint! { + /// **What it does:** This lint warns when you use `writeln!(buf, "")` to + /// print a newline. + /// + /// **Why is this bad?** You should use `writeln!(buf)`, which is simpler. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// // Bad + /// writeln!(buf, ""); + /// + /// // Good + /// writeln!(buf); + /// ``` + pub WRITELN_EMPTY_STRING, + style, + "using `writeln!(buf, \"\")` with an empty string" +} + +declare_clippy_lint! { + /// **What it does:** This lint warns when you use `write!()` with a format + /// string that + /// ends in a newline. + /// + /// **Why is this bad?** You should use `writeln!()` instead, which appends the + /// newline. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let name = "World"; + /// // Bad + /// write!(buf, "Hello {}!\n", name); + /// + /// // Good + /// writeln!(buf, "Hello {}!", name); + /// ``` + pub WRITE_WITH_NEWLINE, + style, + "using `write!()` with a format string that ends in a single newline" +} + +declare_clippy_lint! { + /// **What it does:** This lint warns about the use of literals as `write!`/`writeln!` args. + /// + /// **Why is this bad?** Using literals as `writeln!` args is inefficient + /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary + /// (i.e., just put the literal in the format string) + /// + /// **Known problems:** Will also warn with macro calls as arguments that expand to literals + /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`. + /// + /// **Example:** + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// // Bad + /// writeln!(buf, "{}", "foo"); + /// + /// // Good + /// writeln!(buf, "foo"); + /// ``` + pub WRITE_LITERAL, + style, + "writing a literal with a format string" +} + +#[derive(Default)] +pub struct Write { + in_debug_impl: bool, +} + +impl_lint_pass!(Write => [ + PRINT_WITH_NEWLINE, + PRINTLN_EMPTY_STRING, + PRINT_STDOUT, + PRINT_STDERR, + USE_DEBUG, + PRINT_LITERAL, + WRITE_WITH_NEWLINE, + WRITELN_EMPTY_STRING, + WRITE_LITERAL +]); + +impl EarlyLintPass for Write { + fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) { + if let ItemKind::Impl(box ImplKind { + of_trait: Some(trait_ref), + .. + }) = &item.kind + { + let trait_name = trait_ref + .path + .segments + .iter() + .last() + .expect("path has at least one segment") + .ident + .name; + if trait_name == sym::Debug { + self.in_debug_impl = true; + } + } + } + + fn check_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) { + self.in_debug_impl = false; + } + + fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) { + fn is_build_script(cx: &EarlyContext<'_>) -> bool { + // Cargo sets the crate name for build scripts to `build_script_build` + cx.sess + .opts + .crate_name + .as_ref() + .map_or(false, |crate_name| crate_name == "build_script_build") + } + + if mac.path == sym!(print) { + if !is_build_script(cx) { + span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`"); + } + self.lint_print_with_newline(cx, mac); + } else if mac.path == sym!(println) { + if !is_build_script(cx) { + span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`"); + } + self.lint_println_empty_string(cx, mac); + } else if mac.path == sym!(eprint) { + span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprint!`"); + self.lint_print_with_newline(cx, mac); + } else if mac.path == sym!(eprintln) { + span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprintln!`"); + self.lint_println_empty_string(cx, mac); + } else if mac.path == sym!(write) { + if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), true) { + if check_newlines(&fmt_str) { + span_lint_and_then( + cx, + WRITE_WITH_NEWLINE, + mac.span(), + "using `write!()` with a format string that ends in a single newline", + |err| { + err.multipart_suggestion( + "use `writeln!()` instead", + vec![ + (mac.path.span, String::from("writeln")), + (newline_span(&fmt_str), String::new()), + ], + Applicability::MachineApplicable, + ); + }, + ) + } + } + } else if mac.path == sym!(writeln) { + if let (Some(fmt_str), expr) = self.check_tts(cx, mac.args.inner_tokens(), true) { + if fmt_str.symbol == kw::Empty { + let mut applicability = Applicability::MachineApplicable; + // FIXME: remove this `#[allow(...)]` once the issue #5822 gets fixed + #[allow(clippy::option_if_let_else)] + let suggestion = if let Some(e) = expr { + snippet_with_applicability(cx, e.span, "v", &mut applicability) + } else { + applicability = Applicability::HasPlaceholders; + Cow::Borrowed("v") + }; + + span_lint_and_sugg( + cx, + WRITELN_EMPTY_STRING, + mac.span(), + format!("using `writeln!({}, \"\")`", suggestion).as_str(), + "replace it with", + format!("writeln!({})", suggestion), + applicability, + ); + } + } + } + } +} + +/// Given a format string that ends in a newline and its span, calculates the span of the +/// newline, or the format string itself if the format string consists solely of a newline. +fn newline_span(fmtstr: &StrLit) -> Span { + let sp = fmtstr.span; + let contents = &fmtstr.symbol.as_str(); + + if *contents == r"\n" { + return sp; + } + + let newline_sp_hi = sp.hi() + - match fmtstr.style { + StrStyle::Cooked => BytePos(1), + StrStyle::Raw(hashes) => BytePos((1 + hashes).into()), + }; + + let newline_sp_len = if contents.ends_with('\n') { + BytePos(1) + } else if contents.ends_with(r"\n") { + BytePos(2) + } else { + panic!("expected format string to contain a newline"); + }; + + sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi) +} + +impl Write { + /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two + /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes + /// the contents of the string, whether it's a raw string, and the span of the literal in the + /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the + /// `format_str` should be written to. + /// + /// Example: + /// + /// Calling this function on + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let something = "something"; + /// writeln!(buf, "string to write: {}", something); + /// ``` + /// will return + /// ```rust,ignore + /// (Some("string to write: {}"), Some(buf)) + /// ``` + #[allow(clippy::too_many_lines)] + fn check_tts<'a>( + &self, + cx: &EarlyContext<'a>, + tts: TokenStream, + is_write: bool, + ) -> (Option, Option) { + use rustc_parse_format::{ + AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, + FormatSpec, ParseMode, Parser, Piece, + }; + + let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None); + let mut expr: Option = None; + if is_write { + expr = match parser.parse_expr().map_err(|mut err| err.cancel()) { + Ok(p) => Some(p.into_inner()), + Err(_) => return (None, None), + }; + // might be `writeln!(foo)` + if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() { + return (None, expr); + } + } + + let fmtstr = match parser.parse_str_lit() { + Ok(fmtstr) => fmtstr, + Err(_) => return (None, expr), + }; + let tmp = fmtstr.symbol.as_str(); + let mut args = vec![]; + let mut fmt_parser = Parser::new(&tmp, None, None, false, ParseMode::Format); + while let Some(piece) = fmt_parser.next() { + if !fmt_parser.errors.is_empty() { + return (None, expr); + } + if let Piece::NextArgument(arg) = piece { + if !self.in_debug_impl && arg.format.ty == "?" { + // FIXME: modify rustc's fmt string parser to give us the current span + span_lint( + cx, + USE_DEBUG, + parser.prev_token.span, + "use of `Debug`-based formatting", + ); + } + args.push(arg); + } + } + let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL }; + let mut idx = 0; + loop { + const SIMPLE: FormatSpec<'_> = FormatSpec { + fill: None, + align: AlignUnknown, + flags: 0, + precision: CountImplied, + precision_span: None, + width: CountImplied, + width_span: None, + ty: "", + ty_span: None, + }; + if !parser.eat(&token::Comma) { + return (Some(fmtstr), expr); + } + let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) { + expr + } else { + return (Some(fmtstr), None); + }; + match &token_expr.kind { + ExprKind::Lit(lit) + if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)) => + { + let mut all_simple = true; + let mut seen = false; + for arg in &args { + match arg.position { + ArgumentImplicitlyIs(n) | ArgumentIs(n) => { + if n == idx { + all_simple &= arg.format == SIMPLE; + seen = true; + } + } + ArgumentNamed(_) => {} + } + } + if all_simple && seen { + span_lint(cx, lint, token_expr.span, "literal with an empty format string"); + } + idx += 1; + } + ExprKind::Assign(lhs, rhs, _) => { + if_chain! { + if let ExprKind::Lit(ref lit) = rhs.kind; + if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)); + if let ExprKind::Path(_, p) = &lhs.kind; + then { + let mut all_simple = true; + let mut seen = false; + for arg in &args { + match arg.position { + ArgumentImplicitlyIs(_) | ArgumentIs(_) => {}, + ArgumentNamed(name) => { + if *p == name { + seen = true; + all_simple &= arg.format == SIMPLE; + } + }, + } + } + if all_simple && seen { + span_lint(cx, lint, rhs.span, "literal with an empty format string"); + } + } + } + } + _ => idx += 1, + } + } + } + + fn lint_println_empty_string(&self, cx: &EarlyContext<'_>, mac: &MacCall) { + if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) { + if fmt_str.symbol == kw::Empty { + let name = mac.path.segments[0].ident.name; + span_lint_and_sugg( + cx, + PRINTLN_EMPTY_STRING, + mac.span(), + &format!("using `{}!(\"\")`", name), + "replace it with", + format!("{}!()", name), + Applicability::MachineApplicable, + ); + } + } + } + + fn lint_print_with_newline(&self, cx: &EarlyContext<'_>, mac: &MacCall) { + if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) { + if check_newlines(&fmt_str) { + let name = mac.path.segments[0].ident.name; + let suggested = format!("{}ln", name); + span_lint_and_then( + cx, + PRINT_WITH_NEWLINE, + mac.span(), + &format!( + "using `{}!()` with a format string that ends in a single newline", + name + ), + |err| { + err.multipart_suggestion( + &format!("use `{}!` instead", suggested), + vec![ + (mac.path.span, suggested), + (newline_span(&fmt_str), String::new()), + ], + Applicability::MachineApplicable, + ); + }, + ); + } + } + } +} + +/// Checks if the format string contains a single newline that terminates it. +/// +/// Literal and escaped newlines are both checked (only literal for raw strings). +fn check_newlines(fmtstr: &StrLit) -> bool { + let mut has_internal_newline = false; + let mut last_was_cr = false; + let mut should_lint = false; + + let contents = &fmtstr.symbol.as_str(); + + let mut cb = |r: Range, c: Result| { + let c = c.unwrap(); + + if r.end == contents.len() && c == '\n' && !last_was_cr && !has_internal_newline { + should_lint = true; + } else { + last_was_cr = c == '\r'; + if c == '\n' { + has_internal_newline = true; + } + } + }; + + match fmtstr.style { + StrStyle::Cooked => unescape::unescape_literal(contents, unescape::Mode::Str, &mut cb), + StrStyle::Raw(_) => unescape::unescape_literal(contents, unescape::Mode::RawStr, &mut cb), + } + + should_lint +} diff --git a/src/tools/clippy/clippy_lints/src/zero_div_zero.rs b/src/tools/clippy/clippy_lints/src/zero_div_zero.rs new file mode 100644 index 0000000000..11d96e15ff --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/zero_div_zero.rs @@ -0,0 +1,65 @@ +use crate::consts::{constant_simple, Constant}; +use crate::utils::span_lint_and_help; +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `0.0 / 0.0`. + /// + /// **Why is this bad?** It's less readable than `f32::NAN` or `f64::NAN`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// let nan = 0.0f32 / 0.0; + /// + /// // Good + /// let nan = f32::NAN; + /// ``` + pub ZERO_DIVIDED_BY_ZERO, + complexity, + "usage of `0.0 / 0.0` to obtain NaN instead of `f32::NAN` or `f64::NAN`" +} + +declare_lint_pass!(ZeroDiv => [ZERO_DIVIDED_BY_ZERO]); + +impl<'tcx> LateLintPass<'tcx> for ZeroDiv { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // check for instances of 0.0/0.0 + if_chain! { + if let ExprKind::Binary(ref op, ref left, ref right) = expr.kind; + if let BinOpKind::Div = op.node; + // TODO - constant_simple does not fold many operations involving floats. + // That's probably fine for this lint - it's pretty unlikely that someone would + // do something like 0.0/(2.0 - 2.0), but it would be nice to warn on that case too. + if let Some(lhs_value) = constant_simple(cx, cx.typeck_results(), left); + if let Some(rhs_value) = constant_simple(cx, cx.typeck_results(), right); + if Constant::F32(0.0) == lhs_value || Constant::F64(0.0) == lhs_value; + if Constant::F32(0.0) == rhs_value || Constant::F64(0.0) == rhs_value; + then { + // since we're about to suggest a use of f32::NAN or f64::NAN, + // match the precision of the literals that are given. + let float_type = match (lhs_value, rhs_value) { + (Constant::F64(_), _) + | (_, Constant::F64(_)) => "f64", + _ => "f32" + }; + span_lint_and_help( + cx, + ZERO_DIVIDED_BY_ZERO, + expr.span, + "constant division of `0.0` with `0.0` will always result in NaN", + None, + &format!( + "consider using `{}::NAN` if you would like a constant representing NaN", + float_type, + ), + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs b/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs new file mode 100644 index 0000000000..adf7077e65 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs @@ -0,0 +1,85 @@ +use if_chain::if_chain; +use rustc_hir::{self as hir, HirId, ItemKind, Node}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{Adt, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; +use rustc_target::abi::LayoutOf as _; +use rustc_typeck::hir_ty_to_ty; + +use crate::utils::{is_normalizable, is_type_diagnostic_item, match_type, paths, span_lint_and_help}; + +declare_clippy_lint! { + /// **What it does:** Checks for maps with zero-sized value types anywhere in the code. + /// + /// **Why is this bad?** Since there is only a single value for a zero-sized type, a map + /// containing zero sized values is effectively a set. Using a set in that case improves + /// readability and communicates intent more clearly. + /// + /// **Known problems:** + /// * A zero-sized type cannot be recovered later if it contains private fields. + /// * This lints the signature of public items + /// + /// **Example:** + /// + /// ```rust + /// # use std::collections::HashMap; + /// fn unique_words(text: &str) -> HashMap<&str, ()> { + /// todo!(); + /// } + /// ``` + /// Use instead: + /// ```rust + /// # use std::collections::HashSet; + /// fn unique_words(text: &str) -> HashSet<&str> { + /// todo!(); + /// } + /// ``` + pub ZERO_SIZED_MAP_VALUES, + pedantic, + "usage of map with zero-sized value type" +} + +declare_lint_pass!(ZeroSizedMapValues => [ZERO_SIZED_MAP_VALUES]); + +impl LateLintPass<'_> for ZeroSizedMapValues { + fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) { + if_chain! { + if !hir_ty.span.from_expansion(); + if !in_trait_impl(cx, hir_ty.hir_id); + let ty = ty_from_hir_ty(cx, hir_ty); + if is_type_diagnostic_item(cx, ty, sym::hashmap_type) || match_type(cx, ty, &paths::BTREEMAP); + if let Adt(_, ref substs) = ty.kind(); + let ty = substs.type_at(1); + // Do this to prevent `layout_of` crashing, being unable to fully normalize `ty`. + if is_normalizable(cx, cx.param_env, ty); + if let Ok(layout) = cx.layout_of(ty); + if layout.is_zst(); + then { + span_lint_and_help(cx, ZERO_SIZED_MAP_VALUES, hir_ty.span, "map with zero-sized value type", None, "consider using a set instead"); + } + } + } +} + +fn in_trait_impl(cx: &LateContext<'_>, hir_id: HirId) -> bool { + let parent_id = cx.tcx.hir().get_parent_item(hir_id); + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_item(parent_id)) { + if let ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) = item.kind { + return true; + } + } + false +} + +fn ty_from_hir_ty<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Ty<'tcx> { + cx.maybe_typeck_results() + .and_then(|results| { + if results.hir_owner == hir_ty.hir_id.owner { + results.node_type_opt(hir_ty.hir_id) + } else { + None + } + }) + .unwrap_or_else(|| hir_ty_to_ty(cx.tcx, hir_ty)) +} diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml new file mode 100644 index 0000000000..9e07f140cf --- /dev/null +++ b/src/tools/clippy/clippy_utils/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "clippy_utils" +version = "0.1.52" +authors = ["The Rust Clippy Developers"] +edition = "2018" +publish = false + +[dependencies] +if_chain = "1.0.0" +itertools = "0.9" +regex-syntax = "0.6" +serde = { version = "1.0", features = ["derive"] } +smallvec = { version = "1", features = ["union"] } +toml = "0.5.3" +unicode-normalization = "0.1" +rustc-semver="1.1.0" + +[features] +internal-lints = [] + +[package.metadata.rust-analyzer] +# This crate uses #[feature(rustc_private)] +rustc_private = true diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs new file mode 100644 index 0000000000..ea9a910d1b --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs @@ -0,0 +1,573 @@ +//! Utilities for manipulating and extracting information from `rustc_ast::ast`. +//! +//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s. + +#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)] + +use crate::{both, over}; +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, *}; +use rustc_span::symbol::Ident; +use std::mem; + +pub mod ident_iter; +pub use ident_iter::IdentIter; + +pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool { + use BinOpKind::*; + matches!( + kind, + Sub | Div | Eq | Lt | Le | Gt | Ge | Ne | And | Or | BitXor | BitAnd | BitOr + ) +} + +/// Checks if each element in the first slice is contained within the latter as per `eq_fn`. +pub fn unordered_over(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool { + left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r))) +} + +pub fn eq_id(l: Ident, r: Ident) -> bool { + l.name == r.name +} + +pub fn eq_pat(l: &Pat, r: &Pat) -> bool { + use PatKind::*; + match (&l.kind, &r.kind) { + (Paren(l), _) => eq_pat(l, r), + (_, Paren(r)) => eq_pat(l, r), + (Wild, Wild) | (Rest, Rest) => true, + (Lit(l), Lit(r)) => eq_expr(l, r), + (Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)), + (Range(lf, lt, le), Range(rf, rt, re)) => { + eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node) + }, + (Box(l), Box(r)) + | (Ref(l, Mutability::Not), Ref(r, Mutability::Not)) + | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r), + (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)), + (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp), + (TupleStruct(lp, lfs), TupleStruct(rp, rfs)) => eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)), + (Struct(lp, lfs, lr), Struct(rp, rfs, rr)) => { + lr == rr && eq_path(lp, rp) && unordered_over(lfs, rfs, |lf, rf| eq_field_pat(lf, rf)) + }, + (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)), + (MacCall(l), MacCall(r)) => eq_mac_call(l, r), + _ => false, + } +} + +pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool { + match (l, r) { + (RangeEnd::Excluded, RangeEnd::Excluded) => true, + (RangeEnd::Included(l), RangeEnd::Included(r)) => { + matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq) + }, + _ => false, + } +} + +pub fn eq_field_pat(l: &PatField, r: &PatField) -> bool { + l.is_placeholder == r.is_placeholder + && eq_id(l.ident, r.ident) + && eq_pat(&l.pat, &r.pat) + && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) +} + +pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool { + l.position == r.position && eq_ty(&l.ty, &r.ty) +} + +pub fn eq_path(l: &Path, r: &Path) -> bool { + over(&l.segments, &r.segments, |l, r| eq_path_seg(l, r)) +} + +pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool { + eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r)) +} + +pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool { + match (l, r) { + (GenericArgs::AngleBracketed(l), GenericArgs::AngleBracketed(r)) => { + over(&l.args, &r.args, |l, r| eq_angle_arg(l, r)) + }, + (GenericArgs::Parenthesized(l), GenericArgs::Parenthesized(r)) => { + over(&l.inputs, &r.inputs, |l, r| eq_ty(l, r)) && eq_fn_ret_ty(&l.output, &r.output) + }, + _ => false, + } +} + +pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool { + match (l, r) { + (AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r), + (AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r), + _ => false, + } +} + +pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool { + match (l, r) { + (GenericArg::Lifetime(l), GenericArg::Lifetime(r)) => eq_id(l.ident, r.ident), + (GenericArg::Type(l), GenericArg::Type(r)) => eq_ty(l, r), + (GenericArg::Const(l), GenericArg::Const(r)) => eq_expr(&l.value, &r.value), + _ => false, + } +} + +pub fn eq_expr_opt(l: &Option>, r: &Option>) -> bool { + both(l, r, |l, r| eq_expr(l, r)) +} + +pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool { + match (l, r) { + (StructRest::Base(lb), StructRest::Base(rb)) => eq_expr(lb, rb), + (StructRest::Rest(_), StructRest::Rest(_)) | (StructRest::None, StructRest::None) => true, + _ => false, + } +} + +pub fn eq_expr(l: &Expr, r: &Expr) -> bool { + use ExprKind::*; + if !over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) { + return false; + } + match (&l.kind, &r.kind) { + (Paren(l), _) => eq_expr(l, r), + (_, Paren(r)) => eq_expr(l, r), + (Err, Err) => true, + (Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r), + (Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)), + (Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value), + (Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)), + (MethodCall(lc, la, _), MethodCall(rc, ra, _)) => eq_path_seg(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)), + (Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr), + (Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r), + (Lit(l), Lit(r)) => l.kind == r.kind, + (Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt), + (Let(lp, le), Let(rp, re)) => eq_pat(lp, rp) && eq_expr(le, re), + (If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re), + (While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt), + (ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => { + eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt) + }, + (Loop(lt, ll), Loop(rt, rl)) => eq_label(ll, rl) && eq_block(lt, rt), + (Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb), + (TryBlock(l), TryBlock(r)) => eq_block(l, r), + (Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r), + (Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re), + (Continue(ll), Continue(rl)) => eq_label(ll, rl), + (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2), + (AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv), + (Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp), + (Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, |l, r| eq_arm(l, r)), + (Closure(lc, la, lm, lf, lb, _), Closure(rc, ra, rm, rf, rb, _)) => { + lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(lb, rb) + }, + (Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb), + (Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt), + (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re), + (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp), + (MacCall(l), MacCall(r)) => eq_mac_call(l, r), + (Struct(lse), Struct(rse)) => { + eq_path(&lse.path, &rse.path) && + eq_struct_rest(&lse.rest, &rse.rest) && + unordered_over(&lse.fields, &rse.fields, |l, r| eq_field(l, r)) + }, + _ => false, + } +} + +pub fn eq_field(l: &ExprField, r: &ExprField) -> bool { + l.is_placeholder == r.is_placeholder + && eq_id(l.ident, r.ident) + && eq_expr(&l.expr, &r.expr) + && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) +} + +pub fn eq_arm(l: &Arm, r: &Arm) -> bool { + l.is_placeholder == r.is_placeholder + && eq_pat(&l.pat, &r.pat) + && eq_expr(&l.body, &r.body) + && eq_expr_opt(&l.guard, &r.guard) + && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) +} + +pub fn eq_label(l: &Option)` on an `Option` value. This can be done more directly by calling `map_or(, )` instead" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/resolver/types.rs:187:5 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/resolver/types.rs:261:5 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:113:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:130:9 clippy::single_match_else "you seem to be trying to use `match` for an equality check. Consider using `if`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:148:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:153:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:163:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:18:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:198:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:206:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:214:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:228:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:239:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:250:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:259:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:267:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:26:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:277:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:282:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:314:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:322:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:330:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:345:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:459:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/shell.rs:98:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:103:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:247:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:261:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:268:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:273:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:291:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:302:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:307:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:31:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:37:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:39:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:47:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:50:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:52:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:63:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:74:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/mod.rs:83:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:107:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:107:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:128:50 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:147:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:156:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:162:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:166:19 clippy::doc_markdown "you should put `SourceId` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:167:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:171:19 clippy::doc_markdown "you should put `SourceId` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:172:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:178:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:187:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:187:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:18:74 clippy::default_trait_access "calling `std::sync::Mutex::default()` is more clear than this expression" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:195:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:207:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:213:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:217:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:225:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:228:16 clippy::option_if_let_else "use Option::map_or_else instead of an if let/else" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:236:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:241:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:252:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:257:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:262:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:305:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:310:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:318:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:326:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:338:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:355:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:393:61 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:394:42 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:395:42 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:397:71 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:397:71 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:398:47 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:398:47 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:399:47 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:399:47 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:401:63 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:401:63 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:401:63 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:402:43 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:402:43 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:402:43 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:403:43 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:403:43 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:403:43 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:406:21 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:412:41 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:413:36 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:414:36 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:420:47 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:420:47 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:512:17 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:513:17 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:517:17 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:518:17 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:525:17 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:526:17 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:530:17 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:531:17 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:535:33 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:536:37 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:537:42 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:538:38 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:548:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/source/source_id.rs:597:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:103:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:123:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:150:1 clippy::too_many_lines "this function has too many lines (141/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:158:9 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:181:21 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:192:28 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:258:32 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:281:28 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:303:28 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:321:51 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:344:5 clippy::doc_markdown "you should put `FeatureValue` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:350:85 clippy::doc_markdown "you should put `FeatureValue` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:36:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:378:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:386:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:387:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:407:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:69:34 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:75:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:78:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:81:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:84:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:87:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:90:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:93:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:96:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/summary.rs:99:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:1056:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:113:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:1157:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:128:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:150:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:159:16 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:197:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:225:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:225:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:255:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:267:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:317:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:329:37 clippy::doc_markdown "you should put `VirtualManifest` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:410:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:440:9 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:511:32 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:561:25 clippy::non_ascii_literal "literal non-ASCII character detected" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:613:13 clippy::filter_map "called `filter_map(..).map(..)` on an `Iterator`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:615:22 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:762:27 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:784:17 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:849:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:849:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:893:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:906:24 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/core/workspace.rs:932:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:177:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:177:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:180:36 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:180:36 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:180:36 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:180:36 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:180:36 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:1:null clippy::cargo_common_metadata "package `cargo` is missing `package.categories` metadata" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:1:null clippy::cargo_common_metadata "package `cargo` is missing `package.keywords` metadata" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:1:null clippy::multiple_crate_versions "multiple versions for dependency `crossbeam-utils`: 0.6.6, 0.7.2" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:1:null clippy::multiple_crate_versions "multiple versions for dependency `hex`: 0.3.2, 0.4.0" +target/lintcheck/sources/cargo-0.49.0/src/cargo/lib.rs:1:null clippy::multiple_crate_versions "multiple versions for dependency `humantime`: 1.3.0, 2.0.0" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_clean.rs:205:23 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_clean.rs:27:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_clean.rs:27:1 clippy::too_many_lines "this function has too many lines (120/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:1078:14 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:109:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:119:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:1227:17 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:127:35 clippy::from_iter_instead_of_collect "usage of `FromIterator::from_iter`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:173:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:205:36 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:242:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:249:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:258:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:267:16 clippy::needless_question_mark "question mark operator is useless here" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:275:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:275:1 clippy::too_many_lines "this function has too many lines (219/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:468:9 clippy::default_trait_access "calling `std::collections::HashMap::default()` is more clear than this expression" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:548:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:556:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:574:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:583:21 clippy::doc_markdown "you should put `CompileFilter` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:584:5 clippy::fn_params_excessive_bools "more than 3 bools in function parameters" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:584:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:592:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:593:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:607:13 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:612:21 clippy::doc_markdown "you should put `CompileFilter` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:613:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:618:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:641:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:652:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:655:50 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:673:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:692:49 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:703:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:729:1 clippy::too_many_lines "this function has too many lines (205/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:82:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_compile.rs:874:69 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_doc.rs:20:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_fetch.rs:15:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_fetch.rs:27:46 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_generate_lockfile.rs:160:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_generate_lockfile.rs:175:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_generate_lockfile.rs:22:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_generate_lockfile.rs:37:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_generate_lockfile.rs:37:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_generate_lockfile.rs:37:1 clippy::too_many_lines "this function has too many lines (171/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:13:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:148:1 clippy::fn_params_excessive_bools "more than 3 bools in function parameters" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:148:1 clippy::too_many_lines "this function has too many lines (316/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:178:24 clippy::collapsible_else_if "this `else { if .. }` block can be collapsed" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:202:17 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:236:16 clippy::collapsible_else_if "this `else { if .. }` block can be collapsed" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:312:64 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:32:13 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:339:12 clippy::collapsible_else_if "this `else { if .. }` block can be collapsed" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:37:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:454:22 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:483:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:683:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_install.rs:708:5 clippy::manual_flatten "unnecessary `if let` since only the `Some` variant of the iterator element is used" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:101:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:245:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:251:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:367:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:405:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:489:5 clippy::doc_markdown "you should put `IgnoreList` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:525:47 clippy::doc_markdown "you should put `IgnoreList` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:525:9 clippy::doc_markdown "you should put `format_existing` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:572:34 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:623:1 clippy::too_many_lines "this function has too many lines (130/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:781:5 clippy::filter_map_next "called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling `.find_map(..)` instead" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_new.rs:800:16 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_output_metadata.rs:163:36 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_output_metadata.rs:27:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_output_metadata.rs:45:45 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_package.rs:144:1 clippy::too_many_lines "this function has too many lines (112/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_package.rs:207:13 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_package.rs:25:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_package.rs:307:54 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_package.rs:394:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_package.rs:425:61 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_package.rs:459:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_package.rs:66:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_package.rs:69:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_package.rs:93:20 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_pkgid.rs:5:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_read_manifest.rs:14:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_read_manifest.rs:171:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_read_manifest.rs:37:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_read_manifest.rs:37:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_read_manifest.rs:57:49 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_read_manifest.rs:69:37 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_run.rs:25:24 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_run.rs:35:9 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_run.rs:37:16 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_run.rs:53:9 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_run.rs:65:16 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_run.rs:82:23 clippy::implicit_clone "implicitly cloning a `PathBuf` by calling `to_path_buf` on its dereferenced type" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_run.rs:9:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_test.rs:16:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_test.rs:43:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_test.rs:84:17 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_uninstall.rs:14:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/cargo_uninstall.rs:7:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:147:9 clippy::doc_markdown "you should put `PackageId` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:233:21 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:244:22 clippy::doc_markdown "you should put `PackageId` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:244:63 clippy::doc_markdown "you should put `PackageId` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:253:17 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:370:5 clippy::unnecessary_wraps "this function's return value is unnecessary" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:505:8 clippy::map_unwrap_or "called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling `map_or_else(, )` instead" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:525:10 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:542:27 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:542:5 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:561:20 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:613:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:645:41 clippy::doc_markdown "you should put `BTreeSet` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:92:19 clippy::doc_markdown "you should put `InstallTracker` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:200:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:200:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:424:20 clippy::map_unwrap_or "called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:455:13 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:506:17 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:608:9 clippy::field_reassign_with_default "field assignment outside of initializer for an instance created with Default::default()" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:612:42 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:619:48 clippy::manual_strip "stripping a prefix manually" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:66:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:66:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:708:18 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/fix.rs:77:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/lockfile.rs:154:13 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/lockfile.rs:217:9 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/lockfile.rs:30:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/lockfile.rs:35:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/lockfile.rs:35:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/lockfile.rs:87:1 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/lockfile.rs:8:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/lockfile.rs:8:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:150:21 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:188:1 clippy::too_many_lines "this function has too many lines (130/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:212:32 clippy::if_not_else "unnecessary `!=` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:222:53 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:224:44 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:31:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:346:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:346:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:351:26 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:385:12 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:386:15 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:38:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:477:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:483:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:503:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:505:38 clippy::default_trait_access "calling `util::config::CargoHttpConfig::default()` is more clear than this expression" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:510:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:529:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:53:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:53:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:573:22 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:608:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:621:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:671:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:671:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:674:10 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:678:17 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:730:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:731:16 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:785:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:794:16 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:828:14 clippy::doc_markdown "you should put `SourceId` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/registry.rs:848:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:199:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:199:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:199:1 clippy::too_many_lines "this function has too many lines (137/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:241:28 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:28:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:384:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:417:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:589:9 clippy::shadow_unrelated "`keep` is being shadowed" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:58:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:58:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:602:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:75:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/resolve.rs:75:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/graph.rs:129:26 clippy::doc_markdown "you should put `PackageIds` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/graph.rs:152:15 clippy::match_on_vec_items "indexing into a vector may panic" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/graph.rs:173:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/graph.rs:234:46 clippy::filter_map "called `filter(..).flat_map(..)` on an `Iterator`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/graph.rs:328:44 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/graph.rs:330:50 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/graph.rs:563:35 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/mod.rs:112:11 clippy::non_ascii_literal "literal non-ASCII character detected" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/mod.rs:113:10 clippy::non_ascii_literal "literal non-ASCII character detected" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/mod.rs:114:10 clippy::non_ascii_literal "literal non-ASCII character detected" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/mod.rs:115:12 clippy::non_ascii_literal "literal non-ASCII character detected" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/mod.rs:126:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/mod.rs:21:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/mod.rs:21:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/mod.rs:360:30 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/tree/mod.rs:58:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/vendor.rs:14:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/vendor.rs:215:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/vendor.rs:21:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/vendor.rs:21:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/vendor.rs:314:34 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/vendor.rs:320:29 clippy::case_sensitive_file_extension_comparisons "case-sensitive file extension comparison" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/vendor.rs:320:60 clippy::case_sensitive_file_extension_comparisons "case-sensitive file extension comparison" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/vendor.rs:324:13 clippy::match_wildcard_for_single_variants "wildcard match will miss any future added variants" +target/lintcheck/sources/cargo-0.49.0/src/cargo/ops/vendor.rs:70:1 clippy::too_many_lines "this function has too many lines (175/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/config.rs:102:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/config.rs:111:28 clippy::needless_question_mark "question mark operator is useless here" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/config.rs:133:48 clippy::needless_question_mark "question mark operator is useless here" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/config.rs:135:67 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/config.rs:206:36 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/config.rs:282:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/config.rs:70:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/config.rs:81:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/config.rs:97:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/directory.rs:14:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/directory.rs:90:56 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/source.rs:14:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/source.rs:25:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/source.rs:49:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/source.rs:53:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/source.rs:53:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/source.rs:69:20 clippy::comparison_to_empty "comparison to empty slice" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:1025:19 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:1157:36 clippy::case_sensitive_file_extension_comparisons "case-sensitive file extension comparison" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:1158:9 clippy::manual_strip "stripping a suffix manually" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:176:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:180:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:184:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:188:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:242:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:253:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:262:13 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:289:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:294:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:298:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:308:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:472:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:489:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:503:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:528:28 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:537:21 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:588:1 clippy::too_many_lines "this function has too many lines (135/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:692:9 clippy::vec_init_then_push "calls to `push` immediately after creation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:758:9 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/git/utils.rs:858:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:129:44 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:143:44 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:15:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:282:50 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:313:21 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:314:21 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:319:21 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:339:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:339:9 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:380:9 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:419:50 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:429:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:460:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:63:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:77:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/path.rs:98:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/index.rs:117:23 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/index.rs:121:70 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/index.rs:167:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/index.rs:215:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/index.rs:324:23 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/index.rs:468:40 clippy::doc_markdown "you should put `SourceId` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/index.rs:590:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/index.rs:648:17 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/index.rs:736:1 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/index.rs:95:37 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/local.rs:12:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:192:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:203:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:229:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:372:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:373:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:375:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:381:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:382:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:383:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:384:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:452:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:582:20 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/mod.rs:621:9 clippy::if_not_else "unnecessary `!=` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/remote.rs:139:17 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/remote.rs:32:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/registry/remote.rs:72:13 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/replaced.rs:12:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/sources/replaced.rs:5:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/canonical_url.rs:19:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/canonical_url.rs:19:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/canonical_url.rs:50:41 clippy::case_sensitive_file_extension_comparisons "case-sensitive file extension comparison" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/canonical_url.rs:65:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:218:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:222:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:234:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:249:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:264:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:279:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:298:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:320:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:328:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:352:13 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:363:13 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:378:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:387:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:387:5 clippy::too_many_lines "this function has too many lines (104/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:39:20 clippy::doc_markdown "you should put `arg_package_spec` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:504:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:516:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:530:40 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:531:43 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:536:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:556:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:575:49 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:580:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:631:18 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:638:18 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:647:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:651:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:662:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/command_prelude.rs:665:51 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/de.rs:420:16 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/de.rs:46:25 clippy::doc_markdown "you should put `CV::List` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/de.rs:47:24 clippy::doc_markdown "you should put `ConfigSeqAccess` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/de.rs:527:53 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/de.rs:530:53 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/de.rs:532:68 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/key.rs:11:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/key.rs:69:9 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:100:71 clippy::doc_markdown "you should put `OptValue` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:100:71 clippy::doc_markdown "you should put `OptValue` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:100:71 clippy::doc_markdown "you should put `OptValue` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1049:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1064:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1090:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1166:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1179:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1181:33 clippy::needless_question_mark "question mark operator is useless here" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1184:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1186:33 clippy::needless_question_mark "question mark operator is useless here" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1189:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1191:33 clippy::needless_question_mark "question mark operator is useless here" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1203:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1211:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1216:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1225:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1229:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:124:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1254:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1279:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1281:9 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1323:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1339:39 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1344:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1420:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1553:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1560:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1567:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1574:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1581:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1588:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1598:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1619:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1623:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1623:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1623:64 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1649:9 clippy::option_if_let_else "use Option::map_or_else instead of an if let/else" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1699:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1730:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1757:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1770:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1778:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1804:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1896:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:1901:5 clippy::doc_markdown "you should put `StringList` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:214:13 clippy::match_wildcard_for_single_variants "wildcard match will miss any future added variants" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:259:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:298:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:311:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:318:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:353:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:401:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:411:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:419:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:431:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:449:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:454:16 clippy::option_if_let_else "use Option::map_or instead of an if let/else" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:547:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:556:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:582:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:595:20 clippy::doc_markdown "you should put `StringList` between ticks in the documentation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:689:20 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:699:5 clippy::fn_params_excessive_bools "more than 3 bools in function parameters" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:699:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:719:58 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:748:30 clippy::manual_map "manual implementation of `Option::map`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/mod.rs:816:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/path.rs:10:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/path.rs:14:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/path.rs:48:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/target.rs:12:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/target.rs:24:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/value.rs:29:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/value.rs:70:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/value.rs:80:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/config/value.rs:81:9 clippy::match_like_matches_macro "match expression looks like `matches!` macro" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/cpu.rs:11:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/cpu.rs:22:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/cpu.rs:82:25 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/cpu.rs:82:9 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/dependency_queue.rs:109:27 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/dependency_queue.rs:125:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/dependency_queue.rs:151:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/dependency_queue.rs:156:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/dependency_queue.rs:168:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/dependency_queue.rs:46:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/dependency_queue.rs:91:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/diagnostic_server.rs:218:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/diagnostic_server.rs:230:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/diagnostic_server.rs:242:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/diagnostic_server.rs:58:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/diagnostic_server.rs:96:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/diagnostic_server.rs:96:5 clippy::too_many_lines "this function has too many lines (110/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/diagnostic_server.rs:99:21 clippy::shadow_unrelated "`msg` is being shadowed" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:101:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:143:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:150:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:15:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:237:5 clippy::pub_enum_variant_names "variant name ends with the enum's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:245:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:321:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:328:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:356:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:391:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:392:13 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:465:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:473:5 clippy::manual_range_contains "manual `RangeInclusive::contains` implementation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/errors.rs:66:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:115:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:11:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:134:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:142:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:150:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:156:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:170:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:192:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:29:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:29:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:321:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:335:23 clippy::cast_possible_truncation "casting `i64` to `u32` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:335:23 clippy::cast_sign_loss "casting `i64` to `u32` may lose the sign of the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:335:44 clippy::cast_possible_truncation "casting `i64` to `u32` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:379:35 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:37:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:43:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:43:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:52:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:52:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/flock.rs:96:17 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/graph.rs:10:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/graph.rs:41:51 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/graph.rs:45:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hasher.rs:12:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hasher.rs:9:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:10:9 clippy::cast_possible_truncation "casting `u64` to `u8` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:11:9 clippy::cast_possible_truncation "casting `u64` to `u8` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:12:9 clippy::cast_possible_truncation "casting `u64` to `u8` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:13:9 clippy::cast_possible_truncation "casting `u64` to `u8` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:14:9 clippy::cast_possible_truncation "casting `u64` to `u8` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:15:9 clippy::cast_possible_truncation "casting `u64` to `u8` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:25:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:6:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:6:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:8:9 clippy::cast_possible_truncation "casting `u64` to `u8` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/hex.rs:9:9 clippy::cast_possible_truncation "casting `u64` to `u8` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/important_paths.rs:23:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/important_paths.rs:6:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/interning.rs:66:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/interning.rs:66:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/interning.rs:77:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/into_url.rs:10:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/into_url_with_base.rs:9:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/job.rs:20:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/lev_distance.rs:3:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/lockserver.rs:111:32 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/lockserver.rs:158:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/lockserver.rs:46:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/lockserver.rs:58:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/lockserver.rs:62:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/mod.rs:68:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/mod.rs:79:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/network.rs:12:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/network.rs:19:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/network.rs:84:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:109:12 clippy::redundant_else "redundant else block" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:114:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:121:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:125:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:130:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:14:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:14:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:151:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:167:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:173:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:178:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:185:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:199:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:215:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:228:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:251:9 clippy::option_if_let_else "use Option::map_or instead of an if let/else" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:267:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:276:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:29:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:303:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:312:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:346:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:415:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:445:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:459:45 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:469:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:469:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:514:5 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:54:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:61:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:63:19 clippy::option_if_let_else "use Option::map_or_else instead of an if let/else" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:88:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/paths.rs:93:31 clippy::comparison_to_empty "comparison to empty slice" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:106:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:111:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:122:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:132:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:152:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:185:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:190:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:218:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:218:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:278:22 clippy::inconsistent_struct_constructor "inconsistent struct constructor" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:307:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/process_builder.rs:343:39 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:122:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:136:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:15:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:249:19 clippy::cast_precision_loss "casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:249:34 clippy::cast_precision_loss "casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:250:19 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:263:22 clippy::cast_precision_loss "casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:264:22 clippy::cast_possible_truncation "casting `f64` to `usize` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:264:22 clippy::cast_sign_loss "casting `f64` to `usize` may lose the sign of the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:269:17 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:272:17 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:274:17 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:280:13 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:282:9 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:89:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/progress.rs:97:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/queue.rs:25:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/queue.rs:36:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/queue.rs:42:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/queue.rs:52:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/queue.rs:69:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/read2.rs:11:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/read2.rs:31:17 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/restricted_names.rs:13:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/restricted_names.rs:26:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/restricted_names.rs:35:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/restricted_names.rs:45:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/restricted_names.rs:87:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/restricted_names.rs:87:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/restricted_names.rs:89:21 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/restricted_names.rs:8:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/rustc.rs:103:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/rustc.rs:103:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/rustc.rs:114:5 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/rustc.rs:115:5 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/rustc.rs:162:17 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/rustc.rs:39:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/sha256.rs:10:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/sha256.rs:16:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/sha256.rs:20:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/sha256.rs:31:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/sha256.rs:40:24 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/to_semver.rs:5:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:1005:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:1005:5 clippy::too_many_lines "this function has too many lines (282/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:1094:36 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:1121:13 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:1197:32 clippy::map_unwrap_or "called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling `map_or_else(, )` instead" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:124:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:1504:9 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:1526:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:1582:19 clippy::default_trait_access "calling `util::toml::DetailedTomlDependency::default()` is more clear than this expression" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:1598:5 clippy::too_many_lines "this function has too many lines (153/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:1687:33 clippy::unnecessary_lazy_evaluations "unnecessary closure used to substitute value for `Option::None`" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:178:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:248:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:274:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:277:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:281:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:285:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:294:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:31:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:381:35 clippy::cast_possible_truncation "casting `i64` to `u32` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:381:35 clippy::cast_sign_loss "casting `i64` to `u32` may lose the sign of the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:388:35 clippy::cast_possible_truncation "casting `u64` to `u32` may truncate the value" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:398:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:450:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:536:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:783:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:824:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:834:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:83:42 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:852:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:852:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:852:5 clippy::too_many_lines "this function has too many lines (138/100)" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:962:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:979:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:98:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/mod.rs:999:23 clippy::default_trait_access "calling `util::toml::DetailedTomlDependency::default()` is more clear than this expression" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/targets.rs:112:27 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/targets.rs:325:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/targets.rs:586:21 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/targets.rs:593:42 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/targets.rs:605:19 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/toml/targets.rs:612:42 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/vcs.rs:10:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/vcs.rs:33:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/vcs.rs:37:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/vcs.rs:43:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/vcs.rs:47:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/vcs.rs:59:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/vcs.rs:66:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/workspace.rs:52:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/workspace.rs:56:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/workspace.rs:60:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cargo-0.49.0/src/cargo/util/workspace.rs:64:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cfg-expr-0.7.1/src/error.rs:107:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/cfg-expr-0.7.1/src/error.rs:5:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/cfg-expr-0.7.1/src/error.rs:74:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/cfg-expr-0.7.1/src/error.rs:91:24 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/lexer.rs:102:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/lexer.rs:125:33 clippy::redundant_slicing "redundant slicing of the whole range" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/lexer.rs:4:5 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/lexer.rs:58:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/lexer.rs:76:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/lexer.rs:97:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/mod.rs:351:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/mod.rs:464:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/mod.rs:57:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/mod.rs:586:33 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/mod.rs:599:32 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/mod.rs:609:9 clippy::manual_map "manual implementation of `Option::map`" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:116:31 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:124:36 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:17:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:17:5 clippy::too_many_lines "this function has too many lines (345/100)" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:22:13 clippy::shadow_unrelated "`original` is being shadowed" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:243:36 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:254:34 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:25:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:390:9 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:392:17 clippy::if_not_else "unnecessary `!=` operation" +target/lintcheck/sources/cfg-expr-0.7.1/src/expr/parser.rs:67:13 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/cfg-expr-0.7.1/src/lib.rs:1:null clippy::cargo_common_metadata "package `cfg-expr` is missing `package.categories` metadata" +target/lintcheck/sources/cfg-expr-0.7.1/src/targets/builtins.rs:11:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/cfg-expr-0.7.1/src/targets/mod.rs:139:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cfg-expr-0.7.1/src/targets/mod.rs:153:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/cxx-1.0.32/src/rust_string.rs:15:20 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/rust_string.rs:19:24 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/rust_vec.rs:21:20 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/rust_vec.rs:25:24 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/rust_vec.rs:74:35 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/rust_vec.rs:78:39 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/rust_vec.rs:90:20 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/rust_vec.rs:94:24 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/shared_ptr.rs:108:20 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/shared_ptr.rs:165:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cxx-1.0.32/src/shared_ptr.rs:54:20 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/shared_ptr.rs:62:20 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/shared_ptr.rs:75:20 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/unique_ptr.rs:185:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cxx-1.0.32/src/unwind.rs:22:5 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/cxx-1.0.32/src/weak_ptr.rs:47:20 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/cxx-1.0.32/src/weak_ptr.rs:80:20 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/iron-0.6.1/src/error.rs:24:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/iron-0.6.1/src/iron.rs:105:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/iron-0.6.1/src/iron.rs:119:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/iron.rs:133:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/iron.rs:143:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/iron.rs:149:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/iron-0.6.1/src/iron.rs:167:49 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/iron.rs:196:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/iron-0.6.1/src/iron.rs:80:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/iron.rs:85:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/iron.rs:90:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/lib.rs:1:null clippy::cargo_common_metadata "package `iron` is missing `package.categories` metadata" +target/lintcheck/sources/iron-0.6.1/src/lib.rs:1:null clippy::cargo_common_metadata "package `iron` is missing `package.keywords` metadata" +target/lintcheck/sources/iron-0.6.1/src/lib.rs:1:null clippy::multiple_crate_versions "multiple versions for dependency `log`: 0.3.9, 0.4.8" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:137:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:150:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:152:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:159:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:171:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:173:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:182:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:192:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:217:25 clippy::doc_markdown "you should put `ChainBuilder` between ticks in the documentation" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:264:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:328:20 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:360:16 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:368:33 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:428:40 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:434:40 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/middleware/mod.rs:444:40 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/modifiers.rs:132:14 clippy::expect_fun_call "use of `expect` followed by a function call" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:113:24 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:121:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:123:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:124:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:126:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:128:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:153:69 clippy::doc_markdown "you should put `HttpReader` between ticks in the documentation" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:154:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:32:1 clippy::manual_non_exhaustive "this seems like a manual implementation of the non-exhaustive pattern" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:75:34 clippy::doc_markdown "you should put `HttpRequest` between ticks in the documentation" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:77:39 clippy::doc_markdown "you should put `HttpRequest` between ticks in the documentation" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:78:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:82:13 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:83:29 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/request/mod.rs:85:24 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:109:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:117:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:129:1 clippy::from_over_into "an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:21:14 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:22:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:31:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:47:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:52:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:57:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:57:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:63:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:63:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:73:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:73:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:83:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/request/url.rs:96:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/response.rs:121:19 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/iron-0.6.1/src/response.rs:125:43 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/iron-0.6.1/src/response.rs:139:41 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/iron-0.6.1/src/response.rs:24:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/iron-0.6.1/src/response.rs:95:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/iron-0.6.1/src/response.rs:95:5 clippy::new_without_default "you should consider adding a `Default` implementation for `response::Response`" +target/lintcheck/sources/libc-0.2.81/build.rs:114:19 clippy::map_unwrap_or "called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead" +target/lintcheck/sources/libc-0.2.81/build.rs:124:5 clippy::question_mark "this block may be rewritten with the `?` operator" +target/lintcheck/sources/libc-0.2.81/build.rs:133:5 clippy::question_mark "this block may be rewritten with the `?` operator" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:120:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:17 clippy::missing_safety_doc "unsafe function's docs miss `# Safety` section" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:17 clippy::missing_safety_doc "unsafe function's docs miss `# Safety` section" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:17 clippy::missing_safety_doc "unsafe function's docs miss `# Safety` section" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:243:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:259:null clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/macros.rs:84:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:428:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:429:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:431:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:432:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:433:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:434:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:595:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:596:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:597:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:622:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:673:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:696:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:697:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:698:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:699:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:712:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:721:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:722:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:723:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:751:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:752:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:753:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:754:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:755:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:756:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:757:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:758:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:759:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:760:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:768:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:769:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:771:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:772:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:773:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:774:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:775:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:776:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:777:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:778:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:779:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:780:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:781:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:782:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:783:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:784:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:785:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:786:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:787:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:788:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:789:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:790:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:791:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:792:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:794:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:795:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:796:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:797:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:798:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:799:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:800:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:801:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:803:27 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:804:28 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:805:28 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:806:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:807:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:808:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:809:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:810:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:811:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:812:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:813:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:814:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:815:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:816:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:817:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:818:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:821:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:822:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:823:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:824:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:825:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:826:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:827:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:828:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:829:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:830:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:831:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:832:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:833:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:834:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:835:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:836:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:841:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:842:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:843:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/b64/x86_64/mod.rs:844:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:1120:38 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:178:34 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:291:5 clippy::missing_safety_doc "unsafe function's docs miss `# Safety` section" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:291:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:299:11 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:302:5 clippy::missing_safety_doc "unsafe function's docs miss `# Safety` section" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:302:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:312:11 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:328:9 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:352:20 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:355:13 clippy::missing_safety_doc "unsafe function's docs miss `# Safety` section" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:355:13 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:359:13 clippy::missing_safety_doc "unsafe function's docs miss `# Safety` section" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:359:13 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:363:13 clippy::missing_safety_doc "unsafe function's docs miss `# Safety` section" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:363:13 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:367:13 clippy::missing_safety_doc "unsafe function's docs miss `# Safety` section" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:367:13 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:371:13 clippy::missing_safety_doc "unsafe function's docs miss `# Safety` section" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:371:13 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:534:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:645:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:727:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:728:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:729:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:731:44 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:732:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:733:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:734:43 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:735:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:736:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:737:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:738:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:741:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:742:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:743:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:744:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:745:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:746:43 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:747:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:748:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:749:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:750:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:751:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:752:43 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:753:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:755:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:756:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:757:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:758:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:759:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:761:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:762:44 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:763:45 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:764:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:765:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:766:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:767:44 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:768:44 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:769:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:770:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:771:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:772:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:773:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:774:45 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:775:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:776:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:803:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:841:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:842:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:982:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/gnu/mod.rs:984:46 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1209:36 clippy::cast_possible_truncation "casting `i32` to `i16` may truncate the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1210:36 clippy::cast_possible_truncation "casting `i32` to `i16` may truncate the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1235:39 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1236:41 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1274:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1324:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1333:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1334:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1346:34 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1346:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1346:34 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1347:37 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1347:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1347:37 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1348:36 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1348:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1348:36 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1349:37 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1349:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1349:37 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1350:35 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1350:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1350:35 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1351:36 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1351:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1351:36 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1352:31 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1352:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1352:31 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1419:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1420:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1421:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1422:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1423:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1490:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1561:46 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1562:45 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1567:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1568:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1586:26 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1587:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1588:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1589:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1897:38 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1898:51 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1900:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1969:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1970:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1971:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1972:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1973:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1974:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1975:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1976:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1977:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1978:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1979:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1980:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1981:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1982:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1983:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1984:38 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1985:38 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1986:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1987:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1988:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1989:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1990:38 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1991:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1992:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1993:38 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1994:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1995:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1996:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1997:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1998:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:1999:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2000:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2001:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2002:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2003:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2004:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2005:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2032:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2033:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2034:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2035:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2036:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2037:28 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2038:27 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2039:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2041:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2042:28 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2043:27 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2044:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2045:27 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2046:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2048:28 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2049:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2050:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2051:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2052:26 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2053:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2318:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2321:38 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2331:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2487:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2488:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2489:43 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2490:43 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2491:43 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2493:47 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2494:44 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2495:46 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2496:47 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2497:49 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2498:48 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2499:50 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2500:45 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2572:9 clippy::needless_return "unneeded `return` statement" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2578:20 clippy::zero_ptr "`0 as *mut _` detected" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2588:13 clippy::zero_ptr "`0 as *mut _` detected" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2590:13 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2596:52 clippy::used_underscore_binding "used binding `_dummy` which is prefixed with an underscore. A leading underscore signals that a binding will not be used" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2597:11 clippy::cast_sign_loss "casting `i32` to `usize` may lose the sign of the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2601:21 clippy::explicit_iter_loop "it is more concise to loop over references to containers instead of using explicit iteration methods" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2611:9 clippy::unused_unit "unneeded unit expression" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2619:9 clippy::unused_unit "unneeded unit expression" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2634:9 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2647:25 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2648:25 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2649:9 clippy::cast_possible_truncation "casting `u64` to `u32` may truncate the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2654:18 clippy::identity_op "the operation is ineffective. Consider reducing it to `(dev & 0x00000000000000ff)`" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2654:25 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2655:25 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2656:9 clippy::cast_possible_truncation "casting `u64` to `u32` may truncate the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2660:21 clippy::cast_lossless "casting `u32` to `u64` may become silently lossy if you later change the type" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2661:21 clippy::cast_lossless "casting `u32` to `u64` may become silently lossy if you later change the type" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2663:25 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2664:25 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2665:16 clippy::identity_op "the operation is ineffective. Consider reducing it to `(minor & 0x000000ff)`" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2665:25 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:2666:25 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:42:1 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/linux/mod.rs:954:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1000:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1001:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1002:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1016:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1017:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1018:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1019:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1020:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1029:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1030:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1031:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1032:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1033:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1034:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1035:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1041:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1042:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1043:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1044:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1045:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1046:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1047:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1048:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1049:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1050:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1051:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1053:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1054:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1055:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1056:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1057:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1058:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1059:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1060:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1073:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1074:43 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1075:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1076:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1077:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1078:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1079:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1080:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1081:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1082:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1083:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1084:38 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1086:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1087:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1089:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1090:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1091:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1094:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1095:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1096:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1097:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1098:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1099:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1100:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1101:38 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1102:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1105:44 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1106:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1107:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1108:42 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1109:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1110:46 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1111:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1112:44 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1113:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1114:47 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1115:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1126:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1127:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1128:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1179:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1180:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:11:1 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1218:27 clippy::identity_op "the operation is ineffective. Consider reducing it to `IPOPT_CONTROL`" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1314:9 clippy::precedence "operator precedence can trip the unwary" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1321:13 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1323:13 clippy::zero_ptr "`0 as *mut _` detected" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1332:9 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1337:9 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1341:18 clippy::cast_sign_loss "casting `i32` to `usize` may lose the sign of the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1344:9 clippy::needless_return "unneeded `return` statement" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1348:18 clippy::cast_sign_loss "casting `i32` to `usize` may lose the sign of the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1350:9 clippy::needless_return "unneeded `return` statement" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1354:18 clippy::cast_sign_loss "casting `i32` to `usize` may lose the sign of the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1357:9 clippy::needless_return "unneeded `return` statement" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1361:21 clippy::explicit_iter_loop "it is more concise to loop over references to containers instead of using explicit iteration methods" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1381:9 clippy::cast_possible_truncation "casting `i32` to `i8` may truncate the value" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:1389:9 clippy::verbose_bit_mask "bit mask could be simplified with a call to `trailing_zeros`" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:446:31 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:591:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:592:38 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:593:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:594:33 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:595:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:596:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:597:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:598:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:599:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:600:34 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:601:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:602:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:607:37 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:608:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:764:35 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:765:39 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/linux_like/mod.rs:991:30 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:198:29 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:199:28 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:201:35 clippy::unnecessary_cast "casting integer literal to `usize` is unnecessary" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:202:35 clippy::unnecessary_cast "casting integer literal to `usize` is unnecessary" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:282:40 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:284:41 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:285:36 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:34:10 clippy::upper_case_acronyms "name `DIR` contains a capitalized acronym" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:36:1 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:386:10 clippy::upper_case_acronyms "name `FILE` contains a capitalized acronym" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:388:1 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/libc-0.2.81/src/unix/mod.rs:396:1 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1047:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1053:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1059:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1093:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1093:5 clippy::new_without_default "you should consider adding a `Default` implementation for `MetadataBuilder<'a>`" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1118:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1177:1 clippy::inline_always "you have declared `#[inline(always)]` on `max_level`. This is usually a bad idea" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1178:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1306:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1358:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1359:5 clippy::if_not_else "unnecessary `!=` operation" +target/lintcheck/sources/log-0.4.11/src/lib.rs:1407:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:356:1 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/log-0.4.11/src/lib.rs:448:12 clippy::manual_range_contains "manual `RangeInclusive::contains` implementation" +target/lintcheck/sources/log-0.4.11/src/lib.rs:500:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:506:28 clippy::trivially_copy_pass_by_ref "this argument (8 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/log-0.4.11/src/lib.rs:506:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/log-0.4.11/src/lib.rs:506:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:538:1 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type" +target/lintcheck/sources/log-0.4.11/src/lib.rs:653:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:661:21 clippy::trivially_copy_pass_by_ref "this argument (8 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/log-0.4.11/src/lib.rs:661:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:677:44 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/log-0.4.11/src/lib.rs:758:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:764:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:770:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:776:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:782:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:788:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:794:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:803:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:809:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:818:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:908:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/log-0.4.11/src/lib.rs:908:5 clippy::new_without_default "you should consider adding a `Default` implementation for `RecordBuilder<'a>`" +target/lintcheck/sources/log-0.4.11/src/lib.rs:995:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/detection.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:108:17 clippy::match_wildcard_for_single_variants "wildcard match will miss any future added variants" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:269:20 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:430:24 clippy::trivially_copy_pass_by_ref "this argument (0 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:437:23 clippy::trivially_copy_pass_by_ref "this argument (0 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:437:23 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:471:17 clippy::trivially_copy_pass_by_ref "this argument (0 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:471:17 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:654:5 clippy::manual_range_contains "manual `RangeInclusive::contains` implementation" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:655:12 clippy::manual_range_contains "manual `RangeInclusive::contains` implementation" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:661:5 clippy::manual_range_contains "manual `RangeInclusive::contains` implementation" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:662:12 clippy::manual_range_contains "manual `RangeInclusive::contains` implementation" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:664:12 clippy::manual_range_contains "manual `RangeInclusive::contains` implementation" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:674:37 clippy::manual_range_contains "manual `RangeInclusive::contains` implementation" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:678:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:85:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/proc-macro2-1.0.24/src/fallback.rs:882:43 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:1017:9 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:1081:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:1099:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:1117:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:1135:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:1141:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:1146:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:1151:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:1156:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:152:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:157:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:373:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:383:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:397:24 clippy::trivially_copy_pass_by_ref "this argument (4 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:397:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:403:23 clippy::trivially_copy_pass_by_ref "this argument (4 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:403:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:418:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:425:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:464:17 clippy::trivially_copy_pass_by_ref "this argument (4 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:500:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:626:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:633:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:641:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:652:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:662:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:672:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:734:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:743:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:752:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:757:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:788:19 clippy::doc_markdown "you should put `XID_Start` between ticks in the documentation" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:788:69 clippy::doc_markdown "you should put `XID_Continue` between ticks in the documentation" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:891:36 clippy::doc_markdown "you should put `syn::parse_str` between ticks in the documentation" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:894:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:911:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/lib.rs:996:9 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/proc-macro2-1.0.24/src/parse.rs:552:5 clippy::while_let_on_iterator "this loop could be written as a `for` loop" +target/lintcheck/sources/proc-macro2-1.0.24/src/parse.rs:584:21 clippy::manual_range_contains "manual `RangeInclusive::contains` implementation" +target/lintcheck/sources/proc-macro2-1.0.24/src/parse.rs:602:20 clippy::map_unwrap_or "called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead" +target/lintcheck/sources/proc-macro2-1.0.24/src/parse.rs:696:29 clippy::cast_lossless "casting `u8` to `u64` may become silently lossy if you later change the type" +target/lintcheck/sources/proc-macro2-1.0.24/src/parse.rs:702:34 clippy::cast_lossless "casting `u8` to `u64` may become silently lossy if you later change the type" +target/lintcheck/sources/proc-macro2-1.0.24/src/parse.rs:708:34 clippy::cast_lossless "casting `u8` to `u64` may become silently lossy if you later change the type" +target/lintcheck/sources/proc-macro2-1.0.24/src/parse.rs:793:5 clippy::vec_init_then_push "calls to `push` immediately after creation" +target/lintcheck/sources/proc-macro2-1.0.24/src/parse.rs:803:15 clippy::explicit_iter_loop "it is more concise to loop over references to containers instead of using explicit iteration methods" +target/lintcheck/sources/proc-macro2-1.0.24/src/parse.rs:808:15 clippy::explicit_iter_loop "it is more concise to loop over references to containers instead of using explicit iteration methods" +target/lintcheck/sources/proc-macro2-1.0.24/src/wrapper.rs:415:24 clippy::trivially_copy_pass_by_ref "this argument (4 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/proc-macro2-1.0.24/src/wrapper.rs:429:23 clippy::trivially_copy_pass_by_ref "this argument (4 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/proc-macro2-1.0.24/src/wrapper.rs:492:17 clippy::trivially_copy_pass_by_ref "this argument (4 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:158:15 clippy::cast_precision_loss "casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:175:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:183:5 clippy::too_many_lines "this function has too many lines (115/100)" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:207:16 clippy::collapsible_else_if "this `else { if .. }` block can be collapsed" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:271:67 clippy::cast_precision_loss "casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:376:29 clippy::cast_precision_loss "casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:381:44 clippy::cast_precision_loss "casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:453:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:540:14 clippy::cast_possible_truncation "casting `f64` to `f32` may truncate the value" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:551:5 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:584:39 clippy::cast_precision_loss "casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:59:26 clippy::unsafe_derive_deserialize "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:61:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:627:39 clippy::cast_precision_loss "casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:674:47 clippy::cast_precision_loss "casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/puffin-02dd4a3/puffin-imgui/src/ui.rs:690:9 clippy::cast_precision_loss "casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/data.rs:102:25 clippy::cast_possible_truncation "casting `usize` to `u8` may truncate the value" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/data.rs:112:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/data.rs:116:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/data.rs:137:24 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/data.rs:177:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/data.rs:211:21 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/data.rs:24:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:113:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:147:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:147:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:165:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:200:21 clippy::default_trait_access "calling `Stream::default()` is more clear than this expression" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:257:78 clippy::default_trait_access "calling `std::cell::RefCell::default()` is more clear than this expression" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:297:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:302:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:308:28 clippy::default_trait_access "calling `FullProfileData::default()` is more clear than this expression" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:316:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:321:5 clippy::cast_possible_truncation "casting `u128` to `i64` may truncate the value" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:348:28 clippy::default_trait_access "calling `std::marker::PhantomData::default()` is more clear than this expression" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:359:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:375:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:376:5 clippy::option_if_let_else "use Option::map_or instead of an if let/else" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:377:9 clippy::option_if_let_else "use Option::map_or instead of an if let/else" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:406:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:408:5 clippy::option_if_let_else "use Option::map_or instead of an if let/else" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:69:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:73:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/lib.rs:77:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/merge.rs:21:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/merge.rs:28:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/merge.rs:28:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/merge.rs:35:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/merge.rs:35:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/merge.rs:64:43 clippy::default_trait_access "calling `std::vec::Vec::default()` is more clear than this expression" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/merge.rs:65:54 clippy::default_trait_access "calling `std::collections::HashMap::default()` is more clear than this expression" +target/lintcheck/sources/puffin-02dd4a3/puffin/src/merge.rs:9:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/quote-1.0.7/src/ext.rs:10:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/quote-1.0.7/src/ext.rs:7:5 clippy::doc_markdown "you should put `TokenStream` between ticks in the documentation" +target/lintcheck/sources/quote-1.0.7/src/ident_fragment.rs:13:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/quote-1.0.7/src/ident_fragment.rs:51:31 clippy::manual_strip "stripping a prefix manually" +target/lintcheck/sources/quote-1.0.7/src/runtime.rs:52:5 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/quote-1.0.7/src/runtime.rs:63:5 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/quote-1.0.7/src/runtime.rs:66:33 clippy::doc_markdown "you should put `DoesNotHaveIter` between ticks in the documentation" +target/lintcheck/sources/quote-1.0.7/src/runtime.rs:80:5 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/bernoulli.rs:103:20 clippy::cast_possible_truncation "casting `f64` to `u64` may truncate the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/bernoulli.rs:103:20 clippy::cast_sign_loss "casting `f64` to `u64` may lose the sign of the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/bernoulli.rs:116:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand-0.7.3/src/distributions/bernoulli.rs:123:21 clippy::cast_possible_truncation "casting `f64` to `u64` may truncate the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/bernoulli.rs:123:21 clippy::cast_sign_loss "casting `f64` to `u64` may lose the sign of the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/bernoulli.rs:63:26 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/bernoulli.rs:63:27 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/rand-0.7.3/src/distributions/bernoulli.rs:67:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/bernoulli.rs:95:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand-0.7.3/src/distributions/bernoulli.rs:96:13 clippy::manual_range_contains "manual `Range::contains` implementation" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:107:23 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:112:44 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:116:13 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:150:28 clippy::redundant_else "redundant else block" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:153:24 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:158:28 clippy::redundant_else "redundant else block" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:164:33 clippy::cast_sign_loss "casting `i64` to `u64` may lose the sign of the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:166:28 clippy::redundant_else "redundant else block" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:175:47 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:185:38 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:194:38 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:202:28 clippy::redundant_else "redundant else block" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:209:25 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:221:26 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:222:26 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:223:25 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:224:25 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:226:17 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:233:32 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:234:27 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:251:22 clippy::cast_sign_loss "casting `i64` to `u64` may lose the sign of the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:255:9 clippy::if_not_else "unnecessary `!=` operation" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:35:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:45:17 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:46:5 clippy::cast_possible_truncation "casting `f64` to `i64` may truncate the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:50:5 clippy::too_many_lines "this function has too many lines (143/100)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:76:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:78:12 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:81:21 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:82:32 clippy::cast_possible_truncation "casting `u64` to `i32` may truncate the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:88:26 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/binomial.rs:99:21 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/distributions/cauchy.rs:33:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/dirichlet.rs:52:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/dirichlet.rs:64:32 clippy::unseparated_literal_suffix "float type suffix should be separated by an underscore" +target/lintcheck/sources/rand-0.7.3/src/distributions/dirichlet.rs:65:23 clippy::unseparated_literal_suffix "float type suffix should be separated by an underscore" +target/lintcheck/sources/rand-0.7.3/src/distributions/exponential.rs:76:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/float.rs:73:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/gamma.rs:13:5 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/rand-0.7.3/src/distributions/gamma.rs:14:5 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/rand-0.7.3/src/distributions/gamma.rs:189:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/gamma.rs:230:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/gamma.rs:259:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/gamma.rs:287:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/gamma.rs:90:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/integer.rs:23:9 clippy::cast_possible_truncation "casting `u32` to `u8` may truncate the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/integer.rs:30:9 clippy::cast_possible_truncation "casting `u32` to `u16` may truncate the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/integer.rs:69:9 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/rand-0.7.3/src/distributions/mod.rs:263:5 clippy::inline_always "you have declared `#[inline(always)]` on `next`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/distributions/normal.rs:100:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/normal.rs:119:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/normal.rs:131:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/normal.rs:31:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/normal.rs:47:25 clippy::unseparated_literal_suffix "float type suffix should be separated by an underscore" +target/lintcheck/sources/rand-0.7.3/src/distributions/normal.rs:48:25 clippy::unseparated_literal_suffix "float type suffix should be separated by an underscore" +target/lintcheck/sources/rand-0.7.3/src/distributions/other.rs:89:9 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/pareto.rs:32:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/poisson.rs:35:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/poisson.rs:87:30 clippy::cast_possible_truncation "casting `f64` to `u64` may truncate the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/poisson.rs:87:30 clippy::cast_sign_loss "casting `f64` to `u64` may lose the sign of the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/triangular.rs:32:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:146:4 clippy::needless_doctest_main "needless `fn main` in doctest" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:199:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:214:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:283:14 clippy::doc_markdown "you should put `SampleUniform` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:283:46 clippy::doc_markdown "you should put `SampleUniform` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:296:5 clippy::inline_always "you have declared `#[inline(always)]` on `borrow`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:304:5 clippy::inline_always "you have declared `#[inline(always)]` on `borrow`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:350:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:407:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:441:31 clippy::invalid_upcast_comparisons "because of the numeric bounds on `::core::u16::MAX` prior to casting, this expression is always false" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:441:31 clippy::invalid_upcast_comparisons "because of the numeric bounds on `::core::u16::MAX` prior to casting, this expression is always false" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:441:31 clippy::invalid_upcast_comparisons "because of the numeric bounds on `::core::u16::MAX` prior to casting, this expression is always false" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:441:31 clippy::invalid_upcast_comparisons "because of the numeric bounds on `::core::u16::MAX` prior to casting, this expression is always false" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:441:31 clippy::invalid_upcast_comparisons "because of the numeric bounds on `::core::u16::MAX` prior to casting, this expression is always false" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:441:31 clippy::invalid_upcast_comparisons "because of the numeric bounds on `::core::u16::MAX` prior to casting, this expression is always false" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:441:31 clippy::invalid_upcast_comparisons "because of the numeric bounds on `::core::u16::MAX` prior to casting, this expression is always false" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:441:31 clippy::invalid_upcast_comparisons "because of the numeric bounds on `::core::u16::MAX` prior to casting, this expression is always false" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:56:10 clippy::doc_markdown "you should put `SampleBorrow` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:647:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:840:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:913:13 clippy::option_if_let_else "use Option::map_or_else instead of an if let/else" +target/lintcheck/sources/rand-0.7.3/src/distributions/uniform.rs:943:54 clippy::cast_possible_truncation "casting `u64` to `u32` may truncate the value" +target/lintcheck/sources/rand-0.7.3/src/distributions/unit_circle.rs:30:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/unit_sphere.rs:24:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/unit_sphere.rs:29:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:247:15 clippy::wrong_self_convention "methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:248:20 clippy::wrong_self_convention "methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:249:18 clippy::wrong_self_convention "methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:254:5 clippy::inline_always "you have declared `#[inline(always)]` on `lanes`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:258:5 clippy::inline_always "you have declared `#[inline(always)]` on `splat`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:262:5 clippy::inline_always "you have declared `#[inline(always)]` on `extract`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:267:5 clippy::inline_always "you have declared `#[inline(always)]` on `replace`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:281:5 clippy::inline_always "you have declared `#[inline(always)]` on `any`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:286:5 clippy::inline_always "you have declared `#[inline(always)]` on `all`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:291:5 clippy::inline_always "you have declared `#[inline(always)]` on `none`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:488:17 clippy::doc_markdown "you should put `x_i` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:489:50 clippy::doc_markdown "you should put `x_i` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:489:63 clippy::doc_markdown "you should put `f(x_i` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:490:40 clippy::doc_markdown "you should put `f(x_i` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:490:49 clippy::doc_markdown "you should put `f(x_{i+1` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/distributions/utils.rs:518:17 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/rand-0.7.3/src/distributions/weibull.rs:29:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:113:21 clippy::explicit_iter_loop "it is more concise to loop over references to containers instead of using explicit iteration methods" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:125:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:131:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:180:36 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:182:34 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:259:28 clippy::clone_on_copy "using `clone` on type `distributions::uniform::Uniform` which implements the `Copy` trait" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:296:9 clippy::map_clone "you are using an explicit closure for copying elements" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:321:9 clippy::map_clone "you are using an explicit closure for copying elements" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:78:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:78:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:78:5 clippy::too_many_lines "this function has too many lines (106/100)" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:85:17 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/alias_method.rs:87:31 clippy::map_unwrap_or "called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/mod.rs:100:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/mod.rs:144:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/mod.rs:144:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/mod.rs:169:16 clippy::int_plus_one "unnecessary `>= y + 1` or `x - 1 >=`" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/mod.rs:386:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/distributions/weighted/mod.rs:85:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/lib.rs:333:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand-0.7.3/src/lib.rs:404:14 clippy::wrong_self_convention "methods called `to_*` usually take self by reference; consider choosing a less ambiguous name" +target/lintcheck/sources/rand-0.7.3/src/lib.rs:552:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/rngs/adapter/read.rs:47:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/rngs/adapter/read.rs:89:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/rngs/adapter/reseeding.rs:100:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand-0.7.3/src/rngs/adapter/reseeding.rs:112:5 clippy::inline_always "you have declared `#[inline(always)]` on `next_u32`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/rngs/adapter/reseeding.rs:117:5 clippy::inline_always "you have declared `#[inline(always)]` on `next_u64`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/rngs/adapter/reseeding.rs:198:13 clippy::cast_possible_wrap "casting `u64` to `i64` may wrap around the value" +target/lintcheck/sources/rand-0.7.3/src/rngs/adapter/reseeding.rs:231:9 clippy::cast_possible_wrap "casting `usize` to `isize` may wrap around the value" +target/lintcheck/sources/rand-0.7.3/src/rngs/adapter/reseeding.rs:249:13 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/rand-0.7.3/src/rngs/adapter/reseeding.rs:27:28 clippy::doc_markdown "you should put `ChaCha` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/rngs/adapter/reseeding.rs:79:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/rngs/entropy.rs:24:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/rngs/entropy.rs:34:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/rngs/mock.rs:36:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/rngs/mock.rs:47:9 clippy::cast_possible_truncation "casting `u64` to `u32` may truncate the value" +target/lintcheck/sources/rand-0.7.3/src/rngs/mod.rs:61:74 clippy::doc_markdown "you should put `ChaCha20` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/rngs/std.rs:25:39 clippy::doc_markdown "you should put `ChaCha` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/rngs/std.rs:32:10 clippy::doc_markdown "you should put `rand_chacha` between ticks in the documentation" +target/lintcheck/sources/rand-0.7.3/src/rngs/std.rs:36:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/rngs/std.rs:39:5 clippy::inline_always "you have declared `#[inline(always)]` on `next_u32`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/rngs/std.rs:44:5 clippy::inline_always "you have declared `#[inline(always)]` on `next_u64`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/rngs/std.rs:49:5 clippy::inline_always "you have declared `#[inline(always)]` on `fill_bytes`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/rngs/std.rs:54:5 clippy::inline_always "you have declared `#[inline(always)]` on `try_fill_bytes`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/rngs/std.rs:63:5 clippy::inline_always "you have declared `#[inline(always)]` on `from_seed`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/rngs/std.rs:68:5 clippy::inline_always "you have declared `#[inline(always)]` on `from_rng`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/rngs/thread.rs:57:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/rngs/thread.rs:80:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/rand-0.7.3/src/rngs/thread.rs:80:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/rngs/thread.rs:80:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/rngs/thread.rs:81:35 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/rand-0.7.3/src/rngs/thread.rs:93:5 clippy::inline_always "you have declared `#[inline(always)]` on `next_u32`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/rngs/thread.rs:98:5 clippy::inline_always "you have declared `#[inline(always)]` on `next_u64`. This is usually a bad idea" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:127:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:139:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:159:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:171:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:180:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:223:18 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:224:18 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:233:25 clippy::cast_precision_loss "casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:236:27 clippy::cast_precision_loss "casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:244:12 clippy::cast_precision_loss "casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:244:37 clippy::cast_precision_loss "casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:29:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:39:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:48:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:60:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:69:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:78:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:87:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:87:5 clippy::should_implement_trait "method `into_iter` can be confused for the standard trait method `std::iter::IntoIterator::into_iter`" +target/lintcheck/sources/rand-0.7.3/src/seq/index.rs:97:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/rand-0.7.3/src/seq/mod.rs:141:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand-0.7.3/src/seq/mod.rs:168:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand-0.7.3/src/seq/mod.rs:229:4 clippy::needless_doctest_main "needless `fn main` in doctest" +target/lintcheck/sources/rand-0.7.3/src/seq/mod.rs:292:29 clippy::cast_precision_loss "casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/rand-0.7.3/src/seq/mod.rs:410:23 clippy::default_trait_access "calling `std::marker::PhantomData::default()` is more clear than this expression" +target/lintcheck/sources/rand-0.7.3/src/seq/mod.rs:45:4 clippy::needless_doctest_main "needless `fn main` in doctest" +target/lintcheck/sources/rand-0.7.3/src/seq/mod.rs:527:26 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:117:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:153:5 clippy::inline_always "you have declared `#[inline(always)]` on `index`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:230:5 clippy::inline_always "you have declared `#[inline(always)]` on `try_fill_bytes`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:240:5 clippy::inline_always "you have declared `#[inline(always)]` on `from_seed`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:245:5 clippy::inline_always "you have declared `#[inline(always)]` on `seed_from_u64`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:250:5 clippy::inline_always "you have declared `#[inline(always)]` on `from_rng`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:280:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:319:5 clippy::inline_always "you have declared `#[inline(always)]` on `index`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:405:5 clippy::inline_always "you have declared `#[inline(always)]` on `try_fill_bytes`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:415:5 clippy::inline_always "you have declared `#[inline(always)]` on `from_seed`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:420:5 clippy::inline_always "you have declared `#[inline(always)]` on `seed_from_u64`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:425:5 clippy::inline_always "you have declared `#[inline(always)]` on `from_rng`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:67:14 clippy::doc_markdown "you should put `module][crate::block` between ticks in the documentation" +target/lintcheck/sources/rand_core-0.6.0/src/block.rs:68:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rand_core-0.6.0/src/error.rs:106:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand_core-0.6.0/src/error.rs:87:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand_core-0.6.0/src/error.rs:95:74 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:179:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:301:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:303:26 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:304:26 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:313:30 clippy::cast_possible_truncation "casting `u64` to `u32` may truncate the value" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:314:23 clippy::cast_possible_truncation "casting `u64` to `u32` may truncate the value" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:346:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:381:5 clippy::inline_always "you have declared `#[inline(always)]` on `next_u32`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:386:5 clippy::inline_always "you have declared `#[inline(always)]` on `next_u64`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:391:5 clippy::inline_always "you have declared `#[inline(always)]` on `fill_bytes`. This is usually a bad idea" +target/lintcheck/sources/rand_core-0.6.0/src/lib.rs:396:5 clippy::inline_always "you have declared `#[inline(always)]` on `try_fill_bytes`. This is usually a bad idea" +target/lintcheck/sources/rayon-1.5.0/src/collections/binary_heap.rs:7:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/binary_heap.rs:8:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/btree_map.rs:7:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/btree_map.rs:8:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/btree_set.rs:7:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/btree_set.rs:8:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/hash_map.rs:10:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/hash_map.rs:9:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/hash_set.rs:10:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/hash_set.rs:9:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/linked_list.rs:7:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/linked_list.rs:8:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/mod.rs:59:32 clippy::mem_replace_with_default "replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`" +target/lintcheck/sources/rayon-1.5.0/src/collections/vec_deque.rs:8:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/collections/vec_deque.rs:9:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/compile_fail/cannot_collect_filtermap_data.rs:2:1 clippy::needless_doctest_main "needless `fn main` in doctest" +target/lintcheck/sources/rayon-1.5.0/src/compile_fail/cannot_zip_filtered_data.rs:2:1 clippy::needless_doctest_main "needless `fn main` in doctest" +target/lintcheck/sources/rayon-1.5.0/src/compile_fail/cell_par_iter.rs:2:1 clippy::needless_doctest_main "needless `fn main` in doctest" +target/lintcheck/sources/rayon-1.5.0/src/compile_fail/no_send_par_iter.rs:25:1 clippy::needless_doctest_main "needless `fn main` in doctest" +target/lintcheck/sources/rayon-1.5.0/src/compile_fail/no_send_par_iter.rs:46:1 clippy::needless_doctest_main "needless `fn main` in doctest" +target/lintcheck/sources/rayon-1.5.0/src/compile_fail/no_send_par_iter.rs:4:1 clippy::needless_doctest_main "needless `fn main` in doctest" +target/lintcheck/sources/rayon-1.5.0/src/compile_fail/rc_par_iter.rs:2:1 clippy::needless_doctest_main "needless `fn main` in doctest" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:103:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:122:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:128:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:221:36 clippy::doc_markdown "you should put `ExactSizeIterator` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:51:38 clippy::option_if_let_else "use Option::map_or_else instead of an if let/else" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:58:14 clippy::shadow_unrelated "`a` is being shadowed" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:58:17 clippy::shadow_unrelated "`b` is being shadowed" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:78:14 clippy::shadow_unrelated "`a` is being shadowed" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:78:17 clippy::shadow_unrelated "`b` is being shadowed" +target/lintcheck/sources/rayon-1.5.0/src/iter/chain.rs:97:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/chunks.rs:29:9 clippy::inconsistent_struct_constructor "inconsistent struct constructor" +target/lintcheck/sources/rayon-1.5.0/src/iter/chunks.rs:3:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/chunks.rs:4:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/chunks.rs:77:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/chunks.rs:83:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/cloned.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/cloned.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/cloned.rs:71:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/cloned.rs:75:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/collect/consumer.rs:141:5 clippy::doc_markdown "you should put `CollectReducer` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/collect/consumer.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/collect/consumer.rs:28:5 clippy::doc_markdown "you should put `CollectResult` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/collect/consumer.rs:36:37 clippy::mut_mut "generally you want to avoid `&mut &mut _` if possible" +target/lintcheck/sources/rayon-1.5.0/src/iter/collect/consumer.rs:36:37 clippy::mut_mut "generally you want to avoid `&mut &mut _` if possible" +target/lintcheck/sources/rayon-1.5.0/src/iter/collect/mod.rs:154:9 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/rayon-1.5.0/src/iter/copied.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/copied.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/copied.rs:71:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/copied.rs:75:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/empty.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/empty.rs:24:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/rayon-1.5.0/src/iter/empty.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/enumerate.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/enumerate.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/enumerate.rs:64:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/enumerate.rs:68:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:143:63 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:182:57 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:218:32 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:218:59 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:25:42 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:287:62 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:322:56 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:41:27 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:47:30 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:47:56 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:47:74 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:53:29 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:57:36 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/extend.rs:59:61 clippy::linkedlist "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?" +target/lintcheck/sources/rayon-1.5.0/src/iter/filter.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/filter.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/filter_map.rs:123:9 clippy::option_if_let_else "use Option::map_or instead of an if let/else" +target/lintcheck/sources/rayon-1.5.0/src/iter/filter_map.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/filter_map.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/find.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/find.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/find_first_last/mod.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/find_first_last/mod.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/find_first_last/mod.rs:32:67 clippy::doc_markdown "you should put `MatchPosition` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/flat_map.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/flat_map.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/flat_map_iter.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/flat_map_iter.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/flatten.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/flatten.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/flatten_iter.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/flatten_iter.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/fold.rs:158:13 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/rayon-1.5.0/src/iter/fold.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/fold.rs:204:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rayon-1.5.0/src/iter/fold.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/for_each.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/for_each.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/inspect.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/inspect.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/inspect.rs:83:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/inspect.rs:88:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:111:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:119:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:195:30 clippy::doc_markdown "you should put `self.i_len` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:195:43 clippy::doc_markdown "you should put `self.j_len` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:199:23 clippy::doc_markdown "you should put `self.i_len` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:200:23 clippy::doc_markdown "you should put `self.j_len` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:249:41 clippy::doc_markdown "you should put `DoubleEndedIterator` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:250:5 clippy::doc_markdown "you should put `ExactSizeIterator` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:263:33 clippy::doc_markdown "you should put `InterleaveSeq` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:280:17 clippy::match_wildcard_for_single_variants "wildcard match will miss any future added variants" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:285:17 clippy::match_wildcard_for_single_variants "wildcard match will miss any future added variants" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:313:9 clippy::comparison_chain "`if` chain can be rewritten with `match`" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:82:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave.rs:90:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/interleave_shortest.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/intersperse.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/intersperse.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/intersperse.rs:90:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/intersperse.rs:96:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/len.rs:12:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/rayon-1.5.0/src/iter/len.rs:146:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/rayon-1.5.0/src/iter/len.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/len.rs:200:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/len.rs:205:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/len.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/len.rs:66:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/len.rs:71:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/map.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/map.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/map.rs:84:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/map.rs:89:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/map_with.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/map_with.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/map_with.rs:419:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/map_with.rs:425:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/map_with.rs:90:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/map_with.rs:96:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/mod.rs:1874:24 clippy::trivially_copy_pass_by_ref "this argument (1 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/rayon-1.5.0/src/iter/mod.rs:2171:1 clippy::len_without_is_empty "trait `IndexedParallelIterator` has a `len` method but no (possibly inherited) `is_empty` method" +target/lintcheck/sources/rayon-1.5.0/src/iter/mod.rs:2371:26 clippy::trivially_copy_pass_by_ref "this argument (1 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/rayon-1.5.0/src/iter/mod.rs:2411:26 clippy::trivially_copy_pass_by_ref "this argument (1 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/rayon-1.5.0/src/iter/mod.rs:82:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/multizip.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/multizip.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/noop.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/once.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/once.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/panic_fuse.rs:102:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/panic_fuse.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/panic_fuse.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/panic_fuse.rs:98:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/par_bridge.rs:136:28 clippy::redundant_else "redundant else block" +target/lintcheck/sources/rayon-1.5.0/src/iter/par_bridge.rs:163:28 clippy::redundant_else "redundant else block" +target/lintcheck/sources/rayon-1.5.0/src/iter/plumbing/mod.rs:216:58 clippy::doc_markdown "you should put `find_first` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/plumbing/mod.rs:359:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/plumbing/mod.rs:364:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/plumbing/mod.rs:399:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/plumbing/mod.rs:53:19 clippy::doc_markdown "you should put `DoubleEndedIterator` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/plumbing/mod.rs:53:43 clippy::doc_markdown "you should put `ExactSizeIterator` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/plumbing/mod.rs:54:31 clippy::doc_markdown "you should put `IntoIterator` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/plumbing/mod.rs:55:5 clippy::doc_markdown "you should put `IntoIterator` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/iter/positions.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/positions.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/product.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/reduce.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/repeat.rs:103:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rayon-1.5.0/src/iter/repeat.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/repeat.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/rev.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/rev.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/rev.rs:63:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/rev.rs:68:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/skip.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/skip.rs:3:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/skip.rs:68:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/skip.rs:73:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/splitter.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/splitter.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/step_by.rs:4:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/step_by.rs:5:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/step_by.rs:73:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/step_by.rs:79:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/sum.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/take.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/take.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/take.rs:67:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/take.rs:72:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/try_fold.rs:190:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rayon-1.5.0/src/iter/try_fold.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/try_fold.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/try_reduce.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/try_reduce_with.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/unzip.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/unzip.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/update.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/update.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/update.rs:82:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/update.rs:87:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/while_some.rs:130:22 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/rayon-1.5.0/src/iter/while_some.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/while_some.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/zip.rs:102:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/zip.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/zip.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/zip.rs:74:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/zip.rs:79:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/zip.rs:97:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/iter/zip_eq.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/iter/zip_eq.rs:2:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/option.rs:8:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/option.rs:9:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/par_either.rs:1:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/par_either.rs:3:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/private.rs:9:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/rayon-1.5.0/src/range.rs:19:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/range.rs:20:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/range_inclusive.rs:194:9 clippy::range_plus_one "an inclusive range would be more readable" +target/lintcheck/sources/rayon-1.5.0/src/range_inclusive.rs:194:9 clippy::range_plus_one "an inclusive range would be more readable" +target/lintcheck/sources/rayon-1.5.0/src/range_inclusive.rs:19:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/range_inclusive.rs:209:9 clippy::range_plus_one "an inclusive range would be more readable" +target/lintcheck/sources/rayon-1.5.0/src/range_inclusive.rs:209:9 clippy::range_plus_one "an inclusive range would be more readable" +target/lintcheck/sources/rayon-1.5.0/src/range_inclusive.rs:20:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/range_inclusive.rs:231:9 clippy::range_plus_one "an inclusive range would be more readable" +target/lintcheck/sources/rayon-1.5.0/src/range_inclusive.rs:231:9 clippy::range_plus_one "an inclusive range would be more readable" +target/lintcheck/sources/rayon-1.5.0/src/result.rs:8:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/result.rs:9:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:102:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:109:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:114:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:211:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:217:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:251:5 clippy::doc_markdown "you should put `TimSort` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:252:5 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:286:59 clippy::doc_markdown "you should put `TimSort` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:333:24 clippy::redundant_else "redundant else block" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:513:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:521:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:7:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/slice/mergesort.rs:98:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/slice/mod.rs:15:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/slice/mod.rs:16:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/slice/mod.rs:17:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/slice/mod.rs:25:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/rayon-1.5.0/src/slice/mod.rs:657:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rayon-1.5.0/src/slice/mod.rs:971:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:230:36 clippy::doc_markdown "you should put `BlockQuicksort` between ticks in the documentation" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:233:1 clippy::too_many_lines "this function has too many lines (117/100)" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:258:26 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:265:26 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:268:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:308:30 clippy::cast_possible_truncation "casting `usize` to `u8` may truncate the value" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:325:30 clippy::cast_possible_truncation "casting `usize` to `u8` may truncate the value" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:393:36 clippy::cast_possible_wrap "casting `u8` to `isize` may wrap around the value on targets with 32-bit wide pointers" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:405:40 clippy::cast_possible_wrap "casting `u8` to `isize` may wrap around the value on targets with 32-bit wide pointers" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:430:14 clippy::shadow_unrelated "`pivot` is being shadowed" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:439:13 clippy::shadow_unrelated "`pivot` is being shadowed" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:482:10 clippy::shadow_unrelated "`pivot` is being shadowed" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:491:9 clippy::shadow_unrelated "`pivot` is being shadowed" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:534:26 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:545:17 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:588:17 clippy::identity_op "the operation is ineffective. Consider reducing it to `len / 4`" +target/lintcheck/sources/rayon-1.5.0/src/slice/quicksort.rs:716:14 clippy::shadow_unrelated "`pivot` is being shadowed" +target/lintcheck/sources/rayon-1.5.0/src/split_producer.rs:56:16 clippy::option_if_let_else "use Option::map_or_else instead of an if let/else" +target/lintcheck/sources/rayon-1.5.0/src/split_producer.rs:92:9 clippy::option_if_let_else "use Option::map_or instead of an if let/else" +target/lintcheck/sources/rayon-1.5.0/src/str.rs:16:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/str.rs:17:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/str.rs:18:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/str.rs:25:5 clippy::cast_possible_wrap "casting `u8` to `i8` may wrap around the value" +target/lintcheck/sources/rayon-1.5.0/src/str.rs:715:9 clippy::manual_strip "stripping a suffix manually" +target/lintcheck/sources/rayon-1.5.0/src/string.rs:5:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/vec.rs:137:12 clippy::len_zero "length comparison to zero" +target/lintcheck/sources/rayon-1.5.0/src/vec.rs:8:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/rayon-1.5.0/src/vec.rs:9:5 clippy::wildcard_imports "usage of wildcard import" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:100:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:133:17 clippy::same_item_push "it looks like the same item is being pushed into this Vec" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:145:20 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:199:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:223:29 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:230:66 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:284:21 clippy::cast_lossless "casting `u32` to `u64` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:287:5 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:97:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:98:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/backtrack.rs:99:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1005:32 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1006:21 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1008:18 clippy::cast_lossless "casting `u8` to `u64` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1009:18 clippy::cast_lossless "casting `u8` to `u64` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1010:9 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:102:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1037:37 clippy::cast_possible_truncation "casting `u16` to `u8` may truncate the value" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1037:55 clippy::cast_possible_truncation "casting `u16` to `u8` may truncate the value" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1040:28 clippy::cast_possible_truncation "casting `u16` to `u8` may truncate the value" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1040:38 clippy::cast_possible_truncation "casting `u16` to `u8` may truncate the value" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1051:25 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:1071:8 clippy::cast_lossless "casting `u32` to `u64` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:112:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:154:30 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:156:30 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:185:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:187:40 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:201:53 clippy::doc_markdown "you should put `MaybeInsts` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:241:63 clippy::doc_markdown "you should put `c_concat` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:245:5 clippy::too_many_lines "this function has too many lines (111/100)" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:247:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:373:24 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:373:36 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:378:12 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:400:37 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:407:51 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:409:24 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:417:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:42:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:42:5 clippy::new_without_default "you should consider adding a `Default` implementation for `compile::Compiler`" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:444:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:445:57 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:446:20 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:466:20 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:466:32 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:519:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:55:57 clippy::doc_markdown "you should put `size_limit` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:58:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:748:41 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:74:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:751:54 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:765:41 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:765:55 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:825:39 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:825:51 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:828:49 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:828:61 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:830:59 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:830:71 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:832:43 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:835:41 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:835:53 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:835:67 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:83:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:896:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:905:17 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:953:17 clippy::doc_markdown "you should put `HashMap` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:95:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:980:26 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:994:44 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/compile.rs:994:54 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1007:17 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1010:22 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1059:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1060:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1084:38 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1087:38 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1090:38 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1093:38 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1096:38 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1101:38 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1104:38 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1107:38 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1117:30 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1120:47 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1121:30 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1129:13 clippy::doc_markdown "you should put `is_match` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1134:13 clippy::doc_markdown "you should put `is_match` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1185:68 clippy::doc_markdown "you should put `is_match` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1193:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1244:50 clippy::doc_markdown "you should put `current_state` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1338:58 clippy::doc_markdown "you should put `STATE_DEAD` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1339:9 clippy::doc_markdown "you should put `STATE_UNKNOWN` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1366:25 clippy::doc_markdown "you should put `STATE_DEAD` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1366:46 clippy::doc_markdown "you should put `STATE_UNKNOWN` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1367:41 clippy::inline_always "you have declared `#[inline(always)]` on `start_state`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1380:14 clippy::identity_op "the operation is ineffective. Consider reducing it to `(empty_flags.start as u8)`" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1388:15 clippy::match_on_vec_items "indexing into a vector may panic" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1412:20 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1438:9 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1472:9 clippy::doc_markdown "you should put `StatePtr` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1490:54 clippy::cast_possible_truncation "casting `i32` to `u8` may truncate the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1490:54 clippy::cast_sign_loss "casting `i32` to `u8` may lose the sign of the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1521:20 clippy::doc_markdown "you should put `num_byte_classes` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1529:41 clippy::inline_always "you have declared `#[inline(always)]` on `byte_class`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1537:14 clippy::doc_markdown "you should put `byte_class` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1538:41 clippy::inline_always "you have declared `#[inline(always)]` on `u8_class`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1562:18 clippy::doc_markdown "you should put `STATE_START` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1614:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1651:38 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1700:17 clippy::trivially_copy_pass_by_ref "this argument (1 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1701:18 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1705:19 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1708:16 clippy::trivially_copy_pass_by_ref "this argument (1 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1709:18 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1713:19 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1716:18 clippy::trivially_copy_pass_by_ref "this argument (1 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1717:18 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1721:19 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1727:14 clippy::cast_lossless "casting `u8` to `u16` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1732:15 clippy::trivially_copy_pass_by_ref "this argument (2 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1736:22 clippy::trivially_copy_pass_by_ref "this argument (2 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1741:9 clippy::match_like_matches_macro "match expression looks like `matches!` macro" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1747:16 clippy::trivially_copy_pass_by_ref "this argument (2 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1751:18 clippy::cast_possible_truncation "casting `u16` to `u8` may truncate the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1815:38 clippy::cast_possible_truncation "casting `usize` to `u8` may truncate the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1821:21 clippy::cast_lossless "casting `u32` to `u64` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1824:5 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1848:5 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1850:18 clippy::cast_sign_loss "casting `i32` to `u32` may lose the sign of the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1857:5 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1860:17 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1867:5 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1870:19 clippy::cast_possible_truncation "casting `u32` to `u8` may truncate the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1873:15 clippy::cast_possible_truncation "casting `u32` to `u8` may truncate the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1876:5 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1882:26 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:1884:15 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:277:17 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:277:31 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:295:20 clippy::cast_possible_truncation "casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:295:20 clippy::cast_possible_wrap "casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:299:21 clippy::cast_sign_loss "casting `i32` to `usize` may lose the sign of the value" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:34:46 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:398:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:446:41 clippy::inline_always "you have declared `#[inline(always)]` on `forward`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:457:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:459:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:460:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:476:41 clippy::inline_always "you have declared `#[inline(always)]` on `reverse`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:487:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:489:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:490:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:506:41 clippy::inline_always "you have declared `#[inline(always)]` on `forward_many`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:518:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:520:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:554:41 clippy::inline_always "you have declared `#[inline(always)]` on `exec_at`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:555:5 clippy::too_many_lines "this function has too many lines (101/100)" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:58:9 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:667:21 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:747:41 clippy::inline_always "you have declared `#[inline(always)]` on `exec_at_reverse`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:795:21 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:848:9 clippy::doc_markdown "you should put `next_si` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:852:41 clippy::inline_always "you have declared `#[inline(always)]` on `next_si`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:885:12 clippy::doc_markdown "you should put `STATE_DEAD` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:889:9 clippy::doc_markdown "you should put `STATE_UNKNOWN` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:897:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/dfa.rs:979:29 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers" +target/lintcheck/sources/regex-1.3.2/src/error.rs:6:1 clippy::manual_non_exhaustive "this seems like a manual implementation of the non-exhaustive pattern" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:1000:14 clippy::doc_markdown "you should put `captures_nfa` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:100:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:1028:5 clippy::too_many_arguments "this function has too many arguments (9/7)" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:1039:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:1144:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:1179:26 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:122:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:1250:41 clippy::inline_always "you have declared `#[inline(always)]` on `searcher`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:1260:41 clippy::inline_always "you have declared `#[inline(always)]` on `searcher_str`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:1270:17 clippy::doc_markdown "you should put `RegexSet` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:1280:17 clippy::doc_markdown "you should put `RegexSet` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:137:9 clippy::field_reassign_with_default "field assignment outside of initializer for an instance created with Default::default()" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:142:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:158:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:168:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:181:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:195:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:204:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:210:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:245:62 clippy::if_same_then_else "this `if` has identical blocks" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:251:21 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:262:60 clippy::if_same_then_else "this `if` has identical blocks" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:268:21 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:278:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:281:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:286:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:300:30 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:308:17 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:329:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:330:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:331:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:334:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:340:19 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:344:27 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:383:41 clippy::inline_always "you have declared `#[inline(always)]` on `shortest_match_at`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:388:41 clippy::inline_always "you have declared `#[inline(always)]` on `is_match_at`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:393:41 clippy::inline_always "you have declared `#[inline(always)]` on `find_at`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:398:41 clippy::inline_always "you have declared `#[inline(always)]` on `captures_read_at`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:425:41 clippy::inline_always "you have declared `#[inline(always)]` on `shortest_match_at`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:44:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:473:9 clippy::doc_markdown "you should put `shortest_match(...).is_some` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:474:41 clippy::inline_always "you have declared `#[inline(always)]` on `is_match_at`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:524:41 clippy::inline_always "you have declared `#[inline(always)]` on `find_at`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:52:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:686:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:727:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:767:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:783:41 clippy::inline_always "you have declared `#[inline(always)]` on `shortest_dfa`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:791:41 clippy::inline_always "you have declared `#[inline(always)]` on `shortest_dfa_reverse_suffix`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:823:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:868:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:897:31 clippy::doc_markdown "you should put `shortest_nfa(...).is_some` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:899:9 clippy::doc_markdown "you should put `shortest_nfa` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:905:14 clippy::doc_markdown "you should put `match_nfa` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:930:14 clippy::doc_markdown "you should put `shortest_nfa` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/exec.rs:981:14 clippy::doc_markdown "you should put `find_nfa` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:170:27 clippy::trivially_copy_pass_by_ref "this argument (1 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:171:5 clippy::match_like_matches_macro "match expression looks like `matches!` macro" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:22:13 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:27:23 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:30:17 clippy::single_char_add_str "calling `push_str()` using a single-character string literal" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:38:30 clippy::map_unwrap_or "called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:42:21 clippy::map_unwrap_or "called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:50:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:69:23 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:80:28 clippy::map_unwrap_or "called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:84:21 clippy::map_unwrap_or "called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead" +target/lintcheck/sources/regex-1.3.2/src/expand.rs:8:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/input.rs:142:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/input.rs:146:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:15:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/input.rs:165:31 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/input.rs:178:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/input.rs:228:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/input.rs:236:21 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/input.rs:236:33 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/input.rs:24:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:271:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/input.rs:29:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:362:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:370:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:371:42 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/regex-1.3.2/src/input.rs:37:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:388:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:42:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:47:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:53:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:58:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/input.rs:63:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/lib.rs:1:null clippy::cargo_common_metadata "package `regex` is missing `package.keywords` metadata" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:101:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:114:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:127:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:139:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:144:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:149:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:154:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:155:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:160:30 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:167:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:168:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:211:20 clippy::redundant_else "redundant else block" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:276:50 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:342:41 clippy::inline_always "you have declared `#[inline(always)]` on `find`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:435:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:436:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:437:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:438:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:439:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:440:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:455:41 clippy::inline_always "you have declared `#[inline(always)]` on `find`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:46:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:481:41 clippy::inline_always "you have declared `#[inline(always)]` on `is_suffix`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:51:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:579:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:57:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:580:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:583:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:602:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:622:24 clippy::redundant_else "redundant else block" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:62:18 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:637:24 clippy::redundant_else "redundant else block" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:648:9 clippy::needless_return "unneeded `return` statement" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:651:44 clippy::doc_markdown "you should put `BoyerMooreSearch` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:65:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:68:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:783:32 clippy::redundant_else "redundant else block" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:786:42 clippy::manual_saturating_arithmetic "manual saturating arithmetic" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:78:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:84:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:850:20 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/regex-1.3.2/src/literal/imp.rs:85:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:103:15 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:103:52 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:114:5 clippy::too_many_arguments "this function has too many arguments (8/7)" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:117:13 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:124:17 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:220:9 clippy::doc_markdown "you should put `thread_caps` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:222:16 clippy::doc_markdown "you should put `at_next` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:223:9 clippy::doc_markdown "you should put `at_next` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:224:5 clippy::too_many_arguments "this function has too many arguments (8/7)" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:234:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:303:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:331:29 clippy::mut_mut "this expression mutably borrows a mutable reference. Consider reborrowing" +target/lintcheck/sources/regex-1.3.2/src/pikevm.rs:88:5 clippy::too_many_arguments "this function has too many arguments (8/7)" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:102:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:113:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:120:9 clippy::match_like_matches_macro "match expression looks like `matches!` macro" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:128:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:134:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:141:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:147:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:164:41 clippy::inline_always "you have declared `#[inline(always)]` on `deref`. This is usually a bad idea" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:172:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:18:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:236:13 clippy::write_with_newline "using `write!()` with a format string that ends in a single newline" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:300:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:301:9 clippy::match_like_matches_macro "match expression looks like `matches!` macro" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:382:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:409:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:80:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/prog.rs:80:5 clippy::new_without_default "you should consider adding a `Default` implementation for `prog::Program`" +target/lintcheck/sources/regex-1.3.2/src/re_builder.rs:267:17 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/regex-1.3.2/src/re_builder.rs:267:17 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/regex-1.3.2/src/re_builder.rs:4:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/regex-1.3.2/src/re_builder.rs:57:17 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_builder.rs:57:17 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_builder.rs:68:17 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/regex-1.3.2/src/re_builder.rs:68:17 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:1017:9 clippy::map_unwrap_or "called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling `map_or_else(, )` instead" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:1039:9 clippy::map_unwrap_or "called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling `map_or_else(, )` instead" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:1093:5 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:1118:5 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:1133:5 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:118:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:256:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:29:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:35:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:42:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:483:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:48:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:558:29 clippy::doc_markdown "you should put `shortest_match` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:55:33 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:55:47 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:572:29 clippy::doc_markdown "you should put `is_match` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:720:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:817:5 clippy::doc_markdown "you should put `CaptureLocations` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:849:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:858:5 clippy::len_without_is_empty "struct `CaptureLocations` has a public `len` method, but no `is_empty` method" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:858:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:869:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:911:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:917:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:926:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:955:5 clippy::len_without_is_empty "struct `Captures` has a public `len` method, but no `is_empty` method" +target/lintcheck/sources/regex-1.3.2/src/re_bytes.rs:955:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:179:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:179:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:206:5 clippy::len_without_is_empty "struct `RegexSet` has a public `len` method, but no `is_empty` method" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:206:5 clippy::len_without_is_empty "struct `RegexSet` has a public `len` method, but no `is_empty` method" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:251:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:251:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:263:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:263:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:268:5 clippy::len_without_is_empty "struct `SetMatches` has a public `len` method, but no `is_empty` method" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:268:5 clippy::len_without_is_empty "struct `SetMatches` has a public `len` method, but no `is_empty` method" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:268:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:268:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:277:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:277:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:94:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/regex-1.3.2/src/re_set.rs:94:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/regex-1.3.2/src/re_trait.rs:136:29 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:1019:9 clippy::map_unwrap_or "called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling `map_or_else(, )` instead" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:1041:9 clippy::map_unwrap_or "called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling `map_or_else(, )` instead" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:1088:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:1135:5 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:1160:5 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:174:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:21:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:313:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:38:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:44:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:51:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:533:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:57:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:617:29 clippy::doc_markdown "you should put `shortest_match` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:631:29 clippy::doc_markdown "you should put `is_match` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:64:33 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:64:47 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:834:5 clippy::doc_markdown "you should put `CaptureLocations` between ticks in the documentation" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:866:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:875:5 clippy::len_without_is_empty "struct `CaptureLocations` has a public `len` method, but no `is_empty` method" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:875:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:886:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:928:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:934:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:943:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:972:5 clippy::len_without_is_empty "struct `Captures` has a public `len` method, but no `is_empty` method" +target/lintcheck/sources/regex-1.3.2/src/re_unicode.rs:972:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/regex-1.3.2/src/sparse.rs:10:37 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/regex-1.3.2/src/sparse.rs:15:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:100:16 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:103:16 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:106:22 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:107:19 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:108:19 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:109:19 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:111:27 clippy::unreadable_literal "long literal lacking separators" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:121:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:143:24 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:143:9 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:23:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:30:20 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:51:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:58:23 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:58:9 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:63:16 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:66:22 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:66:54 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:77:16 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:80:16 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:83:22 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:84:19 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:85:19 clippy::cast_lossless "casting `u8` to `u32` may become silently lossy if you later change the type" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:92:23 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:92:9 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/regex-1.3.2/src/utf8.rs:97:16 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four" +target/lintcheck/sources/ripgrep-12.1.1/build.rs:133:19 clippy::option_as_ref_deref "called `.as_ref().map(|x| &**x)` on an Option value. This can be done more directly by calling `githash.as_deref()` instead" +target/lintcheck/sources/ripgrep-12.1.1/build.rs:18:18 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/ripgrep-12.1.1/build.rs:225:14 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/ripgrep-12.1.1/build.rs:92:19 clippy::option_as_ref_deref "called `.as_ref().map(|x| &**x)` on an Option value. This can be done more directly by calling `githash.as_deref()` instead" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1408:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1408:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1409:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1409:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:152:32 clippy::doc_markdown "you should put `clap::Arg` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:152:32 clippy::doc_markdown "you should put `clap::Arg` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:156:39 clippy::doc_markdown "you should put `clap::Arg` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:156:39 clippy::doc_markdown "you should put `clap::Arg` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:156:5 clippy::doc_markdown "you should put `RGArg` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:156:5 clippy::doc_markdown "you should put `RGArg` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1668:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1668:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1669:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1669:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1821:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1821:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1822:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:1822:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:2999:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:2999:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:3000:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:3000:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:367:54 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:367:54 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:414:59 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:414:59 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:417:57 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:417:57 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:417:57 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:417:57 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:75:9 clippy::doc_markdown "you should put `RIPGREP_BUILD_GIT_HASH` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:75:9 clippy::doc_markdown "you should put `RIPGREP_BUILD_GIT_HASH` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:87:5 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/app.rs:87:5 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1143:22 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:11:1 clippy::single_component_path_imports "this import is redundant" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1209:74 clippy::if_same_then_else "this `if` has identical blocks" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1282:13 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1430:22 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1438:21 clippy::doc_markdown "you should put `OsStr` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1520:44 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1524:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1635:14 clippy::doc_markdown "you should put `values_of_lossy` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1693:41 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1770:17 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:1829:5 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:287:13 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:33:1 clippy::single_component_path_imports "this import is redundant" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:34:1 clippy::single_component_path_imports "this import is redundant" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:35:1 clippy::single_component_path_imports "this import is redundant" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:369:5 clippy::upper_case_acronyms "name `JSON` contains a capitalized acronym" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:410:14 clippy::trivially_copy_pass_by_ref "this argument (2 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:475:18 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:512:19 clippy::doc_markdown "you should put `ArgMatches` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:549:16 clippy::wrong_self_convention "methods called `to_*` usually take self by reference; consider choosing a less ambiguous name" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:76:18 clippy::trivially_copy_pass_by_ref "this argument (1 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:77:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/args.rs:923:42 clippy::doc_markdown "you should put `BinaryDetection::quit` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/config.rs:13:1 clippy::single_component_path_imports "this import is redundant" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/config.rs:58:6 clippy::type_complexity "very complex type used. Consider factoring parts into `type` definitions" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/config.rs:79:6 clippy::type_complexity "very complex type used. Consider factoring parts into `type` definitions" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/logger.rs:11:30 clippy::doc_markdown "you should put `max_level` between ticks in the documentation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/logger.rs:15:16 clippy::redundant_static_lifetimes "constants have by default a `'static` lifetime" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/main.rs:114:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/main.rs:189:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/main.rs:55:19 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/main.rs:56:9 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/messages.rs:46:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/messages.rs:51:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/messages.rs:62:1 clippy::module_name_repetitions "item name ends with its containing module's name" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/path_printer.rs:27:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/path_printer.rs:89:9 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:185:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:224:5 clippy::upper_case_acronyms "name `JSON` contains a capitalized acronym" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:292:9 clippy::write_with_newline "using `write!()` with a format string that ends in a single newline" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:311:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:377:12 clippy::nonminimal_bool "this boolean expression can be simplified" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:423:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:447:13 clippy::enum_glob_use "usage of wildcard import for enum variants" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:472:24 clippy::map_clone "you are using an explicit closure for cloning elements" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:472:41 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:480:24 clippy::map_clone "you are using an explicit closure for cloning elements" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:480:41 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:49:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:509:24 clippy::map_clone "you are using an explicit closure for cloning elements" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:509:41 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:517:24 clippy::map_clone "you are using an explicit closure for cloning elements" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:517:41 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:533:36 clippy::cast_lossless "casting `u32` to `f64` may become silently lossy if you later change the type" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/search.rs:533:5 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/subject.rs:20:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/ripgrep-12.1.1/crates/core/subject.rs:4:1 clippy::single_component_path_imports "this import is redundant" +target/lintcheck/sources/rpmalloc-0.2.0/src/lib.rs:103:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rpmalloc-0.2.0/src/lib.rs:114:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute" +target/lintcheck/sources/rpmalloc-0.2.0/src/lib.rs:71:73 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/rpmalloc-0.2.0/src/lib.rs:72:50 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/rpmalloc-0.2.0/src/lib.rs:92:9 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/rpmalloc-0.2.0/src/lib.rs:95:21 clippy::ptr_as_ptr "`as` casting between raw pointers without changing its mutability" +target/lintcheck/sources/serde-1.0.118/src/de/mod.rs:1592:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/serde-1.0.118/src/de/mod.rs:1616:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/serde-1.0.118/src/de/mod.rs:1627:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/serde-1.0.118/src/de/mod.rs:1638:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/serde-1.0.118/src/de/mod.rs:1649:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/serde-1.0.118/src/de/mod.rs:952:13 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/serde-1.0.118/src/de/mod.rs:986:13 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`" +target/lintcheck/sources/serde_yaml-0.8.17/src/lib.rs:1:null clippy::cargo_common_metadata "package `serde_yaml` is missing `package.categories` metadata" +target/lintcheck/sources/syn-1.0.54/build.rs:1:null clippy::cargo_common_metadata "package `syn` is missing `package.keywords` metadata" +target/lintcheck/sources/syn-1.0.54/src/lib.rs:1:null clippy::cargo_common_metadata "package `syn` is missing `package.keywords` metadata" +target/lintcheck/sources/syn-1.0.54/src/lit.rs:1397:40 clippy::redundant_else "redundant else block" +target/lintcheck/sources/syn-1.0.54/src/lit.rs:1405:28 clippy::redundant_else "redundant else block" +target/lintcheck/sources/syn-1.0.54/src/lit.rs:1485:32 clippy::redundant_else "redundant else block" +target/lintcheck/sources/syn-1.0.54/src/token.rs:974:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/errors.rs:9:5 clippy::upper_case_acronyms "name `HTTP` contains a capitalized acronym" +target/lintcheck/sources/tame-oidc-0.1.0/src/lib.rs:1:null clippy::cargo_common_metadata "package `tame-oidc` is missing `package.categories` metadata" +target/lintcheck/sources/tame-oidc-0.1.0/src/oidc.rs:111:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/oidc.rs:127:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/oidc.rs:52:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/oidc.rs:60:1 clippy::from_over_into "an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true" +target/lintcheck/sources/tame-oidc-0.1.0/src/oidc.rs:76:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:107:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:107:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:118:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:143:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:159:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:26:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:38:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:57:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:71:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:77:12 clippy::upper_case_acronyms "name `JWK` contains a capitalized acronym" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:90:12 clippy::upper_case_acronyms "name `JWKS` contains a capitalized acronym" +target/lintcheck/sources/tame-oidc-0.1.0/src/provider.rs:95:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section" +target/lintcheck/sources/thiserror-1.0.24/src/lib.rs:1:null clippy::cargo_common_metadata "package `thiserror` is missing `package.keywords` metadata" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:1:null clippy::cargo_common_metadata "package `unicode-xid` is missing `package.categories` metadata" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:57:64 clippy::doc_markdown "you should put `XID_Start` between ticks in the documentation" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:60:10 clippy::doc_markdown "you should put `XID_Start` between ticks in the documentation" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:62:27 clippy::doc_markdown "you should put `ID_Start` between ticks in the documentation" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:62:67 clippy::doc_markdown "you should put `NFKx` between ticks in the documentation" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:63:21 clippy::wrong_self_convention "methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:65:61 clippy::doc_markdown "you should put `XID_Continue` between ticks in the documentation" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:68:10 clippy::doc_markdown "you should put `XID_Continue` between ticks in the documentation" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:70:28 clippy::doc_markdown "you should put `ID_Continue` between ticks in the documentation" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:70:72 clippy::doc_markdown "you should put `NFKx` between ticks in the documentation" +target/lintcheck/sources/unicode-xid-0.2.1/src/lib.rs:71:24 clippy::wrong_self_convention "methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name" +target/lintcheck/sources/xsv-0.13.0/src/cmd/cat.rs:101:34 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/xsv-0.13.0/src/cmd/cat.rs:42:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/xsv-0.13.0/src/cmd/cat.rs:53:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/cat.rs:7:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/count.rs:32:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/count.rs:38:9 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/count.rs:42:33 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/xsv-0.13.0/src/cmd/count.rs:7:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/fixlengths.rs:45:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/fixlengths.rs:50:18 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/fixlengths.rs:62:30 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/xsv-0.13.0/src/cmd/fixlengths.rs:9:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/flatten.rs:10:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/flatten.rs:51:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/fmt.rs:50:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/fmt.rs:55:13 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/fmt.rs:7:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/frequency.rs:148:43 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/xsv-0.13.0/src/cmd/frequency.rs:149:43 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/xsv-0.13.0/src/cmd/frequency.rs:15:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/frequency.rs:169:13 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/frequency.rs:176:17 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/xsv-0.13.0/src/cmd/frequency.rs:178:24 clippy::collapsible_else_if "this `else { if .. }` block can be collapsed" +target/lintcheck/sources/xsv-0.13.0/src/cmd/frequency.rs:77:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/frequency.rs:93:31 clippy::explicit_into_iter_loop "it is more concise to loop over containers instead of using explicit iteration methods" +target/lintcheck/sources/xsv-0.13.0/src/cmd/headers.rs:43:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/headers.rs:49:17 clippy::explicit_into_iter_loop "it is more concise to loop over containers instead of using explicit iteration methods" +target/lintcheck/sources/xsv-0.13.0/src/cmd/headers.rs:9:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/index.rs:11:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/index.rs:45:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/input.rs:42:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/input.rs:47:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/input.rs:7:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:17:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:194:29 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:224:22 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:293:14 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:293:20 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:297:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:298:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:299:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:300:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:308:9 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:342:38 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:342:46 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:347:9 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:372:44 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:375:33 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:392:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:403:29 clippy::explicit_into_iter_loop "it is more concise to loop over containers instead of using explicit iteration methods" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:406:57 clippy::implicit_clone "implicitly cloning a `Vec` by calling `to_vec` on its dereferenced type" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:426:13 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:77:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/xsv-0.13.0/src/cmd/join.rs:94:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/partition.rs:105:22 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/partition.rs:106:22 clippy::redundant_slicing "redundant slicing of the whole range" +target/lintcheck/sources/xsv-0.13.0/src/cmd/partition.rs:139:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/partition.rs:15:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/partition.rs:169:9 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/xsv-0.13.0/src/cmd/partition.rs:56:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/partition.rs:77:9 clippy::unused_self "unused `self` argument" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sample.rs:105:44 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sample.rs:115:21 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sample.rs:11:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sample.rs:51:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sample.rs:58:19 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sample.rs:69:9 clippy::match_wildcard_for_single_variants "wildcard match will miss any future added variants" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sample.rs:75:16 clippy::explicit_into_iter_loop "it is more concise to loop over containers instead of using explicit iteration methods" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sample.rs:91:42 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sample.rs:92:43 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/xsv-0.13.0/src/cmd/search.rs:51:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/search.rs:9:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/select.rs:60:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/select.rs:8:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/slice.rs:57:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/slice.rs:9:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sort.rs:11:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sort.rs:138:47 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sort.rs:139:51 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sort.rs:48:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/sort.rs:91:14 clippy::explicit_into_iter_loop "it is more concise to loop over containers instead of using explicit iteration methods" +target/lintcheck/sources/xsv-0.13.0/src/cmd/split.rs:14:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/split.rs:61:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/split.rs:94:5 clippy::unnecessary_wraps "this function's return value is unnecessary" +target/lintcheck/sources/xsv-0.13.0/src/cmd/split.rs:96:14 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/xsv-0.13.0/src/cmd/split.rs:99:13 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:110:36 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:127:14 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:138:43 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:139:43 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:162:25 clippy::explicit_into_iter_loop "it is more concise to loop over containers instead of using explicit iteration methods" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:22:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:231:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:262:35 clippy::default_trait_access "calling `cmd::stats::TypedSum::default()` is more clear than this expression" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:263:40 clippy::default_trait_access "calling `cmd::stats::TypedMinMax::default()` is more clear than this expression" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:264:39 clippy::default_trait_access "calling `stats::OnlineStats::default()` is more clear than this expression" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:265:58 clippy::default_trait_access "calling `stats::Unsorted::default()` is more clear than this expression" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:266:41 clippy::default_trait_access "calling `stats::Unsorted::default()` is more clear than this expression" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:268:18 clippy::default_trait_access "calling `cmd::stats::FieldType::default()` is more clear than this expression" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:269:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:270:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:271:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:272:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:273:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:274:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:283:9 clippy::option_map_unit_fn "called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:284:9 clippy::option_map_unit_fn "called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:285:9 clippy::option_map_unit_fn "called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:290:21 clippy::option_map_unit_fn "called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:293:25 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:297:25 clippy::option_map_unit_fn "called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:301:21 clippy::option_map_unit_fn "called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:302:21 clippy::option_map_unit_fn "called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:308:18 clippy::wrong_self_convention "methods called `to_*` usually take self by reference; consider choosing a less ambiguous name" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:318:9 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:322:45 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:322:9 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:327:9 clippy::if_not_else "unnecessary boolean `not` operation" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:330:13 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:338:45 clippy::redundant_closure_for_method_calls "redundant closure" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:402:16 clippy::redundant_pattern_matching "redundant pattern matching, consider using `is_ok()`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:403:16 clippy::redundant_pattern_matching "redundant pattern matching, consider using `is_ok()`" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:407:18 clippy::trivially_copy_pass_by_ref "this argument (1 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:411:16 clippy::trivially_copy_pass_by_ref "this argument (1 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:427:56 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:429:56 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:430:60 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:430:60 clippy::match_same_arms "this `match` has identical arm bodies" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:454:5 clippy::doc_markdown "you should put `TypedSum` between ticks in the documentation" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:473:43 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:504:56 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:505:51 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:511:5 clippy::doc_markdown "you should put `TypedMinMax` between ticks in the documentation" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:536:35 clippy::cast_possible_truncation "casting `f64` to `i64` may truncate the value" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:544:33 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:592:22 clippy::default_trait_access "calling `stats::MinMax::default()` is more clear than this expression" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:593:22 clippy::default_trait_access "calling `stats::MinMax::default()` is more clear than this expression" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:594:23 clippy::default_trait_access "calling `stats::MinMax::default()` is more clear than this expression" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:595:21 clippy::default_trait_access "calling `stats::MinMax::default()` is more clear than this expression" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:71:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/xsv-0.13.0/src/cmd/stats.rs:86:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/table.rs:10:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/cmd/table.rs:50:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/cmd/table.rs:54:9 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/config.rs:113:43 clippy::or_fun_call "use of `unwrap_or` followed by a function call" +target/lintcheck/sources/xsv-0.13.0/src/config.rs:58:1 clippy::struct_excessive_bools "more than 3 bools in a struct" +target/lintcheck/sources/xsv-0.13.0/src/config.rs:77:28 clippy::explicit_deref_methods "explicit deref method call" +target/lintcheck/sources/xsv-0.13.0/src/config.rs:90:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/index.rs:31:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/main.rs:164:49 clippy::redundant_clone "redundant clone" +target/lintcheck/sources/xsv-0.13.0/src/main.rs:164:50 clippy::implicit_clone "implicitly cloning a `String` by calling `to_owned` on its dereferenced type" +target/lintcheck/sources/xsv-0.13.0/src/main.rs:1:null clippy::cargo_common_metadata "package `xsv` is missing `package.categories` metadata" +target/lintcheck/sources/xsv-0.13.0/src/main.rs:1:null clippy::multiple_crate_versions "multiple versions for dependency `rand_core`: 0.3.1, 0.4.2" +target/lintcheck/sources/xsv-0.13.0/src/main.rs:1:null clippy::multiple_crate_versions "multiple versions for dependency `rand`: 0.3.23, 0.4.6" +target/lintcheck/sources/xsv-0.13.0/src/main.rs:75:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:13:1 clippy::module_name_repetitions "item name starts with its containing module's name" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:154:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:250:33 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:250:43 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:255:39 clippy::range_plus_one "an inclusive range would be more readable" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:280:20 clippy::len_zero "length comparison to zero" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:29:13 clippy::redundant_field_names "redundant field names in struct initialization" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:360:62 clippy::trivially_copy_pass_by_ref "this argument (8 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:360:9 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Option`" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:375:9 clippy::stable_sort_primitive "used `sort` on primitive type `usize`" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:379:18 clippy::explicit_into_iter_loop "it is more concise to loop over containers instead of using explicit iteration methods" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:416:5 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:419:9 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Option`" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:420:27 clippy::option_option "consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases" +target/lintcheck/sources/xsv-0.13.0/src/select.rs:99:17 clippy::similar_names "binding's name is too similar to existing binding" +target/lintcheck/sources/xsv-0.13.0/src/util.rs:150:5 clippy::doc_markdown "you should put bare URLs between `<`/`>` or make a proper Markdown link" +target/lintcheck/sources/xsv-0.13.0/src/util.rs:37:33 clippy::map_clone "you are using an explicit closure for copying elements" +target/lintcheck/sources/xsv-0.13.0/src/util.rs:90:1 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)" + + + + +Stats: +clippy::clone_on_copy 1 +clippy::comparison_chain 1 +clippy::expect_fun_call 1 +clippy::explicit_deref_methods 1 +clippy::from_iter_instead_of_collect 1 +clippy::int_plus_one 1 +clippy::manual_flatten 1 +clippy::manual_saturating_arithmetic 1 +clippy::mem_replace_with_default 1 +clippy::nonminimal_bool 1 +clippy::or_fun_call 1 +clippy::precedence 1 +clippy::pub_enum_variant_names 1 +clippy::redundant_clone 1 +clippy::same_item_push 1 +clippy::should_implement_trait 1 +clippy::stable_sort_primitive 1 +clippy::unnecessary_lazy_evaluations 1 +clippy::unsafe_derive_deserialize 1 +clippy::used_underscore_binding 1 +clippy::verbose_bit_mask 1 +clippy::while_let_on_iterator 1 +clippy::comparison_to_empty 2 +clippy::filter_map 2 +clippy::from_over_into 2 +clippy::len_zero 2 +clippy::manual_non_exhaustive 2 +clippy::match_on_vec_items 2 +clippy::option_as_ref_deref 2 +clippy::option_option 2 +clippy::question_mark 2 +clippy::redundant_pattern_matching 2 +clippy::redundant_slicing 2 +clippy::type_complexity 2 +clippy::unnecessary_cast 2 +clippy::unused_unit 2 +clippy::vec_init_then_push 2 +clippy::write_with_newline 2 +clippy::filter_map_next 3 +clippy::fn_params_excessive_bools 3 +clippy::if_same_then_else 3 +clippy::inconsistent_struct_constructor 3 +clippy::manual_map 3 +clippy::mut_mut 3 +clippy::ptr_arg 3 +clippy::zero_ptr 3 +clippy::too_many_arguments 4 +clippy::explicit_iter_loop 5 +clippy::field_reassign_with_default 5 +clippy::identity_op 5 +clippy::match_like_matches_macro 5 +clippy::needless_return 5 +clippy::new_without_default 5 +clippy::collapsible_else_if 6 +clippy::manual_strip 6 +clippy::non_ascii_literal 6 +clippy::single_component_path_imports 6 +clippy::case_sensitive_file_extension_comparisons 7 +clippy::explicit_into_iter_loop 7 +clippy::implicit_clone 7 +clippy::map_clone 7 +clippy::option_map_unit_fn 7 +clippy::range_plus_one 7 +clippy::upper_case_acronyms 7 +clippy::invalid_upcast_comparisons 8 +clippy::needless_question_mark 8 +clippy::wrong_self_convention 8 +clippy::len_without_is_empty 9 +clippy::multiple_crate_versions 9 +clippy::manual_range_contains 10 +clippy::match_wildcard_for_single_variants 10 +clippy::missing_safety_doc 10 +clippy::needless_doctest_main 10 +clippy::needless_lifetimes 12 +clippy::linkedlist 14 +clippy::single_char_add_str 14 +clippy::option_if_let_else 15 +clippy::shadow_unrelated 15 +clippy::needless_pass_by_value 18 +clippy::cast_possible_wrap 19 +clippy::cast_sign_loss 19 +clippy::unnecessary_wraps 19 +clippy::unused_self 19 +clippy::unusual_byte_groupings 19 +clippy::map_unwrap_or 20 +clippy::struct_excessive_bools 20 +clippy::cargo_common_metadata 21 +clippy::ptr_as_ptr 21 +clippy::redundant_static_lifetimes 21 +clippy::cast_lossless 23 +clippy::default_trait_access 26 +clippy::let_underscore_drop 26 +clippy::trivially_copy_pass_by_ref 26 +clippy::redundant_else 29 +clippy::too_many_lines 35 +clippy::if_not_else 38 +clippy::unseparated_literal_suffix 41 +clippy::cast_precision_loss 44 +clippy::enum_glob_use 44 +clippy::single_match_else 48 +clippy::inline_always 59 +clippy::missing_panics_doc 59 +clippy::match_same_arms 62 +clippy::similar_names 83 +clippy::cast_possible_truncation 95 +clippy::redundant_field_names 111 +clippy::redundant_closure_for_method_calls 131 +clippy::items_after_statements 143 +clippy::module_name_repetitions 146 +clippy::wildcard_imports 164 +clippy::expl_impl_clone_on_copy 165 +clippy::doc_markdown 184 +clippy::missing_errors_doc 356 +clippy::unreadable_literal 365 +clippy::must_use_candidate 571 +ICEs: diff --git a/src/tools/clippy/lintcheck/Cargo.toml b/src/tools/clippy/lintcheck/Cargo.toml new file mode 100644 index 0000000000..8db6d28e5a --- /dev/null +++ b/src/tools/clippy/lintcheck/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "lintcheck" +version = "0.0.1" +authors = ["The Rust Clippy Developers"] +description = "tool to monitor impact of changes in Clippys lints on a part of the ecosystem" +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-lang/rust-clippy" +categories = ["development-tools"] +edition = "2018" +publish = false + +[dependencies] +clap = "2.33" +flate2 = {version = "1.0.19"} +fs_extra = {version = "1.2.0"} +rayon = {version = "1.5.0"} +serde = {version = "1.0", features = ["derive"]} +serde_json = {version = "1.0"} +tar = {version = "0.4.30"} +toml = {version = "0.5"} +ureq = {version = "2.0.0-rc3"} + +[features] +deny-warnings = [] diff --git a/src/tools/clippy/lintcheck/README.md b/src/tools/clippy/lintcheck/README.md new file mode 100644 index 0000000000..52bbcc0a83 --- /dev/null +++ b/src/tools/clippy/lintcheck/README.md @@ -0,0 +1,77 @@ +## `cargo lintcheck` + +Runs clippy on a fixed set of crates read from +`lintcheck/lintcheck_crates.toml` and saves logs of the lint warnings into the +repo. We can then check the diff and spot new or disappearing warnings. + +From the repo root, run: + +``` +cargo run --target-dir lintcheck/target --manifest-path lintcheck/Cargo.toml +``` + +or + +``` +cargo lintcheck +``` + +By default the logs will be saved into +`lintcheck-logs/lintcheck_crates_logs.txt`. + +You can set a custom sources.toml by adding `--crates-toml custom.toml` or using +`LINTCHECK_TOML="custom.toml"` where `custom.toml` must be a relative path from +the repo root. + +The results will then be saved to `lintcheck-logs/custom_logs.toml`. + +### Configuring the Crate Sources + +The sources to check are saved in a `toml` file. There are three types of +sources. + +1. Crates-io Source + + ```toml + bitflags = {name = "bitflags", versions = ['1.2.1']} + ``` + Requires a "name" and one or multiple "versions" to be checked. + +2. `git` Source + ````toml + puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"} + ```` + Requires a name, the url to the repo and unique identifier of a commit, + branch or tag which is checked out before linting. There is no way to always + check `HEAD` because that would lead to changing lint-results as the repo + would get updated. If `git_url` or `git_hash` is missing, an error will be + thrown. + +3. Local Dependency + ```toml + clippy = {name = "clippy", path = "/home/user/clippy"} + ``` + For when you want to add a repository that is not published yet. + +#### Command Line Options (optional) + +```toml +bitflags = {name = "bitflags", versions = ['1.2.1'], options = ['-Wclippy::pedantic', '-Wclippy::cargo']} +``` + +It is possible to specify command line options for each crate. This makes it +possible to only check a crate for certain lint groups. If no options are +specified, the lint groups `clippy::all`, `clippy::pedantic`, and +`clippy::cargo` are checked. If an empty array is specified only `clippy::all` +is checked. + +**Note:** `-Wclippy::all` is always enabled by default, unless `-Aclippy::all` +is explicitly specified in the options. + +### Fix mode +You can run `./lintcheck/target/debug/lintcheck --fix` which will run Clippy with `-Zunstable-options --fix` and +print a warning if Clippys suggestions fail to apply (if the resulting code does not build). +This lets us spot bad suggestions or false positives automatically in some cases. + +Please note that the target dir should be cleaned afterwards since clippy will modify +the downloaded sources which can lead to unexpected results when running lintcheck again afterwards. diff --git a/src/tools/clippy/lintcheck/lintcheck_crates.toml b/src/tools/clippy/lintcheck/lintcheck_crates.toml new file mode 100644 index 0000000000..dfee28f1a8 --- /dev/null +++ b/src/tools/clippy/lintcheck/lintcheck_crates.toml @@ -0,0 +1,35 @@ +[crates] +# some of these are from cargotest +cargo = {name = "cargo", versions = ['0.49.0']} +iron = {name = "iron", versions = ['0.6.1']} +ripgrep = {name = "ripgrep", versions = ['12.1.1']} +xsv = {name = "xsv", versions = ['0.13.0']} +# commented out because of 173K clippy::match_same_arms msgs in language_type.rs +#tokei = { name = "tokei", versions = ['12.0.4']} +rayon = {name = "rayon", versions = ['1.5.0']} +serde = {name = "serde", versions = ['1.0.118']} +# top 10 crates.io dls +bitflags = {name = "bitflags", versions = ['1.2.1']} +# crash = {name = "clippy_crash", path = "/tmp/clippy_crash"} +libc = {name = "libc", versions = ['0.2.81']} +log = {name = "log", versions = ['0.4.11']} +proc-macro2 = {name = "proc-macro2", versions = ['1.0.24']} +quote = {name = "quote", versions = ['1.0.7']} +rand = {name = "rand", versions = ['0.7.3']} +rand_core = {name = "rand_core", versions = ['0.6.0']} +regex = {name = "regex", versions = ['1.3.2']} +syn = {name = "syn", versions = ['1.0.54']} +unicode-xid = {name = "unicode-xid", versions = ['0.2.1']} +# some more of dtolnays crates +anyhow = {name = "anyhow", versions = ['1.0.38']} +async-trait = {name = "async-trait", versions = ['0.1.42']} +cxx = {name = "cxx", versions = ['1.0.32']} +ryu = {name = "ryu", version = ['1.0.5']} +serde_yaml = {name = "serde_yaml", versions = ['0.8.17']} +thiserror = {name = "thiserror", versions = ['1.0.24']} +# some embark crates, there are other interesting crates but +# unfortunately adding them increases lintcheck runtime drastically +cfg-expr = {name = "cfg-expr", versions = ['0.7.1']} +puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"} +rpmalloc = {name = "rpmalloc", versions = ['0.2.0']} +tame-oidc = {name = "tame-oidc", versions = ['0.1.0']} diff --git a/src/tools/clippy/lintcheck/src/main.rs b/src/tools/clippy/lintcheck/src/main.rs new file mode 100644 index 0000000000..581b47647e --- /dev/null +++ b/src/tools/clippy/lintcheck/src/main.rs @@ -0,0 +1,917 @@ +// Run clippy on a fixed set of crates and collect the warnings. +// This helps observing the impact clippy changes have on a set of real-world code (and not just our +// testsuite). +// +// When a new lint is introduced, we can search the results for new warnings and check for false +// positives. + +#![allow(clippy::filter_map, clippy::collapsible_else_if)] + +use std::ffi::OsStr; +use std::process::Command; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{collections::HashMap, io::ErrorKind}; +use std::{ + env, fmt, + fs::write, + path::{Path, PathBuf}, +}; + +use clap::{App, Arg, ArgMatches}; +use rayon::prelude::*; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver"; +const CARGO_CLIPPY_PATH: &str = "target/debug/cargo-clippy"; + +const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads"; +const LINTCHECK_SOURCES: &str = "target/lintcheck/sources"; + +/// List of sources to check, loaded from a .toml file +#[derive(Debug, Serialize, Deserialize)] +struct SourceList { + crates: HashMap, +} + +/// A crate source stored inside the .toml +/// will be translated into on one of the `CrateSource` variants +#[derive(Debug, Serialize, Deserialize)] +struct TomlCrate { + name: String, + versions: Option>, + git_url: Option, + git_hash: Option, + path: Option, + options: Option>, +} + +/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder +/// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate` +#[derive(Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)] +enum CrateSource { + CratesIo { + name: String, + version: String, + options: Option>, + }, + Git { + name: String, + url: String, + commit: String, + options: Option>, + }, + Path { + name: String, + path: PathBuf, + options: Option>, + }, +} + +/// Represents the actual source code of a crate that we ran "cargo clippy" on +#[derive(Debug)] +struct Crate { + version: String, + name: String, + // path to the extracted sources that clippy can check + path: PathBuf, + options: Option>, +} + +/// A single warning that clippy issued while checking a `Crate` +#[derive(Debug)] +struct ClippyWarning { + crate_name: String, + crate_version: String, + file: String, + line: String, + column: String, + linttype: String, + message: String, + is_ice: bool, +} + +impl std::fmt::Display for ClippyWarning { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!( + f, + r#"target/lintcheck/sources/{}-{}/{}:{}:{} {} "{}""#, + &self.crate_name, &self.crate_version, &self.file, &self.line, &self.column, &self.linttype, &self.message + ) + } +} + +impl CrateSource { + /// Makes the sources available on the disk for clippy to check. + /// Clones a git repo and checks out the specified commit or downloads a crate from crates.io or + /// copies a local folder + fn download_and_extract(&self) -> Crate { + match self { + CrateSource::CratesIo { name, version, options } => { + let extract_dir = PathBuf::from(LINTCHECK_SOURCES); + let krate_download_dir = PathBuf::from(LINTCHECK_DOWNLOADS); + + // url to download the crate from crates.io + let url = format!("https://crates.io/api/v1/crates/{}/{}/download", name, version); + println!("Downloading and extracting {} {} from {}", name, version, url); + create_dirs(&krate_download_dir, &extract_dir); + + let krate_file_path = krate_download_dir.join(format!("{}-{}.crate.tar.gz", name, version)); + // don't download/extract if we already have done so + if !krate_file_path.is_file() { + // create a file path to download and write the crate data into + let mut krate_dest = std::fs::File::create(&krate_file_path).unwrap(); + let mut krate_req = ureq::get(&url).call().unwrap().into_reader(); + // copy the crate into the file + std::io::copy(&mut krate_req, &mut krate_dest).unwrap(); + + // unzip the tarball + let ungz_tar = flate2::read::GzDecoder::new(std::fs::File::open(&krate_file_path).unwrap()); + // extract the tar archive + let mut archive = tar::Archive::new(ungz_tar); + archive.unpack(&extract_dir).expect("Failed to extract!"); + } + // crate is extracted, return a new Krate object which contains the path to the extracted + // sources that clippy can check + Crate { + version: version.clone(), + name: name.clone(), + path: extract_dir.join(format!("{}-{}/", name, version)), + options: options.clone(), + } + }, + CrateSource::Git { + name, + url, + commit, + options, + } => { + let repo_path = { + let mut repo_path = PathBuf::from(LINTCHECK_SOURCES); + // add a -git suffix in case we have the same crate from crates.io and a git repo + repo_path.push(format!("{}-git", name)); + repo_path + }; + // clone the repo if we have not done so + if !repo_path.is_dir() { + println!("Cloning {} and checking out {}", url, commit); + if !Command::new("git") + .arg("clone") + .arg(url) + .arg(&repo_path) + .status() + .expect("Failed to clone git repo!") + .success() + { + eprintln!("Failed to clone {} into {}", url, repo_path.display()) + } + } + // check out the commit/branch/whatever + if !Command::new("git") + .arg("checkout") + .arg(commit) + .current_dir(&repo_path) + .status() + .expect("Failed to check out commit") + .success() + { + eprintln!("Failed to checkout {} of repo at {}", commit, repo_path.display()) + } + + Crate { + version: commit.clone(), + name: name.clone(), + path: repo_path, + options: options.clone(), + } + }, + CrateSource::Path { name, path, options } => { + use fs_extra::dir; + + // simply copy the entire directory into our target dir + let copy_dest = PathBuf::from(format!("{}/", LINTCHECK_SOURCES)); + + // the source path of the crate we copied, ${copy_dest}/crate_name + let crate_root = copy_dest.join(name); // .../crates/local_crate + + if crate_root.exists() { + println!( + "Not copying {} to {}, destination already exists", + path.display(), + crate_root.display() + ); + } else { + println!("Copying {} to {}", path.display(), copy_dest.display()); + + dir::copy(path, ©_dest, &dir::CopyOptions::new()).unwrap_or_else(|_| { + panic!("Failed to copy from {}, to {}", path.display(), crate_root.display()) + }); + } + + Crate { + version: String::from("local"), + name: name.clone(), + path: crate_root, + options: options.clone(), + } + }, + } + } +} + +impl Crate { + /// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy + /// issued + fn run_clippy_lints( + &self, + cargo_clippy_path: &Path, + target_dir_index: &AtomicUsize, + thread_limit: usize, + total_crates_to_lint: usize, + fix: bool, + ) -> Vec { + // advance the atomic index by one + let index = target_dir_index.fetch_add(1, Ordering::SeqCst); + // "loop" the index within 0..thread_limit + let thread_index = index % thread_limit; + let perc = (index * 100) / total_crates_to_lint; + + if thread_limit == 1 { + println!( + "{}/{} {}% Linting {} {}", + index, total_crates_to_lint, perc, &self.name, &self.version + ); + } else { + println!( + "{}/{} {}% Linting {} {} in target dir {:?}", + index, total_crates_to_lint, perc, &self.name, &self.version, thread_index + ); + } + + let cargo_clippy_path = std::fs::canonicalize(cargo_clippy_path).unwrap(); + + let shared_target_dir = clippy_project_root().join("target/lintcheck/shared_target_dir"); + + let mut args = if fix { + vec![ + "-Zunstable-options", + "--fix", + "-Zunstable-options", + "--allow-no-vcs", + "--", + "--cap-lints=warn", + ] + } else { + vec!["--", "--message-format=json", "--", "--cap-lints=warn"] + }; + + if let Some(options) = &self.options { + for opt in options { + args.push(opt); + } + } else { + args.extend(&["-Wclippy::pedantic", "-Wclippy::cargo"]) + } + + let all_output = std::process::Command::new(&cargo_clippy_path) + // use the looping index to create individual target dirs + .env( + "CARGO_TARGET_DIR", + shared_target_dir.join(format!("_{:?}", thread_index)), + ) + // lint warnings will look like this: + // src/cargo/ops/cargo_compile.rs:127:35: warning: usage of `FromIterator::from_iter` + .args(&args) + .current_dir(&self.path) + .output() + .unwrap_or_else(|error| { + panic!( + "Encountered error:\n{:?}\ncargo_clippy_path: {}\ncrate path:{}\n", + error, + &cargo_clippy_path.display(), + &self.path.display() + ); + }); + let stdout = String::from_utf8_lossy(&all_output.stdout); + let stderr = String::from_utf8_lossy(&all_output.stderr); + + if fix { + if let Some(stderr) = stderr + .lines() + .find(|line| line.contains("failed to automatically apply fixes suggested by rustc to crate")) + { + let subcrate = &stderr[63..]; + println!( + "ERROR: failed to apply some suggetion to {} / to (sub)crate {}", + self.name, subcrate + ); + } + // fast path, we don't need the warnings anyway + return Vec::new(); + } + + let output_lines = stdout.lines(); + let warnings: Vec = output_lines + .into_iter() + // get all clippy warnings and ICEs + .filter(|line| filter_clippy_warnings(&line)) + .map(|json_msg| parse_json_message(json_msg, &self)) + .collect(); + + warnings + } +} + +#[derive(Debug)] +struct LintcheckConfig { + // max number of jobs to spawn (default 1) + max_jobs: usize, + // we read the sources to check from here + sources_toml_path: PathBuf, + // we save the clippy lint results here + lintcheck_results_path: PathBuf, + // whether to just run --fix and not collect all the warnings + fix: bool, +} + +impl LintcheckConfig { + fn from_clap(clap_config: &ArgMatches) -> Self { + // first, check if we got anything passed via the LINTCHECK_TOML env var, + // if not, ask clap if we got any value for --crates-toml + // if not, use the default "lintcheck/lintcheck_crates.toml" + let sources_toml = env::var("LINTCHECK_TOML").unwrap_or_else(|_| { + clap_config + .value_of("crates-toml") + .clone() + .unwrap_or("lintcheck/lintcheck_crates.toml") + .to_string() + }); + + let sources_toml_path = PathBuf::from(sources_toml); + + // for the path where we save the lint results, get the filename without extension (so for + // wasd.toml, use "wasd"...) + let filename: PathBuf = sources_toml_path.file_stem().unwrap().into(); + let lintcheck_results_path = PathBuf::from(format!("lintcheck-logs/{}_logs.txt", filename.display())); + + // look at the --threads arg, if 0 is passed, ask rayon rayon how many threads it would spawn and + // use half of that for the physical core count + // by default use a single thread + let max_jobs = match clap_config.value_of("threads") { + Some(threads) => { + let threads: usize = threads + .parse() + .unwrap_or_else(|_| panic!("Failed to parse '{}' to a digit", threads)); + if threads == 0 { + // automatic choice + // Rayon seems to return thread count so half that for core count + (rayon::current_num_threads() / 2) as usize + } else { + threads + } + }, + // no -j passed, use a single thread + None => 1, + }; + let fix: bool = clap_config.is_present("fix"); + + LintcheckConfig { + max_jobs, + sources_toml_path, + lintcheck_results_path, + fix, + } + } +} + +/// takes a single json-formatted clippy warnings and returns true (we are interested in that line) +/// or false (we aren't) +fn filter_clippy_warnings(line: &str) -> bool { + // we want to collect ICEs because clippy might have crashed. + // these are summarized later + if line.contains("internal compiler error: ") { + return true; + } + // in general, we want all clippy warnings + // however due to some kind of bug, sometimes there are absolute paths + // to libcore files inside the message + // or we end up with cargo-metadata output (https://github.com/rust-lang/rust-clippy/issues/6508) + + // filter out these message to avoid unnecessary noise in the logs + if line.contains("clippy::") + && !(line.contains("could not read cargo metadata") + || (line.contains(".rustup") && line.contains("toolchains"))) + { + return true; + } + false +} + +/// Builds clippy inside the repo to make sure we have a clippy executable we can use. +fn build_clippy() { + let status = Command::new("cargo") + .arg("build") + .status() + .expect("Failed to build clippy!"); + if !status.success() { + eprintln!("Error: Failed to compile Clippy!"); + std::process::exit(1); + } +} + +/// Read a `toml` file and return a list of `CrateSources` that we want to check with clippy +fn read_crates(toml_path: &Path) -> Vec { + let toml_content: String = + std::fs::read_to_string(&toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display())); + let crate_list: SourceList = + toml::from_str(&toml_content).unwrap_or_else(|e| panic!("Failed to parse {}: \n{}", toml_path.display(), e)); + // parse the hashmap of the toml file into a list of crates + let tomlcrates: Vec = crate_list + .crates + .into_iter() + .map(|(_cratename, tomlcrate)| tomlcrate) + .collect(); + + // flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate => + // multiple Cratesources) + let mut crate_sources = Vec::new(); + tomlcrates.into_iter().for_each(|tk| { + if let Some(ref path) = tk.path { + crate_sources.push(CrateSource::Path { + name: tk.name.clone(), + path: PathBuf::from(path), + options: tk.options.clone(), + }); + } + + // if we have multiple versions, save each one + if let Some(ref versions) = tk.versions { + versions.iter().for_each(|ver| { + crate_sources.push(CrateSource::CratesIo { + name: tk.name.clone(), + version: ver.to_string(), + options: tk.options.clone(), + }); + }) + } + // otherwise, we should have a git source + if tk.git_url.is_some() && tk.git_hash.is_some() { + crate_sources.push(CrateSource::Git { + name: tk.name.clone(), + url: tk.git_url.clone().unwrap(), + commit: tk.git_hash.clone().unwrap(), + options: tk.options.clone(), + }); + } + // if we have a version as well as a git data OR only one git data, something is funky + if tk.versions.is_some() && (tk.git_url.is_some() || tk.git_hash.is_some()) + || tk.git_hash.is_some() != tk.git_url.is_some() + { + eprintln!("tomlkrate: {:?}", tk); + if tk.git_hash.is_some() != tk.git_url.is_some() { + panic!("Error: Encountered TomlCrate with only one of git_hash and git_url!"); + } + if tk.path.is_some() && (tk.git_hash.is_some() || tk.versions.is_some()) { + panic!("Error: TomlCrate can only have one of 'git_.*', 'version' or 'path' fields"); + } + unreachable!("Failed to translate TomlCrate into CrateSource!"); + } + }); + // sort the crates + crate_sources.sort(); + + crate_sources +} + +/// Parse the json output of clippy and return a `ClippyWarning` +fn parse_json_message(json_message: &str, krate: &Crate) -> ClippyWarning { + let jmsg: Value = serde_json::from_str(&json_message).unwrap_or_else(|e| panic!("Failed to parse json:\n{:?}", e)); + + let file: String = jmsg["message"]["spans"][0]["file_name"] + .to_string() + .trim_matches('"') + .into(); + + let file = if file.contains(".cargo") { + // if we deal with macros, a filename may show the origin of a macro which can be inside a dep from + // the registry. + // don't show the full path in that case. + + // /home/matthias/.cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.63/src/custom_keyword.rs + let path = PathBuf::from(file); + let mut piter = path.iter(); + // consume all elements until we find ".cargo", so that "/home/matthias" is skipped + let _: Option<&OsStr> = piter.find(|x| x == &std::ffi::OsString::from(".cargo")); + // collect the remaining segments + let file = piter.collect::(); + format!("{}", file.display()) + } else { + file + }; + + ClippyWarning { + crate_name: krate.name.to_string(), + crate_version: krate.version.to_string(), + file, + line: jmsg["message"]["spans"][0]["line_start"] + .to_string() + .trim_matches('"') + .into(), + column: jmsg["message"]["spans"][0]["text"][0]["highlight_start"] + .to_string() + .trim_matches('"') + .into(), + linttype: jmsg["message"]["code"]["code"].to_string().trim_matches('"').into(), + message: jmsg["message"]["message"].to_string().trim_matches('"').into(), + is_ice: json_message.contains("internal compiler error: "), + } +} + +/// Generate a short list of occuring lints-types and their count +fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) { + // count lint type occurrences + let mut counter: HashMap<&String, usize> = HashMap::new(); + clippy_warnings + .iter() + .for_each(|wrn| *counter.entry(&wrn.linttype).or_insert(0) += 1); + + // collect into a tupled list for sorting + let mut stats: Vec<(&&String, &usize)> = counter.iter().map(|(lint, count)| (lint, count)).collect(); + // sort by "000{count} {clippy::lintname}" + // to not have a lint with 200 and 2 warnings take the same spot + stats.sort_by_key(|(lint, count)| format!("{:0>4}, {}", count, lint)); + + let stats_string = stats + .iter() + .map(|(lint, count)| format!("{} {}\n", lint, count)) + .collect::(); + + (stats_string, counter) +} + +/// check if the latest modification of the logfile is older than the modification date of the +/// clippy binary, if this is true, we should clean the lintchec shared target directory and recheck +fn lintcheck_needs_rerun(lintcheck_logs_path: &Path) -> bool { + if !lintcheck_logs_path.exists() { + return true; + } + + let clippy_modified: std::time::SystemTime = { + let mut times = [CLIPPY_DRIVER_PATH, CARGO_CLIPPY_PATH].iter().map(|p| { + std::fs::metadata(p) + .expect("failed to get metadata of file") + .modified() + .expect("failed to get modification date") + }); + // the oldest modification of either of the binaries + std::cmp::max(times.next().unwrap(), times.next().unwrap()) + }; + + let logs_modified: std::time::SystemTime = std::fs::metadata(lintcheck_logs_path) + .expect("failed to get metadata of file") + .modified() + .expect("failed to get modification date"); + + // time is represented in seconds since X + // logs_modified 2 and clippy_modified 5 means clippy binary is older and we need to recheck + logs_modified < clippy_modified +} + +fn is_in_clippy_root() -> bool { + if let Ok(pb) = std::env::current_dir() { + if let Some(file) = pb.file_name() { + return file == PathBuf::from("rust-clippy"); + } + } + + false +} + +/// lintchecks `main()` function +/// +/// # Panics +/// +/// This function panics if the clippy binaries don't exist +/// or if lintcheck is executed from the wrong directory (aka none-repo-root) +pub fn main() { + // assert that we launch lintcheck from the repo root (via cargo lintcheck) + if !is_in_clippy_root() { + eprintln!("lintcheck needs to be run from clippys repo root!\nUse `cargo lintcheck` alternatively."); + std::process::exit(3); + } + + let clap_config = &get_clap_config(); + + let config = LintcheckConfig::from_clap(clap_config); + + println!("Compiling clippy..."); + build_clippy(); + println!("Done compiling"); + + // if the clippy bin is newer than our logs, throw away target dirs to force clippy to + // refresh the logs + if lintcheck_needs_rerun(&config.lintcheck_results_path) { + let shared_target_dir = "target/lintcheck/shared_target_dir"; + // if we get an Err here, the shared target dir probably does simply not exist + if let Ok(metadata) = std::fs::metadata(&shared_target_dir) { + if metadata.is_dir() { + println!("Clippy is newer than lint check logs, clearing lintcheck shared target dir..."); + std::fs::remove_dir_all(&shared_target_dir) + .expect("failed to remove target/lintcheck/shared_target_dir"); + } + } + } + + let cargo_clippy_path: PathBuf = PathBuf::from(CARGO_CLIPPY_PATH) + .canonicalize() + .expect("failed to canonicalize path to clippy binary"); + + // assert that clippy is found + assert!( + cargo_clippy_path.is_file(), + "target/debug/cargo-clippy binary not found! {}", + cargo_clippy_path.display() + ); + + let clippy_ver = std::process::Command::new(CARGO_CLIPPY_PATH) + .arg("--version") + .output() + .map(|o| String::from_utf8_lossy(&o.stdout).into_owned()) + .expect("could not get clippy version!"); + + // download and extract the crates, then run clippy on them and collect clippys warnings + // flatten into one big list of warnings + + let crates = read_crates(&config.sources_toml_path); + let old_stats = read_stats_from_file(&config.lintcheck_results_path); + + let counter = AtomicUsize::new(1); + + let clippy_warnings: Vec = if let Some(only_one_crate) = clap_config.value_of("only") { + // if we don't have the specified crate in the .toml, throw an error + if !crates.iter().any(|krate| { + let name = match krate { + CrateSource::CratesIo { name, .. } | CrateSource::Git { name, .. } | CrateSource::Path { name, .. } => { + name + }, + }; + name == only_one_crate + }) { + eprintln!( + "ERROR: could not find crate '{}' in lintcheck/lintcheck_crates.toml", + only_one_crate + ); + std::process::exit(1); + } + + // only check a single crate that was passed via cmdline + crates + .into_iter() + .map(|krate| krate.download_and_extract()) + .filter(|krate| krate.name == only_one_crate) + .flat_map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &AtomicUsize::new(0), 1, 1, config.fix)) + .collect() + } else { + if config.max_jobs > 1 { + // run parallel with rayon + + // Ask rayon for thread count. Assume that half of that is the number of physical cores + // Use one target dir for each core so that we can run N clippys in parallel. + // We need to use different target dirs because cargo would lock them for a single build otherwise, + // killing the parallelism. However this also means that deps will only be reused half/a + // quarter of the time which might result in a longer wall clock runtime + + // This helps when we check many small crates with dep-trees that don't have a lot of branches in + // order to achive some kind of parallelism + + // by default, use a single thread + let num_cpus = config.max_jobs; + let num_crates = crates.len(); + + // check all crates (default) + crates + .into_par_iter() + .map(|krate| krate.download_and_extract()) + .flat_map(|krate| { + krate.run_clippy_lints(&cargo_clippy_path, &counter, num_cpus, num_crates, config.fix) + }) + .collect() + } else { + // run sequential + let num_crates = crates.len(); + crates + .into_iter() + .map(|krate| krate.download_and_extract()) + .flat_map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &counter, 1, num_crates, config.fix)) + .collect() + } + }; + + // if we are in --fix mode, don't change the log files, terminate here + if config.fix { + return; + } + + // generate some stats + let (stats_formatted, new_stats) = gather_stats(&clippy_warnings); + + // grab crashes/ICEs, save the crate name and the ice message + let ices: Vec<(&String, &String)> = clippy_warnings + .iter() + .filter(|warning| warning.is_ice) + .map(|w| (&w.crate_name, &w.message)) + .collect(); + + let mut all_msgs: Vec = clippy_warnings.iter().map(ToString::to_string).collect(); + all_msgs.sort(); + all_msgs.push("\n\n\n\nStats:\n".into()); + all_msgs.push(stats_formatted); + + // save the text into lintcheck-logs/logs.txt + let mut text = clippy_ver; // clippy version number on top + text.push_str(&format!("\n{}", all_msgs.join(""))); + text.push_str("ICEs:\n"); + ices.iter() + .for_each(|(cratename, msg)| text.push_str(&format!("{}: '{}'", cratename, msg))); + + println!("Writing logs to {}", config.lintcheck_results_path.display()); + write(&config.lintcheck_results_path, text).unwrap(); + + print_stats(old_stats, new_stats); +} + +/// read the previous stats from the lintcheck-log file +fn read_stats_from_file(file_path: &Path) -> HashMap { + let file_content: String = match std::fs::read_to_string(file_path).ok() { + Some(content) => content, + None => { + return HashMap::new(); + }, + }; + + let lines: Vec = file_content.lines().map(ToString::to_string).collect(); + + // search for the beginning "Stats:" and the end "ICEs:" of the section we want + let start = lines.iter().position(|line| line == "Stats:").unwrap(); + let end = lines.iter().position(|line| line == "ICEs:").unwrap(); + + let stats_lines = &lines[start + 1..end]; + + stats_lines + .iter() + .map(|line| { + let mut spl = line.split(' '); + ( + spl.next().unwrap().to_string(), + spl.next().unwrap().parse::().unwrap(), + ) + }) + .collect::>() +} + +/// print how lint counts changed between runs +fn print_stats(old_stats: HashMap, new_stats: HashMap<&String, usize>) { + let same_in_both_hashmaps = old_stats + .iter() + .filter(|(old_key, old_val)| new_stats.get::<&String>(&old_key) == Some(old_val)) + .map(|(k, v)| (k.to_string(), *v)) + .collect::>(); + + let mut old_stats_deduped = old_stats; + let mut new_stats_deduped = new_stats; + + // remove duplicates from both hashmaps + same_in_both_hashmaps.iter().for_each(|(k, v)| { + assert!(old_stats_deduped.remove(k) == Some(*v)); + assert!(new_stats_deduped.remove(k) == Some(*v)); + }); + + println!("\nStats:"); + + // list all new counts (key is in new stats but not in old stats) + new_stats_deduped + .iter() + .filter(|(new_key, _)| old_stats_deduped.get::(&new_key).is_none()) + .for_each(|(new_key, new_value)| { + println!("{} 0 => {}", new_key, new_value); + }); + + // list all changed counts (key is in both maps but value differs) + new_stats_deduped + .iter() + .filter(|(new_key, _new_val)| old_stats_deduped.get::(&new_key).is_some()) + .for_each(|(new_key, new_val)| { + let old_val = old_stats_deduped.get::(&new_key).unwrap(); + println!("{} {} => {}", new_key, old_val, new_val); + }); + + // list all gone counts (key is in old status but not in new stats) + old_stats_deduped + .iter() + .filter(|(old_key, _)| new_stats_deduped.get::<&String>(&old_key).is_none()) + .for_each(|(old_key, old_value)| { + println!("{} {} => 0", old_key, old_value); + }); +} + +/// Create necessary directories to run the lintcheck tool. +/// +/// # Panics +/// +/// This function panics if creating one of the dirs fails. +fn create_dirs(krate_download_dir: &Path, extract_dir: &Path) { + std::fs::create_dir("target/lintcheck/").unwrap_or_else(|err| { + if err.kind() != ErrorKind::AlreadyExists { + panic!("cannot create lintcheck target dir"); + } + }); + std::fs::create_dir(&krate_download_dir).unwrap_or_else(|err| { + if err.kind() != ErrorKind::AlreadyExists { + panic!("cannot create crate download dir"); + } + }); + std::fs::create_dir(&extract_dir).unwrap_or_else(|err| { + if err.kind() != ErrorKind::AlreadyExists { + panic!("cannot create crate extraction dir"); + } + }); +} + +fn get_clap_config<'a>() -> ArgMatches<'a> { + App::new("lintcheck") + .about("run clippy on a set of crates and check output") + .arg( + Arg::with_name("only") + .takes_value(true) + .value_name("CRATE") + .long("only") + .help("only process a single crate of the list"), + ) + .arg( + Arg::with_name("crates-toml") + .takes_value(true) + .value_name("CRATES-SOURCES-TOML-PATH") + .long("crates-toml") + .help("set the path for a crates.toml where lintcheck should read the sources from"), + ) + .arg( + Arg::with_name("threads") + .takes_value(true) + .value_name("N") + .short("j") + .long("jobs") + .help("number of threads to use, 0 automatic choice"), + ) + .arg( + Arg::with_name("fix") + .long("--fix") + .help("runs cargo clippy --fix and checks if all suggestions apply"), + ) + .get_matches() +} + +/// Returns the path to the Clippy project directory +/// +/// # Panics +/// +/// Panics if the current directory could not be retrieved, there was an error reading any of the +/// Cargo.toml files or ancestor directory is the clippy root 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 lintcheck_test() { + let args = [ + "run", + "--target-dir", + "lintcheck/target", + "--manifest-path", + "./lintcheck/Cargo.toml", + "--", + "--crates-toml", + "lintcheck/test_sources.toml", + ]; + let status = std::process::Command::new("cargo") + .args(&args) + .current_dir("..") // repo root + .status(); + //.output(); + + assert!(status.unwrap().success()); +} diff --git a/src/tools/clippy/lintcheck/test_sources.toml b/src/tools/clippy/lintcheck/test_sources.toml new file mode 100644 index 0000000000..4b0eb71ef4 --- /dev/null +++ b/src/tools/clippy/lintcheck/test_sources.toml @@ -0,0 +1,4 @@ +[crates] +cc = {name = "cc", versions = ['1.0.67']} +home = {name = "home", git_url = "https://github.com/brson/home", git_hash = "32044e53dfbdcd32bafad3109d1fbab805fc0f40"} +rustc_tools_util = {name = "rustc_tools_util", versions = ['0.2.0']} diff --git a/src/tools/clippy/mini-macro/Cargo.toml b/src/tools/clippy/mini-macro/Cargo.toml new file mode 100644 index 0000000000..0d95c86aef --- /dev/null +++ b/src/tools/clippy/mini-macro/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "clippy-mini-macro-test" +version = "0.2.0" +authors = ["The Rust Clippy Developers"] +license = "MIT OR Apache-2.0" +description = "A macro to test clippy's procedural macro checks" +repository = "https://github.com/rust-lang/rust-clippy" +edition = "2018" + +[lib] +name = "clippy_mini_macro_test" +proc-macro = true + +[dependencies] diff --git a/src/tools/clippy/mini-macro/src/lib.rs b/src/tools/clippy/mini-macro/src/lib.rs new file mode 100644 index 0000000000..2b79358904 --- /dev/null +++ b/src/tools/clippy/mini-macro/src/lib.rs @@ -0,0 +1,29 @@ +#![feature(proc_macro_quote)] +#![deny(rust_2018_idioms)] +// FIXME: Remove this attribute once the weird failure is gone. +#![allow(unused_extern_crates)] +extern crate proc_macro; + +use proc_macro::{quote, TokenStream}; + +#[proc_macro_derive(ClippyMiniMacroTest)] +/// # Panics +/// +/// Panics if the macro derivation fails +pub fn mini_macro(_: TokenStream) -> TokenStream { + quote!( + #[allow(unused)] + fn needless_take_by_value(s: String) { + println!("{}", s.len()); + } + #[allow(unused)] + fn needless_loop(items: &[u8]) { + for i in 0..items.len() { + println!("{}", items[i]); + } + } + fn line_wrapper() { + println!("{}", line!()); + } + ) +} diff --git a/src/tools/clippy/rust-toolchain b/src/tools/clippy/rust-toolchain new file mode 100644 index 0000000000..c52a7f2e74 --- /dev/null +++ b/src/tools/clippy/rust-toolchain @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2021-03-11" +components = ["llvm-tools-preview", "rustc-dev", "rust-src"] diff --git a/src/tools/clippy/rustc_tools_util/Cargo.toml b/src/tools/clippy/rustc_tools_util/Cargo.toml new file mode 100644 index 0000000000..2972bc6d51 --- /dev/null +++ b/src/tools/clippy/rustc_tools_util/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rustc_tools_util" +version = "0.2.0" +authors = ["The Rust Clippy Developers"] +description = "small helper to generate version information for git packages" +repository = "https://github.com/rust-lang/rust-clippy" +readme = "README.md" +license = "MIT OR Apache-2.0" +keywords = ["rustc", "tool", "git", "version", "hash"] +categories = ["development-tools"] +edition = "2018" + +[dependencies] + +[features] +deny-warnings = [] diff --git a/src/tools/clippy/rustc_tools_util/LICENSE-APACHE b/src/tools/clippy/rustc_tools_util/LICENSE-APACHE new file mode 120000 index 0000000000..965b606f33 --- /dev/null +++ b/src/tools/clippy/rustc_tools_util/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/src/tools/clippy/rustc_tools_util/LICENSE-MIT b/src/tools/clippy/rustc_tools_util/LICENSE-MIT new file mode 120000 index 0000000000..76219eb72e --- /dev/null +++ b/src/tools/clippy/rustc_tools_util/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/src/tools/clippy/rustc_tools_util/README.md b/src/tools/clippy/rustc_tools_util/README.md new file mode 100644 index 0000000000..6027538dc4 --- /dev/null +++ b/src/tools/clippy/rustc_tools_util/README.md @@ -0,0 +1,62 @@ +# rustc_tools_util + +A small tool to help you generate version information +for packages installed from a git repo + +## Usage + +Add a `build.rs` file to your repo and list it in `Cargo.toml` +```` +build = "build.rs" +```` + +List rustc_tools_util as regular AND build dependency. +```` +[dependencies] +rustc_tools_util = "0.1" + +[build-dependencies] +rustc_tools_util = "0.1" +```` + +In `build.rs`, generate the data in your `main()` +````rust +fn main() { + println!( + "cargo:rustc-env=GIT_HASH={}", + rustc_tools_util::get_commit_hash().unwrap_or_default() + ); + println!( + "cargo:rustc-env=COMMIT_DATE={}", + rustc_tools_util::get_commit_date().unwrap_or_default() + ); + println!( + "cargo:rustc-env=RUSTC_RELEASE_CHANNEL={}", + rustc_tools_util::get_channel().unwrap_or_default() + ); +} + +```` + +Use the version information in your main.rs +````rust +use rustc_tools_util::*; + +fn show_version() { + let version_info = rustc_tools_util::get_version_info!(); + println!("{}", version_info); +} +```` +This gives the following output in clippy: +`clippy 0.0.212 (a416c5e 2018-12-14)` + + +## License + +Copyright 2014-2020 The Rust Project Developers + +Licensed under the Apache License, Version 2.0 or the MIT license +, at your +option. All files in the project carrying such notice may not be +copied, modified, or distributed except according to those terms. diff --git a/src/tools/clippy/rustc_tools_util/src/lib.rs b/src/tools/clippy/rustc_tools_util/src/lib.rs new file mode 100644 index 0000000000..ff2a7de572 --- /dev/null +++ b/src/tools/clippy/rustc_tools_util/src/lib.rs @@ -0,0 +1,162 @@ +#![cfg_attr(feature = "deny-warnings", deny(warnings))] + +use std::env; + +#[macro_export] +macro_rules! get_version_info { + () => {{ + let major = env!("CARGO_PKG_VERSION_MAJOR").parse::().unwrap(); + let minor = env!("CARGO_PKG_VERSION_MINOR").parse::().unwrap(); + let patch = env!("CARGO_PKG_VERSION_PATCH").parse::().unwrap(); + let crate_name = String::from(env!("CARGO_PKG_NAME")); + + let host_compiler = option_env!("RUSTC_RELEASE_CHANNEL").map(str::to_string); + let commit_hash = option_env!("GIT_HASH").map(str::to_string); + let commit_date = option_env!("COMMIT_DATE").map(str::to_string); + + VersionInfo { + major, + minor, + patch, + host_compiler, + commit_hash, + commit_date, + crate_name, + } + }}; +} + +// some code taken and adapted from RLS and cargo +pub struct VersionInfo { + pub major: u8, + pub minor: u8, + pub patch: u16, + pub host_compiler: Option, + pub commit_hash: Option, + pub commit_date: Option, + pub crate_name: String, +} + +impl std::fmt::Display for VersionInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let hash = self.commit_hash.clone().unwrap_or_default(); + let hash_trimmed = hash.trim(); + + let date = self.commit_date.clone().unwrap_or_default(); + let date_trimmed = date.trim(); + + if (hash_trimmed.len() + date_trimmed.len()) > 0 { + write!( + f, + "{} {}.{}.{} ({} {})", + self.crate_name, self.major, self.minor, self.patch, hash_trimmed, date_trimmed, + )?; + } else { + write!(f, "{} {}.{}.{}", self.crate_name, self.major, self.minor, self.patch)?; + } + + Ok(()) + } +} + +impl std::fmt::Debug for VersionInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "VersionInfo {{ crate_name: \"{}\", major: {}, minor: {}, patch: {}", + self.crate_name, self.major, self.minor, self.patch, + )?; + if self.commit_hash.is_some() { + write!( + f, + ", commit_hash: \"{}\", commit_date: \"{}\" }}", + self.commit_hash.clone().unwrap_or_default().trim(), + self.commit_date.clone().unwrap_or_default().trim() + )?; + } else { + write!(f, " }}")?; + } + + Ok(()) + } +} + +#[must_use] +pub fn get_commit_hash() -> Option { + std::process::Command::new("git") + .args(&["rev-parse", "--short", "HEAD"]) + .output() + .ok() + .and_then(|r| String::from_utf8(r.stdout).ok()) +} + +#[must_use] +pub fn get_commit_date() -> Option { + std::process::Command::new("git") + .args(&["log", "-1", "--date=short", "--pretty=format:%cd"]) + .output() + .ok() + .and_then(|r| String::from_utf8(r.stdout).ok()) +} + +#[must_use] +pub fn get_channel() -> Option { + match env::var("CFG_RELEASE_CHANNEL") { + Ok(channel) => Some(channel), + Err(_) => { + // if that failed, try to ask rustc -V, do some parsing and find out + match std::process::Command::new("rustc") + .arg("-V") + .output() + .ok() + .and_then(|r| String::from_utf8(r.stdout).ok()) + { + Some(rustc_output) => { + if rustc_output.contains("beta") { + Some(String::from("beta")) + } else if rustc_output.contains("stable") { + Some(String::from("stable")) + } else { + // default to nightly if we fail to parse + Some(String::from("nightly")) + } + }, + // default to nightly + None => Some(String::from("nightly")), + } + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_struct_local() { + let vi = get_version_info!(); + assert_eq!(vi.major, 0); + assert_eq!(vi.minor, 2); + assert_eq!(vi.patch, 0); + assert_eq!(vi.crate_name, "rustc_tools_util"); + // hard to make positive tests for these since they will always change + assert!(vi.commit_hash.is_none()); + assert!(vi.commit_date.is_none()); + } + + #[test] + fn test_display_local() { + let vi = get_version_info!(); + assert_eq!(vi.to_string(), "rustc_tools_util 0.2.0"); + } + + #[test] + fn test_debug_local() { + let vi = get_version_info!(); + let s = format!("{:?}", vi); + assert_eq!( + s, + "VersionInfo { crate_name: \"rustc_tools_util\", major: 0, minor: 2, patch: 0 }" + ); + } +} diff --git a/src/tools/clippy/rustfmt.toml b/src/tools/clippy/rustfmt.toml new file mode 100644 index 0000000000..4b415a31b2 --- /dev/null +++ b/src/tools/clippy/rustfmt.toml @@ -0,0 +1,7 @@ +max_width = 120 +comment_width = 100 +match_block_trailing_comma = true +wrap_comments = true +edition = "2018" +error_on_line_overflow = true +version = "Two" diff --git a/src/tools/clippy/src/driver.rs b/src/tools/clippy/src/driver.rs new file mode 100644 index 0000000000..b6aed862e8 --- /dev/null +++ b/src/tools/clippy/src/driver.rs @@ -0,0 +1,349 @@ +#![feature(rustc_private)] +#![feature(once_cell)] +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +// warn on lints, that are included in `rust-lang/rust`s bootstrap +#![warn(rust_2018_idioms, unused_lifetimes)] +// warn on rustc internal lints +#![deny(rustc::internal)] + +// FIXME: switch to something more ergonomic here, once available. +// (Currently there is no way to opt into sysroot crates without `extern crate`.) +extern crate rustc_driver; +extern crate rustc_errors; +extern crate rustc_interface; +extern crate rustc_session; +extern crate rustc_span; + +use rustc_interface::interface; +use rustc_session::parse::ParseSess; +use rustc_span::symbol::Symbol; +use rustc_tools_util::VersionInfo; + +use std::borrow::Cow; +use std::env; +use std::lazy::SyncLazy; +use std::ops::Deref; +use std::panic; +use std::path::{Path, PathBuf}; +use std::process::{exit, Command}; + +/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If +/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`. +fn arg_value<'a, T: Deref>( + args: &'a [T], + find_arg: &str, + pred: impl Fn(&str) -> bool, +) -> Option<&'a str> { + let mut args = args.iter().map(Deref::deref); + while let Some(arg) = args.next() { + let mut arg = arg.splitn(2, '='); + if arg.next() != Some(find_arg) { + continue; + } + + match arg.next().or_else(|| args.next()) { + Some(v) if pred(v) => return Some(v), + _ => {}, + } + } + None +} + +#[test] +fn test_arg_value() { + let args = &["--bar=bar", "--foobar", "123", "--foo"]; + + assert_eq!(arg_value(&[] as &[&str], "--foobar", |_| true), None); + assert_eq!(arg_value(args, "--bar", |_| false), None); + assert_eq!(arg_value(args, "--bar", |_| true), Some("bar")); + assert_eq!(arg_value(args, "--bar", |p| p == "bar"), Some("bar")); + assert_eq!(arg_value(args, "--bar", |p| p == "foo"), None); + assert_eq!(arg_value(args, "--foobar", |p| p == "foo"), None); + assert_eq!(arg_value(args, "--foobar", |p| p == "123"), Some("123")); + assert_eq!(arg_value(args, "--foo", |_| true), None); +} + +fn track_clippy_args(parse_sess: &mut ParseSess, args_env_var: &Option) { + parse_sess.env_depinfo.get_mut().insert(( + Symbol::intern("CLIPPY_ARGS"), + args_env_var.as_deref().map(Symbol::intern), + )); +} + +struct DefaultCallbacks; +impl rustc_driver::Callbacks for DefaultCallbacks {} + +/// This is different from `DefaultCallbacks` that it will inform Cargo to track the value of +/// `CLIPPY_ARGS` environment variable. +struct RustcCallbacks { + clippy_args_var: Option, +} + +impl rustc_driver::Callbacks for RustcCallbacks { + fn config(&mut self, config: &mut interface::Config) { + let clippy_args_var = self.clippy_args_var.take(); + config.parse_sess_created = Some(Box::new(move |parse_sess| { + track_clippy_args(parse_sess, &clippy_args_var); + })); + } +} + +struct ClippyCallbacks { + clippy_args_var: Option, +} + +impl rustc_driver::Callbacks for ClippyCallbacks { + fn config(&mut self, config: &mut interface::Config) { + let previous = config.register_lints.take(); + let clippy_args_var = self.clippy_args_var.take(); + config.parse_sess_created = Some(Box::new(move |parse_sess| { + track_clippy_args(parse_sess, &clippy_args_var); + })); + config.register_lints = Some(Box::new(move |sess, mut lint_store| { + // technically we're ~guaranteed that this is none but might as well call anything that + // is there already. Certainly it can't hurt. + if let Some(previous) = &previous { + (previous)(sess, lint_store); + } + + let conf = clippy_lints::read_conf(&[], &sess); + clippy_lints::register_plugins(&mut lint_store, &sess, &conf); + clippy_lints::register_pre_expansion_lints(&mut lint_store); + clippy_lints::register_renamed(&mut lint_store); + })); + + // FIXME: #4825; This is required, because Clippy lints that are based on MIR have to be + // run on the unoptimized MIR. On the other hand this results in some false negatives. If + // MIR passes can be enabled / disabled separately, we should figure out, what passes to + // use for Clippy. + config.opts.debugging_opts.mir_opt_level = Some(0); + } +} + +fn display_help() { + println!( + "\ +Checks a package to catch common mistakes and improve your Rust code. + +Usage: + cargo clippy [options] [--] [...] + +Common options: + -h, --help Print this message + --rustc Pass all args to rustc + -V, --version Print version info and exit + +Other options are the same as `cargo check`. + +To allow or deny a lint from the command line you can use `cargo clippy --` +with: + + -W --warn OPT Set lint warnings + -A --allow OPT Set lint allowed + -D --deny OPT Set lint denied + -F --forbid OPT Set lint forbidden + +You can use tool lints to allow or deny lints from your code, eg.: + + #[allow(clippy::needless_lifetimes)] +" + ); +} + +const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new"; + +static ICE_HOOK: SyncLazy) + Sync + Send + 'static>> = SyncLazy::new(|| { + let hook = panic::take_hook(); + panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL))); + hook +}); + +fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { + // Invoke our ICE handler, which prints the actual panic message and optionally a backtrace + (*ICE_HOOK)(info); + + // Separate the output with an empty line + eprintln!(); + + let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( + rustc_errors::ColorConfig::Auto, + None, + false, + false, + None, + false, + )); + let handler = rustc_errors::Handler::with_emitter(true, None, emitter); + + // a .span_bug or .bug call has already printed what + // it wants to print. + if !info.payload().is::() { + let d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic"); + handler.emit_diagnostic(&d); + } + + let version_info = rustc_tools_util::get_version_info!(); + + let xs: Vec> = vec![ + "the compiler unexpectedly panicked. this is a bug.".into(), + format!("we would appreciate a bug report: {}", bug_report_url).into(), + format!("Clippy version: {}", version_info).into(), + ]; + + for note in &xs { + handler.note_without_error(¬e); + } + + // If backtraces are enabled, also print the query stack + let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0"); + + let num_frames = if backtrace { None } else { Some(2) }; + + interface::try_print_query_stack(&handler, num_frames); +} + +fn toolchain_path(home: Option, toolchain: Option) -> Option { + home.and_then(|home| { + toolchain.map(|toolchain| { + let mut path = PathBuf::from(home); + path.push("toolchains"); + path.push(toolchain); + path + }) + }) +} + +#[allow(clippy::too_many_lines)] +pub fn main() { + rustc_driver::init_rustc_env_logger(); + SyncLazy::force(&ICE_HOOK); + exit(rustc_driver::catch_with_exit_code(move || { + let mut orig_args: Vec = env::args().collect(); + + // Get the sysroot, looking from most specific to this invocation to the least: + // - command line + // - runtime environment + // - SYSROOT + // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN + // - sysroot from rustc in the path + // - compile-time environment + // - SYSROOT + // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN + let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true); + let have_sys_root_arg = sys_root_arg.is_some(); + let sys_root = sys_root_arg + .map(PathBuf::from) + .or_else(|| std::env::var("SYSROOT").ok().map(PathBuf::from)) + .or_else(|| { + let home = std::env::var("RUSTUP_HOME") + .or_else(|_| std::env::var("MULTIRUST_HOME")) + .ok(); + let toolchain = std::env::var("RUSTUP_TOOLCHAIN") + .or_else(|_| std::env::var("MULTIRUST_TOOLCHAIN")) + .ok(); + toolchain_path(home, toolchain) + }) + .or_else(|| { + Command::new("rustc") + .arg("--print") + .arg("sysroot") + .output() + .ok() + .and_then(|out| String::from_utf8(out.stdout).ok()) + .map(|s| PathBuf::from(s.trim())) + }) + .or_else(|| option_env!("SYSROOT").map(PathBuf::from)) + .or_else(|| { + let home = option_env!("RUSTUP_HOME") + .or(option_env!("MULTIRUST_HOME")) + .map(ToString::to_string); + let toolchain = option_env!("RUSTUP_TOOLCHAIN") + .or(option_env!("MULTIRUST_TOOLCHAIN")) + .map(ToString::to_string); + toolchain_path(home, toolchain) + }) + .map(|pb| pb.to_string_lossy().to_string()) + .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust"); + + // make "clippy-driver --rustc" work like a subcommand that passes further args to "rustc" + // for example `clippy-driver --rustc --version` will print the rustc version that clippy-driver + // uses + if let Some(pos) = orig_args.iter().position(|arg| arg == "--rustc") { + orig_args.remove(pos); + orig_args[0] = "rustc".to_string(); + + // if we call "rustc", we need to pass --sysroot here as well + let mut args: Vec = orig_args.clone(); + if !have_sys_root_arg { + args.extend(vec!["--sysroot".into(), sys_root]); + }; + + return rustc_driver::RunCompiler::new(&args, &mut DefaultCallbacks).run(); + } + + if orig_args.iter().any(|a| a == "--version" || a == "-V") { + let version_info = rustc_tools_util::get_version_info!(); + println!("{}", version_info); + exit(0); + } + + // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument. + // We're invoking the compiler programmatically, so we ignore this/ + let wrapper_mode = orig_args.get(1).map(Path::new).and_then(Path::file_stem) == Some("rustc".as_ref()); + + if wrapper_mode { + // we still want to be able to invoke it normally though + orig_args.remove(1); + } + + if !wrapper_mode && (orig_args.iter().any(|a| a == "--help" || a == "-h") || orig_args.len() == 1) { + display_help(); + exit(0); + } + + // this conditional check for the --sysroot flag is there so users can call + // `clippy_driver` directly + // without having to pass --sysroot or anything + let mut args: Vec = orig_args.clone(); + if !have_sys_root_arg { + args.extend(vec!["--sysroot".into(), sys_root]); + }; + + let mut no_deps = false; + let clippy_args_var = env::var("CLIPPY_ARGS").ok(); + let clippy_args = clippy_args_var + .as_deref() + .unwrap_or_default() + .split("__CLIPPY_HACKERY__") + .filter_map(|s| match s { + "" => None, + "--no-deps" => { + no_deps = true; + None + }, + _ => Some(s.to_string()), + }) + .chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()]) + .collect::>(); + + // We enable Clippy if one of the following conditions is met + // - IF Clippy is run on its test suite OR + // - IF Clippy is run on the main crate, not on deps (`!cap_lints_allow`) THEN + // - IF `--no-deps` is not set (`!no_deps`) OR + // - IF `--no-deps` is set and Clippy is run on the specified primary package + let clippy_tests_set = env::var("__CLIPPY_INTERNAL_TESTS").map_or(false, |val| val == "true"); + let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some(); + let in_primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok(); + + let clippy_enabled = clippy_tests_set || (!cap_lints_allow && (!no_deps || in_primary_package)); + if clippy_enabled { + args.extend(clippy_args); + } + + if clippy_enabled { + rustc_driver::RunCompiler::new(&args, &mut ClippyCallbacks { clippy_args_var }).run() + } else { + rustc_driver::RunCompiler::new(&args, &mut RustcCallbacks { clippy_args_var }).run() + } + })) +} diff --git a/src/tools/clippy/src/main.rs b/src/tools/clippy/src/main.rs new file mode 100644 index 0000000000..7bb80b1196 --- /dev/null +++ b/src/tools/clippy/src/main.rs @@ -0,0 +1,219 @@ +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +// warn on lints, that are included in `rust-lang/rust`s bootstrap +#![warn(rust_2018_idioms, unused_lifetimes)] + +use rustc_tools_util::VersionInfo; +use std::env; +use std::ffi::OsString; +use std::path::PathBuf; +use std::process::{self, Command}; + +const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code. + +Usage: + cargo clippy [options] [--] [...] + +Common options: + -h, --help Print this message + -V, --version Print version info and exit + +Other options are the same as `cargo check`. + +To allow or deny a lint from the command line you can use `cargo clippy --` +with: + + -W --warn OPT Set lint warnings + -A --allow OPT Set lint allowed + -D --deny OPT Set lint denied + -F --forbid OPT Set lint forbidden + +You can use tool lints to allow or deny lints from your code, eg.: + + #[allow(clippy::needless_lifetimes)] +"#; + +fn show_help() { + println!("{}", CARGO_CLIPPY_HELP); +} + +fn show_version() { + let version_info = rustc_tools_util::get_version_info!(); + println!("{}", version_info); +} + +pub fn main() { + // Check for version and help flags even when invoked as 'cargo-clippy' + if env::args().any(|a| a == "--help" || a == "-h") { + show_help(); + return; + } + + if env::args().any(|a| a == "--version" || a == "-V") { + show_version(); + return; + } + + if let Err(code) = process(env::args().skip(2)) { + process::exit(code); + } +} + +struct ClippyCmd { + cargo_subcommand: &'static str, + args: Vec, + clippy_args: Vec, +} + +impl ClippyCmd { + fn new(mut old_args: I) -> Self + where + I: Iterator, + { + let mut cargo_subcommand = "check"; + let mut unstable_options = false; + let mut args = vec![]; + + for arg in old_args.by_ref() { + match arg.as_str() { + "--fix" => { + cargo_subcommand = "fix"; + continue; + }, + "--" => break, + // Cover -Zunstable-options and -Z unstable-options + s if s.ends_with("unstable-options") => unstable_options = true, + _ => {}, + } + + args.push(arg); + } + + if cargo_subcommand == "fix" && !unstable_options { + panic!("Usage of `--fix` requires `-Z unstable-options`"); + } + + let mut clippy_args: Vec = old_args.collect(); + if cargo_subcommand == "fix" && !clippy_args.iter().any(|arg| arg == "--no-deps") { + clippy_args.push("--no-deps".into()); + } + + ClippyCmd { + cargo_subcommand, + args, + clippy_args, + } + } + + fn path() -> PathBuf { + let mut path = env::current_exe() + .expect("current executable path invalid") + .with_file_name("clippy-driver"); + + if cfg!(windows) { + path.set_extension("exe"); + } + + path + } + + fn target_dir() -> Option<(&'static str, OsString)> { + env::var_os("CLIPPY_DOGFOOD") + .map(|_| { + env::var_os("CARGO_MANIFEST_DIR").map_or_else( + || std::ffi::OsString::from("clippy_dogfood"), + |d| { + std::path::PathBuf::from(d) + .join("target") + .join("dogfood") + .into_os_string() + }, + ) + }) + .map(|p| ("CARGO_TARGET_DIR", p)) + } + + fn into_std_cmd(self) -> Command { + let mut cmd = Command::new("cargo"); + let clippy_args: String = self + .clippy_args + .iter() + .map(|arg| format!("{}__CLIPPY_HACKERY__", arg)) + .collect(); + + cmd.env("RUSTC_WORKSPACE_WRAPPER", Self::path()) + .envs(ClippyCmd::target_dir()) + .env("CLIPPY_ARGS", clippy_args) + .arg(self.cargo_subcommand) + .args(&self.args); + + cmd + } +} + +fn process(old_args: I) -> Result<(), i32> +where + I: Iterator, +{ + let cmd = ClippyCmd::new(old_args); + + let mut cmd = cmd.into_std_cmd(); + + let exit_status = cmd + .spawn() + .expect("could not run cargo") + .wait() + .expect("failed to wait for cargo?"); + + if exit_status.success() { + Ok(()) + } else { + Err(exit_status.code().unwrap_or(-1)) + } +} + +#[cfg(test)] +mod tests { + use super::ClippyCmd; + + #[test] + #[should_panic] + fn fix_without_unstable() { + let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string); + ClippyCmd::new(args); + } + + #[test] + fn fix_unstable() { + let args = "cargo clippy --fix -Zunstable-options" + .split_whitespace() + .map(ToString::to_string); + let cmd = ClippyCmd::new(args); + assert_eq!("fix", cmd.cargo_subcommand); + assert!(cmd.args.iter().any(|arg| arg.ends_with("unstable-options"))); + } + + #[test] + fn fix_implies_no_deps() { + let args = "cargo clippy --fix -Zunstable-options" + .split_whitespace() + .map(ToString::to_string); + let cmd = ClippyCmd::new(args); + assert!(cmd.clippy_args.iter().any(|arg| arg == "--no-deps")); + } + + #[test] + fn no_deps_not_duplicated_with_fix() { + let args = "cargo clippy --fix -Zunstable-options -- --no-deps" + .split_whitespace() + .map(ToString::to_string); + let cmd = ClippyCmd::new(args); + assert_eq!(cmd.clippy_args.iter().filter(|arg| *arg == "--no-deps").count(), 1); + } + + #[test] + fn check() { + let args = "cargo clippy".split_whitespace().map(ToString::to_string); + let cmd = ClippyCmd::new(args); + assert_eq!("check", cmd.cargo_subcommand); + } +} diff --git a/src/tools/clippy/tests/auxiliary/test_macro.rs b/src/tools/clippy/tests/auxiliary/test_macro.rs new file mode 100644 index 0000000000..624ca892ad --- /dev/null +++ b/src/tools/clippy/tests/auxiliary/test_macro.rs @@ -0,0 +1,11 @@ +pub trait A {} + +macro_rules! __implicit_hasher_test_macro { + (impl< $($impl_arg:tt),* > for $kind:ty where $($bounds:tt)*) => { + __implicit_hasher_test_macro!( ($($impl_arg),*) ($kind) ($($bounds)*) ); + }; + + (($($impl_arg:tt)*) ($($kind_arg:tt)*) ($($bounds:tt)*)) => { + impl< $($impl_arg)* > test_macro::A for $($kind_arg)* where $($bounds)* { } + }; +} diff --git a/src/tools/clippy/tests/cargo/mod.rs b/src/tools/clippy/tests/cargo/mod.rs new file mode 100644 index 0000000000..a8f3e3145f --- /dev/null +++ b/src/tools/clippy/tests/cargo/mod.rs @@ -0,0 +1,26 @@ +use std::env; +use std::lazy::SyncLazy; +use std::path::PathBuf; + +pub static CARGO_TARGET_DIR: SyncLazy = SyncLazy::new(|| match env::var_os("CARGO_TARGET_DIR") { + Some(v) => v.into(), + None => env::current_dir().unwrap().join("target"), +}); + +pub static TARGET_LIB: SyncLazy = SyncLazy::new(|| { + if let Some(path) = option_env!("TARGET_LIBS") { + path.into() + } else { + let mut dir = CARGO_TARGET_DIR.clone(); + if let Some(target) = env::var_os("CARGO_BUILD_TARGET") { + dir.push(target); + } + dir.push(env!("PROFILE")); + dir + } +}); + +#[must_use] +pub fn is_rustc_test_suite() -> bool { + option_env!("RUSTC_TEST_SUITE").is_some() +} diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs new file mode 100644 index 0000000000..0594663786 --- /dev/null +++ b/src/tools/clippy/tests/compile-test.rs @@ -0,0 +1,274 @@ +#![feature(test)] // compiletest_rs requires this attribute +#![feature(once_cell)] + +use compiletest_rs as compiletest; +use compiletest_rs::common::Mode as TestMode; + +use std::env::{self, set_var, var}; +use std::ffi::OsStr; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +mod cargo; + +// whether to run internal tests or not +const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints"); + +fn host_lib() -> PathBuf { + option_env!("HOST_LIBS").map_or(cargo::CARGO_TARGET_DIR.join(env!("PROFILE")), PathBuf::from) +} + +fn clippy_driver_path() -> PathBuf { + option_env!("CLIPPY_DRIVER_PATH").map_or(cargo::TARGET_LIB.join("clippy-driver"), PathBuf::from) +} + +// When we'll want to use `extern crate ..` for a dependency that is used +// both by the crate and the compiler itself, we can't simply pass -L flags +// as we'll get a duplicate matching versions. Instead, disambiguate with +// `--extern dep=path`. +// See https://github.com/rust-lang/rust-clippy/issues/4015. +// +// FIXME: We cannot use `cargo build --message-format=json` to resolve to dependency files. +// Because it would force-rebuild if the options passed to `build` command is not the same +// as what we manually pass to `cargo` invocation +fn third_party_crates() -> String { + use std::collections::HashMap; + static CRATES: &[&str] = &["serde", "serde_derive", "regex", "clippy_lints", "syn", "quote"]; + let dep_dir = cargo::TARGET_LIB.join("deps"); + let mut crates: HashMap<&str, PathBuf> = HashMap::with_capacity(CRATES.len()); + for entry in fs::read_dir(dep_dir).unwrap() { + let path = match entry { + Ok(entry) => entry.path(), + Err(_) => continue, + }; + if let Some(name) = path.file_name().and_then(OsStr::to_str) { + for dep in CRATES { + if name.starts_with(&format!("lib{}-", dep)) + && name.rsplit('.').next().map(|ext| ext.eq_ignore_ascii_case("rlib")) == Some(true) + { + if let Some(old) = crates.insert(dep, path.clone()) { + panic!("Found multiple rlibs for crate `{}`: `{:?}` and `{:?}", dep, old, path); + } + break; + } + } + } + } + + let v: Vec<_> = crates + .into_iter() + .map(|(dep, path)| format!("--extern {}={}", dep, path.display())) + .collect(); + v.join(" ") +} + +fn default_config() -> compiletest::Config { + let mut config = compiletest::Config::default(); + + if let Ok(filters) = env::var("TESTNAME") { + config.filters = filters.split(',').map(std::string::ToString::to_string).collect(); + } + + if let Some(path) = option_env!("RUSTC_LIB_PATH") { + let path = PathBuf::from(path); + config.run_lib_path = path.clone(); + config.compile_lib_path = path; + } + + config.target_rustcflags = Some(format!( + "--emit=metadata -L {0} -L {1} -Dwarnings -Zui-testing {2}", + host_lib().join("deps").display(), + cargo::TARGET_LIB.join("deps").display(), + third_party_crates(), + )); + + config.build_base = if cargo::is_rustc_test_suite() { + // This make the stderr files go to clippy OUT_DIR on rustc repo build dir + let mut path = PathBuf::from(env!("OUT_DIR")); + path.push("test_build_base"); + path + } else { + host_lib().join("test_build_base") + }; + config.rustc_path = clippy_driver_path(); + config +} + +fn run_mode(cfg: &mut compiletest::Config) { + cfg.mode = TestMode::Ui; + cfg.src_base = Path::new("tests").join("ui"); + compiletest::run_tests(&cfg); +} + +fn run_internal_tests(cfg: &mut compiletest::Config) { + // only run internal tests with the internal-tests feature + if !RUN_INTERNAL_TESTS { + return; + } + cfg.mode = TestMode::Ui; + cfg.src_base = Path::new("tests").join("ui-internal"); + compiletest::run_tests(&cfg); +} + +fn run_ui_toml(config: &mut compiletest::Config) { + fn run_tests(config: &compiletest::Config, mut tests: Vec) -> Result { + let mut result = true; + let opts = compiletest::test_opts(config); + for dir in fs::read_dir(&config.src_base)? { + let dir = dir?; + if !dir.file_type()?.is_dir() { + continue; + } + let dir_path = dir.path(); + set_var("CARGO_MANIFEST_DIR", &dir_path); + for file in fs::read_dir(&dir_path)? { + let file = file?; + let file_path = file.path(); + if file.file_type()?.is_dir() { + continue; + } + if file_path.extension() != Some(OsStr::new("rs")) { + continue; + } + let paths = compiletest::common::TestPaths { + file: file_path, + base: config.src_base.clone(), + relative_dir: dir_path.file_name().unwrap().into(), + }; + let test_name = compiletest::make_test_name(&config, &paths); + let index = tests + .iter() + .position(|test| test.desc.name == test_name) + .expect("The test should be in there"); + result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?; + } + } + Ok(result) + } + + config.mode = TestMode::Ui; + config.src_base = Path::new("tests").join("ui-toml").canonicalize().unwrap(); + + let tests = compiletest::make_tests(&config); + + let manifest_dir = var("CARGO_MANIFEST_DIR").unwrap_or_default(); + let res = run_tests(&config, tests); + set_var("CARGO_MANIFEST_DIR", &manifest_dir); + match res { + Ok(true) => {}, + Ok(false) => panic!("Some tests failed"), + Err(e) => { + panic!("I/O failure during tests: {:?}", e); + }, + } +} + +fn run_ui_cargo(config: &mut compiletest::Config) { + fn run_tests( + config: &compiletest::Config, + filters: &[String], + mut tests: Vec, + ) -> Result { + let mut result = true; + let opts = compiletest::test_opts(config); + + for dir in fs::read_dir(&config.src_base)? { + let dir = dir?; + if !dir.file_type()?.is_dir() { + continue; + } + + // Use the filter if provided + let dir_path = dir.path(); + for filter in filters { + if !dir_path.ends_with(filter) { + continue; + } + } + + for case in fs::read_dir(&dir_path)? { + let case = case?; + if !case.file_type()?.is_dir() { + continue; + } + + let src_path = case.path().join("src"); + + // When switching between branches, if the previous branch had a test + // that the current branch does not have, the directory is not removed + // because an ignored Cargo.lock file exists. + if !src_path.exists() { + continue; + } + + env::set_current_dir(&src_path)?; + for file in fs::read_dir(&src_path)? { + let file = file?; + if file.file_type()?.is_dir() { + continue; + } + + // Search for the main file to avoid running a test for each file in the project + let file_path = file.path(); + match file_path.file_name().and_then(OsStr::to_str) { + Some("main.rs") => {}, + _ => continue, + } + set_var("CLIPPY_CONF_DIR", case.path()); + let paths = compiletest::common::TestPaths { + file: file_path, + base: config.src_base.clone(), + relative_dir: src_path.strip_prefix(&config.src_base).unwrap().into(), + }; + let test_name = compiletest::make_test_name(&config, &paths); + let index = tests + .iter() + .position(|test| test.desc.name == test_name) + .expect("The test should be in there"); + result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?; + } + } + } + Ok(result) + } + + if cargo::is_rustc_test_suite() { + return; + } + + config.mode = TestMode::Ui; + config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap(); + + let tests = compiletest::make_tests(&config); + + let current_dir = env::current_dir().unwrap(); + let conf_dir = var("CLIPPY_CONF_DIR").unwrap_or_default(); + let res = run_tests(&config, &config.filters, tests); + env::set_current_dir(current_dir).unwrap(); + set_var("CLIPPY_CONF_DIR", conf_dir); + + match res { + Ok(true) => {}, + Ok(false) => panic!("Some tests failed"), + Err(e) => { + panic!("I/O failure during tests: {:?}", e); + }, + } +} + +fn prepare_env() { + set_var("CLIPPY_DISABLE_DOCS_LINKS", "true"); + set_var("__CLIPPY_INTERNAL_TESTS", "true"); + //set_var("RUST_BACKTRACE", "0"); +} + +#[test] +fn compile_test() { + prepare_env(); + let mut config = default_config(); + run_mode(&mut config); + run_ui_toml(&mut config); + run_ui_cargo(&mut config); + run_internal_tests(&mut config); +} diff --git a/src/tools/clippy/tests/dogfood.rs b/src/tools/clippy/tests/dogfood.rs new file mode 100644 index 0000000000..d92530f073 --- /dev/null +++ b/src/tools/clippy/tests/dogfood.rs @@ -0,0 +1,196 @@ +// Dogfood cannot run on Windows +#![cfg(not(windows))] +#![feature(once_cell)] + +use std::lazy::SyncLazy; +use std::path::PathBuf; +use std::process::Command; + +mod cargo; + +static CLIPPY_PATH: SyncLazy = SyncLazy::new(|| cargo::TARGET_LIB.join("cargo-clippy")); + +#[test] +fn dogfood_clippy() { + // run clippy on itself and fail the test if lint warnings are reported + if cargo::is_rustc_test_suite() { + return; + } + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + let mut command = Command::new(&*CLIPPY_PATH); + command + .current_dir(root_dir) + .env("CLIPPY_DOGFOOD", "1") + .env("CARGO_INCREMENTAL", "0") + .arg("clippy") + .arg("--all-targets") + .arg("--all-features") + .arg("--") + .args(&["-D", "clippy::all"]) + .args(&["-D", "clippy::pedantic"]) + .arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir + + // internal lints only exist if we build with the internal-lints feature + if cfg!(feature = "internal-lints") { + command.args(&["-D", "clippy::internal"]); + } + + let output = command.output().unwrap(); + + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + assert!(output.status.success()); +} + +fn test_no_deps_ignores_path_deps_in_workspaces() { + if cargo::is_rustc_test_suite() { + return; + } + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let target_dir = root.join("target").join("dogfood"); + let cwd = root.join("clippy_workspace_tests"); + + // Make sure we start with a clean state + Command::new("cargo") + .current_dir(&cwd) + .env("CARGO_TARGET_DIR", &target_dir) + .arg("clean") + .args(&["-p", "subcrate"]) + .args(&["-p", "path_dep"]) + .output() + .unwrap(); + + // `path_dep` is a path dependency of `subcrate` that would trigger a denied lint. + // Make sure that with the `--no-deps` argument Clippy does not run on `path_dep`. + let output = Command::new(&*CLIPPY_PATH) + .current_dir(&cwd) + .env("CLIPPY_DOGFOOD", "1") + .env("CARGO_INCREMENTAL", "0") + .arg("clippy") + .args(&["-p", "subcrate"]) + .arg("--") + .arg("--no-deps") + .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir + .args(&["--cfg", r#"feature="primary_package_test""#]) + .output() + .unwrap(); + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + assert!(output.status.success()); + + let lint_path_dep = || { + // Test that without the `--no-deps` argument, `path_dep` is linted. + let output = Command::new(&*CLIPPY_PATH) + .current_dir(&cwd) + .env("CLIPPY_DOGFOOD", "1") + .env("CARGO_INCREMENTAL", "0") + .arg("clippy") + .args(&["-p", "subcrate"]) + .arg("--") + .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir + .args(&["--cfg", r#"feature="primary_package_test""#]) + .output() + .unwrap(); + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + assert!(!output.status.success()); + assert!( + String::from_utf8(output.stderr) + .unwrap() + .contains("error: empty `loop {}` wastes CPU cycles") + ); + }; + + // Make sure Cargo is aware of the removal of `--no-deps`. + lint_path_dep(); + + let successful_build = || { + let output = Command::new(&*CLIPPY_PATH) + .current_dir(&cwd) + .env("CLIPPY_DOGFOOD", "1") + .env("CARGO_INCREMENTAL", "0") + .arg("clippy") + .args(&["-p", "subcrate"]) + .arg("--") + .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir + .output() + .unwrap(); + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + assert!(output.status.success()); + + output + }; + + // Trigger a sucessful build, so Cargo would like to cache the build result. + successful_build(); + + // Make sure there's no spurious rebuild when nothing changes. + let stderr = String::from_utf8(successful_build().stderr).unwrap(); + assert!(!stderr.contains("Compiling")); + assert!(!stderr.contains("Checking")); + assert!(stderr.contains("Finished")); + + // Make sure Cargo is aware of the new `--cfg` flag. + lint_path_dep(); +} + +#[test] +fn dogfood_subprojects() { + // run clippy on remaining subprojects and fail the test if lint warnings are reported + if cargo::is_rustc_test_suite() { + return; + } + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + // NOTE: `path_dep` crate is omitted on purpose here + for d in &[ + "clippy_workspace_tests", + "clippy_workspace_tests/src", + "clippy_workspace_tests/subcrate", + "clippy_workspace_tests/subcrate/src", + "clippy_dev", + "clippy_lints", + "clippy_utils", + "rustc_tools_util", + ] { + let mut command = Command::new(&*CLIPPY_PATH); + command + .current_dir(root_dir.join(d)) + .env("CLIPPY_DOGFOOD", "1") + .env("CARGO_INCREMENTAL", "0") + .arg("clippy") + .arg("--all-targets") + .arg("--all-features") + .arg("--") + .args(&["-D", "clippy::all"]) + .args(&["-D", "clippy::pedantic"]) + .arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir + + // internal lints only exist if we build with the internal-lints feature + if cfg!(feature = "internal-lints") { + command.args(&["-D", "clippy::internal"]); + } + + let output = command.output().unwrap(); + + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + assert!(output.status.success()); + } + + // NOTE: Since tests run in parallel we can't run cargo commands on the same workspace at the + // same time, so we test this immediately after the dogfood for workspaces. + test_no_deps_ignores_path_deps_in_workspaces(); +} diff --git a/src/tools/clippy/tests/fmt.rs b/src/tools/clippy/tests/fmt.rs new file mode 100644 index 0000000000..7616d8001e --- /dev/null +++ b/src/tools/clippy/tests/fmt.rs @@ -0,0 +1,36 @@ +use std::path::PathBuf; +use std::process::Command; + +#[test] +fn fmt() { + if option_env!("RUSTC_TEST_SUITE").is_some() || option_env!("NO_FMT_TEST").is_some() { + return; + } + + // Skip this test if nightly rustfmt is unavailable + let rustup_output = Command::new("rustup") + .args(&["component", "list", "--toolchain", "nightly"]) + .output() + .unwrap(); + assert!(rustup_output.status.success()); + let component_output = String::from_utf8_lossy(&rustup_output.stdout); + if !component_output.contains("rustfmt") { + return; + } + + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let output = Command::new("cargo") + .current_dir(root_dir) + .args(&["dev", "fmt", "--check"]) + .output() + .unwrap(); + + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + assert!( + output.status.success(), + "Formatting check failed. Run `cargo dev fmt` to update formatting." + ); +} diff --git a/src/tools/clippy/tests/integration.rs b/src/tools/clippy/tests/integration.rs new file mode 100644 index 0000000000..1718089e8b --- /dev/null +++ b/src/tools/clippy/tests/integration.rs @@ -0,0 +1,84 @@ +#![cfg(feature = "integration")] + +use std::env; +use std::ffi::OsStr; +use std::process::Command; + +#[cfg_attr(feature = "integration", test)] +fn integration_test() { + let repo_name = env::var("INTEGRATION").expect("`INTEGRATION` var not set"); + let repo_url = format!("https://github.com/{}", repo_name); + let crate_name = repo_name + .split('/') + .nth(1) + .expect("repo name should have format `/`"); + + let mut repo_dir = tempfile::tempdir().expect("couldn't create temp dir").into_path(); + repo_dir.push(crate_name); + + let st = Command::new("git") + .args(&[ + OsStr::new("clone"), + OsStr::new("--depth=1"), + OsStr::new(&repo_url), + OsStr::new(&repo_dir), + ]) + .status() + .expect("unable to run git"); + assert!(st.success()); + + let root_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let target_dir = std::path::Path::new(&root_dir).join("target"); + let clippy_binary = target_dir.join(env!("PROFILE")).join("cargo-clippy"); + + let output = Command::new(clippy_binary) + .current_dir(repo_dir) + .env("RUST_BACKTRACE", "full") + .env("CARGO_TARGET_DIR", target_dir) + .args(&[ + "clippy", + "--all-targets", + "--all-features", + "--", + "--cap-lints", + "warn", + "-Wclippy::pedantic", + "-Wclippy::nursery", + ]) + .output() + .expect("unable to run clippy"); + + let stderr = String::from_utf8_lossy(&output.stderr); + if stderr.contains("internal compiler error") { + let backtrace_start = stderr + .find("thread 'rustc' panicked at") + .expect("start of backtrace not found"); + let backtrace_end = stderr + .rfind("error: internal compiler error") + .expect("end of backtrace not found"); + + panic!( + "internal compiler error\nBacktrace:\n\n{}", + &stderr[backtrace_start..backtrace_end] + ); + } else if stderr.contains("query stack during panic") { + panic!("query stack during panic in the output"); + } else if stderr.contains("E0463") { + // Encountering E0463 (can't find crate for `x`) did _not_ cause the build to fail in the + // past. Even though it should have. That's why we explicitly panic here. + // See PR #3552 and issue #3523 for more background. + panic!("error: E0463"); + } else if stderr.contains("E0514") { + panic!("incompatible crate versions"); + } else if stderr.contains("failed to run `rustc` to learn about target-specific information") { + panic!("couldn't find librustc_driver, consider setting `LD_LIBRARY_PATH`"); + } else if stderr.contains("toolchain") && stderr.contains("is not installed") { + panic!("missing required toolchain"); + } + + match output.status.code() { + Some(0) => println!("Compilation successful"), + Some(code) => eprintln!("Compilation failed. Exit code: {}", code), + None => panic!("Process terminated by signal"), + } +} diff --git a/src/tools/clippy/tests/lint_message_convention.rs b/src/tools/clippy/tests/lint_message_convention.rs new file mode 100644 index 0000000000..3f754c255b --- /dev/null +++ b/src/tools/clippy/tests/lint_message_convention.rs @@ -0,0 +1,108 @@ +use std::ffi::OsStr; +use std::path::PathBuf; + +use regex::RegexSet; + +#[derive(Debug)] +struct Message { + path: PathBuf, + bad_lines: Vec, +} + +impl Message { + fn new(path: PathBuf) -> Self { + let content: String = std::fs::read_to_string(&path).unwrap(); + // we don't want the first letter after "error: ", "help: " ... to be capitalized + // also no puncutation (except for "?" ?) at the end of a line + let regex_set: RegexSet = RegexSet::new(&[ + r"error: [A-Z]", + r"help: [A-Z]", + r"warning: [A-Z]", + r"note: [A-Z]", + r"try this: [A-Z]", + r"error: .*[.!]$", + r"help: .*[.!]$", + r"warning: .*[.!]$", + r"note: .*[.!]$", + r"try this: .*[.!]$", + ]) + .unwrap(); + + // sometimes the first character is capitalized and it is legal (like in "C-like enum variants") or + // we want to ask a question ending in "?" + let exceptions_set: RegexSet = RegexSet::new(&[ + r".*C-like enum variant discriminant is not portable to 32-bit targets", + r".*did you mean `unix`?", + r".*the arguments may be inverted...", + r".*Intel x86 assembly syntax used", + r".*AT&T x86 assembly syntax used", + r".*remove .*the return type...", + r"note: Clippy version: .*", + r"the compiler unexpectedly panicked. this is a bug.", + ]) + .unwrap(); + + let bad_lines = content + .lines() + .filter(|line| regex_set.matches(line).matched_any()) + // ignore exceptions + .filter(|line| !exceptions_set.matches(line).matched_any()) + .map(ToOwned::to_owned) + .collect::>(); + + Message { path, bad_lines } + } +} + +#[test] +fn lint_message_convention() { + // disable the test inside the rustc test suite + if option_env!("RUSTC_TEST_SUITE").is_some() { + return; + } + + // make sure that lint messages: + // * are not capitalized + // * don't have puncuation at the end of the last sentence + + // these directories have interesting tests + let test_dirs = ["ui", "ui-cargo", "ui-internal", "ui-toml"] + .iter() + .map(PathBuf::from) + .map(|p| { + let base = PathBuf::from("tests"); + base.join(p) + }); + + // gather all .stderr files + let tests = test_dirs + .flat_map(|dir| { + std::fs::read_dir(dir) + .expect("failed to read dir") + .map(|direntry| direntry.unwrap().path()) + }) + .filter(|file| matches!(file.extension().map(OsStr::to_str), Some(Some("stderr")))); + + // get all files that have any "bad lines" in them + let bad_tests: Vec = tests + .map(Message::new) + .filter(|message| !message.bad_lines.is_empty()) + .collect(); + + bad_tests.iter().for_each(|message| { + eprintln!( + "error: the test '{}' contained the following nonconforming lines :", + message.path.display() + ); + message.bad_lines.iter().for_each(|line| eprintln!("{}", line)); + eprintln!("\n\n"); + }); + + eprintln!( + "\n\n\nLint message should not start with a capital letter and should not have punctuation at the end of the message unless multiple sentences are needed." + ); + eprintln!("Check out the rustc-dev-guide for more information:"); + eprintln!("https://rustc-dev-guide.rust-lang.org/diagnostics.html#diagnostic-structure\n\n\n"); + + assert!(bad_tests.is_empty()); +} diff --git a/src/tools/clippy/tests/missing-test-files.rs b/src/tools/clippy/tests/missing-test-files.rs new file mode 100644 index 0000000000..9cef7438d2 --- /dev/null +++ b/src/tools/clippy/tests/missing-test-files.rs @@ -0,0 +1,54 @@ +#![allow(clippy::assertions_on_constants)] + +use std::fs::{self, DirEntry}; +use std::path::Path; + +#[test] +fn test_missing_tests() { + let missing_files = explore_directory(Path::new("./tests")); + if !missing_files.is_empty() { + assert!( + false, + "Didn't see a test file for the following files:\n\n{}\n", + missing_files + .iter() + .map(|s| format!("\t{}", s)) + .collect::>() + .join("\n") + ); + } +} + +/* +Test for missing files. + +Since rs files are alphabetically before stderr/stdout, we can sort by the full name +and iter in that order. If we've seen the file stem for the first time and it's not +a rust file, it means the rust file has to be missing. +*/ +fn explore_directory(dir: &Path) -> Vec { + let mut missing_files: Vec = Vec::new(); + let mut current_file = String::new(); + let mut files: Vec = fs::read_dir(dir).unwrap().filter_map(Result::ok).collect(); + files.sort_by_key(std::fs::DirEntry::path); + for entry in &files { + let path = entry.path(); + if path.is_dir() { + missing_files.extend(explore_directory(&path)); + } else { + let file_stem = path.file_stem().unwrap().to_str().unwrap().to_string(); + if let Some(ext) = path.extension() { + match ext.to_str().unwrap() { + "rs" => current_file = file_stem.clone(), + "stderr" | "stdout" => { + if file_stem != current_file { + missing_files.push(path.to_str().unwrap().to_string()); + } + }, + _ => continue, + }; + } + } + } + missing_files +} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml new file mode 100644 index 0000000000..ae0a603299 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "cargo_common_metadata" +version = "0.1.0" +publish = false + +[workspace] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/clippy.toml b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/clippy.toml new file mode 100644 index 0000000000..de4f04b24f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/clippy.toml @@ -0,0 +1 @@ +cargo-ignore-publish = true diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs new file mode 100644 index 0000000000..27841e18aa --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=cargo_common_metadata +#![warn(clippy::cargo_common_metadata)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr new file mode 100644 index 0000000000..c8ae6c820d --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr @@ -0,0 +1,18 @@ +error: package `cargo_common_metadata` is missing `package.authors` metadata + | + = note: `-D clippy::cargo-common-metadata` implied by `-D warnings` + +error: package `cargo_common_metadata` is missing `package.description` metadata + +error: package `cargo_common_metadata` is missing `either package.license or package.license_file` metadata + +error: package `cargo_common_metadata` is missing `package.repository` metadata + +error: package `cargo_common_metadata` is missing `package.readme` metadata + +error: package `cargo_common_metadata` is missing `package.keywords` metadata + +error: package `cargo_common_metadata` is missing `package.categories` metadata + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/Cargo.toml new file mode 100644 index 0000000000..7595696353 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "cargo_common_metadata" +version = "0.1.0" +publish = ["some-registry-name"] + +[workspace] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs new file mode 100644 index 0000000000..27841e18aa --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=cargo_common_metadata +#![warn(clippy::cargo_common_metadata)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.stderr b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.stderr new file mode 100644 index 0000000000..c8ae6c820d --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.stderr @@ -0,0 +1,18 @@ +error: package `cargo_common_metadata` is missing `package.authors` metadata + | + = note: `-D clippy::cargo-common-metadata` implied by `-D warnings` + +error: package `cargo_common_metadata` is missing `package.description` metadata + +error: package `cargo_common_metadata` is missing `either package.license or package.license_file` metadata + +error: package `cargo_common_metadata` is missing `package.repository` metadata + +error: package `cargo_common_metadata` is missing `package.readme` metadata + +error: package `cargo_common_metadata` is missing `package.keywords` metadata + +error: package `cargo_common_metadata` is missing `package.categories` metadata + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/Cargo.toml new file mode 100644 index 0000000000..7e5b88383c --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "cargo_common_metadata" +version = "0.1.0" +publish = true + +[workspace] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs new file mode 100644 index 0000000000..27841e18aa --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=cargo_common_metadata +#![warn(clippy::cargo_common_metadata)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.stderr b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.stderr new file mode 100644 index 0000000000..c8ae6c820d --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.stderr @@ -0,0 +1,18 @@ +error: package `cargo_common_metadata` is missing `package.authors` metadata + | + = note: `-D clippy::cargo-common-metadata` implied by `-D warnings` + +error: package `cargo_common_metadata` is missing `package.description` metadata + +error: package `cargo_common_metadata` is missing `either package.license or package.license_file` metadata + +error: package `cargo_common_metadata` is missing `package.repository` metadata + +error: package `cargo_common_metadata` is missing `package.readme` metadata + +error: package `cargo_common_metadata` is missing `package.keywords` metadata + +error: package `cargo_common_metadata` is missing `package.categories` metadata + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml new file mode 100644 index 0000000000..737e84e963 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cargo_common_metadata" +version = "0.1.0" +publish = false +authors = ["Random person from the Internet "] +description = "A test package for the cargo_common_metadata lint" +repository = "https://github.com/someone/cargo_common_metadata" +readme = "README.md" +license = "MIT OR Apache-2.0" +keywords = ["metadata", "lint", "clippy"] +categories = ["development-tools::testing"] + +[workspace] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/clippy.toml b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/clippy.toml new file mode 100644 index 0000000000..de4f04b24f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/clippy.toml @@ -0,0 +1 @@ +cargo-ignore-publish = true diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs new file mode 100644 index 0000000000..27841e18aa --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=cargo_common_metadata +#![warn(clippy::cargo_common_metadata)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/Cargo.toml new file mode 100644 index 0000000000..0a879c99b5 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "cargo_common_metadata" +version = "0.1.0" +publish = [] + +[workspace] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs new file mode 100644 index 0000000000..27841e18aa --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=cargo_common_metadata +#![warn(clippy::cargo_common_metadata)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_false/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_false/Cargo.toml new file mode 100644 index 0000000000..ae0a603299 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_false/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "cargo_common_metadata" +version = "0.1.0" +publish = false + +[workspace] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs new file mode 100644 index 0000000000..27841e18aa --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=cargo_common_metadata +#![warn(clippy::cargo_common_metadata)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml new file mode 100644 index 0000000000..278bebbbd9 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml @@ -0,0 +1,19 @@ +# Should not lint for dev or build dependencies. See issue 5041. + +[package] +name = "multiple_crate_versions" +version = "0.1.0" +publish = false + +[workspace] + +# One of the versions of winapi is only a dev dependency: allowed +[dependencies] +ctrlc = "=3.1.0" +[dev-dependencies] +ansi_term = "=0.11.0" + +# Both versions of winapi are a build dependency: allowed +[build-dependencies] +ctrlc = "=3.1.0" +ansi_term = "=0.11.0" diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs new file mode 100644 index 0000000000..1b2d3ec945 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=multiple_crate_versions +#![warn(clippy::multiple_crate_versions)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/Cargo.lock b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/Cargo.lock new file mode 100644 index 0000000000..7e96aa36fe --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/Cargo.lock @@ -0,0 +1,109 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "ctrlc" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653abc99aa905f693d89df4797fadc08085baee379db92be9f2496cefe8a6f2c" +dependencies = [ + "kernel32-sys", + "nix", + "winapi 0.2.8", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "libc" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "multiple_crate_versions" +version = "0.1.0" +dependencies = [ + "ansi_term", + "ctrlc", +] + +[[package]] +name = "nix" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "void", +] + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml new file mode 100644 index 0000000000..4f97b01133 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "multiple_crate_versions" +version = "0.1.0" +publish = false + +[workspace] + +[dependencies] +ctrlc = "=3.1.0" +ansi_term = "=0.11.0" diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs new file mode 100644 index 0000000000..1b2d3ec945 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=multiple_crate_versions +#![warn(clippy::multiple_crate_versions)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/src/main.stderr b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/src/main.stderr new file mode 100644 index 0000000000..f3113e0936 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/src/main.stderr @@ -0,0 +1,6 @@ +error: multiple versions for dependency `winapi`: 0.2.8, 0.3.9 + | + = note: `-D clippy::multiple-crate-versions` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml new file mode 100644 index 0000000000..b4b49bb369 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cargo_common_metadata" +version = "0.1.0" +publish = false + +[workspace] + +[dependencies] +regex = "1.3.7" +serde = "1.0.110" diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs new file mode 100644 index 0000000000..1b2d3ec945 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=multiple_crate_versions +#![warn(clippy::multiple_crate_versions)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/update-all-references.sh b/src/tools/clippy/tests/ui-cargo/update-all-references.sh new file mode 100755 index 0000000000..4391499a1e --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/update-all-references.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "Please use 'cargo dev bless' instead." diff --git a/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml new file mode 100644 index 0000000000..3e1a02cbb3 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "wildcard_dependencies" +version = "0.1.0" +publish = false + +[workspace] + +[dependencies] +regex = "*" diff --git a/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs new file mode 100644 index 0000000000..581babfeac --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=wildcard_dependencies +#![warn(clippy::wildcard_dependencies)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/src/main.stderr b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/src/main.stderr new file mode 100644 index 0000000000..9e65d2f994 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/src/main.stderr @@ -0,0 +1,6 @@ +error: wildcard dependency for `regex` + | + = note: `-D clippy::wildcard-dependencies` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml new file mode 100644 index 0000000000..f844cab09b --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "wildcard_dependencies" +version = "0.1.0" +publish = false + +[workspace] + +[dependencies] +regex = "1" diff --git a/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs new file mode 100644 index 0000000000..581babfeac --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=wildcard_dependencies +#![warn(clippy::wildcard_dependencies)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.fixed b/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.fixed new file mode 100644 index 0000000000..e588c23345 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.fixed @@ -0,0 +1,91 @@ +// run-rustfix +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_ast; +extern crate rustc_errors; +extern crate rustc_lint; +extern crate rustc_session; +extern crate rustc_span; + +use rustc_ast::ast::Expr; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +#[allow(unused_variables)] +pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F) +where + F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>), +{ +} + +#[allow(unused_variables)] +fn span_lint_and_help<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + span: Span, + msg: &str, + option_span: Option, + help: &str, +) { +} + +#[allow(unused_variables)] +fn span_lint_and_note<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + span: Span, + msg: &str, + note_span: Option, + note: &str, +) { +} + +#[allow(unused_variables)] +fn span_lint_and_sugg<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + sp: Span, + msg: &str, + help: &str, + sugg: String, + applicability: Applicability, +) { +} + +declare_tool_lint! { + pub clippy::TEST_LINT, + Warn, + "", + report_in_external_macro: true +} + +declare_lint_pass!(Pass => [TEST_LINT]); + +impl EarlyLintPass for Pass { + fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) { + let lint_msg = "lint message"; + let help_msg = "help message"; + let note_msg = "note message"; + let sugg = "new_call()"; + let predicate = true; + + span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable); + span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg); + span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg); + span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg); + span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg); + + // This expr shouldn't trigger this lint. + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.note(note_msg); + if predicate { + db.note(note_msg); + } + }) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.rs b/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.rs new file mode 100644 index 0000000000..d5dd3bb562 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.rs @@ -0,0 +1,101 @@ +// run-rustfix +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_ast; +extern crate rustc_errors; +extern crate rustc_lint; +extern crate rustc_session; +extern crate rustc_span; + +use rustc_ast::ast::Expr; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +#[allow(unused_variables)] +pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F) +where + F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>), +{ +} + +#[allow(unused_variables)] +fn span_lint_and_help<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + span: Span, + msg: &str, + option_span: Option, + help: &str, +) { +} + +#[allow(unused_variables)] +fn span_lint_and_note<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + span: Span, + msg: &str, + note_span: Option, + note: &str, +) { +} + +#[allow(unused_variables)] +fn span_lint_and_sugg<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + sp: Span, + msg: &str, + help: &str, + sugg: String, + applicability: Applicability, +) { +} + +declare_tool_lint! { + pub clippy::TEST_LINT, + Warn, + "", + report_in_external_macro: true +} + +declare_lint_pass!(Pass => [TEST_LINT]); + +impl EarlyLintPass for Pass { + fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) { + let lint_msg = "lint message"; + let help_msg = "help message"; + let note_msg = "note message"; + let sugg = "new_call()"; + let predicate = true; + + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.span_help(expr.span, help_msg); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.help(help_msg); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.span_note(expr.span, note_msg); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.note(note_msg); + }); + + // This expr shouldn't trigger this lint. + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.note(note_msg); + if predicate { + db.note(note_msg); + } + }) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.stderr b/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.stderr new file mode 100644 index 0000000000..874d4a9f25 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.stderr @@ -0,0 +1,49 @@ +error: this call is collapsible + --> $DIR/collapsible_span_lint_calls.rs:75:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable)` + | +note: the lint level is defined here + --> $DIR/collapsible_span_lint_calls.rs:2:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::collapsible_span_lint_calls)]` implied by `#[deny(clippy::internal)]` + +error: this call is collapsible + --> $DIR/collapsible_span_lint_calls.rs:78:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | db.span_help(expr.span, help_msg); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg)` + +error: this call is collapsible + --> $DIR/collapsible_span_lint_calls.rs:81:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | db.help(help_msg); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg)` + +error: this call is collspible + --> $DIR/collapsible_span_lint_calls.rs:84:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | db.span_note(expr.span, note_msg); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg)` + +error: this call is collspible + --> $DIR/collapsible_span_lint_calls.rs:87:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | db.note(note_msg); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg)` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui-internal/custom_ice_message.rs b/src/tools/clippy/tests/ui-internal/custom_ice_message.rs new file mode 100644 index 0000000000..5b30c9d572 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/custom_ice_message.rs @@ -0,0 +1,10 @@ +// rustc-env:RUST_BACKTRACE=0 +// normalize-stderr-test: "Clippy version: .*" -> "Clippy version: foo" +// normalize-stderr-test: "internal_lints.rs:\d*:\d*" -> "internal_lints.rs" +// normalize-stderr-test: "', .*clippy_lints" -> "', clippy_lints" + +#![deny(clippy::internal)] + +fn it_looks_like_you_are_trying_to_kill_clippy() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr b/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr new file mode 100644 index 0000000000..a1b8e2ee16 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr @@ -0,0 +1,13 @@ +thread 'rustc' panicked at 'Would you like some help with that?', clippy_lints/src/utils/internal_lints.rs +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +error: internal compiler error: unexpected panic + +note: the compiler unexpectedly panicked. this is a bug. + +note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy/issues/new + +note: Clippy version: foo + +query stack during panic: +end of query stack diff --git a/src/tools/clippy/tests/ui-internal/default_lint.rs b/src/tools/clippy/tests/ui-internal/default_lint.rs new file mode 100644 index 0000000000..053faae02c --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/default_lint.rs @@ -0,0 +1,27 @@ +#![deny(clippy::internal)] +#![feature(rustc_private)] + +#[macro_use] +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; +extern crate rustc_lint; + +declare_tool_lint! { + pub clippy::TEST_LINT, + Warn, + "", + report_in_external_macro: true +} + +declare_tool_lint! { + pub clippy::TEST_LINT_DEFAULT, + Warn, + "default lint description", + report_in_external_macro: true +} + +declare_lint_pass!(Pass => [TEST_LINT]); +declare_lint_pass!(Pass2 => [TEST_LINT_DEFAULT]); + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/default_lint.stderr b/src/tools/clippy/tests/ui-internal/default_lint.stderr new file mode 100644 index 0000000000..5c5836a7d2 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/default_lint.stderr @@ -0,0 +1,21 @@ +error: the lint `TEST_LINT_DEFAULT` has the default lint description + --> $DIR/default_lint.rs:17:1 + | +LL | / declare_tool_lint! { +LL | | pub clippy::TEST_LINT_DEFAULT, +LL | | Warn, +LL | | "default lint description", +LL | | report_in_external_macro: true +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/default_lint.rs:1:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::default_lint)]` implied by `#[deny(clippy::internal)]` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-internal/interning_defined_symbol.fixed b/src/tools/clippy/tests/ui-internal/interning_defined_symbol.fixed new file mode 100644 index 0000000000..9ab845a573 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/interning_defined_symbol.fixed @@ -0,0 +1,36 @@ +// run-rustfix +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_span; + +use rustc_span::symbol::Symbol; + +macro_rules! sym { + ($tt:tt) => { + rustc_span::symbol::Symbol::intern(stringify!($tt)) + }; +} + +fn main() { + // Direct use of Symbol::intern + let _ = rustc_span::sym::f32; + + // Using a sym macro + let _ = rustc_span::sym::f32; + + // Correct suggestion when symbol isn't stringified constant name + let _ = rustc_span::sym::proc_dash_macro; + + // interning a keyword + let _ = rustc_span::symbol::kw::SelfLower; + + // Interning a symbol that is not defined + let _ = Symbol::intern("xyz123"); + let _ = sym!(xyz123); + + // Using a different `intern` function + let _ = intern("f32"); +} + +fn intern(_: &str) {} diff --git a/src/tools/clippy/tests/ui-internal/interning_defined_symbol.rs b/src/tools/clippy/tests/ui-internal/interning_defined_symbol.rs new file mode 100644 index 0000000000..a58e182971 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/interning_defined_symbol.rs @@ -0,0 +1,36 @@ +// run-rustfix +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_span; + +use rustc_span::symbol::Symbol; + +macro_rules! sym { + ($tt:tt) => { + rustc_span::symbol::Symbol::intern(stringify!($tt)) + }; +} + +fn main() { + // Direct use of Symbol::intern + let _ = Symbol::intern("f32"); + + // Using a sym macro + let _ = sym!(f32); + + // Correct suggestion when symbol isn't stringified constant name + let _ = Symbol::intern("proc-macro"); + + // interning a keyword + let _ = Symbol::intern("self"); + + // Interning a symbol that is not defined + let _ = Symbol::intern("xyz123"); + let _ = sym!(xyz123); + + // Using a different `intern` function + let _ = intern("f32"); +} + +fn intern(_: &str) {} diff --git a/src/tools/clippy/tests/ui-internal/interning_defined_symbol.stderr b/src/tools/clippy/tests/ui-internal/interning_defined_symbol.stderr new file mode 100644 index 0000000000..50c1c268eb --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/interning_defined_symbol.stderr @@ -0,0 +1,33 @@ +error: interning a defined symbol + --> $DIR/interning_defined_symbol.rs:17:13 + | +LL | let _ = Symbol::intern("f32"); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::f32` + | +note: the lint level is defined here + --> $DIR/interning_defined_symbol.rs:2:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::interning_defined_symbol)]` implied by `#[deny(clippy::internal)]` + +error: interning a defined symbol + --> $DIR/interning_defined_symbol.rs:20:13 + | +LL | let _ = sym!(f32); + | ^^^^^^^^^ help: try: `rustc_span::sym::f32` + +error: interning a defined symbol + --> $DIR/interning_defined_symbol.rs:23:13 + | +LL | let _ = Symbol::intern("proc-macro"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::proc_dash_macro` + +error: interning a defined symbol + --> $DIR/interning_defined_symbol.rs:26:13 + | +LL | let _ = Symbol::intern("self"); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::symbol::kw::SelfLower` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui-internal/invalid_paths.rs b/src/tools/clippy/tests/ui-internal/invalid_paths.rs new file mode 100644 index 0000000000..a3b19c2e39 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/invalid_paths.rs @@ -0,0 +1,26 @@ +#![warn(clippy::internal)] + +mod paths { + // Good path + pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"]; + + // Path to method on inherent impl of a primitive type + pub const F32_EPSILON: [&str; 4] = ["core", "f32", "", "EPSILON"]; + + // Path to method on inherent impl + pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"]; + + // Path with empty segment + pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"]; + + // Path with bad crate + pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"]; + + // Path with bad module + pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"]; + + // Path to method on an enum inherent impl + pub const OPTION_IS_SOME: [&str; 4] = ["core", "option", "Option", "is_some"]; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/invalid_paths.stderr b/src/tools/clippy/tests/ui-internal/invalid_paths.stderr new file mode 100644 index 0000000000..bd69d661b7 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/invalid_paths.stderr @@ -0,0 +1,16 @@ +error: invalid path + --> $DIR/invalid_paths.rs:17:5 + | +LL | pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::clippy-lints-internal` implied by `-D warnings` + +error: invalid path + --> $DIR/invalid_paths.rs:20:5 + | +LL | pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui-internal/lint_without_lint_pass.rs b/src/tools/clippy/tests/ui-internal/lint_without_lint_pass.rs new file mode 100644 index 0000000000..beaef79a34 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/lint_without_lint_pass.rs @@ -0,0 +1,44 @@ +#![deny(clippy::internal)] +#![feature(rustc_private)] + +#[macro_use] +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; +extern crate rustc_lint; +use rustc_lint::LintPass; + +declare_tool_lint! { + pub clippy::TEST_LINT, + Warn, + "", + report_in_external_macro: true +} + +declare_tool_lint! { + pub clippy::TEST_LINT_REGISTERED, + Warn, + "", + report_in_external_macro: true +} + +declare_tool_lint! { + pub clippy::TEST_LINT_REGISTERED_ONLY_IMPL, + Warn, + "", + report_in_external_macro: true +} + +pub struct Pass; +impl LintPass for Pass { + fn name(&self) -> &'static str { + "TEST_LINT" + } +} + +declare_lint_pass!(Pass2 => [TEST_LINT_REGISTERED]); + +pub struct Pass3; +impl_lint_pass!(Pass3 => [TEST_LINT_REGISTERED_ONLY_IMPL]); + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/lint_without_lint_pass.stderr b/src/tools/clippy/tests/ui-internal/lint_without_lint_pass.stderr new file mode 100644 index 0000000000..1257dae96d --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/lint_without_lint_pass.stderr @@ -0,0 +1,21 @@ +error: the lint `TEST_LINT` is not added to any `LintPass` + --> $DIR/lint_without_lint_pass.rs:11:1 + | +LL | / declare_tool_lint! { +LL | | pub clippy::TEST_LINT, +LL | | Warn, +LL | | "", +LL | | report_in_external_macro: true +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/lint_without_lint_pass.rs:1:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::lint_without_lint_pass)]` implied by `#[deny(clippy::internal)]` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-internal/match_type_on_diag_item.rs b/src/tools/clippy/tests/ui-internal/match_type_on_diag_item.rs new file mode 100644 index 0000000000..fe950b0aa7 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/match_type_on_diag_item.rs @@ -0,0 +1,50 @@ +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; +use rustc_hir::Expr; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Ty; + +mod paths { + pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"]; +} + +mod utils { + use super::*; + + pub fn match_type(_cx: &LateContext<'_>, _ty: Ty<'_>, _path: &[&str]) -> bool { + false + } +} + +use utils::match_type; + +declare_lint! { + pub TEST_LINT, + Warn, + "" +} + +declare_lint_pass!(Pass => [TEST_LINT]); + +static OPTION: [&str; 3] = ["core", "option", "Option"]; + +impl<'tcx> LateLintPass<'tcx> for Pass { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr) { + let ty = cx.typeck_results().expr_ty(expr); + + let _ = match_type(cx, ty, &paths::VEC); + let _ = match_type(cx, ty, &OPTION); + let _ = match_type(cx, ty, &["core", "result", "Result"]); + + let rc_path = &["alloc", "rc", "Rc"]; + let _ = utils::match_type(cx, ty, rc_path); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/match_type_on_diag_item.stderr b/src/tools/clippy/tests/ui-internal/match_type_on_diag_item.stderr new file mode 100644 index 0000000000..82465dbaf6 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/match_type_on_diag_item.stderr @@ -0,0 +1,33 @@ +error: usage of `utils::match_type()` on a type diagnostic item + --> $DIR/match_type_on_diag_item.rs:41:17 + | +LL | let _ = match_type(cx, ty, &paths::VEC); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `utils::is_type_diagnostic_item(cx, ty, sym::vec_type)` + | +note: the lint level is defined here + --> $DIR/match_type_on_diag_item.rs:1:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::match_type_on_diagnostic_item)]` implied by `#[deny(clippy::internal)]` + +error: usage of `utils::match_type()` on a type diagnostic item + --> $DIR/match_type_on_diag_item.rs:42:17 + | +LL | let _ = match_type(cx, ty, &OPTION); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `utils::is_type_diagnostic_item(cx, ty, sym::option_type)` + +error: usage of `utils::match_type()` on a type diagnostic item + --> $DIR/match_type_on_diag_item.rs:43:17 + | +LL | let _ = match_type(cx, ty, &["core", "result", "Result"]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `utils::is_type_diagnostic_item(cx, ty, sym::result_type)` + +error: usage of `utils::match_type()` on a type diagnostic item + --> $DIR/match_type_on_diag_item.rs:46:17 + | +LL | let _ = utils::match_type(cx, ty, rc_path); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `utils::is_type_diagnostic_item(cx, ty, sym::Rc)` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui-internal/outer_expn_data.fixed b/src/tools/clippy/tests/ui-internal/outer_expn_data.fixed new file mode 100644 index 0000000000..b0b3498f05 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/outer_expn_data.fixed @@ -0,0 +1,28 @@ +// run-rustfix + +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; +use rustc_hir::Expr; +use rustc_lint::{LateContext, LateLintPass}; + +declare_lint! { + pub TEST_LINT, + Warn, + "" +} + +declare_lint_pass!(Pass => [TEST_LINT]); + +impl<'tcx> LateLintPass<'tcx> for Pass { + fn check_expr(&mut self, _cx: &LateContext<'tcx>, expr: &'tcx Expr) { + let _ = expr.span.ctxt().outer_expn_data(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/outer_expn_data.rs b/src/tools/clippy/tests/ui-internal/outer_expn_data.rs new file mode 100644 index 0000000000..55a3fed00d --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/outer_expn_data.rs @@ -0,0 +1,28 @@ +// run-rustfix + +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; +use rustc_hir::Expr; +use rustc_lint::{LateContext, LateLintPass}; + +declare_lint! { + pub TEST_LINT, + Warn, + "" +} + +declare_lint_pass!(Pass => [TEST_LINT]); + +impl<'tcx> LateLintPass<'tcx> for Pass { + fn check_expr(&mut self, _cx: &LateContext<'tcx>, expr: &'tcx Expr) { + let _ = expr.span.ctxt().outer_expn().expn_data(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/outer_expn_data.stderr b/src/tools/clippy/tests/ui-internal/outer_expn_data.stderr new file mode 100644 index 0000000000..56b6ce1f78 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/outer_expn_data.stderr @@ -0,0 +1,15 @@ +error: usage of `outer_expn().expn_data()` + --> $DIR/outer_expn_data.rs:24:34 + | +LL | let _ = expr.span.ctxt().outer_expn().expn_data(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `outer_expn_data()` + | +note: the lint level is defined here + --> $DIR/outer_expn_data.rs:3:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::outer_expn_expn_data)]` implied by `#[deny(clippy::internal)]` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-internal/unnecessary_symbol_str.fixed b/src/tools/clippy/tests/ui-internal/unnecessary_symbol_str.fixed new file mode 100644 index 0000000000..2ec0efe4c1 --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/unnecessary_symbol_str.fixed @@ -0,0 +1,16 @@ +// run-rustfix +#![feature(rustc_private)] +#![deny(clippy::internal)] +#![allow(clippy::unnecessary_operation, unused_must_use)] + +extern crate rustc_span; + +use rustc_span::symbol::{Ident, Symbol}; + +fn main() { + Symbol::intern("foo") == rustc_span::sym::clippy; + Symbol::intern("foo") == rustc_span::symbol::kw::SelfLower; + Symbol::intern("foo") != rustc_span::symbol::kw::SelfUpper; + Ident::invalid().name == rustc_span::sym::clippy; + rustc_span::sym::clippy == Ident::invalid().name; +} diff --git a/src/tools/clippy/tests/ui-internal/unnecessary_symbol_str.rs b/src/tools/clippy/tests/ui-internal/unnecessary_symbol_str.rs new file mode 100644 index 0000000000..87e1b3a2ee --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/unnecessary_symbol_str.rs @@ -0,0 +1,16 @@ +// run-rustfix +#![feature(rustc_private)] +#![deny(clippy::internal)] +#![allow(clippy::unnecessary_operation, unused_must_use)] + +extern crate rustc_span; + +use rustc_span::symbol::{Ident, Symbol}; + +fn main() { + Symbol::intern("foo").as_str() == "clippy"; + Symbol::intern("foo").to_string() == "self"; + Symbol::intern("foo").to_ident_string() != "Self"; + &*Ident::invalid().as_str() == "clippy"; + "clippy" == Ident::invalid().to_string(); +} diff --git a/src/tools/clippy/tests/ui-internal/unnecessary_symbol_str.stderr b/src/tools/clippy/tests/ui-internal/unnecessary_symbol_str.stderr new file mode 100644 index 0000000000..b1284b7c8f --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/unnecessary_symbol_str.stderr @@ -0,0 +1,39 @@ +error: unnecessary `Symbol` to string conversion + --> $DIR/unnecessary_symbol_str.rs:11:5 + | +LL | Symbol::intern("foo").as_str() == "clippy"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::sym::clippy` + | +note: the lint level is defined here + --> $DIR/unnecessary_symbol_str.rs:3:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::unnecessary_symbol_str)]` implied by `#[deny(clippy::internal)]` + +error: unnecessary `Symbol` to string conversion + --> $DIR/unnecessary_symbol_str.rs:12:5 + | +LL | Symbol::intern("foo").to_string() == "self"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::symbol::kw::SelfLower` + +error: unnecessary `Symbol` to string conversion + --> $DIR/unnecessary_symbol_str.rs:13:5 + | +LL | Symbol::intern("foo").to_ident_string() != "Self"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") != rustc_span::symbol::kw::SelfUpper` + +error: unnecessary `Symbol` to string conversion + --> $DIR/unnecessary_symbol_str.rs:14:5 + | +LL | &*Ident::invalid().as_str() == "clippy"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Ident::invalid().name == rustc_span::sym::clippy` + +error: unnecessary `Symbol` to string conversion + --> $DIR/unnecessary_symbol_str.rs:15:5 + | +LL | "clippy" == Ident::invalid().to_string(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::clippy == Ident::invalid().name` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/bad_toml/clippy.toml b/src/tools/clippy/tests/ui-toml/bad_toml/clippy.toml new file mode 100644 index 0000000000..823e01a33b --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml/clippy.toml @@ -0,0 +1,2 @@ +fn this_is_obviously(not: a, toml: file) { +} diff --git a/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.rs b/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.rs new file mode 100644 index 0000000000..3b9458fc28 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.rs @@ -0,0 +1,3 @@ +// error-pattern: error reading Clippy's configuration file + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.stderr b/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.stderr new file mode 100644 index 0000000000..28c1a568a6 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.stderr @@ -0,0 +1,4 @@ +error: error reading Clippy's configuration file `$DIR/clippy.toml`: expected an equals, found an identifier at line 1 column 4 + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/bad_toml_type/clippy.toml b/src/tools/clippy/tests/ui-toml/bad_toml_type/clippy.toml new file mode 100644 index 0000000000..168675394d --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml_type/clippy.toml @@ -0,0 +1 @@ +blacklisted-names = 42 diff --git a/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.rs b/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.rs new file mode 100644 index 0000000000..8a0062423a --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.rs @@ -0,0 +1,4 @@ +// error-pattern: error reading Clippy's configuration file: `blacklisted-names` is expected to be a +// `Vec < String >` but is a `integer` + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.stderr b/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.stderr new file mode 100644 index 0000000000..efd02bcbb6 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.stderr @@ -0,0 +1,4 @@ +error: error reading Clippy's configuration file `$DIR/clippy.toml`: invalid type: integer `42`, expected a sequence + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/conf_deprecated_key/clippy.toml b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/clippy.toml new file mode 100644 index 0000000000..ac47b19504 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/clippy.toml @@ -0,0 +1,6 @@ +# that one is an error +cyclomatic-complexity-threshold = 42 + +# that one is white-listed +[third-party] +clippy-feature = "nightly" diff --git a/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs new file mode 100644 index 0000000000..2577c1eef9 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs @@ -0,0 +1,4 @@ +// error-pattern: error reading Clippy's configuration file: found deprecated field +// `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead. + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr new file mode 100644 index 0000000000..34267c0daf --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr @@ -0,0 +1,4 @@ +error: error reading Clippy's configuration file `$DIR/clippy.toml`: found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead. + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/clippy.toml b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/clippy.toml new file mode 100644 index 0000000000..022eec3e0e --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/clippy.toml @@ -0,0 +1 @@ +max-fn-params-bools = 1 diff --git a/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.rs b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.rs new file mode 100644 index 0000000000..42897b389e --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.rs @@ -0,0 +1,6 @@ +#![warn(clippy::fn_params_excessive_bools)] + +fn f(_: bool) {} +fn g(_: bool, _: bool) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.stderr b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.stderr new file mode 100644 index 0000000000..d05adc3d36 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.stderr @@ -0,0 +1,11 @@ +error: more than 1 bools in function parameters + --> $DIR/test.rs:4:1 + | +LL | fn g(_: bool, _: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::fn-params-excessive-bools` implied by `-D warnings` + = help: consider refactoring bools into two-variant enums + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/functions_maxlines/clippy.toml b/src/tools/clippy/tests/ui-toml/functions_maxlines/clippy.toml new file mode 100644 index 0000000000..951dbb523d --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/functions_maxlines/clippy.toml @@ -0,0 +1 @@ +too-many-lines-threshold = 1 diff --git a/src/tools/clippy/tests/ui-toml/functions_maxlines/test.rs b/src/tools/clippy/tests/ui-toml/functions_maxlines/test.rs new file mode 100644 index 0000000000..a47677a1f3 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/functions_maxlines/test.rs @@ -0,0 +1,45 @@ +#![warn(clippy::too_many_lines)] + +// This function should be considered one line. +fn many_comments_but_one_line_of_code() { + /* println!("This is good."); */ + // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* println!("This is good."); + println!("This is good."); + println!("This is good."); */ + println!("This is good."); +} + +// This should be considered two and a fail. +fn too_many_lines() { + println!("This is bad."); + println!("This is bad."); +} + +// This should be considered one line. +#[rustfmt::skip] +fn comment_starts_after_code() { + let _ = 5; /* closing comment. */ /* + this line shouldn't be counted theoretically. + */ +} + +// This should be considered one line. +fn comment_after_code() { + let _ = 5; /* this line should get counted once. */ +} + +// This should fail since it is technically two lines. +#[rustfmt::skip] +fn comment_before_code() { + let _ = "test"; + /* This comment extends to the front of + the code but this line should still count. */ let _ = 5; +} + +// This should be considered one line. +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr b/src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr new file mode 100644 index 0000000000..a27ce945ca --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr @@ -0,0 +1,23 @@ +error: this function has too many lines (2/1) + --> $DIR/test.rs:18:1 + | +LL | / fn too_many_lines() { +LL | | println!("This is bad."); +LL | | println!("This is bad."); +LL | | } + | |_^ + | + = note: `-D clippy::too-many-lines` implied by `-D warnings` + +error: this function has too many lines (2/1) + --> $DIR/test.rs:38:1 + | +LL | / fn comment_before_code() { +LL | | let _ = "test"; +LL | | /* This comment extends to the front of +LL | | the code but this line should still count. */ let _ = 5; +LL | | } + | |_^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/clippy.toml b/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/clippy.toml new file mode 100644 index 0000000000..a1dd6b2f08 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/clippy.toml @@ -0,0 +1,3 @@ +# that one is white-listed +[third-party] +clippy-feature = "nightly" diff --git a/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs b/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs new file mode 100644 index 0000000000..270b9c5c43 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs @@ -0,0 +1,3 @@ +// error-pattern: should give absolutely no error + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/invalid_min_rust_version/clippy.toml b/src/tools/clippy/tests/ui-toml/invalid_min_rust_version/clippy.toml new file mode 100644 index 0000000000..088b12b2da --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/invalid_min_rust_version/clippy.toml @@ -0,0 +1 @@ +msrv = "invalid.version" diff --git a/src/tools/clippy/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.rs b/src/tools/clippy/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.rs new file mode 100644 index 0000000000..2ebf28645e --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.rs @@ -0,0 +1,3 @@ +#![allow(clippy::redundant_clone)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.stderr b/src/tools/clippy/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.stderr new file mode 100644 index 0000000000..e9d8fd2e0f --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.stderr @@ -0,0 +1,4 @@ +error: error reading Clippy's configuration file. `invalid.version` is not a valid Rust version + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/lint_decimal_readability/clippy.toml b/src/tools/clippy/tests/ui-toml/lint_decimal_readability/clippy.toml new file mode 100644 index 0000000000..6feaf7d5c0 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/lint_decimal_readability/clippy.toml @@ -0,0 +1 @@ +unreadable-literal-lint-fractions = false \ No newline at end of file diff --git a/src/tools/clippy/tests/ui-toml/lint_decimal_readability/test.rs b/src/tools/clippy/tests/ui-toml/lint_decimal_readability/test.rs new file mode 100644 index 0000000000..9377eb69b2 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/lint_decimal_readability/test.rs @@ -0,0 +1,22 @@ +#[deny(clippy::unreadable_literal)] + +fn allow_inconsistent_digit_grouping() { + #![allow(clippy::inconsistent_digit_grouping)] + let _pass1 = 100_200_300.123456789; +} + +fn main() { + allow_inconsistent_digit_grouping(); + + let _pass1 = 100_200_300.100_200_300; + let _pass2 = 1.123456789; + let _pass3 = 1.0; + let _pass4 = 10000.00001; + let _pass5 = 1.123456789e1; + + // due to clippy::inconsistent-digit-grouping + let _fail1 = 100_200_300.123456789; + + // fail due to the integer part + let _fail2 = 100200300.300200100; +} diff --git a/src/tools/clippy/tests/ui-toml/lint_decimal_readability/test.stderr b/src/tools/clippy/tests/ui-toml/lint_decimal_readability/test.stderr new file mode 100644 index 0000000000..9119ef19a7 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/lint_decimal_readability/test.stderr @@ -0,0 +1,10 @@ +error: digits grouped inconsistently by underscores + --> $DIR/test.rs:18:18 + | +LL | let _fail1 = 100_200_300.123456789; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider: `100_200_300.123_456_789` + | + = note: `-D clippy::inconsistent-digit-grouping` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/min_rust_version/clippy.toml b/src/tools/clippy/tests/ui-toml/min_rust_version/clippy.toml new file mode 100644 index 0000000000..8e17d8074c --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/min_rust_version/clippy.toml @@ -0,0 +1 @@ +msrv = "1.0.0" diff --git a/src/tools/clippy/tests/ui-toml/min_rust_version/min_rust_version.rs b/src/tools/clippy/tests/ui-toml/min_rust_version/min_rust_version.rs new file mode 100644 index 0000000000..bc41efa42a --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/min_rust_version/min_rust_version.rs @@ -0,0 +1,68 @@ +#![allow(clippy::redundant_clone)] +#![warn(clippy::manual_non_exhaustive)] + +use std::ops::Deref; + +mod enums { + enum E { + A, + B, + #[doc(hidden)] + _C, + } + + // user forgot to remove the marker + #[non_exhaustive] + enum Ep { + A, + B, + #[doc(hidden)] + _C, + } +} + +fn option_as_ref_deref() { + let mut opt = Some(String::from("123")); + + let _ = opt.as_ref().map(String::as_str); + let _ = opt.as_ref().map(|x| x.as_str()); + let _ = opt.as_mut().map(String::as_mut_str); + let _ = opt.as_mut().map(|x| x.as_mut_str()); +} + +fn match_like_matches() { + let _y = match Some(5) { + Some(0) => true, + _ => false, + }; +} + +fn match_same_arms() { + match (1, 2, 3) { + (1, .., 3) => 42, + (.., 3) => 42, //~ ERROR match arms have same body + _ => 0, + }; +} + +fn match_same_arms2() { + let _ = match Some(42) { + Some(_) => 24, + None => 24, //~ ERROR match arms have same body + }; +} + +fn manual_strip_msrv() { + let s = "hello, world!"; + if s.starts_with("hello, ") { + assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); + } +} + +fn main() { + option_as_ref_deref(); + match_like_matches(); + match_same_arms(); + match_same_arms2(); + manual_strip_msrv(); +} diff --git a/src/tools/clippy/tests/ui-toml/struct_excessive_bools/clippy.toml b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/clippy.toml new file mode 100644 index 0000000000..3912ab5427 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/clippy.toml @@ -0,0 +1 @@ +max-struct-bools = 0 diff --git a/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.rs b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.rs new file mode 100644 index 0000000000..242984680e --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.rs @@ -0,0 +1,9 @@ +#![warn(clippy::struct_excessive_bools)] + +struct S { + a: bool, +} + +struct Foo {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.stderr b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.stderr new file mode 100644 index 0000000000..65861d10d0 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.stderr @@ -0,0 +1,13 @@ +error: more than 0 bools in a struct + --> $DIR/test.rs:3:1 + | +LL | / struct S { +LL | | a: bool, +LL | | } + | |_^ + | + = note: `-D clippy::struct-excessive-bools` implied by `-D warnings` + = help: consider using a state machine or refactoring bools into two-variant enums + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/toml_blacklist/clippy.toml b/src/tools/clippy/tests/ui-toml/toml_blacklist/clippy.toml new file mode 100644 index 0000000000..6abe5a3bbc --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_blacklist/clippy.toml @@ -0,0 +1 @@ +blacklisted-names = ["toto", "tata", "titi"] diff --git a/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.rs b/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.rs new file mode 100644 index 0000000000..cb35d0e858 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.rs @@ -0,0 +1,20 @@ +#![allow(dead_code)] +#![allow(clippy::single_match)] +#![allow(unused_variables)] +#![warn(clippy::blacklisted_name)] + +fn test(toto: ()) {} + +fn main() { + let toto = 42; + let tata = 42; + let titi = 42; + + let tatab = 42; + let tatatataic = 42; + + match (42, Some(1337), Some(0)) { + (toto, Some(tata), titi @ Some(_)) => (), + _ => (), + } +} diff --git a/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.stderr b/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.stderr new file mode 100644 index 0000000000..84ba77851f --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.stderr @@ -0,0 +1,46 @@ +error: use of a blacklisted/placeholder name `toto` + --> $DIR/conf_french_blacklisted_name.rs:6:9 + | +LL | fn test(toto: ()) {} + | ^^^^ + | + = note: `-D clippy::blacklisted-name` implied by `-D warnings` + +error: use of a blacklisted/placeholder name `toto` + --> $DIR/conf_french_blacklisted_name.rs:9:9 + | +LL | let toto = 42; + | ^^^^ + +error: use of a blacklisted/placeholder name `tata` + --> $DIR/conf_french_blacklisted_name.rs:10:9 + | +LL | let tata = 42; + | ^^^^ + +error: use of a blacklisted/placeholder name `titi` + --> $DIR/conf_french_blacklisted_name.rs:11:9 + | +LL | let titi = 42; + | ^^^^ + +error: use of a blacklisted/placeholder name `toto` + --> $DIR/conf_french_blacklisted_name.rs:17:10 + | +LL | (toto, Some(tata), titi @ Some(_)) => (), + | ^^^^ + +error: use of a blacklisted/placeholder name `tata` + --> $DIR/conf_french_blacklisted_name.rs:17:21 + | +LL | (toto, Some(tata), titi @ Some(_)) => (), + | ^^^^ + +error: use of a blacklisted/placeholder name `titi` + --> $DIR/conf_french_blacklisted_name.rs:17:28 + | +LL | (toto, Some(tata), titi @ Some(_)) => (), + | ^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/toml_disallowed_method/clippy.toml b/src/tools/clippy/tests/ui-toml/toml_disallowed_method/clippy.toml new file mode 100644 index 0000000000..c0df3b6e8a --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_disallowed_method/clippy.toml @@ -0,0 +1 @@ +disallowed-methods = ["core::iter::traits::iterator::Iterator::sum", "regex::re_unicode::Regex::is_match", "regex::re_unicode::Regex::new"] diff --git a/src/tools/clippy/tests/ui-toml/toml_disallowed_method/conf_disallowed_method.rs b/src/tools/clippy/tests/ui-toml/toml_disallowed_method/conf_disallowed_method.rs new file mode 100644 index 0000000000..1901a99377 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_disallowed_method/conf_disallowed_method.rs @@ -0,0 +1,12 @@ +#![warn(clippy::disallowed_method)] + +extern crate regex; +use regex::Regex; + +fn main() { + let re = Regex::new(r"ab.*c").unwrap(); + re.is_match("abc"); + + let a = vec![1, 2, 3, 4]; + a.iter().sum::(); +} diff --git a/src/tools/clippy/tests/ui-toml/toml_disallowed_method/conf_disallowed_method.stderr b/src/tools/clippy/tests/ui-toml/toml_disallowed_method/conf_disallowed_method.stderr new file mode 100644 index 0000000000..2b628c67fa --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_disallowed_method/conf_disallowed_method.stderr @@ -0,0 +1,22 @@ +error: use of a disallowed method `regex::re_unicode::Regex::new` + --> $DIR/conf_disallowed_method.rs:7:14 + | +LL | let re = Regex::new(r"ab.*c").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::disallowed-method` implied by `-D warnings` + +error: use of a disallowed method `regex::re_unicode::Regex::is_match` + --> $DIR/conf_disallowed_method.rs:8:5 + | +LL | re.is_match("abc"); + | ^^^^^^^^^^^^^^^^^^ + +error: use of a disallowed method `core::iter::traits::iterator::Iterator::sum` + --> $DIR/conf_disallowed_method.rs:11:5 + | +LL | a.iter().sum::(); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/toml_trivially_copy/clippy.toml b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/clippy.toml new file mode 100644 index 0000000000..3b96f1fd00 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/clippy.toml @@ -0,0 +1 @@ +trivial-copy-size-limit = 2 diff --git a/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.rs b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.rs new file mode 100644 index 0000000000..19019a2541 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.rs @@ -0,0 +1,21 @@ +// normalize-stderr-test "\(\d+ byte\)" -> "(N byte)" +// normalize-stderr-test "\(limit: \d+ byte\)" -> "(limit: N byte)" + +#![deny(clippy::trivially_copy_pass_by_ref)] +#![allow(clippy::many_single_char_names)] + +#[derive(Copy, Clone)] +struct Foo(u8); + +#[derive(Copy, Clone)] +struct Bar(u32); + +fn good(a: &mut u32, b: u32, c: &Bar, d: &u32) {} + +fn bad(x: &u16, y: &Foo) {} + +fn main() { + let (mut a, b, c, d, x, y) = (0, 0, Bar(0), 0, 0, Foo(0)); + good(&mut a, b, &c, &d); + bad(&x, &y); +} diff --git a/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.stderr b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.stderr new file mode 100644 index 0000000000..912761a8f0 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.stderr @@ -0,0 +1,20 @@ +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/test.rs:15:11 + | +LL | fn bad(x: &u16, y: &Foo) {} + | ^^^^ help: consider passing by value instead: `u16` + | +note: the lint level is defined here + --> $DIR/test.rs:4:9 + | +LL | #![deny(clippy::trivially_copy_pass_by_ref)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/test.rs:15:20 + | +LL | fn bad(x: &u16, y: &Foo) {} + | ^^^^ help: consider passing by value instead: `Foo` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/toml_unknown_key/clippy.toml b/src/tools/clippy/tests/ui-toml/toml_unknown_key/clippy.toml new file mode 100644 index 0000000000..554b87cc50 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_unknown_key/clippy.toml @@ -0,0 +1,6 @@ +# that one is an error +foobar = 42 + +# that one is white-listed +[third-party] +clippy-feature = "nightly" diff --git a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs new file mode 100644 index 0000000000..a47569f62a --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs @@ -0,0 +1,3 @@ +// error-pattern: error reading Clippy's configuration file: unknown key `foobar` + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr new file mode 100644 index 0000000000..d83080b69f --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -0,0 +1,4 @@ +error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `unreadable-literal-lint-fractions`, `upper-case-acronyms-aggressive`, `cargo-ignore-publish`, `third-party` at line 5 column 1 + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/update-all-references.sh b/src/tools/clippy/tests/ui-toml/update-all-references.sh new file mode 100755 index 0000000000..4391499a1e --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/update-all-references.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "Please use 'cargo dev bless' instead." diff --git a/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/clippy.toml b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/clippy.toml new file mode 100644 index 0000000000..cc94ec53e1 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/clippy.toml @@ -0,0 +1 @@ +upper-case-acronyms-aggressive = true diff --git a/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.rs b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.rs new file mode 100644 index 0000000000..735909887a --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.rs @@ -0,0 +1,23 @@ +#![warn(clippy::upper_case_acronyms)] + +struct HTTPResponse; // not linted by default, but with cfg option + +struct CString; // not linted + +enum Flags { + NS, // not linted + CWR, + ECE, + URG, + ACK, + PSH, + RST, + SYN, + FIN, +} + +// linted with cfg option, beware that lint suggests `GccllvmSomething` instead of +// `GccLlvmSomething` +struct GCCLLVMSomething; + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.stderr b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.stderr new file mode 100644 index 0000000000..38e30683d5 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.stderr @@ -0,0 +1,70 @@ +error: name `HTTPResponse` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:3:8 + | +LL | struct HTTPResponse; // not linted by default, but with cfg option + | ^^^^^^^^^^^^ help: consider making the acronym lowercase, except the initial letter: `HttpResponse` + | + = note: `-D clippy::upper-case-acronyms` implied by `-D warnings` + +error: name `NS` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:8:5 + | +LL | NS, // not linted + | ^^ help: consider making the acronym lowercase, except the initial letter (notice the capitalization): `Ns` + +error: name `CWR` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:9:5 + | +LL | CWR, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Cwr` + +error: name `ECE` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:10:5 + | +LL | ECE, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Ece` + +error: name `URG` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:11:5 + | +LL | URG, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Urg` + +error: name `ACK` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:12:5 + | +LL | ACK, + | ^^^ help: consider making the acronym lowercase, except the initial letter (notice the capitalization): `Ack` + +error: name `PSH` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:13:5 + | +LL | PSH, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Psh` + +error: name `RST` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:14:5 + | +LL | RST, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Rst` + +error: name `SYN` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:15:5 + | +LL | SYN, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Syn` + +error: name `FIN` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:16:5 + | +LL | FIN, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Fin` + +error: name `GCCLLVMSomething` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:21:8 + | +LL | struct GCCLLVMSomething; + | ^^^^^^^^^^^^^^^^ help: consider making the acronym lowercase, except the initial letter: `GccllvmSomething` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/vec_box_sized/clippy.toml b/src/tools/clippy/tests/ui-toml/vec_box_sized/clippy.toml new file mode 100644 index 0000000000..039ea47fc3 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/vec_box_sized/clippy.toml @@ -0,0 +1 @@ +vec-box-size-threshold = 4 diff --git a/src/tools/clippy/tests/ui-toml/vec_box_sized/test.rs b/src/tools/clippy/tests/ui-toml/vec_box_sized/test.rs new file mode 100644 index 0000000000..bf04bee163 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/vec_box_sized/test.rs @@ -0,0 +1,15 @@ +struct S { + x: u64, +} + +struct C { + y: u16, +} + +struct Foo(Vec>); +struct Bar(Vec>); +struct Baz(Vec>); +struct BarBaz(Vec>); +struct FooBarBaz(Vec>); + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/vec_box_sized/test.stderr b/src/tools/clippy/tests/ui-toml/vec_box_sized/test.stderr new file mode 100644 index 0000000000..cf194de3c5 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/vec_box_sized/test.stderr @@ -0,0 +1,22 @@ +error: `Vec` is already on the heap, the boxing is unnecessary + --> $DIR/test.rs:9:12 + | +LL | struct Foo(Vec>); + | ^^^^^^^^^^^^ help: try: `Vec` + | + = note: `-D clippy::vec-box` implied by `-D warnings` + +error: `Vec` is already on the heap, the boxing is unnecessary + --> $DIR/test.rs:10:12 + | +LL | struct Bar(Vec>); + | ^^^^^^^^^^^^^ help: try: `Vec` + +error: `Vec` is already on the heap, the boxing is unnecessary + --> $DIR/test.rs:13:18 + | +LL | struct FooBarBaz(Vec>); + | ^^^^^^^^^^^ help: try: `Vec` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/zero_single_char_names/clippy.toml b/src/tools/clippy/tests/ui-toml/zero_single_char_names/clippy.toml new file mode 100644 index 0000000000..42a1067b95 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/zero_single_char_names/clippy.toml @@ -0,0 +1 @@ +single-char-binding-names-threshold = 0 diff --git a/src/tools/clippy/tests/ui-toml/zero_single_char_names/zero_single_char_names.rs b/src/tools/clippy/tests/ui-toml/zero_single_char_names/zero_single_char_names.rs new file mode 100644 index 0000000000..22aaa242b9 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/zero_single_char_names/zero_single_char_names.rs @@ -0,0 +1,3 @@ +#![warn(clippy::many_single_char_names)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/absurd-extreme-comparisons.rs b/src/tools/clippy/tests/ui/absurd-extreme-comparisons.rs new file mode 100644 index 0000000000..d205b383d1 --- /dev/null +++ b/src/tools/clippy/tests/ui/absurd-extreme-comparisons.rs @@ -0,0 +1,61 @@ +#![warn(clippy::absurd_extreme_comparisons)] +#![allow( + unused, + clippy::eq_op, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::needless_pass_by_value +)] + +#[rustfmt::skip] +fn main() { + const Z: u32 = 0; + let u: u32 = 42; + u <= 0; + u <= Z; + u < Z; + Z >= u; + Z > u; + u > u32::MAX; + u >= u32::MAX; + u32::MAX < u; + u32::MAX <= u; + 1-1 > u; + u >= !0; + u <= 12 - 2*6; + let i: i8 = 0; + i < -127 - 1; + i8::MAX >= i; + 3-7 < i32::MIN; + let b = false; + b >= true; + false > b; + u > 0; // ok + // this is handled by clippy::unit_cmp + () < {}; +} + +use std::cmp::{Ordering, PartialEq, PartialOrd}; + +#[derive(PartialEq, PartialOrd)] +pub struct U(u64); + +impl PartialEq for U { + fn eq(&self, other: &u32) -> bool { + self.eq(&U(u64::from(*other))) + } +} +impl PartialOrd for U { + fn partial_cmp(&self, other: &u32) -> Option { + self.partial_cmp(&U(u64::from(*other))) + } +} + +pub fn foo(val: U) -> bool { + val > u32::MAX +} + +pub fn bar(len: u64) -> bool { + // This is OK as we are casting from target sized to fixed size + len >= usize::MAX as u64 +} diff --git a/src/tools/clippy/tests/ui/absurd-extreme-comparisons.stderr b/src/tools/clippy/tests/ui/absurd-extreme-comparisons.stderr new file mode 100644 index 0000000000..6de554378a --- /dev/null +++ b/src/tools/clippy/tests/ui/absurd-extreme-comparisons.stderr @@ -0,0 +1,147 @@ +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:14:5 + | +LL | u <= 0; + | ^^^^^^ + | + = note: `-D clippy::absurd-extreme-comparisons` implied by `-D warnings` + = help: because `0` is the minimum value for this type, the case where the two sides are not equal never occurs, consider using `u == 0` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:15:5 + | +LL | u <= Z; + | ^^^^^^ + | + = help: because `Z` is the minimum value for this type, the case where the two sides are not equal never occurs, consider using `u == Z` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:16:5 + | +LL | u < Z; + | ^^^^^ + | + = help: because `Z` is the minimum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:17:5 + | +LL | Z >= u; + | ^^^^^^ + | + = help: because `Z` is the minimum value for this type, the case where the two sides are not equal never occurs, consider using `Z == u` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:18:5 + | +LL | Z > u; + | ^^^^^ + | + = help: because `Z` is the minimum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:19:5 + | +LL | u > u32::MAX; + | ^^^^^^^^^^^^ + | + = help: because `u32::MAX` is the maximum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:20:5 + | +LL | u >= u32::MAX; + | ^^^^^^^^^^^^^ + | + = help: because `u32::MAX` is the maximum value for this type, the case where the two sides are not equal never occurs, consider using `u == u32::MAX` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:21:5 + | +LL | u32::MAX < u; + | ^^^^^^^^^^^^ + | + = help: because `u32::MAX` is the maximum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:22:5 + | +LL | u32::MAX <= u; + | ^^^^^^^^^^^^^ + | + = help: because `u32::MAX` is the maximum value for this type, the case where the two sides are not equal never occurs, consider using `u32::MAX == u` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:23:5 + | +LL | 1-1 > u; + | ^^^^^^^ + | + = help: because `1-1` is the minimum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:24:5 + | +LL | u >= !0; + | ^^^^^^^ + | + = help: because `!0` is the maximum value for this type, the case where the two sides are not equal never occurs, consider using `u == !0` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:25:5 + | +LL | u <= 12 - 2*6; + | ^^^^^^^^^^^^^ + | + = help: because `12 - 2*6` is the minimum value for this type, the case where the two sides are not equal never occurs, consider using `u == 12 - 2*6` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:27:5 + | +LL | i < -127 - 1; + | ^^^^^^^^^^^^ + | + = help: because `-127 - 1` is the minimum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:28:5 + | +LL | i8::MAX >= i; + | ^^^^^^^^^^^^ + | + = help: because `i8::MAX` is the maximum value for this type, this comparison is always true + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:29:5 + | +LL | 3-7 < i32::MIN; + | ^^^^^^^^^^^^^^ + | + = help: because `i32::MIN` is the minimum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:31:5 + | +LL | b >= true; + | ^^^^^^^^^ + | + = help: because `true` is the maximum value for this type, the case where the two sides are not equal never occurs, consider using `b == true` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:32:5 + | +LL | false > b; + | ^^^^^^^^^ + | + = help: because `false` is the minimum value for this type, this comparison is always false + +error: <-comparison of unit values detected. This will always be false + --> $DIR/absurd-extreme-comparisons.rs:35:5 + | +LL | () < {}; + | ^^^^^^^ + | + = note: `#[deny(clippy::unit_cmp)]` on by default + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/approx_const.rs b/src/tools/clippy/tests/ui/approx_const.rs new file mode 100644 index 0000000000..fb57a0becb --- /dev/null +++ b/src/tools/clippy/tests/ui/approx_const.rs @@ -0,0 +1,60 @@ +#[warn(clippy::approx_constant)] +#[allow(unused, clippy::shadow_unrelated, clippy::similar_names)] +fn main() { + let my_e = 2.7182; + let almost_e = 2.718; + let no_e = 2.71; + + let my_1_frac_pi = 0.3183; + let no_1_frac_pi = 0.31; + + let my_frac_1_sqrt_2 = 0.70710678; + let almost_frac_1_sqrt_2 = 0.70711; + let my_frac_1_sqrt_2 = 0.707; + + let my_frac_2_pi = 0.63661977; + let no_frac_2_pi = 0.636; + + let my_frac_2_sq_pi = 1.128379; + let no_frac_2_sq_pi = 1.128; + + let my_frac_pi_2 = 1.57079632679; + let no_frac_pi_2 = 1.5705; + + let my_frac_pi_3 = 1.04719755119; + let no_frac_pi_3 = 1.047; + + let my_frac_pi_4 = 0.785398163397; + let no_frac_pi_4 = 0.785; + + let my_frac_pi_6 = 0.523598775598; + let no_frac_pi_6 = 0.523; + + let my_frac_pi_8 = 0.3926990816987; + let no_frac_pi_8 = 0.392; + + let my_ln_10 = 2.302585092994046; + let no_ln_10 = 2.303; + + let my_ln_2 = 0.6931471805599453; + let no_ln_2 = 0.693; + + let my_log10_e = 0.4342944819032518; + let no_log10_e = 0.434; + + let my_log2_e = 1.4426950408889634; + let no_log2_e = 1.442; + + let log2_10 = 3.321928094887362; + let no_log2_10 = 3.321; + + let log10_2 = 0.301029995663981; + let no_log10_2 = 0.301; + + let my_pi = 3.1415; + let almost_pi = 3.14; + let no_pi = 3.15; + + let my_sq2 = 1.4142; + let no_sq2 = 1.414; +} diff --git a/src/tools/clippy/tests/ui/approx_const.stderr b/src/tools/clippy/tests/ui/approx_const.stderr new file mode 100644 index 0000000000..98b85443f0 --- /dev/null +++ b/src/tools/clippy/tests/ui/approx_const.stderr @@ -0,0 +1,130 @@ +error: approximate value of `f{32, 64}::consts::E` found. Consider using it directly + --> $DIR/approx_const.rs:4:16 + | +LL | let my_e = 2.7182; + | ^^^^^^ + | + = note: `-D clippy::approx-constant` implied by `-D warnings` + +error: approximate value of `f{32, 64}::consts::E` found. Consider using it directly + --> $DIR/approx_const.rs:5:20 + | +LL | let almost_e = 2.718; + | ^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_1_PI` found. Consider using it directly + --> $DIR/approx_const.rs:8:24 + | +LL | let my_1_frac_pi = 0.3183; + | ^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_1_SQRT_2` found. Consider using it directly + --> $DIR/approx_const.rs:11:28 + | +LL | let my_frac_1_sqrt_2 = 0.70710678; + | ^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_1_SQRT_2` found. Consider using it directly + --> $DIR/approx_const.rs:12:32 + | +LL | let almost_frac_1_sqrt_2 = 0.70711; + | ^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_2_PI` found. Consider using it directly + --> $DIR/approx_const.rs:15:24 + | +LL | let my_frac_2_pi = 0.63661977; + | ^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_2_SQRT_PI` found. Consider using it directly + --> $DIR/approx_const.rs:18:27 + | +LL | let my_frac_2_sq_pi = 1.128379; + | ^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_PI_2` found. Consider using it directly + --> $DIR/approx_const.rs:21:24 + | +LL | let my_frac_pi_2 = 1.57079632679; + | ^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_PI_3` found. Consider using it directly + --> $DIR/approx_const.rs:24:24 + | +LL | let my_frac_pi_3 = 1.04719755119; + | ^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_PI_4` found. Consider using it directly + --> $DIR/approx_const.rs:27:24 + | +LL | let my_frac_pi_4 = 0.785398163397; + | ^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_PI_6` found. Consider using it directly + --> $DIR/approx_const.rs:30:24 + | +LL | let my_frac_pi_6 = 0.523598775598; + | ^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_PI_8` found. Consider using it directly + --> $DIR/approx_const.rs:33:24 + | +LL | let my_frac_pi_8 = 0.3926990816987; + | ^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LN_10` found. Consider using it directly + --> $DIR/approx_const.rs:36:20 + | +LL | let my_ln_10 = 2.302585092994046; + | ^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LN_2` found. Consider using it directly + --> $DIR/approx_const.rs:39:19 + | +LL | let my_ln_2 = 0.6931471805599453; + | ^^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LOG10_E` found. Consider using it directly + --> $DIR/approx_const.rs:42:22 + | +LL | let my_log10_e = 0.4342944819032518; + | ^^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LOG2_E` found. Consider using it directly + --> $DIR/approx_const.rs:45:21 + | +LL | let my_log2_e = 1.4426950408889634; + | ^^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LOG2_10` found. Consider using it directly + --> $DIR/approx_const.rs:48:19 + | +LL | let log2_10 = 3.321928094887362; + | ^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LOG10_2` found. Consider using it directly + --> $DIR/approx_const.rs:51:19 + | +LL | let log10_2 = 0.301029995663981; + | ^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly + --> $DIR/approx_const.rs:54:17 + | +LL | let my_pi = 3.1415; + | ^^^^^^ + +error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly + --> $DIR/approx_const.rs:55:21 + | +LL | let almost_pi = 3.14; + | ^^^^ + +error: approximate value of `f{32, 64}::consts::SQRT_2` found. Consider using it directly + --> $DIR/approx_const.rs:58:18 + | +LL | let my_sq2 = 1.4142; + | ^^^^^^ + +error: aborting due to 21 previous errors + diff --git a/src/tools/clippy/tests/ui/as_conversions.rs b/src/tools/clippy/tests/ui/as_conversions.rs new file mode 100644 index 0000000000..cd745feec6 --- /dev/null +++ b/src/tools/clippy/tests/ui/as_conversions.rs @@ -0,0 +1,19 @@ +// aux-build:macro_rules.rs + +#![warn(clippy::as_conversions)] + +#[macro_use] +extern crate macro_rules; + +fn with_external_macro() { + as_conv_with_arg!(0u32 as u64); + as_conv!(); +} + +fn main() { + let i = 0u32 as u64; + + let j = &i as *const u64 as *mut u64; + + with_external_macro(); +} diff --git a/src/tools/clippy/tests/ui/as_conversions.stderr b/src/tools/clippy/tests/ui/as_conversions.stderr new file mode 100644 index 0000000000..f5f75d3aee --- /dev/null +++ b/src/tools/clippy/tests/ui/as_conversions.stderr @@ -0,0 +1,27 @@ +error: using a potentially dangerous silent `as` conversion + --> $DIR/as_conversions.rs:14:13 + | +LL | let i = 0u32 as u64; + | ^^^^^^^^^^^ + | + = note: `-D clippy::as-conversions` implied by `-D warnings` + = help: consider using a safe wrapper for this conversion + +error: using a potentially dangerous silent `as` conversion + --> $DIR/as_conversions.rs:16:13 + | +LL | let j = &i as *const u64 as *mut u64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a safe wrapper for this conversion + +error: using a potentially dangerous silent `as` conversion + --> $DIR/as_conversions.rs:16:13 + | +LL | let j = &i as *const u64 as *mut u64; + | ^^^^^^^^^^^^^^^^ + | + = help: consider using a safe wrapper for this conversion + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/asm_syntax.rs b/src/tools/clippy/tests/ui/asm_syntax.rs new file mode 100644 index 0000000000..658cae397e --- /dev/null +++ b/src/tools/clippy/tests/ui/asm_syntax.rs @@ -0,0 +1,31 @@ +#![feature(asm)] +// only-x86_64 + +#[warn(clippy::inline_asm_x86_intel_syntax)] +mod warn_intel { + pub(super) unsafe fn use_asm() { + asm!(""); + asm!("", options()); + asm!("", options(nostack)); + asm!("", options(att_syntax)); + asm!("", options(nostack, att_syntax)); + } +} + +#[warn(clippy::inline_asm_x86_att_syntax)] +mod warn_att { + pub(super) unsafe fn use_asm() { + asm!(""); + asm!("", options()); + asm!("", options(nostack)); + asm!("", options(att_syntax)); + asm!("", options(nostack, att_syntax)); + } +} + +fn main() { + unsafe { + warn_att::use_asm(); + warn_intel::use_asm(); + } +} diff --git a/src/tools/clippy/tests/ui/asm_syntax.stderr b/src/tools/clippy/tests/ui/asm_syntax.stderr new file mode 100644 index 0000000000..27b51166ea --- /dev/null +++ b/src/tools/clippy/tests/ui/asm_syntax.stderr @@ -0,0 +1,44 @@ +error: Intel x86 assembly syntax used + --> $DIR/asm_syntax.rs:7:9 + | +LL | asm!(""); + | ^^^^^^^^^ + | + = note: `-D clippy::inline-asm-x86-intel-syntax` implied by `-D warnings` + = help: use AT&T x86 assembly syntax + +error: Intel x86 assembly syntax used + --> $DIR/asm_syntax.rs:8:9 + | +LL | asm!("", options()); + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: use AT&T x86 assembly syntax + +error: Intel x86 assembly syntax used + --> $DIR/asm_syntax.rs:9:9 + | +LL | asm!("", options(nostack)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use AT&T x86 assembly syntax + +error: AT&T x86 assembly syntax used + --> $DIR/asm_syntax.rs:21:9 + | +LL | asm!("", options(att_syntax)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::inline-asm-x86-att-syntax` implied by `-D warnings` + = help: use Intel x86 assembly syntax + +error: AT&T x86 assembly syntax used + --> $DIR/asm_syntax.rs:22:9 + | +LL | asm!("", options(nostack, att_syntax)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use Intel x86 assembly syntax + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/assertions_on_constants.rs b/src/tools/clippy/tests/ui/assertions_on_constants.rs new file mode 100644 index 0000000000..e989de6540 --- /dev/null +++ b/src/tools/clippy/tests/ui/assertions_on_constants.rs @@ -0,0 +1,31 @@ +#![allow(non_fmt_panic)] + +macro_rules! assert_const { + ($len:expr) => { + assert!($len > 0); + debug_assert!($len < 0); + }; +} + +fn main() { + assert!(true); + assert!(false); + assert!(true, "true message"); + assert!(false, "false message"); + + let msg = "panic message"; + assert!(false, msg.to_uppercase()); + + const B: bool = true; + assert!(B); + + const C: bool = false; + assert!(C); + assert!(C, "C message"); + + debug_assert!(true); + // Don't lint this, since there is no better way for expressing "Only panic in debug mode". + debug_assert!(false); // #3948 + assert_const!(3); + assert_const!(-1); +} diff --git a/src/tools/clippy/tests/ui/assertions_on_constants.stderr b/src/tools/clippy/tests/ui/assertions_on_constants.stderr new file mode 100644 index 0000000000..c66fdf093f --- /dev/null +++ b/src/tools/clippy/tests/ui/assertions_on_constants.stderr @@ -0,0 +1,84 @@ +error: `assert!(true)` will be optimized out by the compiler + --> $DIR/assertions_on_constants.rs:11:5 + | +LL | assert!(true); + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::assertions-on-constants` implied by `-D warnings` + = help: remove it + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(false)` should probably be replaced + --> $DIR/assertions_on_constants.rs:12:5 + | +LL | assert!(false); + | ^^^^^^^^^^^^^^^ + | + = help: use `panic!()` or `unreachable!()` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(true)` will be optimized out by the compiler + --> $DIR/assertions_on_constants.rs:13:5 + | +LL | assert!(true, "true message"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove it + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(false, "false message")` should probably be replaced + --> $DIR/assertions_on_constants.rs:14:5 + | +LL | assert!(false, "false message"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `panic!("false message")` or `unreachable!("false message")` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(false, msg.to_uppercase())` should probably be replaced + --> $DIR/assertions_on_constants.rs:17:5 + | +LL | assert!(false, msg.to_uppercase()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `panic!(msg.to_uppercase())` or `unreachable!(msg.to_uppercase())` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(true)` will be optimized out by the compiler + --> $DIR/assertions_on_constants.rs:20:5 + | +LL | assert!(B); + | ^^^^^^^^^^^ + | + = help: remove it + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(false)` should probably be replaced + --> $DIR/assertions_on_constants.rs:23:5 + | +LL | assert!(C); + | ^^^^^^^^^^^ + | + = help: use `panic!()` or `unreachable!()` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(false, "C message")` should probably be replaced + --> $DIR/assertions_on_constants.rs:24:5 + | +LL | assert!(C, "C message"); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `panic!("C message")` or `unreachable!("C message")` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `debug_assert!(true)` will be optimized out by the compiler + --> $DIR/assertions_on_constants.rs:26:5 + | +LL | debug_assert!(true); + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: remove it + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/assign_ops.fixed b/src/tools/clippy/tests/ui/assign_ops.fixed new file mode 100644 index 0000000000..52b1b3afe1 --- /dev/null +++ b/src/tools/clippy/tests/ui/assign_ops.fixed @@ -0,0 +1,21 @@ +// run-rustfix + +#[allow(dead_code, unused_assignments)] +#[warn(clippy::assign_op_pattern)] +fn main() { + let mut a = 5; + a += 1; + a += 1; + a -= 1; + a *= 99; + a *= 42; + a /= 2; + a %= 5; + a &= 1; + a = 1 - a; + a = 5 / a; + a = 42 % a; + a = 6 << a; + let mut s = String::new(); + s += "bla"; +} diff --git a/src/tools/clippy/tests/ui/assign_ops.rs b/src/tools/clippy/tests/ui/assign_ops.rs new file mode 100644 index 0000000000..527a46b2c2 --- /dev/null +++ b/src/tools/clippy/tests/ui/assign_ops.rs @@ -0,0 +1,21 @@ +// run-rustfix + +#[allow(dead_code, unused_assignments)] +#[warn(clippy::assign_op_pattern)] +fn main() { + let mut a = 5; + a = a + 1; + a = 1 + a; + a = a - 1; + a = a * 99; + a = 42 * a; + a = a / 2; + a = a % 5; + a = a & 1; + a = 1 - a; + a = 5 / a; + a = 42 % a; + a = 6 << a; + let mut s = String::new(); + s = s + "bla"; +} diff --git a/src/tools/clippy/tests/ui/assign_ops.stderr b/src/tools/clippy/tests/ui/assign_ops.stderr new file mode 100644 index 0000000000..3486bd8da4 --- /dev/null +++ b/src/tools/clippy/tests/ui/assign_ops.stderr @@ -0,0 +1,58 @@ +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:7:5 + | +LL | a = a + 1; + | ^^^^^^^^^ help: replace it with: `a += 1` + | + = note: `-D clippy::assign-op-pattern` implied by `-D warnings` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:8:5 + | +LL | a = 1 + a; + | ^^^^^^^^^ help: replace it with: `a += 1` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:9:5 + | +LL | a = a - 1; + | ^^^^^^^^^ help: replace it with: `a -= 1` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:10:5 + | +LL | a = a * 99; + | ^^^^^^^^^^ help: replace it with: `a *= 99` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:11:5 + | +LL | a = 42 * a; + | ^^^^^^^^^^ help: replace it with: `a *= 42` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:12:5 + | +LL | a = a / 2; + | ^^^^^^^^^ help: replace it with: `a /= 2` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:13:5 + | +LL | a = a % 5; + | ^^^^^^^^^ help: replace it with: `a %= 5` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:14:5 + | +LL | a = a & 1; + | ^^^^^^^^^ help: replace it with: `a &= 1` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:20:5 + | +LL | s = s + "bla"; + | ^^^^^^^^^^^^^ help: replace it with: `s += "bla"` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/assign_ops2.rs b/src/tools/clippy/tests/ui/assign_ops2.rs new file mode 100644 index 0000000000..4703a8c777 --- /dev/null +++ b/src/tools/clippy/tests/ui/assign_ops2.rs @@ -0,0 +1,55 @@ +#[allow(unused_assignments)] +#[warn(clippy::misrefactored_assign_op, clippy::assign_op_pattern)] +fn main() { + let mut a = 5; + a += a + 1; + a += 1 + a; + a -= a - 1; + a *= a * 99; + a *= 42 * a; + a /= a / 2; + a %= a % 5; + a &= a & 1; + a *= a * a; + a = a * a * a; + a = a * 42 * a; + a = a * 2 + a; + a -= 1 - a; + a /= 5 / a; + a %= 42 % a; + a <<= 6 << a; +} + +// check that we don't lint on op assign impls, because that's just the way to impl them + +use std::ops::{Mul, MulAssign}; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Wrap(i64); + +impl Mul for Wrap { + type Output = Self; + + fn mul(self, rhs: i64) -> Self { + Wrap(self.0 * rhs) + } +} + +impl MulAssign for Wrap { + fn mul_assign(&mut self, rhs: i64) { + *self = *self * rhs + } +} + +fn cow_add_assign() { + use std::borrow::Cow; + let mut buf = Cow::Owned(String::from("bar")); + let cows = Cow::Borrowed("foo"); + + // this can be linted + buf = buf + cows.clone(); + + // this should not as cow Add is not commutative + buf = cows + buf; + println!("{}", buf); +} diff --git a/src/tools/clippy/tests/ui/assign_ops2.stderr b/src/tools/clippy/tests/ui/assign_ops2.stderr new file mode 100644 index 0000000000..e40668ed33 --- /dev/null +++ b/src/tools/clippy/tests/ui/assign_ops2.stderr @@ -0,0 +1,146 @@ +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:5:5 + | +LL | a += a + 1; + | ^^^^^^^^^^ + | + = note: `-D clippy::misrefactored-assign-op` implied by `-D warnings` +help: did you mean `a = a + 1` or `a = a + a + 1`? Consider replacing it with + | +LL | a += 1; + | ^^^^^^ +help: or + | +LL | a = a + a + 1; + | ^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:6:5 + | +LL | a += 1 + a; + | ^^^^^^^^^^ + | +help: did you mean `a = a + 1` or `a = a + 1 + a`? Consider replacing it with + | +LL | a += 1; + | ^^^^^^ +help: or + | +LL | a = a + 1 + a; + | ^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:7:5 + | +LL | a -= a - 1; + | ^^^^^^^^^^ + | +help: did you mean `a = a - 1` or `a = a - (a - 1)`? Consider replacing it with + | +LL | a -= 1; + | ^^^^^^ +help: or + | +LL | a = a - (a - 1); + | ^^^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:8:5 + | +LL | a *= a * 99; + | ^^^^^^^^^^^ + | +help: did you mean `a = a * 99` or `a = a * a * 99`? Consider replacing it with + | +LL | a *= 99; + | ^^^^^^^ +help: or + | +LL | a = a * a * 99; + | ^^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:9:5 + | +LL | a *= 42 * a; + | ^^^^^^^^^^^ + | +help: did you mean `a = a * 42` or `a = a * 42 * a`? Consider replacing it with + | +LL | a *= 42; + | ^^^^^^^ +help: or + | +LL | a = a * 42 * a; + | ^^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:10:5 + | +LL | a /= a / 2; + | ^^^^^^^^^^ + | +help: did you mean `a = a / 2` or `a = a / (a / 2)`? Consider replacing it with + | +LL | a /= 2; + | ^^^^^^ +help: or + | +LL | a = a / (a / 2); + | ^^^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:11:5 + | +LL | a %= a % 5; + | ^^^^^^^^^^ + | +help: did you mean `a = a % 5` or `a = a % (a % 5)`? Consider replacing it with + | +LL | a %= 5; + | ^^^^^^ +help: or + | +LL | a = a % (a % 5); + | ^^^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:12:5 + | +LL | a &= a & 1; + | ^^^^^^^^^^ + | +help: did you mean `a = a & 1` or `a = a & a & 1`? Consider replacing it with + | +LL | a &= 1; + | ^^^^^^ +help: or + | +LL | a = a & a & 1; + | ^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:13:5 + | +LL | a *= a * a; + | ^^^^^^^^^^ + | +help: did you mean `a = a * a` or `a = a * a * a`? Consider replacing it with + | +LL | a *= a; + | ^^^^^^ +help: or + | +LL | a = a * a * a; + | ^^^^^^^^^^^^^ + +error: manual implementation of an assign operation + --> $DIR/assign_ops2.rs:50:5 + | +LL | buf = buf + cows.clone(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `buf += cows.clone()` + | + = note: `-D clippy::assign-op-pattern` implied by `-D warnings` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/async_yields_async.fixed b/src/tools/clippy/tests/ui/async_yields_async.fixed new file mode 100644 index 0000000000..9b1a7ac3ba --- /dev/null +++ b/src/tools/clippy/tests/ui/async_yields_async.fixed @@ -0,0 +1,68 @@ +// run-rustfix +// edition:2018 + +#![feature(async_closure)] +#![warn(clippy::async_yields_async)] + +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +struct CustomFutureType; + +impl Future for CustomFutureType { + type Output = u8; + + fn poll(self: Pin<&mut Self>, _: &mut Context) -> Poll { + Poll::Ready(3) + } +} + +fn custom_future_type_ctor() -> CustomFutureType { + CustomFutureType +} + +async fn f() -> CustomFutureType { + // Don't warn for functions since you have to explicitly declare their + // return types. + CustomFutureType +} + +#[rustfmt::skip] +fn main() { + let _f = { + 3 + }; + let _g = async { + 3 + }; + let _h = async { + async { + 3 + }.await + }; + let _i = async { + CustomFutureType.await + }; + let _i = async || { + 3 + }; + let _j = async || { + async { + 3 + }.await + }; + let _k = async || { + CustomFutureType.await + }; + let _l = async || CustomFutureType.await; + let _m = async || { + println!("I'm bored"); + // Some more stuff + + // Finally something to await + CustomFutureType.await + }; + let _n = async || custom_future_type_ctor(); + let _o = async || f(); +} diff --git a/src/tools/clippy/tests/ui/async_yields_async.rs b/src/tools/clippy/tests/ui/async_yields_async.rs new file mode 100644 index 0000000000..731c094edb --- /dev/null +++ b/src/tools/clippy/tests/ui/async_yields_async.rs @@ -0,0 +1,68 @@ +// run-rustfix +// edition:2018 + +#![feature(async_closure)] +#![warn(clippy::async_yields_async)] + +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +struct CustomFutureType; + +impl Future for CustomFutureType { + type Output = u8; + + fn poll(self: Pin<&mut Self>, _: &mut Context) -> Poll { + Poll::Ready(3) + } +} + +fn custom_future_type_ctor() -> CustomFutureType { + CustomFutureType +} + +async fn f() -> CustomFutureType { + // Don't warn for functions since you have to explicitly declare their + // return types. + CustomFutureType +} + +#[rustfmt::skip] +fn main() { + let _f = { + 3 + }; + let _g = async { + 3 + }; + let _h = async { + async { + 3 + } + }; + let _i = async { + CustomFutureType + }; + let _i = async || { + 3 + }; + let _j = async || { + async { + 3 + } + }; + let _k = async || { + CustomFutureType + }; + let _l = async || CustomFutureType; + let _m = async || { + println!("I'm bored"); + // Some more stuff + + // Finally something to await + CustomFutureType + }; + let _n = async || custom_future_type_ctor(); + let _o = async || f(); +} diff --git a/src/tools/clippy/tests/ui/async_yields_async.stderr b/src/tools/clippy/tests/ui/async_yields_async.stderr new file mode 100644 index 0000000000..17d0c37510 --- /dev/null +++ b/src/tools/clippy/tests/ui/async_yields_async.stderr @@ -0,0 +1,96 @@ +error: an async construct yields a type which is itself awaitable + --> $DIR/async_yields_async.rs:40:9 + | +LL | let _h = async { + | ____________________- +LL | | async { + | |_________^ +LL | || 3 +LL | || } + | ||_________^ awaitable value not awaited +LL | | }; + | |_____- outer async construct + | + = note: `-D clippy::async-yields-async` implied by `-D warnings` +help: consider awaiting this value + | +LL | async { +LL | 3 +LL | }.await + | + +error: an async construct yields a type which is itself awaitable + --> $DIR/async_yields_async.rs:45:9 + | +LL | let _i = async { + | ____________________- +LL | | CustomFutureType + | | ^^^^^^^^^^^^^^^^ + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `CustomFutureType.await` +LL | | }; + | |_____- outer async construct + +error: an async construct yields a type which is itself awaitable + --> $DIR/async_yields_async.rs:51:9 + | +LL | let _j = async || { + | _______________________- +LL | | async { + | |_________^ +LL | || 3 +LL | || } + | ||_________^ awaitable value not awaited +LL | | }; + | |_____- outer async construct + | +help: consider awaiting this value + | +LL | async { +LL | 3 +LL | }.await + | + +error: an async construct yields a type which is itself awaitable + --> $DIR/async_yields_async.rs:56:9 + | +LL | let _k = async || { + | _______________________- +LL | | CustomFutureType + | | ^^^^^^^^^^^^^^^^ + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `CustomFutureType.await` +LL | | }; + | |_____- outer async construct + +error: an async construct yields a type which is itself awaitable + --> $DIR/async_yields_async.rs:58:23 + | +LL | let _l = async || CustomFutureType; + | ^^^^^^^^^^^^^^^^ + | | + | outer async construct + | awaitable value not awaited + | help: consider awaiting this value: `CustomFutureType.await` + +error: an async construct yields a type which is itself awaitable + --> $DIR/async_yields_async.rs:64:9 + | +LL | let _m = async || { + | _______________________- +LL | | println!("I'm bored"); +LL | | // Some more stuff +LL | | +LL | | // Finally something to await +LL | | CustomFutureType + | | ^^^^^^^^^^^^^^^^ + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `CustomFutureType.await` +LL | | }; + | |_____- outer async construct + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_bool.rs b/src/tools/clippy/tests/ui/atomic_ordering_bool.rs new file mode 100644 index 0000000000..cdbde79b19 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_bool.rs @@ -0,0 +1,25 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicBool, Ordering}; + +fn main() { + let x = AtomicBool::new(true); + + // Allowed load ordering modes + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + + // Disallowed load ordering modes + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + // Allowed store ordering modes + x.store(false, Ordering::Release); + x.store(false, Ordering::SeqCst); + x.store(false, Ordering::Relaxed); + + // Disallowed store ordering modes + x.store(false, Ordering::Acquire); + x.store(false, Ordering::AcqRel); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_bool.stderr b/src/tools/clippy/tests/ui/atomic_ordering_bool.stderr new file mode 100644 index 0000000000..397b893aed --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_bool.stderr @@ -0,0 +1,35 @@ +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_bool.rs:14:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_bool.rs:15:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_bool.rs:23:20 + | +LL | x.store(false, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_bool.rs:24:20 + | +LL | x.store(false, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_exchange.rs b/src/tools/clippy/tests/ui/atomic_ordering_exchange.rs new file mode 100644 index 0000000000..1ddc12f9ab --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_exchange.rs @@ -0,0 +1,45 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicUsize, Ordering}; + +fn main() { + // `compare_exchange` (not weak) testing + let x = AtomicUsize::new(0); + + // Allowed ordering combos + let _ = x.compare_exchange(0, 0, Ordering::Relaxed, Ordering::Relaxed); + let _ = x.compare_exchange(0, 0, Ordering::Acquire, Ordering::Acquire); + let _ = x.compare_exchange(0, 0, Ordering::Acquire, Ordering::Relaxed); + let _ = x.compare_exchange(0, 0, Ordering::Release, Ordering::Relaxed); + let _ = x.compare_exchange(0, 0, Ordering::AcqRel, Ordering::Acquire); + let _ = x.compare_exchange(0, 0, Ordering::AcqRel, Ordering::Relaxed); + let _ = x.compare_exchange(0, 0, Ordering::SeqCst, Ordering::Relaxed); + let _ = x.compare_exchange(0, 0, Ordering::SeqCst, Ordering::Acquire); + let _ = x.compare_exchange(0, 0, Ordering::SeqCst, Ordering::SeqCst); + + // AcqRel is always forbidden as a failure ordering + let _ = x.compare_exchange(0, 0, Ordering::Relaxed, Ordering::AcqRel); + let _ = x.compare_exchange(0, 0, Ordering::Acquire, Ordering::AcqRel); + let _ = x.compare_exchange(0, 0, Ordering::Release, Ordering::AcqRel); + let _ = x.compare_exchange(0, 0, Ordering::AcqRel, Ordering::AcqRel); + let _ = x.compare_exchange(0, 0, Ordering::SeqCst, Ordering::AcqRel); + + // Release is always forbidden as a failure ordering + let _ = x.compare_exchange(0, 0, Ordering::Relaxed, Ordering::Release); + let _ = x.compare_exchange(0, 0, Ordering::Acquire, Ordering::Release); + let _ = x.compare_exchange(0, 0, Ordering::Release, Ordering::Release); + let _ = x.compare_exchange(0, 0, Ordering::AcqRel, Ordering::Release); + let _ = x.compare_exchange(0, 0, Ordering::SeqCst, Ordering::Release); + + // Release success order forbids failure order of Acquire or SeqCst + let _ = x.compare_exchange(0, 0, Ordering::Release, Ordering::Acquire); + let _ = x.compare_exchange(0, 0, Ordering::Release, Ordering::SeqCst); + + // Relaxed success order also forbids failure order of Acquire or SeqCst + let _ = x.compare_exchange(0, 0, Ordering::Relaxed, Ordering::SeqCst); + let _ = x.compare_exchange(0, 0, Ordering::Relaxed, Ordering::Acquire); + + // Acquire/AcqRel forbids failure order of SeqCst + let _ = x.compare_exchange(0, 0, Ordering::Acquire, Ordering::SeqCst); + let _ = x.compare_exchange(0, 0, Ordering::AcqRel, Ordering::SeqCst); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_exchange.stderr b/src/tools/clippy/tests/ui/atomic_ordering_exchange.stderr new file mode 100644 index 0000000000..4b9bfef797 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_exchange.stderr @@ -0,0 +1,131 @@ +error: compare_exchange's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:21:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Relaxed, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:22:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Acquire, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: compare_exchange's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:23:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Release, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:24:56 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::AcqRel, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: compare_exchange's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:25:56 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::SeqCst, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` instead + +error: compare_exchange's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:28:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Relaxed, Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:29:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Acquire, Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: compare_exchange's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:30:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Release, Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:31:56 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::AcqRel, Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: compare_exchange's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:32:56 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::SeqCst, Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` instead + +error: compare_exchange's failure ordering may not be stronger than the success ordering of `Release` + --> $DIR/atomic_ordering_exchange.rs:35:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Release, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange's failure ordering may not be stronger than the success ordering of `Release` + --> $DIR/atomic_ordering_exchange.rs:36:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Release, Ordering::SeqCst); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange's failure ordering may not be stronger than the success ordering of `Relaxed` + --> $DIR/atomic_ordering_exchange.rs:39:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Relaxed, Ordering::SeqCst); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange's failure ordering may not be stronger than the success ordering of `Relaxed` + --> $DIR/atomic_ordering_exchange.rs:40:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Relaxed, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange's failure ordering may not be stronger than the success ordering of `Acquire` + --> $DIR/atomic_ordering_exchange.rs:43:57 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::Acquire, Ordering::SeqCst); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: compare_exchange's failure ordering may not be stronger than the success ordering of `AcqRel` + --> $DIR/atomic_ordering_exchange.rs:44:56 + | +LL | let _ = x.compare_exchange(0, 0, Ordering::AcqRel, Ordering::SeqCst); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_exchange_weak.rs b/src/tools/clippy/tests/ui/atomic_ordering_exchange_weak.rs new file mode 100644 index 0000000000..5906990250 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_exchange_weak.rs @@ -0,0 +1,47 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicPtr, Ordering}; + +fn main() { + let ptr = &mut 5; + let ptr2 = &mut 10; + // `compare_exchange_weak` testing + let x = AtomicPtr::new(ptr); + + // Allowed ordering combos + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Relaxed, Ordering::Relaxed); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Acquire, Ordering::Acquire); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Acquire, Ordering::Relaxed); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Release, Ordering::Relaxed); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::AcqRel, Ordering::Acquire); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::AcqRel, Ordering::Relaxed); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::SeqCst, Ordering::Relaxed); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::SeqCst, Ordering::Acquire); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::SeqCst, Ordering::SeqCst); + + // AcqRel is always forbidden as a failure ordering + let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Relaxed, Ordering::AcqRel); + let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Acquire, Ordering::AcqRel); + let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Release, Ordering::AcqRel); + let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::AcqRel, Ordering::AcqRel); + let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::SeqCst, Ordering::AcqRel); + + // Release is always forbidden as a failure ordering + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Relaxed, Ordering::Release); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Acquire, Ordering::Release); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Release, Ordering::Release); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::AcqRel, Ordering::Release); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::SeqCst, Ordering::Release); + + // Release success order forbids failure order of Acquire or SeqCst + let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Release, Ordering::Acquire); + let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Release, Ordering::SeqCst); + + // Relaxed success order also forbids failure order of Acquire or SeqCst + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Relaxed, Ordering::SeqCst); + let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Relaxed, Ordering::Acquire); + + // Acquire/AcqRel forbids failure order of SeqCst + let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Acquire, Ordering::SeqCst); + let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::AcqRel, Ordering::SeqCst); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_exchange_weak.stderr b/src/tools/clippy/tests/ui/atomic_ordering_exchange_weak.stderr new file mode 100644 index 0000000000..de7026f3ff --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_exchange_weak.stderr @@ -0,0 +1,131 @@ +error: compare_exchange_weak's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:23:67 + | +LL | let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Relaxed, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:24:67 + | +LL | let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Acquire, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:25:67 + | +LL | let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Release, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:26:66 + | +LL | let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::AcqRel, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:27:66 + | +LL | let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::SeqCst, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:30:67 + | +LL | let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Relaxed, Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:31:67 + | +LL | let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Acquire, Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:32:67 + | +LL | let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Release, Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:33:66 + | +LL | let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::AcqRel, Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:34:66 + | +LL | let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::SeqCst, Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be stronger than the success ordering of `Release` + --> $DIR/atomic_ordering_exchange_weak.rs:37:67 + | +LL | let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Release, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be stronger than the success ordering of `Release` + --> $DIR/atomic_ordering_exchange_weak.rs:38:67 + | +LL | let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Release, Ordering::SeqCst); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be stronger than the success ordering of `Relaxed` + --> $DIR/atomic_ordering_exchange_weak.rs:41:67 + | +LL | let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Relaxed, Ordering::SeqCst); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be stronger than the success ordering of `Relaxed` + --> $DIR/atomic_ordering_exchange_weak.rs:42:67 + | +LL | let _ = x.compare_exchange_weak(ptr, ptr2, Ordering::Relaxed, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be stronger than the success ordering of `Acquire` + --> $DIR/atomic_ordering_exchange_weak.rs:45:67 + | +LL | let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::Acquire, Ordering::SeqCst); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: compare_exchange_weak's failure ordering may not be stronger than the success ordering of `AcqRel` + --> $DIR/atomic_ordering_exchange_weak.rs:46:66 + | +LL | let _ = x.compare_exchange_weak(ptr2, ptr, Ordering::AcqRel, Ordering::SeqCst); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_fence.rs b/src/tools/clippy/tests/ui/atomic_ordering_fence.rs new file mode 100644 index 0000000000..5ee5182ca0 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_fence.rs @@ -0,0 +1,20 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{compiler_fence, fence, Ordering}; + +fn main() { + // Allowed fence ordering modes + fence(Ordering::Acquire); + fence(Ordering::Release); + fence(Ordering::AcqRel); + fence(Ordering::SeqCst); + + // Disallowed fence ordering modes + fence(Ordering::Relaxed); + + compiler_fence(Ordering::Acquire); + compiler_fence(Ordering::Release); + compiler_fence(Ordering::AcqRel); + compiler_fence(Ordering::SeqCst); + compiler_fence(Ordering::Relaxed); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_fence.stderr b/src/tools/clippy/tests/ui/atomic_ordering_fence.stderr new file mode 100644 index 0000000000..3ceff27d9a --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_fence.stderr @@ -0,0 +1,19 @@ +error: memory fences cannot have `Relaxed` ordering + --> $DIR/atomic_ordering_fence.rs:13:11 + | +LL | fence(Ordering::Relaxed); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst` + +error: memory fences cannot have `Relaxed` ordering + --> $DIR/atomic_ordering_fence.rs:19:20 + | +LL | compiler_fence(Ordering::Relaxed); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_fetch_update.rs b/src/tools/clippy/tests/ui/atomic_ordering_fetch_update.rs new file mode 100644 index 0000000000..550bdb001e --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_fetch_update.rs @@ -0,0 +1,45 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicIsize, Ordering}; + +fn main() { + // `fetch_update` testing + let x = AtomicIsize::new(0); + + // Allowed ordering combos + let _ = x.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::Acquire, Ordering::Acquire, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::Acquire, Ordering::Relaxed, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::Release, Ordering::Relaxed, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::AcqRel, Ordering::Acquire, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::AcqRel, Ordering::Relaxed, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::SeqCst, Ordering::Relaxed, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::SeqCst, Ordering::Acquire, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |old| Some(old + 1)); + + // AcqRel is always forbidden as a failure ordering + let _ = x.fetch_update(Ordering::Relaxed, Ordering::AcqRel, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::Acquire, Ordering::AcqRel, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::Release, Ordering::AcqRel, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::AcqRel, Ordering::AcqRel, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::SeqCst, Ordering::AcqRel, |old| Some(old + 1)); + + // Release is always forbidden as a failure ordering + let _ = x.fetch_update(Ordering::Relaxed, Ordering::Release, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::Acquire, Ordering::Release, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::Release, Ordering::Release, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::AcqRel, Ordering::Release, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::SeqCst, Ordering::Release, |old| Some(old + 1)); + + // Release success order forbids failure order of Acquire or SeqCst + let _ = x.fetch_update(Ordering::Release, Ordering::Acquire, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::Release, Ordering::SeqCst, |old| Some(old + 1)); + + // Relaxed success order also forbids failure order of Acquire or SeqCst + let _ = x.fetch_update(Ordering::Relaxed, Ordering::SeqCst, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::Relaxed, Ordering::Acquire, |old| Some(old + 1)); + + // Acquire/AcqRel forbids failure order of SeqCst + let _ = x.fetch_update(Ordering::Acquire, Ordering::SeqCst, |old| Some(old + 1)); + let _ = x.fetch_update(Ordering::AcqRel, Ordering::SeqCst, |old| Some(old + 1)); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_fetch_update.stderr b/src/tools/clippy/tests/ui/atomic_ordering_fetch_update.stderr new file mode 100644 index 0000000000..694548ece9 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_fetch_update.stderr @@ -0,0 +1,131 @@ +error: fetch_update's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:21:47 + | +LL | let _ = x.fetch_update(Ordering::Relaxed, Ordering::AcqRel, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering mode `Relaxed` instead + +error: fetch_update's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:22:47 + | +LL | let _ = x.fetch_update(Ordering::Acquire, Ordering::AcqRel, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: fetch_update's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:23:47 + | +LL | let _ = x.fetch_update(Ordering::Release, Ordering::AcqRel, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: fetch_update's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:24:46 + | +LL | let _ = x.fetch_update(Ordering::AcqRel, Ordering::AcqRel, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: fetch_update's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:25:46 + | +LL | let _ = x.fetch_update(Ordering::SeqCst, Ordering::AcqRel, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` instead + +error: fetch_update's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:28:47 + | +LL | let _ = x.fetch_update(Ordering::Relaxed, Ordering::Release, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: fetch_update's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:29:47 + | +LL | let _ = x.fetch_update(Ordering::Acquire, Ordering::Release, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: fetch_update's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:30:47 + | +LL | let _ = x.fetch_update(Ordering::Release, Ordering::Release, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: fetch_update's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:31:46 + | +LL | let _ = x.fetch_update(Ordering::AcqRel, Ordering::Release, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: fetch_update's failure ordering may not be `Release` or `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:32:46 + | +LL | let _ = x.fetch_update(Ordering::SeqCst, Ordering::Release, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` instead + +error: fetch_update's failure ordering may not be stronger than the success ordering of `Release` + --> $DIR/atomic_ordering_fetch_update.rs:35:47 + | +LL | let _ = x.fetch_update(Ordering::Release, Ordering::Acquire, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: fetch_update's failure ordering may not be stronger than the success ordering of `Release` + --> $DIR/atomic_ordering_fetch_update.rs:36:47 + | +LL | let _ = x.fetch_update(Ordering::Release, Ordering::SeqCst, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: fetch_update's failure ordering may not be stronger than the success ordering of `Relaxed` + --> $DIR/atomic_ordering_fetch_update.rs:39:47 + | +LL | let _ = x.fetch_update(Ordering::Relaxed, Ordering::SeqCst, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: fetch_update's failure ordering may not be stronger than the success ordering of `Relaxed` + --> $DIR/atomic_ordering_fetch_update.rs:40:47 + | +LL | let _ = x.fetch_update(Ordering::Relaxed, Ordering::Acquire, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering mode `Relaxed` instead + +error: fetch_update's failure ordering may not be stronger than the success ordering of `Acquire` + --> $DIR/atomic_ordering_fetch_update.rs:43:47 + | +LL | let _ = x.fetch_update(Ordering::Acquire, Ordering::SeqCst, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: fetch_update's failure ordering may not be stronger than the success ordering of `AcqRel` + --> $DIR/atomic_ordering_fetch_update.rs:44:46 + | +LL | let _ = x.fetch_update(Ordering::AcqRel, Ordering::SeqCst, |old| Some(old + 1)); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire` or `Relaxed` instead + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_int.rs b/src/tools/clippy/tests/ui/atomic_ordering_int.rs new file mode 100644 index 0000000000..40a00ba3de --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_int.rs @@ -0,0 +1,86 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, Ordering}; + +fn main() { + // `AtomicI8` test cases + let x = AtomicI8::new(0); + + // Allowed load ordering modes + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + + // Disallowed load ordering modes + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + // Allowed store ordering modes + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + + // Disallowed store ordering modes + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicI16` test cases + let x = AtomicI16::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicI32` test cases + let x = AtomicI32::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicI64` test cases + let x = AtomicI64::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicIsize` test cases + let x = AtomicIsize::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_int.stderr b/src/tools/clippy/tests/ui/atomic_ordering_int.stderr new file mode 100644 index 0000000000..bbaf234d3c --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_int.stderr @@ -0,0 +1,163 @@ +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:15:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:16:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:24:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:25:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:33:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:34:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:39:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:40:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:48:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:49:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:54:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:55:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:63:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:64:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:69:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:70:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:78:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:79:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:84:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:85:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_ptr.rs b/src/tools/clippy/tests/ui/atomic_ordering_ptr.rs new file mode 100644 index 0000000000..ecbb05c7fb --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_ptr.rs @@ -0,0 +1,27 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicPtr, Ordering}; + +fn main() { + let ptr = &mut 5; + let other_ptr = &mut 10; + let x = AtomicPtr::new(ptr); + + // Allowed load ordering modes + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + + // Disallowed load ordering modes + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + // Allowed store ordering modes + x.store(other_ptr, Ordering::Release); + x.store(other_ptr, Ordering::SeqCst); + x.store(other_ptr, Ordering::Relaxed); + + // Disallowed store ordering modes + x.store(other_ptr, Ordering::Acquire); + x.store(other_ptr, Ordering::AcqRel); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_ptr.stderr b/src/tools/clippy/tests/ui/atomic_ordering_ptr.stderr new file mode 100644 index 0000000000..558ae55518 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_ptr.stderr @@ -0,0 +1,35 @@ +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_ptr.rs:16:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_ptr.rs:17:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_ptr.rs:25:24 + | +LL | x.store(other_ptr, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_ptr.rs:26:24 + | +LL | x.store(other_ptr, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_uint.rs b/src/tools/clippy/tests/ui/atomic_ordering_uint.rs new file mode 100644 index 0000000000..a0d5d7c401 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_uint.rs @@ -0,0 +1,86 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, AtomicU8, AtomicUsize, Ordering}; + +fn main() { + // `AtomicU8` test cases + let x = AtomicU8::new(0); + + // Allowed load ordering modes + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + + // Disallowed load ordering modes + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + // Allowed store ordering modes + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + + // Disallowed store ordering modes + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicU16` test cases + let x = AtomicU16::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicU32` test cases + let x = AtomicU32::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicU64` test cases + let x = AtomicU64::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicUsize` test cases + let x = AtomicUsize::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_uint.stderr b/src/tools/clippy/tests/ui/atomic_ordering_uint.stderr new file mode 100644 index 0000000000..5703135bcf --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_uint.stderr @@ -0,0 +1,163 @@ +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:15:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:16:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:24:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:25:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:33:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:34:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:39:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:40:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:48:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:49:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:54:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:55:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:63:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:64:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:69:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:70:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:78:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:79:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:84:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:85:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/attrs.rs b/src/tools/clippy/tests/ui/attrs.rs new file mode 100644 index 0000000000..8df6e19421 --- /dev/null +++ b/src/tools/clippy/tests/ui/attrs.rs @@ -0,0 +1,45 @@ +#![warn(clippy::inline_always, clippy::deprecated_semver)] +#![allow(clippy::assertions_on_constants)] +#![allow(clippy::missing_docs_in_private_items, clippy::panic, clippy::unreachable)] + +#[inline(always)] +fn test_attr_lint() { + assert!(true) +} + +#[inline(always)] +fn false_positive_expr() { + unreachable!() +} + +#[inline(always)] +fn false_positive_stmt() { + unreachable!(); +} + +#[inline(always)] +fn empty_and_false_positive_stmt() { + unreachable!(); +} + +#[deprecated(since = "forever")] +pub const SOME_CONST: u8 = 42; + +#[deprecated(since = "1")] +pub const ANOTHER_CONST: u8 = 23; + +#[deprecated(since = "0.1.1")] +pub const YET_ANOTHER_CONST: u8 = 0; + +fn main() { + test_attr_lint(); + if false { + false_positive_expr() + } + if false { + false_positive_stmt() + } + if false { + empty_and_false_positive_stmt() + } +} diff --git a/src/tools/clippy/tests/ui/attrs.stderr b/src/tools/clippy/tests/ui/attrs.stderr new file mode 100644 index 0000000000..df4e9e20b6 --- /dev/null +++ b/src/tools/clippy/tests/ui/attrs.stderr @@ -0,0 +1,24 @@ +error: you have declared `#[inline(always)]` on `test_attr_lint`. This is usually a bad idea + --> $DIR/attrs.rs:5:1 + | +LL | #[inline(always)] + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::inline-always` implied by `-D warnings` + +error: the since field must contain a semver-compliant version + --> $DIR/attrs.rs:25:14 + | +LL | #[deprecated(since = "forever")] + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::deprecated-semver` implied by `-D warnings` + +error: the since field must contain a semver-compliant version + --> $DIR/attrs.rs:28:14 + | +LL | #[deprecated(since = "1")] + | ^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/author.rs b/src/tools/clippy/tests/ui/author.rs new file mode 100644 index 0000000000..0a1be35689 --- /dev/null +++ b/src/tools/clippy/tests/ui/author.rs @@ -0,0 +1,4 @@ +fn main() { + #[clippy::author] + let x: char = 0x45 as char; +} diff --git a/src/tools/clippy/tests/ui/author.stdout b/src/tools/clippy/tests/ui/author.stdout new file mode 100644 index 0000000000..211d11c7c1 --- /dev/null +++ b/src/tools/clippy/tests/ui/author.stdout @@ -0,0 +1,14 @@ +if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let Some(ref init) = local.init; + if let ExprKind::Cast(ref expr, ref cast_ty) = init.kind; + if let TyKind::Path(ref qp) = cast_ty.kind; + if match_qpath(qp, &["char"]); + if let ExprKind::Lit(ref lit) = expr.kind; + if let LitKind::Int(69, _) = lit.node; + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local.pat.kind; + if name.as_str() == "x"; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/blocks.rs b/src/tools/clippy/tests/ui/author/blocks.rs new file mode 100644 index 0000000000..a8068436b7 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/blocks.rs @@ -0,0 +1,16 @@ +#![feature(stmt_expr_attributes)] +#![allow(redundant_semicolons, clippy::no_effect)] + +#[rustfmt::skip] +fn main() { + #[clippy::author] + { + ;;;; + } +} + +#[clippy::author] +fn foo() { + let x = 42i32; + -x; +} diff --git a/src/tools/clippy/tests/ui/author/blocks.stdout b/src/tools/clippy/tests/ui/author/blocks.stdout new file mode 100644 index 0000000000..c8ef75e48f --- /dev/null +++ b/src/tools/clippy/tests/ui/author/blocks.stdout @@ -0,0 +1,13 @@ +if_chain! { + if let ExprKind::Block(ref block) = expr.kind; + if let Some(trailing_expr) = &block.expr; + if block.stmts.len() == 0; + then { + // report your lint here + } +} +if_chain! { + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/call.rs b/src/tools/clippy/tests/ui/author/call.rs new file mode 100644 index 0000000000..e99c3c41dc --- /dev/null +++ b/src/tools/clippy/tests/ui/author/call.rs @@ -0,0 +1,4 @@ +fn main() { + #[clippy::author] + let _ = ::std::cmp::min(3, 4); +} diff --git a/src/tools/clippy/tests/ui/author/call.stdout b/src/tools/clippy/tests/ui/author/call.stdout new file mode 100644 index 0000000000..4dccf66663 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/call.stdout @@ -0,0 +1,16 @@ +if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let Some(ref init) = local.init; + if let ExprKind::Call(ref func, ref args) = init.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &["{{root}}", "std", "cmp", "min"]); + if args.len() == 2; + if let ExprKind::Lit(ref lit) = args[0].kind; + if let LitKind::Int(3, _) = lit.node; + if let ExprKind::Lit(ref lit1) = args[1].kind; + if let LitKind::Int(4, _) = lit1.node; + if let PatKind::Wild = local.pat.kind; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/for_loop.rs b/src/tools/clippy/tests/ui/author/for_loop.rs new file mode 100644 index 0000000000..b3dec87653 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/for_loop.rs @@ -0,0 +1,8 @@ +#![feature(stmt_expr_attributes)] + +fn main() { + #[clippy::author] + for y in 0..10 { + let z = y; + } +} diff --git a/src/tools/clippy/tests/ui/author/for_loop.stdout b/src/tools/clippy/tests/ui/author/for_loop.stdout new file mode 100644 index 0000000000..3bf7607c62 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/for_loop.stdout @@ -0,0 +1,64 @@ +if_chain! { + if let ExprKind::DropTemps(ref expr) = expr.kind; + if let ExprKind::Match(ref expr1, ref arms, MatchSource::ForLoopDesugar) = expr.kind; + if let ExprKind::Call(ref func, ref args) = expr1.kind; + if let ExprKind::Path(ref path) = func.kind; + if matches!(path, QPath::LangItem(LangItem::IntoIterIntoIter, _)); + if args.len() == 1; + if let ExprKind::Struct(ref path1, ref fields, None) = args[0].kind; + if matches!(path1, QPath::LangItem(LangItem::Range, _)); + if fields.len() == 2; + // unimplemented: field checks + if arms.len() == 1; + if let ExprKind::Loop(ref body, ref label, LoopSource::ForLoop) = arms[0].body.kind; + if let Some(trailing_expr) = &body.expr; + if body.stmts.len() == 4; + if let StmtKind::Local(ref local) = body.stmts[0].kind; + if let PatKind::Binding(BindingAnnotation::Mutable, _, name, None) = local.pat.kind; + if name.as_str() == "__next"; + if let StmtKind::Expr(ref e, _) = body.stmts[1].kind + if let ExprKind::Match(ref expr2, ref arms1, MatchSource::ForLoopDesugar) = e.kind; + if let ExprKind::Call(ref func1, ref args1) = expr2.kind; + if let ExprKind::Path(ref path2) = func1.kind; + if matches!(path2, QPath::LangItem(LangItem::IteratorNext, _)); + if args1.len() == 1; + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, ref inner) = args1[0].kind; + if let ExprKind::Path(ref path3) = inner.kind; + if match_qpath(path3, &["iter"]); + if arms1.len() == 2; + if let ExprKind::Assign(ref target, ref value, ref _span) = arms1[0].body.kind; + if let ExprKind::Path(ref path4) = target.kind; + if match_qpath(path4, &["__next"]); + if let ExprKind::Path(ref path5) = value.kind; + if match_qpath(path5, &["val"]); + if let PatKind::Struct(ref path6, ref fields1, false) = arms1[0].pat.kind; + if matches!(path6, QPath::LangItem(LangItem::OptionSome, _)); + if fields1.len() == 1; + // unimplemented: field checks + if let ExprKind::Break(ref destination, None) = arms1[1].body.kind; + if let PatKind::Struct(ref path7, ref fields2, false) = arms1[1].pat.kind; + if matches!(path7, QPath::LangItem(LangItem::OptionNone, _)); + if fields2.len() == 0; + // unimplemented: field checks + if let StmtKind::Local(ref local1) = body.stmts[2].kind; + if let Some(ref init) = local1.init; + if let ExprKind::Path(ref path8) = init.kind; + if match_qpath(path8, &["__next"]); + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local1.pat.kind; + if name1.as_str() == "y"; + if let StmtKind::Expr(ref e1, _) = body.stmts[3].kind + if let ExprKind::Block(ref block) = e1.kind; + if let Some(trailing_expr1) = &block.expr; + if block.stmts.len() == 1; + if let StmtKind::Local(ref local2) = block.stmts[0].kind; + if let Some(ref init1) = local2.init; + if let ExprKind::Path(ref path9) = init1.kind; + if match_qpath(path9, &["y"]); + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name2, None) = local2.pat.kind; + if name2.as_str() == "z"; + if let PatKind::Binding(BindingAnnotation::Mutable, _, name3, None) = arms[0].pat.kind; + if name3.as_str() == "iter"; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/if.rs b/src/tools/clippy/tests/ui/author/if.rs new file mode 100644 index 0000000000..2e9cb1466d --- /dev/null +++ b/src/tools/clippy/tests/ui/author/if.rs @@ -0,0 +1,10 @@ +#[allow(clippy::all)] + +fn main() { + #[clippy::author] + let _ = if true { + 1 == 1; + } else { + 2 == 2; + }; +} diff --git a/src/tools/clippy/tests/ui/author/if.stdout b/src/tools/clippy/tests/ui/author/if.stdout new file mode 100644 index 0000000000..cac64a3f40 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/if.stdout @@ -0,0 +1,31 @@ +if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let Some(ref init) = local.init; + if let ExprKind::If(ref cond, ref then, Some(ref else_)) = init.kind; + if let ExprKind::Block(ref block) = else_.kind; + if let Some(trailing_expr) = &block.expr; + if block.stmts.len() == 1; + if let StmtKind::Semi(ref e, _) = block.stmts[0].kind + if let ExprKind::Binary(ref op, ref left, ref right) = e.kind; + if BinOpKind::Eq == op.node; + if let ExprKind::Lit(ref lit) = left.kind; + if let LitKind::Int(2, _) = lit.node; + if let ExprKind::Lit(ref lit1) = right.kind; + if let LitKind::Int(2, _) = lit1.node; + if let ExprKind::Lit(ref lit2) = cond.kind; + if let LitKind::Bool(true) = lit2.node; + if let ExprKind::Block(ref block1) = then.kind; + if let Some(trailing_expr1) = &block1.expr; + if block1.stmts.len() == 1; + if let StmtKind::Semi(ref e1, _) = block1.stmts[0].kind + if let ExprKind::Binary(ref op1, ref left1, ref right1) = e1.kind; + if BinOpKind::Eq == op1.node; + if let ExprKind::Lit(ref lit3) = left1.kind; + if let LitKind::Int(1, _) = lit3.node; + if let ExprKind::Lit(ref lit4) = right1.kind; + if let LitKind::Int(1, _) = lit4.node; + if let PatKind::Wild = local.pat.kind; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/issue_3849.rs b/src/tools/clippy/tests/ui/author/issue_3849.rs new file mode 100644 index 0000000000..bae4570e53 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/issue_3849.rs @@ -0,0 +1,14 @@ +#![allow(dead_code)] +#![allow(clippy::zero_ptr)] +#![allow(clippy::transmute_ptr_to_ref)] +#![allow(clippy::transmuting_null)] + +pub const ZPTR: *const usize = 0 as *const _; + +fn main() { + unsafe { + #[clippy::author] + let _: &i32 = std::mem::transmute(ZPTR); + let _: &i32 = std::mem::transmute(0 as *const i32); + } +} diff --git a/src/tools/clippy/tests/ui/author/issue_3849.stdout b/src/tools/clippy/tests/ui/author/issue_3849.stdout new file mode 100644 index 0000000000..65f93f3cdc --- /dev/null +++ b/src/tools/clippy/tests/ui/author/issue_3849.stdout @@ -0,0 +1,14 @@ +if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let Some(ref init) = local.init; + if let ExprKind::Call(ref func, ref args) = init.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &["std", "mem", "transmute"]); + if args.len() == 1; + if let ExprKind::Path(ref path1) = args[0].kind; + if match_qpath(path1, &["ZPTR"]); + if let PatKind::Wild = local.pat.kind; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/matches.rs b/src/tools/clippy/tests/ui/author/matches.rs new file mode 100644 index 0000000000..674e07ec2d --- /dev/null +++ b/src/tools/clippy/tests/ui/author/matches.rs @@ -0,0 +1,13 @@ +#![allow(clippy::let_and_return)] + +fn main() { + #[clippy::author] + let a = match 42 { + 16 => 5, + 17 => { + let x = 3; + x + }, + _ => 1, + }; +} diff --git a/src/tools/clippy/tests/ui/author/matches.stdout b/src/tools/clippy/tests/ui/author/matches.stdout new file mode 100644 index 0000000000..2e8f8227dc --- /dev/null +++ b/src/tools/clippy/tests/ui/author/matches.stdout @@ -0,0 +1,33 @@ +if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let Some(ref init) = local.init; + if let ExprKind::Match(ref expr, ref arms, MatchSource::Normal) = init.kind; + if let ExprKind::Lit(ref lit) = expr.kind; + if let LitKind::Int(42, _) = lit.node; + if arms.len() == 3; + if let ExprKind::Lit(ref lit1) = arms[0].body.kind; + if let LitKind::Int(5, _) = lit1.node; + if let PatKind::Lit(ref lit_expr) = arms[0].pat.kind + if let ExprKind::Lit(ref lit2) = lit_expr.kind; + if let LitKind::Int(16, _) = lit2.node; + if let ExprKind::Block(ref block) = arms[1].body.kind; + if let Some(trailing_expr) = &block.expr; + if block.stmts.len() == 1; + if let StmtKind::Local(ref local1) = block.stmts[0].kind; + if let Some(ref init1) = local1.init; + if let ExprKind::Lit(ref lit3) = init1.kind; + if let LitKind::Int(3, _) = lit3.node; + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local1.pat.kind; + if name.as_str() == "x"; + if let PatKind::Lit(ref lit_expr1) = arms[1].pat.kind + if let ExprKind::Lit(ref lit4) = lit_expr1.kind; + if let LitKind::Int(17, _) = lit4.node; + if let ExprKind::Lit(ref lit5) = arms[2].body.kind; + if let LitKind::Int(1, _) = lit5.node; + if let PatKind::Wild = arms[2].pat.kind; + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local.pat.kind; + if name1.as_str() == "a"; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/auxiliary/doc_unsafe_macros.rs b/src/tools/clippy/tests/ui/auxiliary/doc_unsafe_macros.rs new file mode 100644 index 0000000000..869672d1ed --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/doc_unsafe_macros.rs @@ -0,0 +1,8 @@ +#[macro_export] +macro_rules! undocd_unsafe { + () => { + pub unsafe fn oy_vey() { + unimplemented!(); + } + }; +} diff --git a/src/tools/clippy/tests/ui/auxiliary/implicit_hasher_macros.rs b/src/tools/clippy/tests/ui/auxiliary/implicit_hasher_macros.rs new file mode 100644 index 0000000000..1eb77c5318 --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/implicit_hasher_macros.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! implicit_hasher_fn { + () => { + pub fn f(input: &HashMap) {} + }; +} diff --git a/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs b/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs new file mode 100644 index 0000000000..d4470d3f40 --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs @@ -0,0 +1,108 @@ +#![allow(dead_code)] + +//! Used to test that certain lints don't trigger in imported external macros + +#[macro_export] +macro_rules! foofoo { + () => { + loop {} + }; +} + +#[macro_export] +macro_rules! must_use_unit { + () => { + #[must_use] + fn foo() {} + }; +} + +#[macro_export] +macro_rules! try_err { + () => { + pub fn try_err_fn() -> Result { + let err: i32 = 1; + // To avoid warnings during rustfix + if true { Err(err)? } else { Ok(2) } + } + }; +} + +#[macro_export] +macro_rules! string_add { + () => { + let y = "".to_owned(); + let z = y + "..."; + }; +} + +#[macro_export] +macro_rules! take_external { + ($s:expr) => { + std::mem::replace($s, Default::default()) + }; +} + +#[macro_export] +macro_rules! option_env_unwrap_external { + ($env: expr) => { + option_env!($env).unwrap() + }; + ($env: expr, $message: expr) => { + option_env!($env).expect($message) + }; +} + +#[macro_export] +macro_rules! ref_arg_binding { + () => { + let ref _y = 42; + }; +} + +#[macro_export] +macro_rules! ref_arg_function { + () => { + fn fun_example(ref _x: usize) {} + }; +} + +#[macro_export] +macro_rules! as_conv_with_arg { + (0u32 as u64) => { + () + }; +} + +#[macro_export] +macro_rules! as_conv { + () => { + 0u32 as u64 + }; +} + +#[macro_export] +macro_rules! large_enum_variant { + () => { + enum LargeEnumInMacro { + A(i32), + B([i32; 8000]), + } + }; +} + +#[macro_export] +macro_rules! field_reassign_with_default { + () => { + #[derive(Default)] + struct A { + pub i: i32, + pub j: i64, + } + fn lint() { + let mut a: A = Default::default(); + a.i = 42; + a; + } + }; +} diff --git a/src/tools/clippy/tests/ui/auxiliary/macro_use_helper.rs b/src/tools/clippy/tests/ui/auxiliary/macro_use_helper.rs new file mode 100644 index 0000000000..ecb55d8cb4 --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/macro_use_helper.rs @@ -0,0 +1,60 @@ +extern crate macro_rules; + +// STMT +#[macro_export] +macro_rules! pub_macro { + () => { + let _ = "hello Mr. Vonnegut"; + }; +} + +pub mod inner { + pub use super::*; + + // RE-EXPORT + // this will stick in `inner` module + pub use macro_rules::foofoo; + pub use macro_rules::try_err; + + pub mod nested { + pub use macro_rules::string_add; + } + + // ITEM + #[macro_export] + macro_rules! inner_mod_macro { + () => { + #[allow(dead_code)] + pub struct Tardis; + }; + } +} + +// EXPR +#[macro_export] +macro_rules! function_macro { + () => { + if true { + } else { + } + }; +} + +// TYPE +#[macro_export] +macro_rules! ty_macro { + () => { + Vec + }; +} + +mod extern_exports { + pub(super) mod private_inner { + #[macro_export] + macro_rules! pub_in_private_macro { + ($name:ident) => { + let $name = String::from("secrets and lies"); + }; + } + } +} diff --git a/src/tools/clippy/tests/ui/auxiliary/option_helpers.rs b/src/tools/clippy/tests/ui/auxiliary/option_helpers.rs new file mode 100644 index 0000000000..7dc3f4ebd4 --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/option_helpers.rs @@ -0,0 +1,55 @@ +#![allow(dead_code, unused_variables)] + +/// Utility macro to test linting behavior in `option_methods()` +/// The lints included in `option_methods()` should not lint if the call to map is partially +/// within a macro +#[macro_export] +macro_rules! opt_map { + ($opt:expr, $map:expr) => { + ($opt).map($map) + }; +} + +/// Struct to generate false positive for Iterator-based lints +#[derive(Copy, Clone)] +pub struct IteratorFalsePositives { + pub foo: u32, +} + +impl IteratorFalsePositives { + pub fn filter(self) -> IteratorFalsePositives { + self + } + + pub fn next(self) -> IteratorFalsePositives { + self + } + + pub fn find(self) -> Option { + Some(self.foo) + } + + pub fn position(self) -> Option { + Some(self.foo) + } + + pub fn rposition(self) -> Option { + Some(self.foo) + } + + pub fn nth(self, n: usize) -> Option { + Some(self.foo) + } + + pub fn skip(self, _: usize) -> IteratorFalsePositives { + self + } + + pub fn skip_while(self) -> IteratorFalsePositives { + self + } + + pub fn count(self) -> usize { + self.foo as usize + } +} diff --git a/src/tools/clippy/tests/ui/auxiliary/proc_macro_attr.rs b/src/tools/clippy/tests/ui/auxiliary/proc_macro_attr.rs new file mode 100644 index 0000000000..e370a98df1 --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/proc_macro_attr.rs @@ -0,0 +1,96 @@ +// compile-flags: --emit=link +// no-prefer-dynamic + +#![crate_type = "proc-macro"] +#![feature(repr128, proc_macro_hygiene, proc_macro_quote, box_patterns)] +#![allow(incomplete_features)] +#![allow(clippy::useless_conversion)] + +extern crate proc_macro; +extern crate quote; +extern crate syn; + +use proc_macro::TokenStream; +use quote::{quote, quote_spanned}; +use syn::parse_macro_input; +use syn::spanned::Spanned; +use syn::token::Star; +use syn::{ + parse_quote, FnArg, ImplItem, ItemImpl, ItemTrait, Lifetime, Pat, PatIdent, PatType, Signature, TraitItem, Type, +}; + +#[proc_macro_attribute] +pub fn fake_async_trait(_args: TokenStream, input: TokenStream) -> TokenStream { + let mut item = parse_macro_input!(input as ItemTrait); + for inner in &mut item.items { + if let TraitItem::Method(method) = inner { + let sig = &method.sig; + let block = &mut method.default; + if let Some(block) = block { + let brace = block.brace_token; + + let my_block = quote_spanned!( brace.span => { + // Should not trigger `empty_line_after_outer_attr` + #[crate_type = "lib"] + #sig #block + Vec::new() + }); + *block = parse_quote!(#my_block); + } + } + } + TokenStream::from(quote!(#item)) +} + +#[proc_macro_attribute] +pub fn rename_my_lifetimes(_args: TokenStream, input: TokenStream) -> TokenStream { + fn make_name(count: usize) -> String { + format!("'life{}", count) + } + + fn mut_receiver_of(sig: &mut Signature) -> Option<&mut FnArg> { + let arg = sig.inputs.first_mut()?; + if let FnArg::Typed(PatType { pat, .. }) = arg { + if let Pat::Ident(PatIdent { ident, .. }) = &**pat { + if ident == "self" { + return Some(arg); + } + } + } + None + } + + let mut elided = 0; + let mut item = parse_macro_input!(input as ItemImpl); + + // Look for methods having arbitrary self type taken by &mut ref + for inner in &mut item.items { + if let ImplItem::Method(method) = inner { + if let Some(FnArg::Typed(pat_type)) = mut_receiver_of(&mut method.sig) { + if let box Type::Reference(reference) = &mut pat_type.ty { + // Target only unnamed lifetimes + let name = match &reference.lifetime { + Some(lt) if lt.ident == "_" => make_name(elided), + None => make_name(elided), + _ => continue, + }; + elided += 1; + + // HACK: Syn uses `Span` from the proc_macro2 crate, and does not seem to reexport it. + // In order to avoid adding the dependency, get a default span from a non-existent token. + // A default span is needed to mark the code as coming from expansion. + let span = Star::default().span(); + + // Replace old lifetime with the named one + let lifetime = Lifetime::new(&name, span); + reference.lifetime = Some(parse_quote!(#lifetime)); + + // Add lifetime to the generics of the method + method.sig.generics.params.push(parse_quote!(#lifetime)); + } + } + } + } + + TokenStream::from(quote!(#item)) +} diff --git a/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs b/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs new file mode 100644 index 0000000000..aebeaf3467 --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs @@ -0,0 +1,55 @@ +// compile-flags: --emit=link +// no-prefer-dynamic + +#![crate_type = "proc-macro"] +#![feature(repr128, proc_macro_quote)] +#![allow(incomplete_features)] +#![allow(clippy::field_reassign_with_default)] +#![allow(clippy::eq_op)] + +extern crate proc_macro; + +use proc_macro::{quote, TokenStream}; + +#[proc_macro_derive(DeriveSomething)] +pub fn derive(_: TokenStream) -> TokenStream { + // Shound not trigger `used_underscore_binding` + let _inside_derive = 1; + assert_eq!(_inside_derive, _inside_derive); + + let output = quote! { + // Should not trigger `useless_attribute` + #[allow(dead_code)] + extern crate rustc_middle; + }; + output +} + +#[proc_macro_derive(FieldReassignWithDefault)] +pub fn derive_foo(_input: TokenStream) -> TokenStream { + quote! { + #[derive(Default)] + struct A { + pub i: i32, + pub j: i64, + } + #[automatically_derived] + fn lint() { + let mut a: A = Default::default(); + a.i = 42; + a; + } + } +} + +#[proc_macro_derive(StructAUseSelf)] +pub fn derive_use_self(_input: TokenStream) -> proc_macro::TokenStream { + quote! { + struct A; + impl A { + fn new() -> A { + A + } + } + } +} diff --git a/src/tools/clippy/tests/ui/auxiliary/use_self_macro.rs b/src/tools/clippy/tests/ui/auxiliary/use_self_macro.rs new file mode 100644 index 0000000000..a8a85b4bae --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/use_self_macro.rs @@ -0,0 +1,15 @@ +macro_rules! use_self { + ( + impl $ty:ident { + fn func(&$this:ident) { + [fields($($field:ident)*)] + } + } + ) => ( + impl $ty { + fn func(&$this) { + let $ty { $($field),* } = $this; + } + } + ) +} diff --git a/src/tools/clippy/tests/ui/auxiliary/wildcard_imports_helper.rs b/src/tools/clippy/tests/ui/auxiliary/wildcard_imports_helper.rs new file mode 100644 index 0000000000..d75cdd625f --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/wildcard_imports_helper.rs @@ -0,0 +1,27 @@ +pub use crate::extern_exports::*; + +pub fn extern_foo() {} +pub fn extern_bar() {} + +pub struct ExternA; + +pub mod inner { + pub mod inner_for_self_import { + pub fn inner_extern_foo() {} + pub fn inner_extern_bar() {} + } +} + +mod extern_exports { + pub fn extern_exported() {} + pub struct ExternExportedStruct; + pub enum ExternExportedEnum { + A, + } +} + +pub mod prelude { + pub mod v1 { + pub struct PreludeModAnywhere; + } +} diff --git a/src/tools/clippy/tests/ui/await_holding_lock.rs b/src/tools/clippy/tests/ui/await_holding_lock.rs new file mode 100644 index 0000000000..0458950ede --- /dev/null +++ b/src/tools/clippy/tests/ui/await_holding_lock.rs @@ -0,0 +1,65 @@ +// edition:2018 +#![warn(clippy::await_holding_lock)] + +use std::sync::Mutex; + +async fn bad(x: &Mutex) -> u32 { + let guard = x.lock().unwrap(); + baz().await +} + +async fn good(x: &Mutex) -> u32 { + { + let guard = x.lock().unwrap(); + let y = *guard + 1; + } + baz().await; + let guard = x.lock().unwrap(); + 47 +} + +async fn baz() -> u32 { + 42 +} + +async fn also_bad(x: &Mutex) -> u32 { + let first = baz().await; + + let guard = x.lock().unwrap(); + + let second = baz().await; + + let third = baz().await; + + first + second + third +} + +async fn not_good(x: &Mutex) -> u32 { + let first = baz().await; + + let second = { + let guard = x.lock().unwrap(); + baz().await + }; + + let third = baz().await; + + first + second + third +} + +#[allow(clippy::manual_async_fn)] +fn block_bad(x: &Mutex) -> impl std::future::Future + '_ { + async move { + let guard = x.lock().unwrap(); + baz().await + } +} + +fn main() { + let m = Mutex::new(100); + good(&m); + bad(&m); + also_bad(&m); + not_good(&m); + block_bad(&m); +} diff --git a/src/tools/clippy/tests/ui/await_holding_lock.stderr b/src/tools/clippy/tests/ui/await_holding_lock.stderr new file mode 100644 index 0000000000..a5fcff7e0e --- /dev/null +++ b/src/tools/clippy/tests/ui/await_holding_lock.stderr @@ -0,0 +1,63 @@ +error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await + --> $DIR/await_holding_lock.rs:7:9 + | +LL | let guard = x.lock().unwrap(); + | ^^^^^ + | + = note: `-D clippy::await-holding-lock` implied by `-D warnings` +note: these are all the await points this lock is held through + --> $DIR/await_holding_lock.rs:7:5 + | +LL | / let guard = x.lock().unwrap(); +LL | | baz().await +LL | | } + | |_^ + +error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await + --> $DIR/await_holding_lock.rs:28:9 + | +LL | let guard = x.lock().unwrap(); + | ^^^^^ + | +note: these are all the await points this lock is held through + --> $DIR/await_holding_lock.rs:28:5 + | +LL | / let guard = x.lock().unwrap(); +LL | | +LL | | let second = baz().await; +LL | | +... | +LL | | first + second + third +LL | | } + | |_^ + +error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await + --> $DIR/await_holding_lock.rs:41:13 + | +LL | let guard = x.lock().unwrap(); + | ^^^^^ + | +note: these are all the await points this lock is held through + --> $DIR/await_holding_lock.rs:41:9 + | +LL | / let guard = x.lock().unwrap(); +LL | | baz().await +LL | | }; + | |_____^ + +error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await + --> $DIR/await_holding_lock.rs:53:13 + | +LL | let guard = x.lock().unwrap(); + | ^^^^^ + | +note: these are all the await points this lock is held through + --> $DIR/await_holding_lock.rs:53:9 + | +LL | / let guard = x.lock().unwrap(); +LL | | baz().await +LL | | } + | |_____^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/await_holding_refcell_ref.rs b/src/tools/clippy/tests/ui/await_holding_refcell_ref.rs new file mode 100644 index 0000000000..88841597bb --- /dev/null +++ b/src/tools/clippy/tests/ui/await_holding_refcell_ref.rs @@ -0,0 +1,86 @@ +// edition:2018 +#![warn(clippy::await_holding_refcell_ref)] + +use std::cell::RefCell; + +async fn bad(x: &RefCell) -> u32 { + let b = x.borrow(); + baz().await +} + +async fn bad_mut(x: &RefCell) -> u32 { + let b = x.borrow_mut(); + baz().await +} + +async fn good(x: &RefCell) -> u32 { + { + let b = x.borrow_mut(); + let y = *b + 1; + } + baz().await; + let b = x.borrow_mut(); + 47 +} + +async fn baz() -> u32 { + 42 +} + +async fn also_bad(x: &RefCell) -> u32 { + let first = baz().await; + + let b = x.borrow_mut(); + + let second = baz().await; + + let third = baz().await; + + first + second + third +} + +async fn less_bad(x: &RefCell) -> u32 { + let first = baz().await; + + let b = x.borrow_mut(); + + let second = baz().await; + + drop(b); + + let third = baz().await; + + first + second + third +} + +async fn not_good(x: &RefCell) -> u32 { + let first = baz().await; + + let second = { + let b = x.borrow_mut(); + baz().await + }; + + let third = baz().await; + + first + second + third +} + +#[allow(clippy::manual_async_fn)] +fn block_bad(x: &RefCell) -> impl std::future::Future + '_ { + async move { + let b = x.borrow_mut(); + baz().await + } +} + +fn main() { + let rc = RefCell::new(100); + good(&rc); + bad(&rc); + bad_mut(&rc); + also_bad(&rc); + less_bad(&rc); + not_good(&rc); + block_bad(&rc); +} diff --git a/src/tools/clippy/tests/ui/await_holding_refcell_ref.stderr b/src/tools/clippy/tests/ui/await_holding_refcell_ref.stderr new file mode 100644 index 0000000000..55e41dbca9 --- /dev/null +++ b/src/tools/clippy/tests/ui/await_holding_refcell_ref.stderr @@ -0,0 +1,95 @@ +error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await + --> $DIR/await_holding_refcell_ref.rs:7:9 + | +LL | let b = x.borrow(); + | ^ + | + = note: `-D clippy::await-holding-refcell-ref` implied by `-D warnings` +note: these are all the await points this ref is held through + --> $DIR/await_holding_refcell_ref.rs:7:5 + | +LL | / let b = x.borrow(); +LL | | baz().await +LL | | } + | |_^ + +error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await + --> $DIR/await_holding_refcell_ref.rs:12:9 + | +LL | let b = x.borrow_mut(); + | ^ + | +note: these are all the await points this ref is held through + --> $DIR/await_holding_refcell_ref.rs:12:5 + | +LL | / let b = x.borrow_mut(); +LL | | baz().await +LL | | } + | |_^ + +error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await + --> $DIR/await_holding_refcell_ref.rs:33:9 + | +LL | let b = x.borrow_mut(); + | ^ + | +note: these are all the await points this ref is held through + --> $DIR/await_holding_refcell_ref.rs:33:5 + | +LL | / let b = x.borrow_mut(); +LL | | +LL | | let second = baz().await; +LL | | +... | +LL | | first + second + third +LL | | } + | |_^ + +error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await + --> $DIR/await_holding_refcell_ref.rs:45:9 + | +LL | let b = x.borrow_mut(); + | ^ + | +note: these are all the await points this ref is held through + --> $DIR/await_holding_refcell_ref.rs:45:5 + | +LL | / let b = x.borrow_mut(); +LL | | +LL | | let second = baz().await; +LL | | +... | +LL | | first + second + third +LL | | } + | |_^ + +error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await + --> $DIR/await_holding_refcell_ref.rs:60:13 + | +LL | let b = x.borrow_mut(); + | ^ + | +note: these are all the await points this ref is held through + --> $DIR/await_holding_refcell_ref.rs:60:9 + | +LL | / let b = x.borrow_mut(); +LL | | baz().await +LL | | }; + | |_____^ + +error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await + --> $DIR/await_holding_refcell_ref.rs:72:13 + | +LL | let b = x.borrow_mut(); + | ^ + | +note: these are all the await points this ref is held through + --> $DIR/await_holding_refcell_ref.rs:72:9 + | +LL | / let b = x.borrow_mut(); +LL | | baz().await +LL | | } + | |_____^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/bind_instead_of_map.fixed b/src/tools/clippy/tests/ui/bind_instead_of_map.fixed new file mode 100644 index 0000000000..5815550d7a --- /dev/null +++ b/src/tools/clippy/tests/ui/bind_instead_of_map.fixed @@ -0,0 +1,25 @@ +// run-rustfix +#![deny(clippy::bind_instead_of_map)] + +// need a main anyway, use it get rid of unused warnings too +pub fn main() { + let x = Some(5); + // the easiest cases + let _ = x; + let _ = x.map(|o| o + 1); + // and an easy counter-example + let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); + + // Different type + let x: Result = Ok(1); + let _ = x; +} + +pub fn foo() -> Option { + let x = Some(String::from("hello")); + Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?))) +} + +pub fn example2(x: bool) -> Option<&'static str> { + Some("a").and_then(|s| Some(if x { s } else { return None })) +} diff --git a/src/tools/clippy/tests/ui/bind_instead_of_map.rs b/src/tools/clippy/tests/ui/bind_instead_of_map.rs new file mode 100644 index 0000000000..623b100a4c --- /dev/null +++ b/src/tools/clippy/tests/ui/bind_instead_of_map.rs @@ -0,0 +1,25 @@ +// run-rustfix +#![deny(clippy::bind_instead_of_map)] + +// need a main anyway, use it get rid of unused warnings too +pub fn main() { + let x = Some(5); + // the easiest cases + let _ = x.and_then(Some); + let _ = x.and_then(|o| Some(o + 1)); + // and an easy counter-example + let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); + + // Different type + let x: Result = Ok(1); + let _ = x.and_then(Ok); +} + +pub fn foo() -> Option { + let x = Some(String::from("hello")); + Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?))) +} + +pub fn example2(x: bool) -> Option<&'static str> { + Some("a").and_then(|s| Some(if x { s } else { return None })) +} diff --git a/src/tools/clippy/tests/ui/bind_instead_of_map.stderr b/src/tools/clippy/tests/ui/bind_instead_of_map.stderr new file mode 100644 index 0000000000..24c6b7f9ef --- /dev/null +++ b/src/tools/clippy/tests/ui/bind_instead_of_map.stderr @@ -0,0 +1,26 @@ +error: using `Option.and_then(Some)`, which is a no-op + --> $DIR/bind_instead_of_map.rs:8:13 + | +LL | let _ = x.and_then(Some); + | ^^^^^^^^^^^^^^^^ help: use the expression directly: `x` + | +note: the lint level is defined here + --> $DIR/bind_instead_of_map.rs:2:9 + | +LL | #![deny(clippy::bind_instead_of_map)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/bind_instead_of_map.rs:9:13 + | +LL | let _ = x.and_then(|o| Some(o + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `x.map(|o| o + 1)` + +error: using `Result.and_then(Ok)`, which is a no-op + --> $DIR/bind_instead_of_map.rs:15:13 + | +LL | let _ = x.and_then(Ok); + | ^^^^^^^^^^^^^^ help: use the expression directly: `x` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/bind_instead_of_map_multipart.rs b/src/tools/clippy/tests/ui/bind_instead_of_map_multipart.rs new file mode 100644 index 0000000000..91d9d11e3c --- /dev/null +++ b/src/tools/clippy/tests/ui/bind_instead_of_map_multipart.rs @@ -0,0 +1,61 @@ +#![deny(clippy::bind_instead_of_map)] +#![allow(clippy::blocks_in_if_conditions)] + +pub fn main() { + let _ = Some("42").and_then(|s| if s.len() < 42 { Some(0) } else { Some(s.len()) }); + let _ = Some("42").and_then(|s| if s.len() < 42 { None } else { Some(s.len()) }); + + let _ = Ok::<_, ()>("42").and_then(|s| if s.len() < 42 { Ok(0) } else { Ok(s.len()) }); + let _ = Ok::<_, ()>("42").and_then(|s| if s.len() < 42 { Err(()) } else { Ok(s.len()) }); + + let _ = Err::<(), _>("42").or_else(|s| if s.len() < 42 { Err(s.len() + 20) } else { Err(s.len()) }); + let _ = Err::<(), _>("42").or_else(|s| if s.len() < 42 { Ok(()) } else { Err(s.len()) }); + + hard_example(); + macro_example(); +} + +fn hard_example() { + Some("42").and_then(|s| { + if { + if s == "43" { + return Some(43); + } + s == "42" + } { + return Some(45); + } + match s.len() { + 10 => Some(2), + 20 => { + if foo() { + return { + if foo() { + return Some(20); + } + println!("foo"); + Some(3) + }; + } + Some(20) + }, + 40 => Some(30), + _ => Some(1), + } + }); +} + +fn foo() -> bool { + true +} + +macro_rules! m { + () => { + Some(10) + }; +} + +fn macro_example() { + let _ = Some("").and_then(|s| if s.len() == 20 { m!() } else { Some(20) }); + let _ = Some("").and_then(|s| if s.len() == 20 { Some(m!()) } else { Some(Some(20)) }); +} diff --git a/src/tools/clippy/tests/ui/bind_instead_of_map_multipart.stderr b/src/tools/clippy/tests/ui/bind_instead_of_map_multipart.stderr new file mode 100644 index 0000000000..50ce2f4051 --- /dev/null +++ b/src/tools/clippy/tests/ui/bind_instead_of_map_multipart.stderr @@ -0,0 +1,73 @@ +error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/bind_instead_of_map_multipart.rs:5:13 + | +LL | let _ = Some("42").and_then(|s| if s.len() < 42 { Some(0) } else { Some(s.len()) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/bind_instead_of_map_multipart.rs:1:9 + | +LL | #![deny(clippy::bind_instead_of_map)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try this + | +LL | let _ = Some("42").map(|s| if s.len() < 42 { 0 } else { s.len() }); + | ^^^ ^ ^^^^^^^ + +error: using `Result.and_then(|x| Ok(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/bind_instead_of_map_multipart.rs:8:13 + | +LL | let _ = Ok::<_, ()>("42").and_then(|s| if s.len() < 42 { Ok(0) } else { Ok(s.len()) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try this + | +LL | let _ = Ok::<_, ()>("42").map(|s| if s.len() < 42 { 0 } else { s.len() }); + | ^^^ ^ ^^^^^^^ + +error: using `Result.or_else(|x| Err(y))`, which is more succinctly expressed as `map_err(|x| y)` + --> $DIR/bind_instead_of_map_multipart.rs:11:13 + | +LL | let _ = Err::<(), _>("42").or_else(|s| if s.len() < 42 { Err(s.len() + 20) } else { Err(s.len()) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try this + | +LL | let _ = Err::<(), _>("42").map_err(|s| if s.len() < 42 { s.len() + 20 } else { s.len() }); + | ^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^ + +error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/bind_instead_of_map_multipart.rs:19:5 + | +LL | / Some("42").and_then(|s| { +LL | | if { +LL | | if s == "43" { +LL | | return Some(43); +... | +LL | | } +LL | | }); + | |______^ + | +help: try this + | +LL | Some("42").map(|s| { +LL | if { +LL | if s == "43" { +LL | return 43; +LL | } +LL | s == "42" + ... + +error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/bind_instead_of_map_multipart.rs:60:13 + | +LL | let _ = Some("").and_then(|s| if s.len() == 20 { Some(m!()) } else { Some(Some(20)) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try this + | +LL | let _ = Some("").map(|s| if s.len() == 20 { m!() } else { Some(20) }); + | ^^^ ^^^^ ^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/bit_masks.rs b/src/tools/clippy/tests/ui/bit_masks.rs new file mode 100644 index 0000000000..cfb493fb52 --- /dev/null +++ b/src/tools/clippy/tests/ui/bit_masks.rs @@ -0,0 +1,63 @@ +const THREE_BITS: i64 = 7; +const EVEN_MORE_REDIRECTION: i64 = THREE_BITS; + +#[warn(clippy::bad_bit_mask)] +#[allow( + clippy::ineffective_bit_mask, + clippy::identity_op, + clippy::no_effect, + clippy::unnecessary_operation +)] +fn main() { + let x = 5; + + x & 0 == 0; + x & 1 == 1; //ok, distinguishes bit 0 + x & 1 == 0; //ok, compared with zero + x & 2 == 1; + x | 0 == 0; //ok, equals x == 0 (maybe warn?) + x | 1 == 3; //ok, equals x == 2 || x == 3 + x | 3 == 3; //ok, equals x <= 3 + x | 3 == 2; + + x & 1 > 1; + x & 2 > 1; // ok, distinguishes x & 2 == 2 from x & 2 == 0 + x & 2 < 1; // ok, distinguishes x & 2 == 2 from x & 2 == 0 + x | 1 > 1; // ok (if a bit silly), equals x > 1 + x | 2 > 1; + x | 2 <= 2; // ok (if a bit silly), equals x <= 2 + + x & 192 == 128; // ok, tests for bit 7 and not bit 6 + x & 0xffc0 == 0xfe80; // ok + + // this also now works with constants + x & THREE_BITS == 8; + x | EVEN_MORE_REDIRECTION < 7; + + 0 & x == 0; + 1 | x > 1; + + // and should now also match uncommon usage + 1 < 2 | x; + 2 == 3 | x; + 1 == x & 2; + + x | 1 > 2; // no error, because we allowed ineffective bit masks + ineffective(); +} + +#[warn(clippy::ineffective_bit_mask)] +#[allow(clippy::bad_bit_mask, clippy::no_effect, clippy::unnecessary_operation)] +fn ineffective() { + let x = 5; + + x | 1 > 3; + x | 1 < 4; + x | 1 <= 3; + x | 1 >= 8; + + x | 1 > 2; // not an error (yet), better written as x >= 2 + x | 1 >= 7; // not an error (yet), better written as x >= 6 + x | 3 > 4; // not an error (yet), better written as x >= 4 + x | 4 <= 19; +} diff --git a/src/tools/clippy/tests/ui/bit_masks.stderr b/src/tools/clippy/tests/ui/bit_masks.stderr new file mode 100644 index 0000000000..dc5ad6dfbd --- /dev/null +++ b/src/tools/clippy/tests/ui/bit_masks.stderr @@ -0,0 +1,110 @@ +error: &-masking with zero + --> $DIR/bit_masks.rs:14:5 + | +LL | x & 0 == 0; + | ^^^^^^^^^^ + | + = note: `-D clippy::bad-bit-mask` implied by `-D warnings` + +error: this operation will always return zero. This is likely not the intended outcome + --> $DIR/bit_masks.rs:14:5 + | +LL | x & 0 == 0; + | ^^^^^ + | + = note: `#[deny(clippy::erasing_op)]` on by default + +error: incompatible bit mask: `_ & 2` can never be equal to `1` + --> $DIR/bit_masks.rs:17:5 + | +LL | x & 2 == 1; + | ^^^^^^^^^^ + +error: incompatible bit mask: `_ | 3` can never be equal to `2` + --> $DIR/bit_masks.rs:21:5 + | +LL | x | 3 == 2; + | ^^^^^^^^^^ + +error: incompatible bit mask: `_ & 1` will never be higher than `1` + --> $DIR/bit_masks.rs:23:5 + | +LL | x & 1 > 1; + | ^^^^^^^^^ + +error: incompatible bit mask: `_ | 2` will always be higher than `1` + --> $DIR/bit_masks.rs:27:5 + | +LL | x | 2 > 1; + | ^^^^^^^^^ + +error: incompatible bit mask: `_ & 7` can never be equal to `8` + --> $DIR/bit_masks.rs:34:5 + | +LL | x & THREE_BITS == 8; + | ^^^^^^^^^^^^^^^^^^^ + +error: incompatible bit mask: `_ | 7` will never be lower than `7` + --> $DIR/bit_masks.rs:35:5 + | +LL | x | EVEN_MORE_REDIRECTION < 7; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: &-masking with zero + --> $DIR/bit_masks.rs:37:5 + | +LL | 0 & x == 0; + | ^^^^^^^^^^ + +error: this operation will always return zero. This is likely not the intended outcome + --> $DIR/bit_masks.rs:37:5 + | +LL | 0 & x == 0; + | ^^^^^ + +error: incompatible bit mask: `_ | 2` will always be higher than `1` + --> $DIR/bit_masks.rs:41:5 + | +LL | 1 < 2 | x; + | ^^^^^^^^^ + +error: incompatible bit mask: `_ | 3` can never be equal to `2` + --> $DIR/bit_masks.rs:42:5 + | +LL | 2 == 3 | x; + | ^^^^^^^^^^ + +error: incompatible bit mask: `_ & 2` can never be equal to `1` + --> $DIR/bit_masks.rs:43:5 + | +LL | 1 == x & 2; + | ^^^^^^^^^^ + +error: ineffective bit mask: `x | 1` compared to `3`, is the same as x compared directly + --> $DIR/bit_masks.rs:54:5 + | +LL | x | 1 > 3; + | ^^^^^^^^^ + | + = note: `-D clippy::ineffective-bit-mask` implied by `-D warnings` + +error: ineffective bit mask: `x | 1` compared to `4`, is the same as x compared directly + --> $DIR/bit_masks.rs:55:5 + | +LL | x | 1 < 4; + | ^^^^^^^^^ + +error: ineffective bit mask: `x | 1` compared to `3`, is the same as x compared directly + --> $DIR/bit_masks.rs:56:5 + | +LL | x | 1 <= 3; + | ^^^^^^^^^^ + +error: ineffective bit mask: `x | 1` compared to `8`, is the same as x compared directly + --> $DIR/bit_masks.rs:57:5 + | +LL | x | 1 >= 8; + | ^^^^^^^^^^ + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/blacklisted_name.rs b/src/tools/clippy/tests/ui/blacklisted_name.rs new file mode 100644 index 0000000000..cb15bdd2f1 --- /dev/null +++ b/src/tools/clippy/tests/ui/blacklisted_name.rs @@ -0,0 +1,45 @@ +#![allow( + dead_code, + clippy::similar_names, + clippy::single_match, + clippy::toplevel_ref_arg, + unused_mut, + unused_variables +)] +#![warn(clippy::blacklisted_name)] + +fn test(foo: ()) {} + +fn main() { + let foo = 42; + let baz = 42; + let quux = 42; + // Unlike these others, `bar` is actually considered an acceptable name. + // Among many other legitimate uses, bar commonly refers to a period of time in music. + // See https://github.com/rust-lang/rust-clippy/issues/5225. + let bar = 42; + + let food = 42; + let foodstuffs = 42; + let bazaar = 42; + + match (42, Some(1337), Some(0)) { + (foo, Some(baz), quux @ Some(_)) => (), + _ => (), + } +} + +fn issue_1647(mut foo: u8) { + let mut baz = 0; + if let Some(mut quux) = Some(42) {} +} + +fn issue_1647_ref() { + let ref baz = 0; + if let Some(ref quux) = Some(42) {} +} + +fn issue_1647_ref_mut() { + let ref mut baz = 0; + if let Some(ref mut quux) = Some(42) {} +} diff --git a/src/tools/clippy/tests/ui/blacklisted_name.stderr b/src/tools/clippy/tests/ui/blacklisted_name.stderr new file mode 100644 index 0000000000..70dbdaece8 --- /dev/null +++ b/src/tools/clippy/tests/ui/blacklisted_name.stderr @@ -0,0 +1,88 @@ +error: use of a blacklisted/placeholder name `foo` + --> $DIR/blacklisted_name.rs:11:9 + | +LL | fn test(foo: ()) {} + | ^^^ + | + = note: `-D clippy::blacklisted-name` implied by `-D warnings` + +error: use of a blacklisted/placeholder name `foo` + --> $DIR/blacklisted_name.rs:14:9 + | +LL | let foo = 42; + | ^^^ + +error: use of a blacklisted/placeholder name `baz` + --> $DIR/blacklisted_name.rs:15:9 + | +LL | let baz = 42; + | ^^^ + +error: use of a blacklisted/placeholder name `quux` + --> $DIR/blacklisted_name.rs:16:9 + | +LL | let quux = 42; + | ^^^^ + +error: use of a blacklisted/placeholder name `foo` + --> $DIR/blacklisted_name.rs:27:10 + | +LL | (foo, Some(baz), quux @ Some(_)) => (), + | ^^^ + +error: use of a blacklisted/placeholder name `baz` + --> $DIR/blacklisted_name.rs:27:20 + | +LL | (foo, Some(baz), quux @ Some(_)) => (), + | ^^^ + +error: use of a blacklisted/placeholder name `quux` + --> $DIR/blacklisted_name.rs:27:26 + | +LL | (foo, Some(baz), quux @ Some(_)) => (), + | ^^^^ + +error: use of a blacklisted/placeholder name `foo` + --> $DIR/blacklisted_name.rs:32:19 + | +LL | fn issue_1647(mut foo: u8) { + | ^^^ + +error: use of a blacklisted/placeholder name `baz` + --> $DIR/blacklisted_name.rs:33:13 + | +LL | let mut baz = 0; + | ^^^ + +error: use of a blacklisted/placeholder name `quux` + --> $DIR/blacklisted_name.rs:34:21 + | +LL | if let Some(mut quux) = Some(42) {} + | ^^^^ + +error: use of a blacklisted/placeholder name `baz` + --> $DIR/blacklisted_name.rs:38:13 + | +LL | let ref baz = 0; + | ^^^ + +error: use of a blacklisted/placeholder name `quux` + --> $DIR/blacklisted_name.rs:39:21 + | +LL | if let Some(ref quux) = Some(42) {} + | ^^^^ + +error: use of a blacklisted/placeholder name `baz` + --> $DIR/blacklisted_name.rs:43:17 + | +LL | let ref mut baz = 0; + | ^^^ + +error: use of a blacklisted/placeholder name `quux` + --> $DIR/blacklisted_name.rs:44:25 + | +LL | if let Some(ref mut quux) = Some(42) {} + | ^^^^ + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/blanket_clippy_restriction_lints.rs b/src/tools/clippy/tests/ui/blanket_clippy_restriction_lints.rs new file mode 100644 index 0000000000..d055f17526 --- /dev/null +++ b/src/tools/clippy/tests/ui/blanket_clippy_restriction_lints.rs @@ -0,0 +1,8 @@ +#![warn(clippy::blanket_clippy_restriction_lints)] + +//! Test that the whole restriction group is not enabled +#![warn(clippy::restriction)] +#![deny(clippy::restriction)] +#![forbid(clippy::restriction)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/blanket_clippy_restriction_lints.stderr b/src/tools/clippy/tests/ui/blanket_clippy_restriction_lints.stderr new file mode 100644 index 0000000000..537557f8b0 --- /dev/null +++ b/src/tools/clippy/tests/ui/blanket_clippy_restriction_lints.stderr @@ -0,0 +1,27 @@ +error: restriction lints are not meant to be all enabled + --> $DIR/blanket_clippy_restriction_lints.rs:4:9 + | +LL | #![warn(clippy::restriction)] + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::blanket-clippy-restriction-lints` implied by `-D warnings` + = help: try enabling only the lints you really need + +error: restriction lints are not meant to be all enabled + --> $DIR/blanket_clippy_restriction_lints.rs:5:9 + | +LL | #![deny(clippy::restriction)] + | ^^^^^^^^^^^^^^^^^^^ + | + = help: try enabling only the lints you really need + +error: restriction lints are not meant to be all enabled + --> $DIR/blanket_clippy_restriction_lints.rs:6:11 + | +LL | #![forbid(clippy::restriction)] + | ^^^^^^^^^^^^^^^^^^^ + | + = help: try enabling only the lints you really need + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/blocks_in_if_conditions.fixed b/src/tools/clippy/tests/ui/blocks_in_if_conditions.fixed new file mode 100644 index 0000000000..e6e40a9948 --- /dev/null +++ b/src/tools/clippy/tests/ui/blocks_in_if_conditions.fixed @@ -0,0 +1,65 @@ +// run-rustfix +#![warn(clippy::blocks_in_if_conditions)] +#![allow(unused, clippy::let_and_return)] +#![warn(clippy::nonminimal_bool)] + +macro_rules! blocky { + () => {{ true }}; +} + +macro_rules! blocky_too { + () => {{ + let r = true; + r + }}; +} + +fn macro_if() { + if blocky!() {} + + if blocky_too!() {} +} + +fn condition_has_block() -> i32 { + let res = { + let x = 3; + x == 3 + }; if res { + 6 + } else { + 10 + } +} + +fn condition_has_block_with_single_expression() -> i32 { + if true { 6 } else { 10 } +} + +fn condition_is_normal() -> i32 { + let x = 3; + if x == 3 { 6 } else { 10 } +} + +fn condition_is_unsafe_block() { + let a: i32 = 1; + + // this should not warn because the condition is an unsafe block + if unsafe { 1u32 == std::mem::transmute(a) } { + println!("1u32 == a"); + } +} + +fn block_in_assert() { + let opt = Some(42); + assert!( + opt.as_ref() + .map(|val| { + let mut v = val * 2; + v -= 1; + v * 3 + }) + .is_some() + ); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/blocks_in_if_conditions.rs b/src/tools/clippy/tests/ui/blocks_in_if_conditions.rs new file mode 100644 index 0000000000..69387ff578 --- /dev/null +++ b/src/tools/clippy/tests/ui/blocks_in_if_conditions.rs @@ -0,0 +1,65 @@ +// run-rustfix +#![warn(clippy::blocks_in_if_conditions)] +#![allow(unused, clippy::let_and_return)] +#![warn(clippy::nonminimal_bool)] + +macro_rules! blocky { + () => {{ true }}; +} + +macro_rules! blocky_too { + () => {{ + let r = true; + r + }}; +} + +fn macro_if() { + if blocky!() {} + + if blocky_too!() {} +} + +fn condition_has_block() -> i32 { + if { + let x = 3; + x == 3 + } { + 6 + } else { + 10 + } +} + +fn condition_has_block_with_single_expression() -> i32 { + if { true } { 6 } else { 10 } +} + +fn condition_is_normal() -> i32 { + let x = 3; + if true && x == 3 { 6 } else { 10 } +} + +fn condition_is_unsafe_block() { + let a: i32 = 1; + + // this should not warn because the condition is an unsafe block + if unsafe { 1u32 == std::mem::transmute(a) } { + println!("1u32 == a"); + } +} + +fn block_in_assert() { + let opt = Some(42); + assert!( + opt.as_ref() + .map(|val| { + let mut v = val * 2; + v -= 1; + v * 3 + }) + .is_some() + ); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/blocks_in_if_conditions.stderr b/src/tools/clippy/tests/ui/blocks_in_if_conditions.stderr new file mode 100644 index 0000000000..9328492733 --- /dev/null +++ b/src/tools/clippy/tests/ui/blocks_in_if_conditions.stderr @@ -0,0 +1,34 @@ +error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` + --> $DIR/blocks_in_if_conditions.rs:24:5 + | +LL | / if { +LL | | let x = 3; +LL | | x == 3 +LL | | } { + | |_____^ + | + = note: `-D clippy::blocks-in-if-conditions` implied by `-D warnings` +help: try + | +LL | let res = { +LL | let x = 3; +LL | x == 3 +LL | }; if res { + | + +error: omit braces around single expression condition + --> $DIR/blocks_in_if_conditions.rs:35:8 + | +LL | if { true } { 6 } else { 10 } + | ^^^^^^^^ help: try: `true` + +error: this boolean expression can be simplified + --> $DIR/blocks_in_if_conditions.rs:40:8 + | +LL | if true && x == 3 { 6 } else { 10 } + | ^^^^^^^^^^^^^^ help: try: `x == 3` + | + = note: `-D clippy::nonminimal-bool` implied by `-D warnings` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/blocks_in_if_conditions_closure.rs b/src/tools/clippy/tests/ui/blocks_in_if_conditions_closure.rs new file mode 100644 index 0000000000..2856943b9b --- /dev/null +++ b/src/tools/clippy/tests/ui/blocks_in_if_conditions_closure.rs @@ -0,0 +1,56 @@ +#![warn(clippy::blocks_in_if_conditions)] +#![allow(unused, clippy::let_and_return)] + +fn predicate bool, T>(pfn: F, val: T) -> bool { + pfn(val) +} + +fn pred_test() { + let v = 3; + let sky = "blue"; + // This is a sneaky case, where the block isn't directly in the condition, + // but is actually inside a closure that the condition is using. + // The same principle applies -- add some extra expressions to make sure + // linter isn't confused by them. + if v == 3 + && sky == "blue" + && predicate( + |x| { + let target = 3; + x == target + }, + v, + ) + {} + + if predicate( + |x| { + let target = 3; + x == target + }, + v, + ) {} +} + +fn closure_without_block() { + if predicate(|x| x == 3, 6) {} +} + +fn macro_in_closure() { + let option = Some(true); + + if option.unwrap_or_else(|| unimplemented!()) { + unimplemented!() + } +} + +#[rustfmt::skip] +fn main() { + let mut range = 0..10; + range.all(|i| {i < 10} ); + + let v = vec![1, 2, 3]; + if v.into_iter().any(|x| {x == 4}) { + println!("contains 4!"); + } +} diff --git a/src/tools/clippy/tests/ui/blocks_in_if_conditions_closure.stderr b/src/tools/clippy/tests/ui/blocks_in_if_conditions_closure.stderr new file mode 100644 index 0000000000..941d604dd5 --- /dev/null +++ b/src/tools/clippy/tests/ui/blocks_in_if_conditions_closure.stderr @@ -0,0 +1,24 @@ +error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` + --> $DIR/blocks_in_if_conditions_closure.rs:18:17 + | +LL | |x| { + | _________________^ +LL | | let target = 3; +LL | | x == target +LL | | }, + | |_____________^ + | + = note: `-D clippy::blocks-in-if-conditions` implied by `-D warnings` + +error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` + --> $DIR/blocks_in_if_conditions_closure.rs:27:13 + | +LL | |x| { + | _____________^ +LL | | let target = 3; +LL | | x == target +LL | | }, + | |_________^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/bool_comparison.fixed b/src/tools/clippy/tests/ui/bool_comparison.fixed new file mode 100644 index 0000000000..5a012ff4d2 --- /dev/null +++ b/src/tools/clippy/tests/ui/bool_comparison.fixed @@ -0,0 +1,167 @@ +// run-rustfix + +#![warn(clippy::bool_comparison)] + +fn main() { + let x = true; + if x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + if x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + if x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + if x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + if x { + "yes" + } else { + "no" + }; + if x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + let y = true; + if !x & y { + "yes" + } else { + "no" + }; + if x & !y { + "yes" + } else { + "no" + }; +} + +#[allow(dead_code)] +fn issue3703() { + struct Foo; + impl PartialEq for Foo { + fn eq(&self, _: &bool) -> bool { + true + } + } + impl PartialEq for bool { + fn eq(&self, _: &Foo) -> bool { + true + } + } + impl PartialOrd for Foo { + fn partial_cmp(&self, _: &bool) -> Option { + None + } + } + impl PartialOrd for bool { + fn partial_cmp(&self, _: &Foo) -> Option { + None + } + } + + if Foo == true {} + if true == Foo {} + if Foo != true {} + if true != Foo {} + if Foo == false {} + if false == Foo {} + if Foo != false {} + if false != Foo {} + if Foo < false {} + if false < Foo {} +} + +#[allow(dead_code)] +fn issue4983() { + let a = true; + let b = false; + + if a != b {}; + if a != b {}; + if a == b {}; + if !a == !b {}; + + if b != a {}; + if b != a {}; + if b == a {}; + if !b == !a {}; +} + +macro_rules! m { + ($func:ident) => { + $func() + }; +} + +fn func() -> bool { + true +} + +#[allow(dead_code)] +fn issue3973() { + // ok, don't lint on `cfg` invocation + if false == cfg!(feature = "debugging") {} + if cfg!(feature = "debugging") == false {} + if true == cfg!(feature = "debugging") {} + if cfg!(feature = "debugging") == true {} + + // lint, could be simplified + if !m!(func) {} + if !m!(func) {} + if m!(func) {} + if m!(func) {} + + // no lint with a variable + let is_debug = false; + if is_debug == cfg!(feature = "debugging") {} + if cfg!(feature = "debugging") == is_debug {} + if is_debug == m!(func) {} + if m!(func) == is_debug {} + let is_debug = true; + if is_debug == cfg!(feature = "debugging") {} + if cfg!(feature = "debugging") == is_debug {} + if is_debug == m!(func) {} + if m!(func) == is_debug {} +} diff --git a/src/tools/clippy/tests/ui/bool_comparison.rs b/src/tools/clippy/tests/ui/bool_comparison.rs new file mode 100644 index 0000000000..c534bc25c2 --- /dev/null +++ b/src/tools/clippy/tests/ui/bool_comparison.rs @@ -0,0 +1,167 @@ +// run-rustfix + +#![warn(clippy::bool_comparison)] + +fn main() { + let x = true; + if x == true { + "yes" + } else { + "no" + }; + if x == false { + "yes" + } else { + "no" + }; + if true == x { + "yes" + } else { + "no" + }; + if false == x { + "yes" + } else { + "no" + }; + if x != true { + "yes" + } else { + "no" + }; + if x != false { + "yes" + } else { + "no" + }; + if true != x { + "yes" + } else { + "no" + }; + if false != x { + "yes" + } else { + "no" + }; + if x < true { + "yes" + } else { + "no" + }; + if false < x { + "yes" + } else { + "no" + }; + if x > false { + "yes" + } else { + "no" + }; + if true > x { + "yes" + } else { + "no" + }; + let y = true; + if x < y { + "yes" + } else { + "no" + }; + if x > y { + "yes" + } else { + "no" + }; +} + +#[allow(dead_code)] +fn issue3703() { + struct Foo; + impl PartialEq for Foo { + fn eq(&self, _: &bool) -> bool { + true + } + } + impl PartialEq for bool { + fn eq(&self, _: &Foo) -> bool { + true + } + } + impl PartialOrd for Foo { + fn partial_cmp(&self, _: &bool) -> Option { + None + } + } + impl PartialOrd for bool { + fn partial_cmp(&self, _: &Foo) -> Option { + None + } + } + + if Foo == true {} + if true == Foo {} + if Foo != true {} + if true != Foo {} + if Foo == false {} + if false == Foo {} + if Foo != false {} + if false != Foo {} + if Foo < false {} + if false < Foo {} +} + +#[allow(dead_code)] +fn issue4983() { + let a = true; + let b = false; + + if a == !b {}; + if !a == b {}; + if a == b {}; + if !a == !b {}; + + if b == !a {}; + if !b == a {}; + if b == a {}; + if !b == !a {}; +} + +macro_rules! m { + ($func:ident) => { + $func() + }; +} + +fn func() -> bool { + true +} + +#[allow(dead_code)] +fn issue3973() { + // ok, don't lint on `cfg` invocation + if false == cfg!(feature = "debugging") {} + if cfg!(feature = "debugging") == false {} + if true == cfg!(feature = "debugging") {} + if cfg!(feature = "debugging") == true {} + + // lint, could be simplified + if false == m!(func) {} + if m!(func) == false {} + if true == m!(func) {} + if m!(func) == true {} + + // no lint with a variable + let is_debug = false; + if is_debug == cfg!(feature = "debugging") {} + if cfg!(feature = "debugging") == is_debug {} + if is_debug == m!(func) {} + if m!(func) == is_debug {} + let is_debug = true; + if is_debug == cfg!(feature = "debugging") {} + if cfg!(feature = "debugging") == is_debug {} + if is_debug == m!(func) {} + if m!(func) == is_debug {} +} diff --git a/src/tools/clippy/tests/ui/bool_comparison.stderr b/src/tools/clippy/tests/ui/bool_comparison.stderr new file mode 100644 index 0000000000..31522d4a52 --- /dev/null +++ b/src/tools/clippy/tests/ui/bool_comparison.stderr @@ -0,0 +1,136 @@ +error: equality checks against true are unnecessary + --> $DIR/bool_comparison.rs:7:8 + | +LL | if x == true { + | ^^^^^^^^^ help: try simplifying it as shown: `x` + | + = note: `-D clippy::bool-comparison` implied by `-D warnings` + +error: equality checks against false can be replaced by a negation + --> $DIR/bool_comparison.rs:12:8 + | +LL | if x == false { + | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: equality checks against true are unnecessary + --> $DIR/bool_comparison.rs:17:8 + | +LL | if true == x { + | ^^^^^^^^^ help: try simplifying it as shown: `x` + +error: equality checks against false can be replaced by a negation + --> $DIR/bool_comparison.rs:22:8 + | +LL | if false == x { + | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: inequality checks against true can be replaced by a negation + --> $DIR/bool_comparison.rs:27:8 + | +LL | if x != true { + | ^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: inequality checks against false are unnecessary + --> $DIR/bool_comparison.rs:32:8 + | +LL | if x != false { + | ^^^^^^^^^^ help: try simplifying it as shown: `x` + +error: inequality checks against true can be replaced by a negation + --> $DIR/bool_comparison.rs:37:8 + | +LL | if true != x { + | ^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: inequality checks against false are unnecessary + --> $DIR/bool_comparison.rs:42:8 + | +LL | if false != x { + | ^^^^^^^^^^ help: try simplifying it as shown: `x` + +error: less than comparison against true can be replaced by a negation + --> $DIR/bool_comparison.rs:47:8 + | +LL | if x < true { + | ^^^^^^^^ help: try simplifying it as shown: `!x` + +error: greater than checks against false are unnecessary + --> $DIR/bool_comparison.rs:52:8 + | +LL | if false < x { + | ^^^^^^^^^ help: try simplifying it as shown: `x` + +error: greater than checks against false are unnecessary + --> $DIR/bool_comparison.rs:57:8 + | +LL | if x > false { + | ^^^^^^^^^ help: try simplifying it as shown: `x` + +error: less than comparison against true can be replaced by a negation + --> $DIR/bool_comparison.rs:62:8 + | +LL | if true > x { + | ^^^^^^^^ help: try simplifying it as shown: `!x` + +error: order comparisons between booleans can be simplified + --> $DIR/bool_comparison.rs:68:8 + | +LL | if x < y { + | ^^^^^ help: try simplifying it as shown: `!x & y` + +error: order comparisons between booleans can be simplified + --> $DIR/bool_comparison.rs:73:8 + | +LL | if x > y { + | ^^^^^ help: try simplifying it as shown: `x & !y` + +error: this comparison might be written more concisely + --> $DIR/bool_comparison.rs:121:8 + | +LL | if a == !b {}; + | ^^^^^^^ help: try simplifying it as shown: `a != b` + +error: this comparison might be written more concisely + --> $DIR/bool_comparison.rs:122:8 + | +LL | if !a == b {}; + | ^^^^^^^ help: try simplifying it as shown: `a != b` + +error: this comparison might be written more concisely + --> $DIR/bool_comparison.rs:126:8 + | +LL | if b == !a {}; + | ^^^^^^^ help: try simplifying it as shown: `b != a` + +error: this comparison might be written more concisely + --> $DIR/bool_comparison.rs:127:8 + | +LL | if !b == a {}; + | ^^^^^^^ help: try simplifying it as shown: `b != a` + +error: equality checks against false can be replaced by a negation + --> $DIR/bool_comparison.rs:151:8 + | +LL | if false == m!(func) {} + | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` + +error: equality checks against false can be replaced by a negation + --> $DIR/bool_comparison.rs:152:8 + | +LL | if m!(func) == false {} + | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` + +error: equality checks against true are unnecessary + --> $DIR/bool_comparison.rs:153:8 + | +LL | if true == m!(func) {} + | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)` + +error: equality checks against true are unnecessary + --> $DIR/bool_comparison.rs:154:8 + | +LL | if m!(func) == true {} + | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)` + +error: aborting due to 22 previous errors + diff --git a/src/tools/clippy/tests/ui/borrow_box.rs b/src/tools/clippy/tests/ui/borrow_box.rs new file mode 100644 index 0000000000..b606f773cf --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_box.rs @@ -0,0 +1,115 @@ +#![deny(clippy::borrowed_box)] +#![allow(clippy::blacklisted_name)] +#![allow(unused_variables)] +#![allow(dead_code)] + +use std::fmt::Display; + +pub fn test1(foo: &mut Box) { + // Although this function could be changed to "&mut bool", + // avoiding the Box, mutable references to boxes are not + // flagged by this lint. + // + // This omission is intentional: By passing a mutable Box, + // the memory location of the pointed-to object could be + // modified. By passing a mutable reference, the contents + // could change, but not the location. + println!("{:?}", foo) +} + +pub fn test2() { + let foo: &Box; +} + +struct Test3<'a> { + foo: &'a Box, +} + +trait Test4 { + fn test4(a: &Box); +} + +impl<'a> Test4 for Test3<'a> { + fn test4(a: &Box) { + unimplemented!(); + } +} + +use std::any::Any; + +pub fn test5(foo: &mut Box) { + println!("{:?}", foo) +} + +pub fn test6() { + let foo: &Box; +} + +struct Test7<'a> { + foo: &'a Box, +} + +trait Test8 { + fn test8(a: &Box); +} + +impl<'a> Test8 for Test7<'a> { + fn test8(a: &Box) { + unimplemented!(); + } +} + +pub fn test9(foo: &mut Box) { + let _ = foo; +} + +pub fn test10() { + let foo: &Box; +} + +struct Test11<'a> { + foo: &'a Box, +} + +trait Test12 { + fn test4(a: &Box); +} + +impl<'a> Test12 for Test11<'a> { + fn test4(a: &Box) { + unimplemented!(); + } +} + +pub fn test13(boxed_slice: &mut Box<[i32]>) { + // Unconditionally replaces the box pointer. + // + // This cannot be accomplished if "&mut [i32]" is passed, + // and provides a test case where passing a reference to + // a Box is valid. + let mut data = vec![12]; + *boxed_slice = data.into_boxed_slice(); +} + +// The suggestion should include proper parentheses to avoid a syntax error. +pub fn test14(_display: &Box) {} +pub fn test15(_display: &Box) {} +pub fn test16<'a>(_display: &'a Box) {} + +pub fn test17(_display: &Box) {} +pub fn test18(_display: &Box) {} +pub fn test19<'a>(_display: &'a Box) {} + +// This exists only to check what happens when parentheses are already present. +// Even though the current implementation doesn't put extra parentheses, +// it's fine that unnecessary parentheses appear in the future for some reason. +pub fn test20(_display: &Box<(dyn Display + Send)>) {} + +fn main() { + test1(&mut Box::new(false)); + test2(); + test5(&mut (Box::new(false) as Box)); + test6(); + test9(&mut (Box::new(false) as Box)); + test10(); +} diff --git a/src/tools/clippy/tests/ui/borrow_box.stderr b/src/tools/clippy/tests/ui/borrow_box.stderr new file mode 100644 index 0000000000..3eac32815b --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_box.stderr @@ -0,0 +1,68 @@ +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:21:14 + | +LL | let foo: &Box; + | ^^^^^^^^^^ help: try: `&bool` + | +note: the lint level is defined here + --> $DIR/borrow_box.rs:1:9 + | +LL | #![deny(clippy::borrowed_box)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:25:10 + | +LL | foo: &'a Box, + | ^^^^^^^^^^^^^ help: try: `&'a bool` + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:29:17 + | +LL | fn test4(a: &Box); + | ^^^^^^^^^^ help: try: `&bool` + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:95:25 + | +LL | pub fn test14(_display: &Box) {} + | ^^^^^^^^^^^^^^^^^ help: try: `&dyn Display` + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:96:25 + | +LL | pub fn test15(_display: &Box) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&(dyn Display + Send)` + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:97:29 + | +LL | pub fn test16<'a>(_display: &'a Box) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&'a (dyn Display + 'a)` + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:99:25 + | +LL | pub fn test17(_display: &Box) {} + | ^^^^^^^^^^^^^^^^^^ help: try: `&impl Display` + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:100:25 + | +LL | pub fn test18(_display: &Box) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&(impl Display + Send)` + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:101:29 + | +LL | pub fn test19<'a>(_display: &'a Box) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&'a (impl Display + 'a)` + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:106:25 + | +LL | pub fn test20(_display: &Box<(dyn Display + Send)>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&(dyn Display + Send)` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs new file mode 100644 index 0000000000..2289f7875f --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs @@ -0,0 +1,16 @@ +// this file solely exists to test constants defined in foreign crates. +// As the most common case is the `http` crate, it replicates `http::HeadewrName`'s structure. + +#![allow(clippy::declare_interior_mutable_const)] + +use std::sync::atomic::AtomicUsize; + +enum Private { + ToBeUnfrozen(T), + Frozen(usize), +} + +pub struct Wrapper(Private); + +pub const WRAPPED_PRIVATE_UNFROZEN_VARIANT: Wrapper = Wrapper(Private::ToBeUnfrozen(AtomicUsize::new(6))); +pub const WRAPPED_PRIVATE_FROZEN_VARIANT: Wrapper = Wrapper(Private::Frozen(7)); diff --git a/src/tools/clippy/tests/ui/borrow_interior_mutable_const/enums.rs b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/enums.rs new file mode 100644 index 0000000000..5027db4456 --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/enums.rs @@ -0,0 +1,101 @@ +// aux-build:helper.rs + +#![warn(clippy::borrow_interior_mutable_const)] +#![allow(clippy::declare_interior_mutable_const)] + +// this file (mostly) replicates its `declare` counterpart. Please see it for more discussions. + +extern crate helper; + +use std::cell::Cell; +use std::sync::atomic::AtomicUsize; + +enum OptionalCell { + Unfrozen(Cell), + Frozen, +} + +const UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(true)); +const FROZEN_VARIANT: OptionalCell = OptionalCell::Frozen; + +fn borrow_optional_cell() { + let _ = &UNFROZEN_VARIANT; //~ ERROR interior mutability + let _ = &FROZEN_VARIANT; +} + +trait AssocConsts { + const TO_BE_UNFROZEN_VARIANT: OptionalCell; + const TO_BE_FROZEN_VARIANT: OptionalCell; + + const DEFAULTED_ON_UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(false)); + const DEFAULTED_ON_FROZEN_VARIANT: OptionalCell = OptionalCell::Frozen; + + fn function() { + // This is the "suboptimal behavior" mentioned in `is_value_unfrozen` + // caused by a similar reason to unfrozen types without any default values + // get linted even if it has frozen variants'. + let _ = &Self::TO_BE_FROZEN_VARIANT; //~ ERROR interior mutable + + // The lint ignores default values because an impl of this trait can set + // an unfrozen variant to `DEFAULTED_ON_FROZEN_VARIANT` and use the default impl for `function`. + let _ = &Self::DEFAULTED_ON_FROZEN_VARIANT; //~ ERROR interior mutable + } +} + +impl AssocConsts for u64 { + const TO_BE_UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(false)); + const TO_BE_FROZEN_VARIANT: OptionalCell = OptionalCell::Frozen; + + fn function() { + let _ = &::TO_BE_UNFROZEN_VARIANT; //~ ERROR interior mutable + let _ = &::TO_BE_FROZEN_VARIANT; + let _ = &Self::DEFAULTED_ON_UNFROZEN_VARIANT; //~ ERROR interior mutable + let _ = &Self::DEFAULTED_ON_FROZEN_VARIANT; + } +} + +trait AssocTypes { + type ToBeUnfrozen; + + const TO_BE_UNFROZEN_VARIANT: Option; + const TO_BE_FROZEN_VARIANT: Option; + + // there's no need to test here because it's the exactly same as `trait::AssocTypes` + fn function(); +} + +impl AssocTypes for u64 { + type ToBeUnfrozen = AtomicUsize; + + const TO_BE_UNFROZEN_VARIANT: Option = Some(Self::ToBeUnfrozen::new(4)); //~ ERROR interior mutable + const TO_BE_FROZEN_VARIANT: Option = None; + + fn function() { + let _ = &::TO_BE_UNFROZEN_VARIANT; //~ ERROR interior mutable + let _ = &::TO_BE_FROZEN_VARIANT; + } +} + +enum BothOfCellAndGeneric { + Unfrozen(Cell<*const T>), + Generic(*const T), + Frozen(usize), +} + +impl BothOfCellAndGeneric { + const UNFROZEN_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Unfrozen(Cell::new(std::ptr::null())); //~ ERROR interior mutable + const GENERIC_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Generic(std::ptr::null()); //~ ERROR interior mutable + const FROZEN_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Frozen(5); + + fn function() { + let _ = &Self::UNFROZEN_VARIANT; //~ ERROR interior mutability + let _ = &Self::GENERIC_VARIANT; //~ ERROR interior mutability + let _ = &Self::FROZEN_VARIANT; + } +} + +fn main() { + // constants defined in foreign crates + let _ = &helper::WRAPPED_PRIVATE_UNFROZEN_VARIANT; //~ ERROR interior mutability + let _ = &helper::WRAPPED_PRIVATE_FROZEN_VARIANT; +} diff --git a/src/tools/clippy/tests/ui/borrow_interior_mutable_const/enums.stderr b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/enums.stderr new file mode 100644 index 0000000000..654a1ee7df --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/enums.stderr @@ -0,0 +1,75 @@ +error: a `const` item with interior mutability should not be borrowed + --> $DIR/enums.rs:22:14 + | +LL | let _ = &UNFROZEN_VARIANT; //~ ERROR interior mutability + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::borrow-interior-mutable-const` implied by `-D warnings` + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/enums.rs:37:18 + | +LL | let _ = &Self::TO_BE_FROZEN_VARIANT; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/enums.rs:41:18 + | +LL | let _ = &Self::DEFAULTED_ON_FROZEN_VARIANT; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/enums.rs:50:18 + | +LL | let _ = &::TO_BE_UNFROZEN_VARIANT; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/enums.rs:52:18 + | +LL | let _ = &Self::DEFAULTED_ON_UNFROZEN_VARIANT; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/enums.rs:74:18 + | +LL | let _ = &::TO_BE_UNFROZEN_VARIANT; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/enums.rs:91:18 + | +LL | let _ = &Self::UNFROZEN_VARIANT; //~ ERROR interior mutability + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/enums.rs:92:18 + | +LL | let _ = &Self::GENERIC_VARIANT; //~ ERROR interior mutability + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/enums.rs:99:14 + | +LL | let _ = &helper::WRAPPED_PRIVATE_UNFROZEN_VARIANT; //~ ERROR interior mutability + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/borrow_interior_mutable_const/others.rs b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/others.rs new file mode 100644 index 0000000000..ea25729d11 --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/others.rs @@ -0,0 +1,104 @@ +#![warn(clippy::borrow_interior_mutable_const)] +#![allow(clippy::declare_interior_mutable_const, clippy::ref_in_deref)] +#![allow(const_item_mutation)] + +use std::borrow::Cow; +use std::cell::{Cell, UnsafeCell}; +use std::fmt::Display; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Once; + +const ATOMIC: AtomicUsize = AtomicUsize::new(5); +const CELL: Cell = Cell::new(6); +const ATOMIC_TUPLE: ([AtomicUsize; 1], Vec, u8) = ([ATOMIC], Vec::new(), 7); +const INTEGER: u8 = 8; +const STRING: String = String::new(); +const STR: &str = "012345"; +const COW: Cow = Cow::Borrowed("abcdef"); +const NO_ANN: &dyn Display = &70; +static STATIC_TUPLE: (AtomicUsize, String) = (ATOMIC, STRING); +const ONCE_INIT: Once = Once::new(); + +// This is just a pointer that can be safely dereferenced, +// it's semantically the same as `&'static T`; +// but it isn't allowed to make a static reference from an arbitrary integer value at the moment. +// For more information, please see the issue #5918. +pub struct StaticRef { + ptr: *const T, +} + +impl StaticRef { + /// Create a new `StaticRef` from a raw pointer + /// + /// ## Safety + /// + /// Callers must pass in a reference to statically allocated memory which + /// does not overlap with other values. + pub const unsafe fn new(ptr: *const T) -> StaticRef { + StaticRef { ptr } + } +} + +impl std::ops::Deref for StaticRef { + type Target = T; + + fn deref(&self) -> &'static T { + unsafe { &*self.ptr } + } +} + +// use a tuple to make sure referencing a field behind a pointer isn't linted. +const CELL_REF: StaticRef<(UnsafeCell,)> = unsafe { StaticRef::new(std::ptr::null()) }; + +fn main() { + ATOMIC.store(1, Ordering::SeqCst); //~ ERROR interior mutability + assert_eq!(ATOMIC.load(Ordering::SeqCst), 5); //~ ERROR interior mutability + + let _once = ONCE_INIT; + let _once_ref = &ONCE_INIT; //~ ERROR interior mutability + let _once_ref_2 = &&ONCE_INIT; //~ ERROR interior mutability + let _once_ref_4 = &&&&ONCE_INIT; //~ ERROR interior mutability + let _once_mut = &mut ONCE_INIT; //~ ERROR interior mutability + let _atomic_into_inner = ATOMIC.into_inner(); + // these should be all fine. + let _twice = (ONCE_INIT, ONCE_INIT); + let _ref_twice = &(ONCE_INIT, ONCE_INIT); + let _ref_once = &(ONCE_INIT, ONCE_INIT).0; + let _array_twice = [ONCE_INIT, ONCE_INIT]; + let _ref_array_twice = &[ONCE_INIT, ONCE_INIT]; + let _ref_array_once = &[ONCE_INIT, ONCE_INIT][0]; + + // referencing projection is still bad. + let _ = &ATOMIC_TUPLE; //~ ERROR interior mutability + let _ = &ATOMIC_TUPLE.0; //~ ERROR interior mutability + let _ = &(&&&&ATOMIC_TUPLE).0; //~ ERROR interior mutability + let _ = &ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability + let _ = ATOMIC_TUPLE.0[0].load(Ordering::SeqCst); //~ ERROR interior mutability + let _ = &*ATOMIC_TUPLE.1; + let _ = &ATOMIC_TUPLE.2; + let _ = (&&&&ATOMIC_TUPLE).0; + let _ = (&&&&ATOMIC_TUPLE).2; + let _ = ATOMIC_TUPLE.0; + let _ = ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability + let _ = ATOMIC_TUPLE.1.into_iter(); + let _ = ATOMIC_TUPLE.2; + let _ = &{ ATOMIC_TUPLE }; + + CELL.set(2); //~ ERROR interior mutability + assert_eq!(CELL.get(), 6); //~ ERROR interior mutability + + assert_eq!(INTEGER, 8); + assert!(STRING.is_empty()); + + let a = ATOMIC; + a.store(4, Ordering::SeqCst); + assert_eq!(a.load(Ordering::SeqCst), 4); + + STATIC_TUPLE.0.store(3, Ordering::SeqCst); + assert_eq!(STATIC_TUPLE.0.load(Ordering::SeqCst), 3); + assert!(STATIC_TUPLE.1.is_empty()); + + assert_eq!(NO_ANN.to_string(), "70"); // should never lint this. + + let _ = &CELL_REF.0; +} diff --git a/src/tools/clippy/tests/ui/borrow_interior_mutable_const/others.stderr b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/others.stderr new file mode 100644 index 0000000000..9a908cf30e --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/others.stderr @@ -0,0 +1,115 @@ +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:54:5 + | +LL | ATOMIC.store(1, Ordering::SeqCst); //~ ERROR interior mutability + | ^^^^^^ + | + = note: `-D clippy::borrow-interior-mutable-const` implied by `-D warnings` + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:55:16 + | +LL | assert_eq!(ATOMIC.load(Ordering::SeqCst), 5); //~ ERROR interior mutability + | ^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:58:22 + | +LL | let _once_ref = &ONCE_INIT; //~ ERROR interior mutability + | ^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:59:25 + | +LL | let _once_ref_2 = &&ONCE_INIT; //~ ERROR interior mutability + | ^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:60:27 + | +LL | let _once_ref_4 = &&&&ONCE_INIT; //~ ERROR interior mutability + | ^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:61:26 + | +LL | let _once_mut = &mut ONCE_INIT; //~ ERROR interior mutability + | ^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:72:14 + | +LL | let _ = &ATOMIC_TUPLE; //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:73:14 + | +LL | let _ = &ATOMIC_TUPLE.0; //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:74:19 + | +LL | let _ = &(&&&&ATOMIC_TUPLE).0; //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:75:14 + | +LL | let _ = &ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:76:13 + | +LL | let _ = ATOMIC_TUPLE.0[0].load(Ordering::SeqCst); //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:82:13 + | +LL | let _ = ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:87:5 + | +LL | CELL.set(2); //~ ERROR interior mutability + | ^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/others.rs:88:16 + | +LL | assert_eq!(CELL.get(), 6); //~ ERROR interior mutability + | ^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/borrow_interior_mutable_const/traits.rs b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/traits.rs new file mode 100644 index 0000000000..06b5d62e8f --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/traits.rs @@ -0,0 +1,202 @@ +#![warn(clippy::borrow_interior_mutable_const)] +#![allow(clippy::declare_interior_mutable_const)] + +// this file replicates its `declare` counterpart. Please see it for more discussions. + +use std::borrow::Cow; +use std::cell::Cell; +use std::sync::atomic::{AtomicUsize, Ordering}; + +trait ConcreteTypes { + const ATOMIC: AtomicUsize; + const STRING: String; + + fn function() { + let _ = &Self::ATOMIC; //~ ERROR interior mutable + let _ = &Self::STRING; + } +} + +impl ConcreteTypes for u64 { + const ATOMIC: AtomicUsize = AtomicUsize::new(9); + const STRING: String = String::new(); + + fn function() { + // Lint this again since implementers can choose not to borrow it. + let _ = &Self::ATOMIC; //~ ERROR interior mutable + let _ = &Self::STRING; + } +} + +// a helper trait used below +trait ConstDefault { + const DEFAULT: Self; +} + +trait GenericTypes { + const TO_REMAIN_GENERIC: T; + const TO_BE_CONCRETE: U; + + fn function() { + let _ = &Self::TO_REMAIN_GENERIC; + } +} + +impl GenericTypes for Vec { + const TO_REMAIN_GENERIC: T = T::DEFAULT; + const TO_BE_CONCRETE: AtomicUsize = AtomicUsize::new(11); + + fn function() { + let _ = &Self::TO_REMAIN_GENERIC; + let _ = &Self::TO_BE_CONCRETE; //~ ERROR interior mutable + } +} + +// a helper type used below +pub struct Wrapper(T); + +trait AssocTypes { + type ToBeFrozen; + type ToBeUnfrozen; + type ToBeGenericParam; + + const TO_BE_FROZEN: Self::ToBeFrozen; + const TO_BE_UNFROZEN: Self::ToBeUnfrozen; + const WRAPPED_TO_BE_UNFROZEN: Wrapper; + const WRAPPED_TO_BE_GENERIC_PARAM: Wrapper; + + fn function() { + let _ = &Self::TO_BE_FROZEN; + let _ = &Self::WRAPPED_TO_BE_UNFROZEN; + } +} + +impl AssocTypes for Vec { + type ToBeFrozen = u16; + type ToBeUnfrozen = AtomicUsize; + type ToBeGenericParam = T; + + const TO_BE_FROZEN: Self::ToBeFrozen = 12; + const TO_BE_UNFROZEN: Self::ToBeUnfrozen = AtomicUsize::new(13); + const WRAPPED_TO_BE_UNFROZEN: Wrapper = Wrapper(AtomicUsize::new(14)); + const WRAPPED_TO_BE_GENERIC_PARAM: Wrapper = Wrapper(T::DEFAULT); + + fn function() { + let _ = &Self::TO_BE_FROZEN; + let _ = &Self::TO_BE_UNFROZEN; //~ ERROR interior mutable + let _ = &Self::WRAPPED_TO_BE_UNFROZEN; //~ ERROR interior mutable + let _ = &Self::WRAPPED_TO_BE_GENERIC_PARAM; + } +} + +// a helper trait used below +trait AssocTypesHelper { + type NotToBeBounded; + type ToBeBounded; + + const NOT_TO_BE_BOUNDED: Self::NotToBeBounded; +} + +trait AssocTypesFromGenericParam +where + T: AssocTypesHelper, +{ + const NOT_BOUNDED: T::NotToBeBounded; + const BOUNDED: T::ToBeBounded; + + fn function() { + let _ = &Self::NOT_BOUNDED; + let _ = &Self::BOUNDED; //~ ERROR interior mutable + } +} + +impl AssocTypesFromGenericParam for Vec +where + T: AssocTypesHelper, +{ + const NOT_BOUNDED: T::NotToBeBounded = T::NOT_TO_BE_BOUNDED; + const BOUNDED: T::ToBeBounded = AtomicUsize::new(15); + + fn function() { + let _ = &Self::NOT_BOUNDED; + let _ = &Self::BOUNDED; //~ ERROR interior mutable + } +} + +trait SelfType: Sized { + const SELF: Self; + const WRAPPED_SELF: Option; + + fn function() { + let _ = &Self::SELF; + let _ = &Self::WRAPPED_SELF; + } +} + +impl SelfType for u64 { + const SELF: Self = 16; + const WRAPPED_SELF: Option = Some(20); + + fn function() { + let _ = &Self::SELF; + let _ = &Self::WRAPPED_SELF; + } +} + +impl SelfType for AtomicUsize { + const SELF: Self = AtomicUsize::new(17); + const WRAPPED_SELF: Option = Some(AtomicUsize::new(21)); + + fn function() { + let _ = &Self::SELF; //~ ERROR interior mutable + let _ = &Self::WRAPPED_SELF; //~ ERROR interior mutable + } +} + +trait BothOfCellAndGeneric { + const DIRECT: Cell; + const INDIRECT: Cell<*const T>; + + fn function() { + let _ = &Self::DIRECT; + let _ = &Self::INDIRECT; //~ ERROR interior mutable + } +} + +impl BothOfCellAndGeneric for Vec { + const DIRECT: Cell = Cell::new(T::DEFAULT); + const INDIRECT: Cell<*const T> = Cell::new(std::ptr::null()); + + fn function() { + let _ = &Self::DIRECT; + let _ = &Self::INDIRECT; //~ ERROR interior mutable + } +} + +struct Local(T); + +impl Local +where + T: ConstDefault + AssocTypesHelper, +{ + const ATOMIC: AtomicUsize = AtomicUsize::new(18); + const COW: Cow<'static, str> = Cow::Borrowed("tuvwxy"); + + const GENERIC_TYPE: T = T::DEFAULT; + + const ASSOC_TYPE: T::NotToBeBounded = T::NOT_TO_BE_BOUNDED; + const BOUNDED_ASSOC_TYPE: T::ToBeBounded = AtomicUsize::new(19); + + fn function() { + let _ = &Self::ATOMIC; //~ ERROR interior mutable + let _ = &Self::COW; + let _ = &Self::GENERIC_TYPE; + let _ = &Self::ASSOC_TYPE; + let _ = &Self::BOUNDED_ASSOC_TYPE; //~ ERROR interior mutable + } +} + +fn main() { + u64::ATOMIC.store(5, Ordering::SeqCst); //~ ERROR interior mutability + assert_eq!(u64::ATOMIC.load(Ordering::SeqCst), 9); //~ ERROR interior mutability +} diff --git a/src/tools/clippy/tests/ui/borrow_interior_mutable_const/traits.stderr b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/traits.stderr new file mode 100644 index 0000000000..8f26403abd --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_interior_mutable_const/traits.stderr @@ -0,0 +1,123 @@ +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:15:18 + | +LL | let _ = &Self::ATOMIC; //~ ERROR interior mutable + | ^^^^^^^^^^^^ + | + = note: `-D clippy::borrow-interior-mutable-const` implied by `-D warnings` + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:26:18 + | +LL | let _ = &Self::ATOMIC; //~ ERROR interior mutable + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:51:18 + | +LL | let _ = &Self::TO_BE_CONCRETE; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:86:18 + | +LL | let _ = &Self::TO_BE_UNFROZEN; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:87:18 + | +LL | let _ = &Self::WRAPPED_TO_BE_UNFROZEN; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:109:18 + | +LL | let _ = &Self::BOUNDED; //~ ERROR interior mutable + | ^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:122:18 + | +LL | let _ = &Self::BOUNDED; //~ ERROR interior mutable + | ^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:151:18 + | +LL | let _ = &Self::SELF; //~ ERROR interior mutable + | ^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:152:18 + | +LL | let _ = &Self::WRAPPED_SELF; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:162:18 + | +LL | let _ = &Self::INDIRECT; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:172:18 + | +LL | let _ = &Self::INDIRECT; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:191:18 + | +LL | let _ = &Self::ATOMIC; //~ ERROR interior mutable + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:195:18 + | +LL | let _ = &Self::BOUNDED_ASSOC_TYPE; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:200:5 + | +LL | u64::ATOMIC.store(5, Ordering::SeqCst); //~ ERROR interior mutability + | ^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/traits.rs:201:16 + | +LL | assert_eq!(u64::ATOMIC.load(Ordering::SeqCst), 9); //~ ERROR interior mutability + | ^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/box_vec.rs b/src/tools/clippy/tests/ui/box_vec.rs new file mode 100644 index 0000000000..87b67c2370 --- /dev/null +++ b/src/tools/clippy/tests/ui/box_vec.rs @@ -0,0 +1,32 @@ +#![warn(clippy::all)] +#![allow(clippy::boxed_local, clippy::needless_pass_by_value)] +#![allow(clippy::blacklisted_name)] + +macro_rules! boxit { + ($init:expr, $x:ty) => { + let _: Box<$x> = Box::new($init); + }; +} + +fn test_macro() { + boxit!(Vec::new(), Vec); +} +pub fn test(foo: Box>) { + println!("{:?}", foo.get(0)) +} + +pub fn test2(foo: Box)>) { + // pass if #31 is fixed + foo(vec![1, 2, 3]) +} + +pub fn test_local_not_linted() { + let _: Box>; +} + +fn main() { + test(Box::new(Vec::new())); + test2(Box::new(|v| println!("{:?}", v))); + test_macro(); + test_local_not_linted(); +} diff --git a/src/tools/clippy/tests/ui/box_vec.stderr b/src/tools/clippy/tests/ui/box_vec.stderr new file mode 100644 index 0000000000..9b789334ba --- /dev/null +++ b/src/tools/clippy/tests/ui/box_vec.stderr @@ -0,0 +1,11 @@ +error: you seem to be trying to use `Box>`. Consider using just `Vec` + --> $DIR/box_vec.rs:14:18 + | +LL | pub fn test(foo: Box>) { + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::box-vec` implied by `-D warnings` + = help: `Vec` is already on the heap, `Box>` makes an extra allocation + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/builtin-type-shadow.rs b/src/tools/clippy/tests/ui/builtin-type-shadow.rs new file mode 100644 index 0000000000..69b8b6a0e6 --- /dev/null +++ b/src/tools/clippy/tests/ui/builtin-type-shadow.rs @@ -0,0 +1,9 @@ +#![warn(clippy::builtin_type_shadow)] +#![allow(non_camel_case_types)] + +fn foo(a: u32) -> u32 { + 42 + // ^ rustc's type error +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/builtin-type-shadow.stderr b/src/tools/clippy/tests/ui/builtin-type-shadow.stderr new file mode 100644 index 0000000000..f42b246afd --- /dev/null +++ b/src/tools/clippy/tests/ui/builtin-type-shadow.stderr @@ -0,0 +1,24 @@ +error: this generic shadows the built-in type `u32` + --> $DIR/builtin-type-shadow.rs:4:8 + | +LL | fn foo(a: u32) -> u32 { + | ^^^ + | + = note: `-D clippy::builtin-type-shadow` implied by `-D warnings` + +error[E0308]: mismatched types + --> $DIR/builtin-type-shadow.rs:5:5 + | +LL | fn foo(a: u32) -> u32 { + | --- --- expected `u32` because of return type + | | + | this type parameter +LL | 42 + | ^^ expected type parameter `u32`, found integer + | + = note: expected type parameter `u32` + found type `{integer}` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/tools/clippy/tests/ui/bytecount.rs b/src/tools/clippy/tests/ui/bytecount.rs new file mode 100644 index 0000000000..c724ee21be --- /dev/null +++ b/src/tools/clippy/tests/ui/bytecount.rs @@ -0,0 +1,24 @@ +#[deny(clippy::naive_bytecount)] +fn main() { + let x = vec![0_u8; 16]; + + let _ = x.iter().filter(|&&a| a == 0).count(); // naive byte count + + let _ = (&x[..]).iter().filter(|&a| *a == 0).count(); // naive byte count + + let _ = x.iter().filter(|a| **a > 0).count(); // not an equality count, OK. + + let _ = x.iter().map(|a| a + 1).filter(|&a| a < 15).count(); // not a slice + + let b = 0; + + let _ = x.iter().filter(|_| b > 0).count(); // woah there + + let _ = x.iter().filter(|_a| b == b + 1).count(); // nothing to see here, move along + + let _ = x.iter().filter(|a| b + 1 == **a).count(); // naive byte count + + let y = vec![0_u16; 3]; + + let _ = y.iter().filter(|&&a| a == 0).count(); // naive count, but not bytes +} diff --git a/src/tools/clippy/tests/ui/bytecount.stderr b/src/tools/clippy/tests/ui/bytecount.stderr new file mode 100644 index 0000000000..1dc37fc8b2 --- /dev/null +++ b/src/tools/clippy/tests/ui/bytecount.stderr @@ -0,0 +1,26 @@ +error: you appear to be counting bytes the naive way + --> $DIR/bytecount.rs:5:13 + | +LL | let _ = x.iter().filter(|&&a| a == 0).count(); // naive byte count + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the bytecount crate: `bytecount::count(x, 0)` + | +note: the lint level is defined here + --> $DIR/bytecount.rs:1:8 + | +LL | #[deny(clippy::naive_bytecount)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: you appear to be counting bytes the naive way + --> $DIR/bytecount.rs:7:13 + | +LL | let _ = (&x[..]).iter().filter(|&a| *a == 0).count(); // naive byte count + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the bytecount crate: `bytecount::count((&x[..]), 0)` + +error: you appear to be counting bytes the naive way + --> $DIR/bytecount.rs:19:13 + | +LL | let _ = x.iter().filter(|a| b + 1 == **a).count(); // naive byte count + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the bytecount crate: `bytecount::count(x, b + 1)` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/bytes_nth.fixed b/src/tools/clippy/tests/ui/bytes_nth.fixed new file mode 100644 index 0000000000..bf68a7bbbf --- /dev/null +++ b/src/tools/clippy/tests/ui/bytes_nth.fixed @@ -0,0 +1,11 @@ +// run-rustfix + +#![allow(clippy::unnecessary_operation)] +#![warn(clippy::bytes_nth)] + +fn main() { + let s = String::from("String"); + s.as_bytes().get(3); + &s.as_bytes().get(3); + s[..].as_bytes().get(3); +} diff --git a/src/tools/clippy/tests/ui/bytes_nth.rs b/src/tools/clippy/tests/ui/bytes_nth.rs new file mode 100644 index 0000000000..629812cc02 --- /dev/null +++ b/src/tools/clippy/tests/ui/bytes_nth.rs @@ -0,0 +1,11 @@ +// run-rustfix + +#![allow(clippy::unnecessary_operation)] +#![warn(clippy::bytes_nth)] + +fn main() { + let s = String::from("String"); + s.bytes().nth(3); + &s.bytes().nth(3); + s[..].bytes().nth(3); +} diff --git a/src/tools/clippy/tests/ui/bytes_nth.stderr b/src/tools/clippy/tests/ui/bytes_nth.stderr new file mode 100644 index 0000000000..9a5742928c --- /dev/null +++ b/src/tools/clippy/tests/ui/bytes_nth.stderr @@ -0,0 +1,22 @@ +error: called `.byte().nth()` on a `String` + --> $DIR/bytes_nth.rs:8:5 + | +LL | s.bytes().nth(3); + | ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3)` + | + = note: `-D clippy::bytes-nth` implied by `-D warnings` + +error: called `.byte().nth()` on a `String` + --> $DIR/bytes_nth.rs:9:6 + | +LL | &s.bytes().nth(3); + | ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3)` + +error: called `.byte().nth()` on a `str` + --> $DIR/bytes_nth.rs:10:5 + | +LL | s[..].bytes().nth(3); + | ^^^^^^^^^^^^^^^^^^^^ help: try: `s[..].as_bytes().get(3)` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/case_sensitive_file_extension_comparisons.rs b/src/tools/clippy/tests/ui/case_sensitive_file_extension_comparisons.rs new file mode 100644 index 0000000000..68719c2bc6 --- /dev/null +++ b/src/tools/clippy/tests/ui/case_sensitive_file_extension_comparisons.rs @@ -0,0 +1,44 @@ +#![warn(clippy::case_sensitive_file_extension_comparisons)] + +use std::string::String; + +struct TestStruct {} + +impl TestStruct { + fn ends_with(self, arg: &str) {} +} + +fn is_rust_file(filename: &str) -> bool { + filename.ends_with(".rs") +} + +fn main() { + // std::string::String and &str should trigger the lint failure with .ext12 + let _ = String::from("").ends_with(".ext12"); + let _ = "str".ends_with(".ext12"); + + // The test struct should not trigger the lint failure with .ext12 + TestStruct {}.ends_with(".ext12"); + + // std::string::String and &str should trigger the lint failure with .EXT12 + let _ = String::from("").ends_with(".EXT12"); + let _ = "str".ends_with(".EXT12"); + + // The test struct should not trigger the lint failure with .EXT12 + TestStruct {}.ends_with(".EXT12"); + + // Should not trigger the lint failure with .eXT12 + let _ = String::from("").ends_with(".eXT12"); + let _ = "str".ends_with(".eXT12"); + TestStruct {}.ends_with(".eXT12"); + + // Should not trigger the lint failure with .EXT123 (too long) + let _ = String::from("").ends_with(".EXT123"); + let _ = "str".ends_with(".EXT123"); + TestStruct {}.ends_with(".EXT123"); + + // Shouldn't fail if it doesn't start with a dot + let _ = String::from("").ends_with("a.ext"); + let _ = "str".ends_with("a.extA"); + TestStruct {}.ends_with("a.ext"); +} diff --git a/src/tools/clippy/tests/ui/case_sensitive_file_extension_comparisons.stderr b/src/tools/clippy/tests/ui/case_sensitive_file_extension_comparisons.stderr new file mode 100644 index 0000000000..05b98169f2 --- /dev/null +++ b/src/tools/clippy/tests/ui/case_sensitive_file_extension_comparisons.stderr @@ -0,0 +1,43 @@ +error: case-sensitive file extension comparison + --> $DIR/case_sensitive_file_extension_comparisons.rs:12:14 + | +LL | filename.ends_with(".rs") + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::case-sensitive-file-extension-comparisons` implied by `-D warnings` + = help: consider using a case-insensitive comparison instead + +error: case-sensitive file extension comparison + --> $DIR/case_sensitive_file_extension_comparisons.rs:17:30 + | +LL | let _ = String::from("").ends_with(".ext12"); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a case-insensitive comparison instead + +error: case-sensitive file extension comparison + --> $DIR/case_sensitive_file_extension_comparisons.rs:18:19 + | +LL | let _ = "str".ends_with(".ext12"); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a case-insensitive comparison instead + +error: case-sensitive file extension comparison + --> $DIR/case_sensitive_file_extension_comparisons.rs:24:30 + | +LL | let _ = String::from("").ends_with(".EXT12"); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a case-insensitive comparison instead + +error: case-sensitive file extension comparison + --> $DIR/case_sensitive_file_extension_comparisons.rs:25:19 + | +LL | let _ = "str".ends_with(".EXT12"); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a case-insensitive comparison instead + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/cast.rs b/src/tools/clippy/tests/ui/cast.rs new file mode 100644 index 0000000000..8ee0969b0f --- /dev/null +++ b/src/tools/clippy/tests/ui/cast.rs @@ -0,0 +1,95 @@ +#[warn( + clippy::cast_precision_loss, + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_possible_wrap +)] +#[allow(clippy::no_effect, clippy::unnecessary_operation)] +fn main() { + // Test clippy::cast_precision_loss + let x0 = 1i32; + x0 as f32; + let x1 = 1i64; + x1 as f32; + x1 as f64; + let x2 = 1u32; + x2 as f32; + let x3 = 1u64; + x3 as f32; + x3 as f64; + // Test clippy::cast_possible_truncation + 1f32 as i32; + 1f32 as u32; + 1f64 as f32; + 1i32 as i8; + 1i32 as u8; + 1f64 as isize; + 1f64 as usize; + // Test clippy::cast_possible_wrap + 1u8 as i8; + 1u16 as i16; + 1u32 as i32; + 1u64 as i64; + 1usize as isize; + // Test clippy::cast_sign_loss + 1i32 as u32; + -1i32 as u32; + 1isize as usize; + -1isize as usize; + 0i8 as u8; + i8::MAX as u8; + i16::MAX as u16; + i32::MAX as u32; + i64::MAX as u64; + i128::MAX as u128; + + (-1i8).abs() as u8; + (-1i16).abs() as u16; + (-1i32).abs() as u32; + (-1i64).abs() as u64; + (-1isize).abs() as usize; + + (-1i8).checked_abs().unwrap() as u8; + (-1i16).checked_abs().unwrap() as u16; + (-1i32).checked_abs().unwrap() as u32; + (-1i64).checked_abs().unwrap() as u64; + (-1isize).checked_abs().unwrap() as usize; + + (-1i8).rem_euclid(1i8) as u8; + (-1i8).rem_euclid(1i8) as u16; + (-1i16).rem_euclid(1i16) as u16; + (-1i16).rem_euclid(1i16) as u32; + (-1i32).rem_euclid(1i32) as u32; + (-1i32).rem_euclid(1i32) as u64; + (-1i64).rem_euclid(1i64) as u64; + (-1i64).rem_euclid(1i64) as u128; + (-1isize).rem_euclid(1isize) as usize; + (1i8).rem_euclid(-1i8) as u8; + (1i8).rem_euclid(-1i8) as u16; + (1i16).rem_euclid(-1i16) as u16; + (1i16).rem_euclid(-1i16) as u32; + (1i32).rem_euclid(-1i32) as u32; + (1i32).rem_euclid(-1i32) as u64; + (1i64).rem_euclid(-1i64) as u64; + (1i64).rem_euclid(-1i64) as u128; + (1isize).rem_euclid(-1isize) as usize; + + (-1i8).checked_rem_euclid(1i8).unwrap() as u8; + (-1i8).checked_rem_euclid(1i8).unwrap() as u16; + (-1i16).checked_rem_euclid(1i16).unwrap() as u16; + (-1i16).checked_rem_euclid(1i16).unwrap() as u32; + (-1i32).checked_rem_euclid(1i32).unwrap() as u32; + (-1i32).checked_rem_euclid(1i32).unwrap() as u64; + (-1i64).checked_rem_euclid(1i64).unwrap() as u64; + (-1i64).checked_rem_euclid(1i64).unwrap() as u128; + (-1isize).checked_rem_euclid(1isize).unwrap() as usize; + (1i8).checked_rem_euclid(-1i8).unwrap() as u8; + (1i8).checked_rem_euclid(-1i8).unwrap() as u16; + (1i16).checked_rem_euclid(-1i16).unwrap() as u16; + (1i16).checked_rem_euclid(-1i16).unwrap() as u32; + (1i32).checked_rem_euclid(-1i32).unwrap() as u32; + (1i32).checked_rem_euclid(-1i32).unwrap() as u64; + (1i64).checked_rem_euclid(-1i64).unwrap() as u64; + (1i64).checked_rem_euclid(-1i64).unwrap() as u128; + (1isize).checked_rem_euclid(-1isize).unwrap() as usize; +} diff --git a/src/tools/clippy/tests/ui/cast.stderr b/src/tools/clippy/tests/ui/cast.stderr new file mode 100644 index 0000000000..4c66d73649 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast.stderr @@ -0,0 +1,142 @@ +error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast.rs:11:5 + | +LL | x0 as f32; + | ^^^^^^^^^ + | + = note: `-D clippy::cast-precision-loss` implied by `-D warnings` + +error: casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast.rs:13:5 + | +LL | x1 as f32; + | ^^^^^^^^^ + +error: casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast.rs:14:5 + | +LL | x1 as f64; + | ^^^^^^^^^ + +error: casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast.rs:16:5 + | +LL | x2 as f32; + | ^^^^^^^^^ + +error: casting `u64` to `f32` causes a loss of precision (`u64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast.rs:18:5 + | +LL | x3 as f32; + | ^^^^^^^^^ + +error: casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast.rs:19:5 + | +LL | x3 as f64; + | ^^^^^^^^^ + +error: casting `f32` to `i32` may truncate the value + --> $DIR/cast.rs:21:5 + | +LL | 1f32 as i32; + | ^^^^^^^^^^^ + | + = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` + +error: casting `f32` to `u32` may truncate the value + --> $DIR/cast.rs:22:5 + | +LL | 1f32 as u32; + | ^^^^^^^^^^^ + +error: casting `f32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:22:5 + | +LL | 1f32 as u32; + | ^^^^^^^^^^^ + | + = note: `-D clippy::cast-sign-loss` implied by `-D warnings` + +error: casting `f64` to `f32` may truncate the value + --> $DIR/cast.rs:23:5 + | +LL | 1f64 as f32; + | ^^^^^^^^^^^ + +error: casting `i32` to `i8` may truncate the value + --> $DIR/cast.rs:24:5 + | +LL | 1i32 as i8; + | ^^^^^^^^^^ + +error: casting `i32` to `u8` may truncate the value + --> $DIR/cast.rs:25:5 + | +LL | 1i32 as u8; + | ^^^^^^^^^^ + +error: casting `f64` to `isize` may truncate the value + --> $DIR/cast.rs:26:5 + | +LL | 1f64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `f64` to `usize` may truncate the value + --> $DIR/cast.rs:27:5 + | +LL | 1f64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `f64` to `usize` may lose the sign of the value + --> $DIR/cast.rs:27:5 + | +LL | 1f64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `u8` to `i8` may wrap around the value + --> $DIR/cast.rs:29:5 + | +LL | 1u8 as i8; + | ^^^^^^^^^ + | + = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` + +error: casting `u16` to `i16` may wrap around the value + --> $DIR/cast.rs:30:5 + | +LL | 1u16 as i16; + | ^^^^^^^^^^^ + +error: casting `u32` to `i32` may wrap around the value + --> $DIR/cast.rs:31:5 + | +LL | 1u32 as i32; + | ^^^^^^^^^^^ + +error: casting `u64` to `i64` may wrap around the value + --> $DIR/cast.rs:32:5 + | +LL | 1u64 as i64; + | ^^^^^^^^^^^ + +error: casting `usize` to `isize` may wrap around the value + --> $DIR/cast.rs:33:5 + | +LL | 1usize as isize; + | ^^^^^^^^^^^^^^^ + +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:36:5 + | +LL | -1i32 as u32; + | ^^^^^^^^^^^^ + +error: casting `isize` to `usize` may lose the sign of the value + --> $DIR/cast.rs:38:5 + | +LL | -1isize as usize; + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 22 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_alignment.rs b/src/tools/clippy/tests/ui/cast_alignment.rs new file mode 100644 index 0000000000..d011e84b11 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_alignment.rs @@ -0,0 +1,31 @@ +//! Test casts for alignment issues + +#![feature(rustc_private)] +extern crate libc; + +#[warn(clippy::cast_ptr_alignment)] +#[allow(clippy::no_effect, clippy::unnecessary_operation, clippy::cast_lossless)] +fn main() { + /* These should be warned against */ + + // cast to more-strictly-aligned type + (&1u8 as *const u8) as *const u16; + (&mut 1u8 as *mut u8) as *mut u16; + + // cast to more-strictly-aligned type, but with the `pointer::cast` function. + (&1u8 as *const u8).cast::(); + (&mut 1u8 as *mut u8).cast::(); + + /* These should be ok */ + + // not a pointer type + 1u8 as u16; + // cast to less-strictly-aligned type + (&1u16 as *const u16) as *const u8; + (&mut 1u16 as *mut u16) as *mut u8; + // For c_void, we should trust the user. See #2677 + (&1u32 as *const u32 as *const std::os::raw::c_void) as *const u32; + (&1u32 as *const u32 as *const libc::c_void) as *const u32; + // For ZST, we should trust the user. See #4256 + (&1u32 as *const u32 as *const ()) as *const u32; +} diff --git a/src/tools/clippy/tests/ui/cast_alignment.stderr b/src/tools/clippy/tests/ui/cast_alignment.stderr new file mode 100644 index 0000000000..7998b787b9 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_alignment.stderr @@ -0,0 +1,28 @@ +error: casting from `*const u8` to a more-strictly-aligned pointer (`*const u16`) (1 < 2 bytes) + --> $DIR/cast_alignment.rs:12:5 + | +LL | (&1u8 as *const u8) as *const u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-ptr-alignment` implied by `-D warnings` + +error: casting from `*mut u8` to a more-strictly-aligned pointer (`*mut u16`) (1 < 2 bytes) + --> $DIR/cast_alignment.rs:13:5 + | +LL | (&mut 1u8 as *mut u8) as *mut u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: casting from `*const u8` to a more-strictly-aligned pointer (`*const u16`) (1 < 2 bytes) + --> $DIR/cast_alignment.rs:16:5 + | +LL | (&1u8 as *const u8).cast::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: casting from `*mut u8` to a more-strictly-aligned pointer (`*mut u16`) (1 < 2 bytes) + --> $DIR/cast_alignment.rs:17:5 + | +LL | (&mut 1u8 as *mut u8).cast::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_lossless_float.fixed b/src/tools/clippy/tests/ui/cast_lossless_float.fixed new file mode 100644 index 0000000000..709d58b596 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_float.fixed @@ -0,0 +1,45 @@ +// run-rustfix + +#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)] +#![warn(clippy::cast_lossless)] + +fn main() { + // Test clippy::cast_lossless with casts to floating-point types + let x0 = 1i8; + f32::from(x0); + f64::from(x0); + let x1 = 1u8; + f32::from(x1); + f64::from(x1); + let x2 = 1i16; + f32::from(x2); + f64::from(x2); + let x3 = 1u16; + f32::from(x3); + f64::from(x3); + let x4 = 1i32; + f64::from(x4); + let x5 = 1u32; + f64::from(x5); + + // Test with casts from floating-point types + f64::from(1.0f32); +} + +// The lint would suggest using `f64::from(input)` here but the `XX::from` function is not const, +// so we skip the lint if the expression is in a const fn. +// See #3656 +const fn abc(input: f32) -> f64 { + input as f64 +} + +// Same as the above issue. We can't suggest `::from` in const fns in impls +mod cast_lossless_in_impl { + struct A; + + impl A { + pub const fn convert(x: f32) -> f64 { + x as f64 + } + } +} diff --git a/src/tools/clippy/tests/ui/cast_lossless_float.rs b/src/tools/clippy/tests/ui/cast_lossless_float.rs new file mode 100644 index 0000000000..eb0aab8864 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_float.rs @@ -0,0 +1,45 @@ +// run-rustfix + +#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)] +#![warn(clippy::cast_lossless)] + +fn main() { + // Test clippy::cast_lossless with casts to floating-point types + let x0 = 1i8; + x0 as f32; + x0 as f64; + let x1 = 1u8; + x1 as f32; + x1 as f64; + let x2 = 1i16; + x2 as f32; + x2 as f64; + let x3 = 1u16; + x3 as f32; + x3 as f64; + let x4 = 1i32; + x4 as f64; + let x5 = 1u32; + x5 as f64; + + // Test with casts from floating-point types + 1.0f32 as f64; +} + +// The lint would suggest using `f64::from(input)` here but the `XX::from` function is not const, +// so we skip the lint if the expression is in a const fn. +// See #3656 +const fn abc(input: f32) -> f64 { + input as f64 +} + +// Same as the above issue. We can't suggest `::from` in const fns in impls +mod cast_lossless_in_impl { + struct A; + + impl A { + pub const fn convert(x: f32) -> f64 { + x as f64 + } + } +} diff --git a/src/tools/clippy/tests/ui/cast_lossless_float.stderr b/src/tools/clippy/tests/ui/cast_lossless_float.stderr new file mode 100644 index 0000000000..0ed09f3083 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_float.stderr @@ -0,0 +1,70 @@ +error: casting `i8` to `f32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:9:5 + | +LL | x0 as f32; + | ^^^^^^^^^ help: try: `f32::from(x0)` + | + = note: `-D clippy::cast-lossless` implied by `-D warnings` + +error: casting `i8` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:10:5 + | +LL | x0 as f64; + | ^^^^^^^^^ help: try: `f64::from(x0)` + +error: casting `u8` to `f32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:12:5 + | +LL | x1 as f32; + | ^^^^^^^^^ help: try: `f32::from(x1)` + +error: casting `u8` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:13:5 + | +LL | x1 as f64; + | ^^^^^^^^^ help: try: `f64::from(x1)` + +error: casting `i16` to `f32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:15:5 + | +LL | x2 as f32; + | ^^^^^^^^^ help: try: `f32::from(x2)` + +error: casting `i16` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:16:5 + | +LL | x2 as f64; + | ^^^^^^^^^ help: try: `f64::from(x2)` + +error: casting `u16` to `f32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:18:5 + | +LL | x3 as f32; + | ^^^^^^^^^ help: try: `f32::from(x3)` + +error: casting `u16` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:19:5 + | +LL | x3 as f64; + | ^^^^^^^^^ help: try: `f64::from(x3)` + +error: casting `i32` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:21:5 + | +LL | x4 as f64; + | ^^^^^^^^^ help: try: `f64::from(x4)` + +error: casting `u32` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:23:5 + | +LL | x5 as f64; + | ^^^^^^^^^ help: try: `f64::from(x5)` + +error: casting `f32` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:26:5 + | +LL | 1.0f32 as f64; + | ^^^^^^^^^^^^^ help: try: `f64::from(1.0f32)` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_lossless_integer.fixed b/src/tools/clippy/tests/ui/cast_lossless_integer.fixed new file mode 100644 index 0000000000..03e49adb11 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_integer.fixed @@ -0,0 +1,47 @@ +// run-rustfix + +#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)] +#![warn(clippy::cast_lossless)] + +fn main() { + // Test clippy::cast_lossless with casts to integer types + i16::from(1i8); + i32::from(1i8); + i64::from(1i8); + i16::from(1u8); + i32::from(1u8); + i64::from(1u8); + u16::from(1u8); + u32::from(1u8); + u64::from(1u8); + i32::from(1i16); + i64::from(1i16); + i32::from(1u16); + i64::from(1u16); + u32::from(1u16); + u64::from(1u16); + i64::from(1i32); + i64::from(1u32); + u64::from(1u32); + + // Test with an expression wrapped in parens + u16::from(1u8 + 1u8); +} + +// The lint would suggest using `f64::from(input)` here but the `XX::from` function is not const, +// so we skip the lint if the expression is in a const fn. +// See #3656 +const fn abc(input: u16) -> u32 { + input as u32 +} + +// Same as the above issue. We can't suggest `::from` in const fns in impls +mod cast_lossless_in_impl { + struct A; + + impl A { + pub const fn convert(x: u32) -> u64 { + x as u64 + } + } +} diff --git a/src/tools/clippy/tests/ui/cast_lossless_integer.rs b/src/tools/clippy/tests/ui/cast_lossless_integer.rs new file mode 100644 index 0000000000..6a984d2459 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_integer.rs @@ -0,0 +1,47 @@ +// run-rustfix + +#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)] +#![warn(clippy::cast_lossless)] + +fn main() { + // Test clippy::cast_lossless with casts to integer types + 1i8 as i16; + 1i8 as i32; + 1i8 as i64; + 1u8 as i16; + 1u8 as i32; + 1u8 as i64; + 1u8 as u16; + 1u8 as u32; + 1u8 as u64; + 1i16 as i32; + 1i16 as i64; + 1u16 as i32; + 1u16 as i64; + 1u16 as u32; + 1u16 as u64; + 1i32 as i64; + 1u32 as i64; + 1u32 as u64; + + // Test with an expression wrapped in parens + (1u8 + 1u8) as u16; +} + +// The lint would suggest using `f64::from(input)` here but the `XX::from` function is not const, +// so we skip the lint if the expression is in a const fn. +// See #3656 +const fn abc(input: u16) -> u32 { + input as u32 +} + +// Same as the above issue. We can't suggest `::from` in const fns in impls +mod cast_lossless_in_impl { + struct A; + + impl A { + pub const fn convert(x: u32) -> u64 { + x as u64 + } + } +} diff --git a/src/tools/clippy/tests/ui/cast_lossless_integer.stderr b/src/tools/clippy/tests/ui/cast_lossless_integer.stderr new file mode 100644 index 0000000000..8e2890f9c2 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_integer.stderr @@ -0,0 +1,118 @@ +error: casting `i8` to `i16` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:8:5 + | +LL | 1i8 as i16; + | ^^^^^^^^^^ help: try: `i16::from(1i8)` + | + = note: `-D clippy::cast-lossless` implied by `-D warnings` + +error: casting `i8` to `i32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:9:5 + | +LL | 1i8 as i32; + | ^^^^^^^^^^ help: try: `i32::from(1i8)` + +error: casting `i8` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:10:5 + | +LL | 1i8 as i64; + | ^^^^^^^^^^ help: try: `i64::from(1i8)` + +error: casting `u8` to `i16` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:11:5 + | +LL | 1u8 as i16; + | ^^^^^^^^^^ help: try: `i16::from(1u8)` + +error: casting `u8` to `i32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:12:5 + | +LL | 1u8 as i32; + | ^^^^^^^^^^ help: try: `i32::from(1u8)` + +error: casting `u8` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:13:5 + | +LL | 1u8 as i64; + | ^^^^^^^^^^ help: try: `i64::from(1u8)` + +error: casting `u8` to `u16` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:14:5 + | +LL | 1u8 as u16; + | ^^^^^^^^^^ help: try: `u16::from(1u8)` + +error: casting `u8` to `u32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:15:5 + | +LL | 1u8 as u32; + | ^^^^^^^^^^ help: try: `u32::from(1u8)` + +error: casting `u8` to `u64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:16:5 + | +LL | 1u8 as u64; + | ^^^^^^^^^^ help: try: `u64::from(1u8)` + +error: casting `i16` to `i32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:17:5 + | +LL | 1i16 as i32; + | ^^^^^^^^^^^ help: try: `i32::from(1i16)` + +error: casting `i16` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:18:5 + | +LL | 1i16 as i64; + | ^^^^^^^^^^^ help: try: `i64::from(1i16)` + +error: casting `u16` to `i32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:19:5 + | +LL | 1u16 as i32; + | ^^^^^^^^^^^ help: try: `i32::from(1u16)` + +error: casting `u16` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:20:5 + | +LL | 1u16 as i64; + | ^^^^^^^^^^^ help: try: `i64::from(1u16)` + +error: casting `u16` to `u32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:21:5 + | +LL | 1u16 as u32; + | ^^^^^^^^^^^ help: try: `u32::from(1u16)` + +error: casting `u16` to `u64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:22:5 + | +LL | 1u16 as u64; + | ^^^^^^^^^^^ help: try: `u64::from(1u16)` + +error: casting `i32` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:23:5 + | +LL | 1i32 as i64; + | ^^^^^^^^^^^ help: try: `i64::from(1i32)` + +error: casting `u32` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:24:5 + | +LL | 1u32 as i64; + | ^^^^^^^^^^^ help: try: `i64::from(1u32)` + +error: casting `u32` to `u64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:25:5 + | +LL | 1u32 as u64; + | ^^^^^^^^^^^ help: try: `u64::from(1u32)` + +error: casting `u8` to `u16` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:28:5 + | +LL | (1u8 + 1u8) as u16; + | ^^^^^^^^^^^^^^^^^^ help: try: `u16::from(1u8 + 1u8)` + +error: aborting due to 19 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_ref_to_mut.rs b/src/tools/clippy/tests/ui/cast_ref_to_mut.rs new file mode 100644 index 0000000000..089e5cfabe --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_ref_to_mut.rs @@ -0,0 +1,31 @@ +#![warn(clippy::cast_ref_to_mut)] +#![allow(clippy::no_effect)] + +extern "C" { + // N.B., mutability can be easily incorrect in FFI calls -- as + // in C, the default is mutable pointers. + fn ffi(c: *mut u8); + fn int_ffi(c: *mut i32); +} + +fn main() { + let s = String::from("Hello"); + let a = &s; + unsafe { + let num = &3i32; + let mut_num = &mut 3i32; + // Should be warned against + (*(a as *const _ as *mut String)).push_str(" world"); + *(a as *const _ as *mut _) = String::from("Replaced"); + *(a as *const _ as *mut String) += " world"; + // Shouldn't be warned against + println!("{}", *(num as *const _ as *const i16)); + println!("{}", *(mut_num as *mut _ as *mut i16)); + ffi(a.as_ptr() as *mut _); + int_ffi(num as *const _ as *mut _); + int_ffi(&3 as *const _ as *mut _); + let mut value = 3; + let value: *const i32 = &mut value; + *(value as *const i16 as *mut i16) = 42; + } +} diff --git a/src/tools/clippy/tests/ui/cast_ref_to_mut.stderr b/src/tools/clippy/tests/ui/cast_ref_to_mut.stderr new file mode 100644 index 0000000000..aacd99437d --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_ref_to_mut.stderr @@ -0,0 +1,22 @@ +error: casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell` + --> $DIR/cast_ref_to_mut.rs:18:9 + | +LL | (*(a as *const _ as *mut String)).push_str(" world"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-ref-to-mut` implied by `-D warnings` + +error: casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell` + --> $DIR/cast_ref_to_mut.rs:19:9 + | +LL | *(a as *const _ as *mut _) = String::from("Replaced"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell` + --> $DIR/cast_ref_to_mut.rs:20:9 + | +LL | *(a as *const _ as *mut String) += " world"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_size.rs b/src/tools/clippy/tests/ui/cast_size.rs new file mode 100644 index 0000000000..595109be46 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_size.rs @@ -0,0 +1,35 @@ +// ignore-32bit +#[warn( + clippy::cast_precision_loss, + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_possible_wrap, + clippy::cast_lossless +)] +#[allow(clippy::no_effect, clippy::unnecessary_operation)] +fn main() { + // Casting from *size + 1isize as i8; + let x0 = 1isize; + let x1 = 1usize; + x0 as f64; + x1 as f64; + x0 as f32; + x1 as f32; + 1isize as i32; + 1isize as u32; + 1usize as u32; + 1usize as i32; + // Casting to *size + 1i64 as isize; + 1i64 as usize; + 1u64 as isize; + 1u64 as usize; + 1u32 as isize; + 1u32 as usize; // Should not trigger any lint + 1i32 as isize; // Neither should this + 1i32 as usize; + // Big integer literal to float + 999_999_999 as f32; + 9_999_999_999_999_999usize as f64; +} diff --git a/src/tools/clippy/tests/ui/cast_size.stderr b/src/tools/clippy/tests/ui/cast_size.stderr new file mode 100644 index 0000000000..95552f2e28 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_size.stderr @@ -0,0 +1,116 @@ +error: casting `isize` to `i8` may truncate the value + --> $DIR/cast_size.rs:12:5 + | +LL | 1isize as i8; + | ^^^^^^^^^^^^ + | + = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` + +error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast_size.rs:15:5 + | +LL | x0 as f64; + | ^^^^^^^^^ + | + = note: `-D clippy::cast-precision-loss` implied by `-D warnings` + +error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast_size.rs:16:5 + | +LL | x1 as f64; + | ^^^^^^^^^ + +error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size.rs:17:5 + | +LL | x0 as f32; + | ^^^^^^^^^ + +error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size.rs:18:5 + | +LL | x1 as f32; + | ^^^^^^^^^ + +error: casting `isize` to `i32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size.rs:19:5 + | +LL | 1isize as i32; + | ^^^^^^^^^^^^^ + +error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size.rs:20:5 + | +LL | 1isize as u32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size.rs:21:5 + | +LL | 1usize as u32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size.rs:22:5 + | +LL | 1usize as i32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:22:5 + | +LL | 1usize as i32; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` + +error: casting `i64` to `isize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:24:5 + | +LL | 1i64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:25:5 + | +LL | 1i64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:26:5 + | +LL | 1u64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers + --> $DIR/cast_size.rs:26:5 + | +LL | 1u64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:27:5 + | +LL | 1u64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:28:5 + | +LL | 1u32 as isize; + | ^^^^^^^^^^^^^ + +error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size.rs:33:5 + | +LL | 999_999_999 as f32; + | ^^^^^^^^^^^^^^^^^^ + +error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast_size.rs:34:5 + | +LL | 9_999_999_999_999_999usize as f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_size_32bit.rs b/src/tools/clippy/tests/ui/cast_size_32bit.rs new file mode 100644 index 0000000000..99aac6deca --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_size_32bit.rs @@ -0,0 +1,35 @@ +// ignore-64bit +#[warn( + clippy::cast_precision_loss, + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_possible_wrap, + clippy::cast_lossless +)] +#[allow(clippy::no_effect, clippy::unnecessary_operation)] +fn main() { + // Casting from *size + 1isize as i8; + let x0 = 1isize; + let x1 = 1usize; + x0 as f64; + x1 as f64; + x0 as f32; + x1 as f32; + 1isize as i32; + 1isize as u32; + 1usize as u32; + 1usize as i32; + // Casting to *size + 1i64 as isize; + 1i64 as usize; + 1u64 as isize; + 1u64 as usize; + 1u32 as isize; + 1u32 as usize; // Should not trigger any lint + 1i32 as isize; // Neither should this + 1i32 as usize; + // Big integer literal to float + 999_999_999 as f32; + 3_999_999_999usize as f64; +} diff --git a/src/tools/clippy/tests/ui/cast_size_32bit.stderr b/src/tools/clippy/tests/ui/cast_size_32bit.stderr new file mode 100644 index 0000000000..140676a5ff --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_size_32bit.stderr @@ -0,0 +1,132 @@ +error: casting `isize` to `i8` may truncate the value + --> $DIR/cast_size_32bit.rs:12:5 + | +LL | 1isize as i8; + | ^^^^^^^^^^^^ + | + = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` + +error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast_size_32bit.rs:15:5 + | +LL | x0 as f64; + | ^^^^^^^^^ + | + = note: `-D clippy::cast-precision-loss` implied by `-D warnings` + +error: casting `isize` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_size_32bit.rs:15:5 + | +LL | x0 as f64; + | ^^^^^^^^^ help: try: `f64::from(x0)` + | + = note: `-D clippy::cast-lossless` implied by `-D warnings` + +error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast_size_32bit.rs:16:5 + | +LL | x1 as f64; + | ^^^^^^^^^ + +error: casting `usize` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_size_32bit.rs:16:5 + | +LL | x1 as f64; + | ^^^^^^^^^ help: try: `f64::from(x1)` + +error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size_32bit.rs:17:5 + | +LL | x0 as f32; + | ^^^^^^^^^ + +error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size_32bit.rs:18:5 + | +LL | x1 as f32; + | ^^^^^^^^^ + +error: casting `isize` to `i32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size_32bit.rs:19:5 + | +LL | 1isize as i32; + | ^^^^^^^^^^^^^ + +error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size_32bit.rs:20:5 + | +LL | 1isize as u32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size_32bit.rs:21:5 + | +LL | 1usize as u32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size_32bit.rs:22:5 + | +LL | 1usize as i32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:22:5 + | +LL | 1usize as i32; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` + +error: casting `i64` to `isize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:24:5 + | +LL | 1i64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:25:5 + | +LL | 1i64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:26:5 + | +LL | 1u64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers + --> $DIR/cast_size_32bit.rs:26:5 + | +LL | 1u64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:27:5 + | +LL | 1u64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:28:5 + | +LL | 1u32 as isize; + | ^^^^^^^^^^^^^ + +error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size_32bit.rs:33:5 + | +LL | 999_999_999 as f32; + | ^^^^^^^^^^^^^^^^^^ + +error: casting integer literal to `f64` is unnecessary + --> $DIR/cast_size_32bit.rs:34:5 + | +LL | 3_999_999_999usize as f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `3_999_999_999_f64` + | + = note: `-D clippy::unnecessary-cast` implied by `-D warnings` + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/cfg_attr_rustfmt.fixed b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.fixed new file mode 100644 index 0000000000..4e583a25b9 --- /dev/null +++ b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.fixed @@ -0,0 +1,31 @@ +// run-rustfix +#![feature(stmt_expr_attributes)] + +#![allow(unused, clippy::no_effect)] +#![warn(clippy::deprecated_cfg_attr)] + +// This doesn't get linted, see known problems +#![cfg_attr(rustfmt, rustfmt_skip)] + +#[rustfmt::skip] +trait Foo +{ +fn foo( +); +} + +fn skip_on_statements() { + #[rustfmt::skip] + 5+3; +} + +#[rustfmt::skip] +fn main() { + foo::f(); +} + +mod foo { + #![cfg_attr(rustfmt, rustfmt_skip)] + + pub fn f() {} +} diff --git a/src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs new file mode 100644 index 0000000000..9c0fcf6fb4 --- /dev/null +++ b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs @@ -0,0 +1,31 @@ +// run-rustfix +#![feature(stmt_expr_attributes)] + +#![allow(unused, clippy::no_effect)] +#![warn(clippy::deprecated_cfg_attr)] + +// This doesn't get linted, see known problems +#![cfg_attr(rustfmt, rustfmt_skip)] + +#[rustfmt::skip] +trait Foo +{ +fn foo( +); +} + +fn skip_on_statements() { + #[cfg_attr(rustfmt, rustfmt::skip)] + 5+3; +} + +#[cfg_attr(rustfmt, rustfmt_skip)] +fn main() { + foo::f(); +} + +mod foo { + #![cfg_attr(rustfmt, rustfmt_skip)] + + pub fn f() {} +} diff --git a/src/tools/clippy/tests/ui/cfg_attr_rustfmt.stderr b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.stderr new file mode 100644 index 0000000000..c1efd47db9 --- /dev/null +++ b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.stderr @@ -0,0 +1,16 @@ +error: `cfg_attr` is deprecated for rustfmt and got replaced by tool attributes + --> $DIR/cfg_attr_rustfmt.rs:18:5 + | +LL | #[cfg_attr(rustfmt, rustfmt::skip)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `#[rustfmt::skip]` + | + = note: `-D clippy::deprecated-cfg-attr` implied by `-D warnings` + +error: `cfg_attr` is deprecated for rustfmt and got replaced by tool attributes + --> $DIR/cfg_attr_rustfmt.rs:22:1 + | +LL | #[cfg_attr(rustfmt, rustfmt_skip)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `#[rustfmt::skip]` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8.rs b/src/tools/clippy/tests/ui/char_lit_as_u8.rs new file mode 100644 index 0000000000..0a53a3d649 --- /dev/null +++ b/src/tools/clippy/tests/ui/char_lit_as_u8.rs @@ -0,0 +1,5 @@ +#![warn(clippy::char_lit_as_u8)] + +fn main() { + let _ = '❤' as u8; // no suggestion, since a byte literal won't work. +} diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8.stderr b/src/tools/clippy/tests/ui/char_lit_as_u8.stderr new file mode 100644 index 0000000000..b9836d2f25 --- /dev/null +++ b/src/tools/clippy/tests/ui/char_lit_as_u8.stderr @@ -0,0 +1,11 @@ +error: casting a character literal to `u8` truncates + --> $DIR/char_lit_as_u8.rs:4:13 + | +LL | let _ = '❤' as u8; // no suggestion, since a byte literal won't work. + | ^^^^^^^^^ + | + = note: `-D clippy::char-lit-as-u8` implied by `-D warnings` + = note: `char` is four bytes wide, but `u8` is a single byte + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.fixed b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.fixed new file mode 100644 index 0000000000..3dc3cb4e75 --- /dev/null +++ b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.fixed @@ -0,0 +1,10 @@ +// run-rustfix + +#![warn(clippy::char_lit_as_u8)] + +fn main() { + let _ = b'a'; + let _ = b'\n'; + let _ = b'\0'; + let _ = b'\x01'; +} diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.rs b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.rs new file mode 100644 index 0000000000..d379a02349 --- /dev/null +++ b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.rs @@ -0,0 +1,10 @@ +// run-rustfix + +#![warn(clippy::char_lit_as_u8)] + +fn main() { + let _ = 'a' as u8; + let _ = '\n' as u8; + let _ = '\0' as u8; + let _ = '\x01' as u8; +} diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.stderr b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.stderr new file mode 100644 index 0000000000..bf7cb1607b --- /dev/null +++ b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.stderr @@ -0,0 +1,35 @@ +error: casting a character literal to `u8` truncates + --> $DIR/char_lit_as_u8_suggestions.rs:6:13 + | +LL | let _ = 'a' as u8; + | ^^^^^^^^^ help: use a byte literal instead: `b'a'` + | + = note: `-D clippy::char-lit-as-u8` implied by `-D warnings` + = note: `char` is four bytes wide, but `u8` is a single byte + +error: casting a character literal to `u8` truncates + --> $DIR/char_lit_as_u8_suggestions.rs:7:13 + | +LL | let _ = '/n' as u8; + | ^^^^^^^^^^ help: use a byte literal instead: `b'/n'` + | + = note: `char` is four bytes wide, but `u8` is a single byte + +error: casting a character literal to `u8` truncates + --> $DIR/char_lit_as_u8_suggestions.rs:8:13 + | +LL | let _ = '/0' as u8; + | ^^^^^^^^^^ help: use a byte literal instead: `b'/0'` + | + = note: `char` is four bytes wide, but `u8` is a single byte + +error: casting a character literal to `u8` truncates + --> $DIR/char_lit_as_u8_suggestions.rs:9:13 + | +LL | let _ = '/x01' as u8; + | ^^^^^^^^^^^^ help: use a byte literal instead: `b'/x01'` + | + = note: `char` is four bytes wide, but `u8` is a single byte + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/checked_conversions.fixed b/src/tools/clippy/tests/ui/checked_conversions.fixed new file mode 100644 index 0000000000..12290db3dc --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_conversions.fixed @@ -0,0 +1,76 @@ +// run-rustfix + +#![allow( + clippy::cast_lossless, + // Int::max_value will be deprecated in the future + deprecated, +)] +#![warn(clippy::checked_conversions)] + +use std::convert::TryFrom; + +// Positive tests + +// Signed to unsigned + +pub fn i64_to_u32(value: i64) { + let _ = u32::try_from(value).is_ok(); + let _ = u32::try_from(value).is_ok(); +} + +pub fn i64_to_u16(value: i64) { + let _ = u16::try_from(value).is_ok(); + let _ = u16::try_from(value).is_ok(); +} + +pub fn isize_to_u8(value: isize) { + let _ = u8::try_from(value).is_ok(); + let _ = u8::try_from(value).is_ok(); +} + +// Signed to signed + +pub fn i64_to_i32(value: i64) { + let _ = i32::try_from(value).is_ok(); + let _ = i32::try_from(value).is_ok(); +} + +pub fn i64_to_i16(value: i64) { + let _ = i16::try_from(value).is_ok(); + let _ = i16::try_from(value).is_ok(); +} + +// Unsigned to X + +pub fn u32_to_i32(value: u32) { + let _ = i32::try_from(value).is_ok(); + let _ = i32::try_from(value).is_ok(); +} + +pub fn usize_to_isize(value: usize) { + let _ = isize::try_from(value).is_ok() && value as i32 == 5; + let _ = isize::try_from(value).is_ok() && value as i32 == 5; +} + +pub fn u32_to_u16(value: u32) { + let _ = u16::try_from(value).is_ok() && value as i32 == 5; + let _ = u16::try_from(value).is_ok() && value as i32 == 5; +} + +// Negative tests + +pub fn no_i64_to_i32(value: i64) { + let _ = value <= (i32::max_value() as i64) && value >= 0; + let _ = value <= (i32::MAX as i64) && value >= 0; +} + +pub fn no_isize_to_u8(value: isize) { + let _ = value <= (u8::max_value() as isize) && value >= (u8::min_value() as isize); + let _ = value <= (u8::MAX as isize) && value >= (u8::MIN as isize); +} + +pub fn i8_to_u8(value: i8) { + let _ = value >= 0; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/checked_conversions.rs b/src/tools/clippy/tests/ui/checked_conversions.rs new file mode 100644 index 0000000000..895a301e82 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_conversions.rs @@ -0,0 +1,76 @@ +// run-rustfix + +#![allow( + clippy::cast_lossless, + // Int::max_value will be deprecated in the future + deprecated, +)] +#![warn(clippy::checked_conversions)] + +use std::convert::TryFrom; + +// Positive tests + +// Signed to unsigned + +pub fn i64_to_u32(value: i64) { + let _ = value <= (u32::max_value() as i64) && value >= 0; + let _ = value <= (u32::MAX as i64) && value >= 0; +} + +pub fn i64_to_u16(value: i64) { + let _ = value <= i64::from(u16::max_value()) && value >= 0; + let _ = value <= i64::from(u16::MAX) && value >= 0; +} + +pub fn isize_to_u8(value: isize) { + let _ = value <= (u8::max_value() as isize) && value >= 0; + let _ = value <= (u8::MAX as isize) && value >= 0; +} + +// Signed to signed + +pub fn i64_to_i32(value: i64) { + let _ = value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64); + let _ = value <= (i32::MAX as i64) && value >= (i32::MIN as i64); +} + +pub fn i64_to_i16(value: i64) { + let _ = value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value()); + let _ = value <= i64::from(i16::MAX) && value >= i64::from(i16::MIN); +} + +// Unsigned to X + +pub fn u32_to_i32(value: u32) { + let _ = value <= i32::max_value() as u32; + let _ = value <= i32::MAX as u32; +} + +pub fn usize_to_isize(value: usize) { + let _ = value <= isize::max_value() as usize && value as i32 == 5; + let _ = value <= isize::MAX as usize && value as i32 == 5; +} + +pub fn u32_to_u16(value: u32) { + let _ = value <= u16::max_value() as u32 && value as i32 == 5; + let _ = value <= u16::MAX as u32 && value as i32 == 5; +} + +// Negative tests + +pub fn no_i64_to_i32(value: i64) { + let _ = value <= (i32::max_value() as i64) && value >= 0; + let _ = value <= (i32::MAX as i64) && value >= 0; +} + +pub fn no_isize_to_u8(value: isize) { + let _ = value <= (u8::max_value() as isize) && value >= (u8::min_value() as isize); + let _ = value <= (u8::MAX as isize) && value >= (u8::MIN as isize); +} + +pub fn i8_to_u8(value: i8) { + let _ = value >= 0; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/checked_conversions.stderr b/src/tools/clippy/tests/ui/checked_conversions.stderr new file mode 100644 index 0000000000..18518def0a --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_conversions.stderr @@ -0,0 +1,100 @@ +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:17:13 + | +LL | let _ = value <= (u32::max_value() as i64) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` + | + = note: `-D clippy::checked-conversions` implied by `-D warnings` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:18:13 + | +LL | let _ = value <= (u32::MAX as i64) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:22:13 + | +LL | let _ = value <= i64::from(u16::max_value()) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:23:13 + | +LL | let _ = value <= i64::from(u16::MAX) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:27:13 + | +LL | let _ = value <= (u8::max_value() as isize) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:28:13 + | +LL | let _ = value <= (u8::MAX as isize) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:34:13 + | +LL | let _ = value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:35:13 + | +LL | let _ = value <= (i32::MAX as i64) && value >= (i32::MIN as i64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:39:13 + | +LL | let _ = value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:40:13 + | +LL | let _ = value <= i64::from(i16::MAX) && value >= i64::from(i16::MIN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:46:13 + | +LL | let _ = value <= i32::max_value() as u32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:47:13 + | +LL | let _ = value <= i32::MAX as u32; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:51:13 + | +LL | let _ = value <= isize::max_value() as usize && value as i32 == 5; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:52:13 + | +LL | let _ = value <= isize::MAX as usize && value as i32 == 5; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:56:13 + | +LL | let _ = value <= u16::max_value() as u32 && value as i32 == 5; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` + +error: checked cast can be simplified + --> $DIR/checked_conversions.rs:57:13 + | +LL | let _ = value <= u16::MAX as u32 && value as i32 == 5; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.rs b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.rs new file mode 100644 index 0000000000..c986c992a0 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.rs @@ -0,0 +1,54 @@ +#![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] +#![allow(clippy::if_same_then_else)] + +fn test_complex_conditions() { + let x: Result<(), ()> = Ok(()); + let y: Result<(), ()> = Ok(()); + if x.is_ok() && y.is_err() { + x.unwrap(); // unnecessary + x.unwrap_err(); // will panic + y.unwrap(); // will panic + y.unwrap_err(); // unnecessary + } else { + // not statically determinable whether any of the following will always succeed or always fail: + x.unwrap(); + x.unwrap_err(); + y.unwrap(); + y.unwrap_err(); + } + + if x.is_ok() || y.is_ok() { + // not statically determinable whether any of the following will always succeed or always fail: + x.unwrap(); + y.unwrap(); + } else { + x.unwrap(); // will panic + x.unwrap_err(); // unnecessary + y.unwrap(); // will panic + y.unwrap_err(); // unnecessary + } + let z: Result<(), ()> = Ok(()); + if x.is_ok() && !(y.is_ok() || z.is_err()) { + x.unwrap(); // unnecessary + x.unwrap_err(); // will panic + y.unwrap(); // will panic + y.unwrap_err(); // unnecessary + z.unwrap(); // unnecessary + z.unwrap_err(); // will panic + } + if x.is_ok() || !(y.is_ok() && z.is_err()) { + // not statically determinable whether any of the following will always succeed or always fail: + x.unwrap(); + y.unwrap(); + z.unwrap(); + } else { + x.unwrap(); // will panic + x.unwrap_err(); // unnecessary + y.unwrap(); // unnecessary + y.unwrap_err(); // will panic + z.unwrap(); // will panic + z.unwrap_err(); // unnecessary + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.stderr b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.stderr new file mode 100644 index 0000000000..33bb5136ef --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.stderr @@ -0,0 +1,192 @@ +error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals.rs:8:9 + | +LL | if x.is_ok() && y.is_err() { + | --------- the check is happening here +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/complex_conditionals.rs:1:35 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this call to `unwrap_err()` will always panic + --> $DIR/complex_conditionals.rs:9:9 + | +LL | if x.is_ok() && y.is_err() { + | --------- because of this check +LL | x.unwrap(); // unnecessary +LL | x.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/complex_conditionals.rs:1:9 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/complex_conditionals.rs:10:9 + | +LL | if x.is_ok() && y.is_err() { + | ---------- because of this check +... +LL | y.unwrap(); // will panic + | ^^^^^^^^^^ + +error: you checked before that `unwrap_err()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals.rs:11:9 + | +LL | if x.is_ok() && y.is_err() { + | ---------- the check is happening here +... +LL | y.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/complex_conditionals.rs:25:9 + | +LL | if x.is_ok() || y.is_ok() { + | --------- because of this check +... +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + +error: you checked before that `unwrap_err()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals.rs:26:9 + | +LL | if x.is_ok() || y.is_ok() { + | --------- the check is happening here +... +LL | x.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/complex_conditionals.rs:27:9 + | +LL | if x.is_ok() || y.is_ok() { + | --------- because of this check +... +LL | y.unwrap(); // will panic + | ^^^^^^^^^^ + +error: you checked before that `unwrap_err()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals.rs:28:9 + | +LL | if x.is_ok() || y.is_ok() { + | --------- the check is happening here +... +LL | y.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals.rs:32:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | --------- the check is happening here +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: this call to `unwrap_err()` will always panic + --> $DIR/complex_conditionals.rs:33:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | --------- because of this check +LL | x.unwrap(); // unnecessary +LL | x.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/complex_conditionals.rs:34:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | --------- because of this check +... +LL | y.unwrap(); // will panic + | ^^^^^^^^^^ + +error: you checked before that `unwrap_err()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals.rs:35:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | --------- the check is happening here +... +LL | y.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals.rs:36:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | ---------- the check is happening here +... +LL | z.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: this call to `unwrap_err()` will always panic + --> $DIR/complex_conditionals.rs:37:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | ---------- because of this check +... +LL | z.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/complex_conditionals.rs:45:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | --------- because of this check +... +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + +error: you checked before that `unwrap_err()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals.rs:46:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | --------- the check is happening here +... +LL | x.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals.rs:47:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | --------- the check is happening here +... +LL | y.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: this call to `unwrap_err()` will always panic + --> $DIR/complex_conditionals.rs:48:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | --------- because of this check +... +LL | y.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/complex_conditionals.rs:49:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | ---------- because of this check +... +LL | z.unwrap(); // will panic + | ^^^^^^^^^^ + +error: you checked before that `unwrap_err()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals.rs:50:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | ---------- the check is happening here +... +LL | z.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.rs b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.rs new file mode 100644 index 0000000000..2307996a48 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.rs @@ -0,0 +1,15 @@ +#![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] +#![allow(clippy::if_same_then_else)] + +fn test_nested() { + fn nested() { + let x = Some(()); + if x.is_some() { + x.unwrap(); // unnecessary + } else { + x.unwrap(); // will panic + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.stderr b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.stderr new file mode 100644 index 0000000000..a01f7f956f --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.stderr @@ -0,0 +1,31 @@ +error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/complex_conditionals_nested.rs:8:13 + | +LL | if x.is_some() { + | ----------- the check is happening here +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/complex_conditionals_nested.rs:1:35 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/complex_conditionals_nested.rs:10:13 + | +LL | if x.is_some() { + | ----------- because of this check +... +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/complex_conditionals_nested.rs:1:9 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.rs b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.rs new file mode 100644 index 0000000000..3696763083 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.rs @@ -0,0 +1,82 @@ +#![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] +#![allow(clippy::if_same_then_else)] + +macro_rules! m { + ($a:expr) => { + if $a.is_some() { + $a.unwrap(); // unnecessary + } + }; +} + +macro_rules! checks_in_param { + ($a:expr, $b:expr) => { + if $a { + $b; + } + }; +} + +macro_rules! checks_unwrap { + ($a:expr, $b:expr) => { + if $a.is_some() { + $b; + } + }; +} + +macro_rules! checks_some { + ($a:expr, $b:expr) => { + if $a { + $b.unwrap(); + } + }; +} + +fn main() { + let x = Some(()); + if x.is_some() { + x.unwrap(); // unnecessary + } else { + x.unwrap(); // will panic + } + if x.is_none() { + x.unwrap(); // will panic + } else { + x.unwrap(); // unnecessary + } + m!(x); + checks_in_param!(x.is_some(), x.unwrap()); // ok + checks_unwrap!(x, x.unwrap()); // ok + checks_some!(x.is_some(), x); // ok + let mut x: Result<(), ()> = Ok(()); + if x.is_ok() { + x.unwrap(); // unnecessary + x.unwrap_err(); // will panic + } else { + x.unwrap(); // will panic + x.unwrap_err(); // unnecessary + } + if x.is_err() { + x.unwrap(); // will panic + x.unwrap_err(); // unnecessary + } else { + x.unwrap(); // unnecessary + x.unwrap_err(); // will panic + } + if x.is_ok() { + x = Err(()); + // not unnecessary because of mutation of x + // it will always panic but the lint is not smart enough to see this (it only + // checks if conditions). + x.unwrap(); + } else { + x = Ok(()); + // not unnecessary because of mutation of x + // it will always panic but the lint is not smart enough to see this (it + // only checks if conditions). + x.unwrap_err(); + } + + assert!(x.is_ok(), "{:?}", x.unwrap_err()); // ok, it's a common test pattern +} diff --git a/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr new file mode 100644 index 0000000000..416ec1a01a --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -0,0 +1,131 @@ +error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/simple_conditionals.rs:39:9 + | +LL | if x.is_some() { + | ----------- the check is happening here +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/simple_conditionals.rs:1:35 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/simple_conditionals.rs:41:9 + | +LL | if x.is_some() { + | ----------- because of this check +... +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/simple_conditionals.rs:1:9 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/simple_conditionals.rs:44:9 + | +LL | if x.is_none() { + | ----------- because of this check +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + +error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/simple_conditionals.rs:46:9 + | +LL | if x.is_none() { + | ----------- the check is happening here +... +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/simple_conditionals.rs:7:13 + | +LL | if $a.is_some() { + | ------------ the check is happening here +LL | $a.unwrap(); // unnecessary + | ^^^^^^^^^^^ +... +LL | m!(x); + | ------ in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/simple_conditionals.rs:54:9 + | +LL | if x.is_ok() { + | --------- the check is happening here +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: this call to `unwrap_err()` will always panic + --> $DIR/simple_conditionals.rs:55:9 + | +LL | if x.is_ok() { + | --------- because of this check +LL | x.unwrap(); // unnecessary +LL | x.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/simple_conditionals.rs:57:9 + | +LL | if x.is_ok() { + | --------- because of this check +... +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + +error: you checked before that `unwrap_err()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/simple_conditionals.rs:58:9 + | +LL | if x.is_ok() { + | --------- the check is happening here +... +LL | x.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> $DIR/simple_conditionals.rs:61:9 + | +LL | if x.is_err() { + | ---------- because of this check +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + +error: you checked before that `unwrap_err()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/simple_conditionals.rs:62:9 + | +LL | if x.is_err() { + | ---------- the check is happening here +LL | x.unwrap(); // will panic +LL | x.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` + --> $DIR/simple_conditionals.rs:64:9 + | +LL | if x.is_err() { + | ---------- the check is happening here +... +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: this call to `unwrap_err()` will always panic + --> $DIR/simple_conditionals.rs:65:9 + | +LL | if x.is_err() { + | ---------- because of this check +... +LL | x.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/clone_on_copy.fixed b/src/tools/clippy/tests/ui/clone_on_copy.fixed new file mode 100644 index 0000000000..d924625132 --- /dev/null +++ b/src/tools/clippy/tests/ui/clone_on_copy.fixed @@ -0,0 +1,41 @@ +// run-rustfix + +#![allow( + unused, + clippy::redundant_clone, + clippy::deref_addrof, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::vec_init_then_push +)] + +use std::cell::RefCell; +use std::rc::{self, Rc}; +use std::sync::{self, Arc}; + +fn main() {} + +fn is_ascii(ch: char) -> bool { + ch.is_ascii() +} + +fn clone_on_copy() { + 42; + + vec![1].clone(); // ok, not a Copy type + Some(vec![1]).clone(); // ok, not a Copy type + *(&42); + + let rc = RefCell::new(0); + *rc.borrow(); + + // Issue #4348 + let mut x = 43; + let _ = &x.clone(); // ok, getting a ref + 'a'.clone().make_ascii_uppercase(); // ok, clone and then mutate + is_ascii('z'); + + // Issue #5436 + let mut vec = Vec::new(); + vec.push(42); +} diff --git a/src/tools/clippy/tests/ui/clone_on_copy.rs b/src/tools/clippy/tests/ui/clone_on_copy.rs new file mode 100644 index 0000000000..97f4946724 --- /dev/null +++ b/src/tools/clippy/tests/ui/clone_on_copy.rs @@ -0,0 +1,41 @@ +// run-rustfix + +#![allow( + unused, + clippy::redundant_clone, + clippy::deref_addrof, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::vec_init_then_push +)] + +use std::cell::RefCell; +use std::rc::{self, Rc}; +use std::sync::{self, Arc}; + +fn main() {} + +fn is_ascii(ch: char) -> bool { + ch.is_ascii() +} + +fn clone_on_copy() { + 42.clone(); + + vec![1].clone(); // ok, not a Copy type + Some(vec![1]).clone(); // ok, not a Copy type + (&42).clone(); + + let rc = RefCell::new(0); + rc.borrow().clone(); + + // Issue #4348 + let mut x = 43; + let _ = &x.clone(); // ok, getting a ref + 'a'.clone().make_ascii_uppercase(); // ok, clone and then mutate + is_ascii('z'.clone()); + + // Issue #5436 + let mut vec = Vec::new(); + vec.push(42.clone()); +} diff --git a/src/tools/clippy/tests/ui/clone_on_copy.stderr b/src/tools/clippy/tests/ui/clone_on_copy.stderr new file mode 100644 index 0000000000..7a706884fb --- /dev/null +++ b/src/tools/clippy/tests/ui/clone_on_copy.stderr @@ -0,0 +1,34 @@ +error: using `clone` on type `i32` which implements the `Copy` trait + --> $DIR/clone_on_copy.rs:23:5 + | +LL | 42.clone(); + | ^^^^^^^^^^ help: try removing the `clone` call: `42` + | + = note: `-D clippy::clone-on-copy` implied by `-D warnings` + +error: using `clone` on type `i32` which implements the `Copy` trait + --> $DIR/clone_on_copy.rs:27:5 + | +LL | (&42).clone(); + | ^^^^^^^^^^^^^ help: try dereferencing it: `*(&42)` + +error: using `clone` on type `i32` which implements the `Copy` trait + --> $DIR/clone_on_copy.rs:30:5 + | +LL | rc.borrow().clone(); + | ^^^^^^^^^^^^^^^^^^^ help: try dereferencing it: `*rc.borrow()` + +error: using `clone` on type `char` which implements the `Copy` trait + --> $DIR/clone_on_copy.rs:36:14 + | +LL | is_ascii('z'.clone()); + | ^^^^^^^^^^^ help: try removing the `clone` call: `'z'` + +error: using `clone` on type `i32` which implements the `Copy` trait + --> $DIR/clone_on_copy.rs:40:14 + | +LL | vec.push(42.clone()); + | ^^^^^^^^^^ help: try removing the `clone` call: `42` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/clone_on_copy_impl.rs b/src/tools/clippy/tests/ui/clone_on_copy_impl.rs new file mode 100644 index 0000000000..8f9f2a0db8 --- /dev/null +++ b/src/tools/clippy/tests/ui/clone_on_copy_impl.rs @@ -0,0 +1,22 @@ +use std::fmt; +use std::marker::PhantomData; + +pub struct Key { + #[doc(hidden)] + pub __name: &'static str, + #[doc(hidden)] + pub __phantom: PhantomData, +} + +impl Copy for Key {} + +impl Clone for Key { + fn clone(&self) -> Self { + Key { + __name: self.__name, + __phantom: self.__phantom, + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/clone_on_copy_mut.rs b/src/tools/clippy/tests/ui/clone_on_copy_mut.rs new file mode 100644 index 0000000000..5bfa256623 --- /dev/null +++ b/src/tools/clippy/tests/ui/clone_on_copy_mut.rs @@ -0,0 +1,18 @@ +pub fn dec_read_dec(i: &mut i32) -> i32 { + *i -= 1; + let ret = *i; + *i -= 1; + ret +} + +pub fn minus_1(i: &i32) -> i32 { + dec_read_dec(&mut i.clone()) +} + +fn main() { + let mut i = 10; + assert_eq!(minus_1(&i), 9); + assert_eq!(i, 10); + assert_eq!(dec_read_dec(&mut i), 9); + assert_eq!(i, 8); +} diff --git a/src/tools/clippy/tests/ui/cmp_nan.rs b/src/tools/clippy/tests/ui/cmp_nan.rs new file mode 100644 index 0000000000..64ca52b010 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_nan.rs @@ -0,0 +1,34 @@ +const NAN_F32: f32 = f32::NAN; +const NAN_F64: f64 = f64::NAN; + +#[warn(clippy::cmp_nan)] +#[allow(clippy::float_cmp, clippy::no_effect, clippy::unnecessary_operation)] +fn main() { + let x = 5f32; + x == f32::NAN; + x != f32::NAN; + x < f32::NAN; + x > f32::NAN; + x <= f32::NAN; + x >= f32::NAN; + x == NAN_F32; + x != NAN_F32; + x < NAN_F32; + x > NAN_F32; + x <= NAN_F32; + x >= NAN_F32; + + let y = 0f64; + y == f64::NAN; + y != f64::NAN; + y < f64::NAN; + y > f64::NAN; + y <= f64::NAN; + y >= f64::NAN; + y == NAN_F64; + y != NAN_F64; + y < NAN_F64; + y > NAN_F64; + y <= NAN_F64; + y >= NAN_F64; +} diff --git a/src/tools/clippy/tests/ui/cmp_nan.stderr b/src/tools/clippy/tests/ui/cmp_nan.stderr new file mode 100644 index 0000000000..867516661a --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_nan.stderr @@ -0,0 +1,148 @@ +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:8:5 + | +LL | x == f32::NAN; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::cmp-nan` implied by `-D warnings` + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:9:5 + | +LL | x != f32::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:10:5 + | +LL | x < f32::NAN; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:11:5 + | +LL | x > f32::NAN; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:12:5 + | +LL | x <= f32::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:13:5 + | +LL | x >= f32::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:14:5 + | +LL | x == NAN_F32; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:15:5 + | +LL | x != NAN_F32; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:16:5 + | +LL | x < NAN_F32; + | ^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:17:5 + | +LL | x > NAN_F32; + | ^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:18:5 + | +LL | x <= NAN_F32; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:19:5 + | +LL | x >= NAN_F32; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:22:5 + | +LL | y == f64::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:23:5 + | +LL | y != f64::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:24:5 + | +LL | y < f64::NAN; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:25:5 + | +LL | y > f64::NAN; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:26:5 + | +LL | y <= f64::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:27:5 + | +LL | y >= f64::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:28:5 + | +LL | y == NAN_F64; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:29:5 + | +LL | y != NAN_F64; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:30:5 + | +LL | y < NAN_F64; + | ^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:31:5 + | +LL | y > NAN_F64; + | ^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:32:5 + | +LL | y <= NAN_F64; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:33:5 + | +LL | y >= NAN_F64; + | ^^^^^^^^^^^^ + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/cmp_null.rs b/src/tools/clippy/tests/ui/cmp_null.rs new file mode 100644 index 0000000000..2d2d04178c --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_null.rs @@ -0,0 +1,17 @@ +#![warn(clippy::cmp_null)] +#![allow(unused_mut)] + +use std::ptr; + +fn main() { + let x = 0; + let p: *const usize = &x; + if p == ptr::null() { + println!("This is surprising!"); + } + let mut y = 0; + let mut m: *mut usize = &mut y; + if m == ptr::null_mut() { + println!("This is surprising, too!"); + } +} diff --git a/src/tools/clippy/tests/ui/cmp_null.stderr b/src/tools/clippy/tests/ui/cmp_null.stderr new file mode 100644 index 0000000000..a1f4c70fb2 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_null.stderr @@ -0,0 +1,16 @@ +error: comparing with null is better expressed by the `.is_null()` method + --> $DIR/cmp_null.rs:9:8 + | +LL | if p == ptr::null() { + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::cmp-null` implied by `-D warnings` + +error: comparing with null is better expressed by the `.is_null()` method + --> $DIR/cmp_null.rs:14:8 + | +LL | if m == ptr::null_mut() { + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/cmp_owned/asymmetric_partial_eq.fixed b/src/tools/clippy/tests/ui/cmp_owned/asymmetric_partial_eq.fixed new file mode 100644 index 0000000000..3305ac9bf8 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/asymmetric_partial_eq.fixed @@ -0,0 +1,93 @@ +// run-rustfix +#![allow(unused, clippy::redundant_clone)] // See #5700 + +// Define the types in each module to avoid trait impls leaking between modules. +macro_rules! impl_types { + () => { + #[derive(PartialEq)] + pub struct Owned; + + pub struct Borrowed; + + impl ToOwned for Borrowed { + type Owned = Owned; + fn to_owned(&self) -> Owned { + Owned {} + } + } + + impl std::borrow::Borrow for Owned { + fn borrow(&self) -> &Borrowed { + static VALUE: Borrowed = Borrowed {}; + &VALUE + } + } + }; +} + +// Only Borrowed == Owned is implemented +mod borrowed_eq_owned { + impl_types!(); + + impl PartialEq for Borrowed { + fn eq(&self, _: &Owned) -> bool { + true + } + } + + pub fn compare() { + let owned = Owned {}; + let borrowed = Borrowed {}; + + if borrowed == owned {} + if borrowed == owned {} + } +} + +// Only Owned == Borrowed is implemented +mod owned_eq_borrowed { + impl_types!(); + + impl PartialEq for Owned { + fn eq(&self, _: &Borrowed) -> bool { + true + } + } + + fn compare() { + let owned = Owned {}; + let borrowed = Borrowed {}; + + if owned == borrowed {} + if owned == borrowed {} + } +} + +mod issue_4874 { + impl_types!(); + + // NOTE: PartialEq for T can't be implemented due to the orphan rules + impl PartialEq for Borrowed + where + T: AsRef + ?Sized, + { + fn eq(&self, _: &T) -> bool { + true + } + } + + impl std::fmt::Display for Borrowed { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "borrowed") + } + } + + fn compare() { + let borrowed = Borrowed {}; + + if borrowed == "Hi" {} + if borrowed == "Hi" {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/cmp_owned/asymmetric_partial_eq.rs b/src/tools/clippy/tests/ui/cmp_owned/asymmetric_partial_eq.rs new file mode 100644 index 0000000000..88bc2f51dd --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/asymmetric_partial_eq.rs @@ -0,0 +1,93 @@ +// run-rustfix +#![allow(unused, clippy::redundant_clone)] // See #5700 + +// Define the types in each module to avoid trait impls leaking between modules. +macro_rules! impl_types { + () => { + #[derive(PartialEq)] + pub struct Owned; + + pub struct Borrowed; + + impl ToOwned for Borrowed { + type Owned = Owned; + fn to_owned(&self) -> Owned { + Owned {} + } + } + + impl std::borrow::Borrow for Owned { + fn borrow(&self) -> &Borrowed { + static VALUE: Borrowed = Borrowed {}; + &VALUE + } + } + }; +} + +// Only Borrowed == Owned is implemented +mod borrowed_eq_owned { + impl_types!(); + + impl PartialEq for Borrowed { + fn eq(&self, _: &Owned) -> bool { + true + } + } + + pub fn compare() { + let owned = Owned {}; + let borrowed = Borrowed {}; + + if borrowed.to_owned() == owned {} + if owned == borrowed.to_owned() {} + } +} + +// Only Owned == Borrowed is implemented +mod owned_eq_borrowed { + impl_types!(); + + impl PartialEq for Owned { + fn eq(&self, _: &Borrowed) -> bool { + true + } + } + + fn compare() { + let owned = Owned {}; + let borrowed = Borrowed {}; + + if owned == borrowed.to_owned() {} + if borrowed.to_owned() == owned {} + } +} + +mod issue_4874 { + impl_types!(); + + // NOTE: PartialEq for T can't be implemented due to the orphan rules + impl PartialEq for Borrowed + where + T: AsRef + ?Sized, + { + fn eq(&self, _: &T) -> bool { + true + } + } + + impl std::fmt::Display for Borrowed { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "borrowed") + } + } + + fn compare() { + let borrowed = Borrowed {}; + + if "Hi" == borrowed.to_string() {} + if borrowed.to_string() == "Hi" {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/cmp_owned/asymmetric_partial_eq.stderr b/src/tools/clippy/tests/ui/cmp_owned/asymmetric_partial_eq.stderr new file mode 100644 index 0000000000..43bf8851fc --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/asymmetric_partial_eq.stderr @@ -0,0 +1,46 @@ +error: this creates an owned instance just for comparison + --> $DIR/asymmetric_partial_eq.rs:42:12 + | +LL | if borrowed.to_owned() == owned {} + | ^^^^^^^^^^^^^^^^^^^ help: try: `borrowed` + | + = note: `-D clippy::cmp-owned` implied by `-D warnings` + +error: this creates an owned instance just for comparison + --> $DIR/asymmetric_partial_eq.rs:43:21 + | +LL | if owned == borrowed.to_owned() {} + | ---------^^^^^^^^^^^^^^^^^^^ + | | + | help: try: `borrowed == owned` + +error: this creates an owned instance just for comparison + --> $DIR/asymmetric_partial_eq.rs:61:21 + | +LL | if owned == borrowed.to_owned() {} + | ^^^^^^^^^^^^^^^^^^^ help: try: `borrowed` + +error: this creates an owned instance just for comparison + --> $DIR/asymmetric_partial_eq.rs:62:12 + | +LL | if borrowed.to_owned() == owned {} + | ^^^^^^^^^^^^^^^^^^^--------- + | | + | help: try: `owned == borrowed` + +error: this creates an owned instance just for comparison + --> $DIR/asymmetric_partial_eq.rs:88:20 + | +LL | if "Hi" == borrowed.to_string() {} + | --------^^^^^^^^^^^^^^^^^^^^ + | | + | help: try: `borrowed == "Hi"` + +error: this creates an owned instance just for comparison + --> $DIR/asymmetric_partial_eq.rs:89:12 + | +LL | if borrowed.to_string() == "Hi" {} + | ^^^^^^^^^^^^^^^^^^^^ help: try: `borrowed` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.fixed b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.fixed new file mode 100644 index 0000000000..05fb96339e --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.fixed @@ -0,0 +1,72 @@ +// run-rustfix + +#[warn(clippy::cmp_owned)] +#[allow(clippy::unnecessary_operation, clippy::no_effect, unused_must_use, clippy::eq_op)] +fn main() { + fn with_to_string(x: &str) { + x != "foo"; + + "foo" != x; + } + + let x = "oh"; + + with_to_string(x); + + x != "foo"; + + x != "foo"; + + 42.to_string() == "42"; + + Foo == Foo; + + "abc".chars().filter(|c| *c != 'X'); + + "abc".chars().filter(|c| *c != 'X'); +} + +struct Foo; + +impl PartialEq for Foo { + // Allow this here, because it emits the lint + // without a suggestion. This is tested in + // `tests/ui/cmp_owned/without_suggestion.rs` + #[allow(clippy::cmp_owned)] + fn eq(&self, other: &Self) -> bool { + self.to_owned() == *other + } +} + +impl ToOwned for Foo { + type Owned = Bar; + fn to_owned(&self) -> Bar { + Bar + } +} + +#[derive(PartialEq)] +struct Bar; + +impl PartialEq for Bar { + fn eq(&self, _: &Foo) -> bool { + true + } +} + +impl std::borrow::Borrow for Bar { + fn borrow(&self) -> &Foo { + static FOO: Foo = Foo; + &FOO + } +} + +#[derive(PartialEq)] +struct Baz; + +impl ToOwned for Baz { + type Owned = Baz; + fn to_owned(&self) -> Baz { + Baz + } +} diff --git a/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.rs b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.rs new file mode 100644 index 0000000000..0a02825ed8 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.rs @@ -0,0 +1,72 @@ +// run-rustfix + +#[warn(clippy::cmp_owned)] +#[allow(clippy::unnecessary_operation, clippy::no_effect, unused_must_use, clippy::eq_op)] +fn main() { + fn with_to_string(x: &str) { + x != "foo".to_string(); + + "foo".to_string() != x; + } + + let x = "oh"; + + with_to_string(x); + + x != "foo".to_owned(); + + x != String::from("foo"); + + 42.to_string() == "42"; + + Foo.to_owned() == Foo; + + "abc".chars().filter(|c| c.to_owned() != 'X'); + + "abc".chars().filter(|c| *c != 'X'); +} + +struct Foo; + +impl PartialEq for Foo { + // Allow this here, because it emits the lint + // without a suggestion. This is tested in + // `tests/ui/cmp_owned/without_suggestion.rs` + #[allow(clippy::cmp_owned)] + fn eq(&self, other: &Self) -> bool { + self.to_owned() == *other + } +} + +impl ToOwned for Foo { + type Owned = Bar; + fn to_owned(&self) -> Bar { + Bar + } +} + +#[derive(PartialEq)] +struct Bar; + +impl PartialEq for Bar { + fn eq(&self, _: &Foo) -> bool { + true + } +} + +impl std::borrow::Borrow for Bar { + fn borrow(&self) -> &Foo { + static FOO: Foo = Foo; + &FOO + } +} + +#[derive(PartialEq)] +struct Baz; + +impl ToOwned for Baz { + type Owned = Baz; + fn to_owned(&self) -> Baz { + Baz + } +} diff --git a/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.stderr b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.stderr new file mode 100644 index 0000000000..2f333e6ea8 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.stderr @@ -0,0 +1,40 @@ +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:7:14 + | +LL | x != "foo".to_string(); + | ^^^^^^^^^^^^^^^^^ help: try: `"foo"` + | + = note: `-D clippy::cmp-owned` implied by `-D warnings` + +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:9:9 + | +LL | "foo".to_string() != x; + | ^^^^^^^^^^^^^^^^^ help: try: `"foo"` + +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:16:10 + | +LL | x != "foo".to_owned(); + | ^^^^^^^^^^^^^^^^ help: try: `"foo"` + +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:18:10 + | +LL | x != String::from("foo"); + | ^^^^^^^^^^^^^^^^^^^ help: try: `"foo"` + +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:22:5 + | +LL | Foo.to_owned() == Foo; + | ^^^^^^^^^^^^^^ help: try: `Foo` + +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:24:30 + | +LL | "abc".chars().filter(|c| c.to_owned() != 'X'); + | ^^^^^^^^^^^^ help: try: `*c` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.rs b/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.rs new file mode 100644 index 0000000000..f44a3901fb --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.rs @@ -0,0 +1,53 @@ +#[allow(clippy::unnecessary_operation)] +#[allow(clippy::implicit_clone)] + +fn main() { + let x = &Baz; + let y = &Baz; + y.to_owned() == *x; + + let x = &&Baz; + let y = &Baz; + y.to_owned() == **x; +} + +struct Foo; + +impl PartialEq for Foo { + fn eq(&self, other: &Self) -> bool { + self.to_owned() == *other + } +} + +impl ToOwned for Foo { + type Owned = Bar; + fn to_owned(&self) -> Bar { + Bar + } +} + +#[derive(PartialEq)] +struct Baz; + +impl ToOwned for Baz { + type Owned = Baz; + fn to_owned(&self) -> Baz { + Baz + } +} + +#[derive(PartialEq)] +struct Bar; + +impl PartialEq for Bar { + fn eq(&self, _: &Foo) -> bool { + true + } +} + +impl std::borrow::Borrow for Bar { + fn borrow(&self) -> &Foo { + static FOO: Foo = Foo; + &FOO + } +} diff --git a/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.stderr b/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.stderr new file mode 100644 index 0000000000..2ea3d8fac0 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.stderr @@ -0,0 +1,22 @@ +error: this creates an owned instance just for comparison + --> $DIR/without_suggestion.rs:7:5 + | +LL | y.to_owned() == *x; + | ^^^^^^^^^^^^^^^^^^ try implementing the comparison without allocating + | + = note: `-D clippy::cmp-owned` implied by `-D warnings` + +error: this creates an owned instance just for comparison + --> $DIR/without_suggestion.rs:11:5 + | +LL | y.to_owned() == **x; + | ^^^^^^^^^^^^^^^^^^^ try implementing the comparison without allocating + +error: this creates an owned instance just for comparison + --> $DIR/without_suggestion.rs:18:9 + | +LL | self.to_owned() == *other + | ^^^^^^^^^^^^^^^^^^^^^^^^^ try implementing the comparison without allocating + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/cognitive_complexity.rs b/src/tools/clippy/tests/ui/cognitive_complexity.rs new file mode 100644 index 0000000000..912e6788af --- /dev/null +++ b/src/tools/clippy/tests/ui/cognitive_complexity.rs @@ -0,0 +1,395 @@ +#![allow(clippy::all)] +#![warn(clippy::cognitive_complexity)] +#![allow(unused, unused_crate_dependencies)] + +#[rustfmt::skip] +fn main() { + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } +} + +#[clippy::cognitive_complexity = "1"] +fn kaboom() { + let n = 0; + 'a: for i in 0..20 { + 'b: for j in i..20 { + for k in j..20 { + if k == 5 { + break 'b; + } + if j == 3 && k == 6 { + continue 'a; + } + if k == j { + continue; + } + println!("bake"); + } + } + println!("cake"); + } +} + +fn bloo() { + match 42 { + 0 => println!("hi"), + 1 => println!("hai"), + 2 => println!("hey"), + 3 => println!("hallo"), + 4 => println!("hello"), + 5 => println!("salut"), + 6 => println!("good morning"), + 7 => println!("good evening"), + 8 => println!("good afternoon"), + 9 => println!("good night"), + 10 => println!("bonjour"), + 11 => println!("hej"), + 12 => println!("hej hej"), + 13 => println!("greetings earthling"), + 14 => println!("take us to you leader"), + 15 | 17 | 19 | 21 | 23 | 25 | 27 | 29 | 31 | 33 => println!("take us to you leader"), + 35 | 37 | 39 | 41 | 43 | 45 | 47 | 49 | 51 | 53 => println!("there is no undefined behavior"), + 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 73 => println!("I know borrow-fu"), + _ => println!("bye"), + } +} + +// Short circuiting operations don't increase the complexity of a function. +// Note that the minimum complexity of a function is 1. +#[clippy::cognitive_complexity = "1"] +fn lots_of_short_circuits() -> bool { + true && false && true && false && true && false && true +} + +#[clippy::cognitive_complexity = "1"] +fn lots_of_short_circuits2() -> bool { + true || false || true || false || true || false || true +} + +#[clippy::cognitive_complexity = "1"] +fn baa() { + let x = || match 99 { + 0 => 0, + 1 => 1, + 2 => 2, + 4 => 4, + 6 => 6, + 9 => 9, + _ => 42, + }; + if x() == 42 { + println!("x"); + } else { + println!("not x"); + } +} + +#[clippy::cognitive_complexity = "1"] +fn bar() { + match 99 { + 0 => println!("hi"), + _ => println!("bye"), + } +} + +#[test] +#[clippy::cognitive_complexity = "1"] +/// Tests are usually complex but simple at the same time. `clippy::cognitive_complexity` used to +/// give lots of false-positives in tests. +fn dont_warn_on_tests() { + match 99 { + 0 => println!("hi"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barr() { + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barr2() { + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barrr() { + match 99 { + 0 => println!("hi"), + 1 => panic!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barrr2() { + match 99 { + 0 => println!("hi"), + 1 => panic!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } + match 99 { + 0 => println!("hi"), + 1 => panic!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barrrr() { + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => panic!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barrrr2() { + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => panic!("blub"), + _ => println!("bye"), + } + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => panic!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn cake() { + if 4 == 5 { + println!("yea"); + } else { + panic!("meh"); + } + println!("whee"); +} + +#[clippy::cognitive_complexity = "1"] +pub fn read_file(input_path: &str) -> String { + use std::fs::File; + use std::io::{Read, Write}; + use std::path::Path; + let mut file = match File::open(&Path::new(input_path)) { + Ok(f) => f, + Err(err) => { + panic!("Can't open {}: {}", input_path, err); + }, + }; + + let mut bytes = Vec::new(); + + match file.read_to_end(&mut bytes) { + Ok(..) => {}, + Err(_) => { + panic!("Can't read {}", input_path); + }, + }; + + match String::from_utf8(bytes) { + Ok(contents) => contents, + Err(_) => { + panic!("{} is not UTF-8 encoded", input_path); + }, + } +} + +enum Void {} + +#[clippy::cognitive_complexity = "1"] +fn void(void: Void) { + if true { + match void {} + } +} + +#[clippy::cognitive_complexity = "1"] +fn mcarton_sees_all() { + panic!("meh"); + panic!("möh"); +} + +#[clippy::cognitive_complexity = "1"] +fn try_() -> Result { + match 5 { + 5 => Ok(5), + _ => return Err("bla"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn try_again() -> Result { + let _ = Ok(42)?; + let _ = Ok(43)?; + let _ = Ok(44)?; + let _ = Ok(45)?; + let _ = Ok(46)?; + let _ = Ok(47)?; + let _ = Ok(48)?; + let _ = Ok(49)?; + match 5 { + 5 => Ok(5), + _ => return Err("bla"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn early() -> Result { + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); +} + +#[rustfmt::skip] +#[clippy::cognitive_complexity = "1"] +fn early_ret() -> i32 { + let a = if true { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + match 5 { + 5 => 5, + _ => return 6, + } +} + +#[clippy::cognitive_complexity = "1"] +fn closures() { + let x = |a: i32, b: i32| -> i32 { + if true { + println!("moo"); + } + + a + b + }; +} + +struct Moo; + +#[clippy::cognitive_complexity = "1"] +impl Moo { + fn moo(&self) { + if true { + println!("moo"); + } + } +} diff --git a/src/tools/clippy/tests/ui/cognitive_complexity.stderr b/src/tools/clippy/tests/ui/cognitive_complexity.stderr new file mode 100644 index 0000000000..a0ddc673ab --- /dev/null +++ b/src/tools/clippy/tests/ui/cognitive_complexity.stderr @@ -0,0 +1,139 @@ +error: the function has a cognitive complexity of (28/25) + --> $DIR/cognitive_complexity.rs:6:4 + | +LL | fn main() { + | ^^^^ + | + = note: `-D clippy::cognitive-complexity` implied by `-D warnings` + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (7/1) + --> $DIR/cognitive_complexity.rs:91:4 + | +LL | fn kaboom() { + | ^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:149:4 + | +LL | fn baa() { + | ^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:150:13 + | +LL | let x = || match 99 { + | ^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:167:4 + | +LL | fn bar() { + | ^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:186:4 + | +LL | fn barr() { + | ^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (3/1) + --> $DIR/cognitive_complexity.rs:196:4 + | +LL | fn barr2() { + | ^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:212:4 + | +LL | fn barrr() { + | ^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (3/1) + --> $DIR/cognitive_complexity.rs:222:4 + | +LL | fn barrr2() { + | ^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:238:4 + | +LL | fn barrrr() { + | ^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (3/1) + --> $DIR/cognitive_complexity.rs:248:4 + | +LL | fn barrrr2() { + | ^^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:264:4 + | +LL | fn cake() { + | ^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (4/1) + --> $DIR/cognitive_complexity.rs:274:8 + | +LL | pub fn read_file(input_path: &str) -> String { + | ^^^^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:305:4 + | +LL | fn void(void: Void) { + | ^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (8/1) + --> $DIR/cognitive_complexity.rs:356:4 + | +LL | fn early_ret() -> i32 { + | ^^^^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:377:13 + | +LL | let x = |a: i32, b: i32| -> i32 { + | ^^^^^^^^^^^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:390:8 + | +LL | fn moo(&self) { + | ^^^ + | + = help: you could split it up into multiple smaller functions + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.rs b/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.rs new file mode 100644 index 0000000000..771a26fc9a --- /dev/null +++ b/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.rs @@ -0,0 +1,15 @@ +#![warn(unused, clippy::cognitive_complexity)] +#![allow(unused_crate_dependencies)] + +fn main() { + kaboom(); +} + +#[clippy::cognitive_complexity = "0"] +fn kaboom() { + if 42 == 43 { + panic!(); + } else if "cake" == "lie" { + println!("what?"); + } +} diff --git a/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.stderr b/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.stderr new file mode 100644 index 0000000000..f5ff53dda6 --- /dev/null +++ b/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.stderr @@ -0,0 +1,11 @@ +error: the function has a cognitive complexity of (3/0) + --> $DIR/cognitive_complexity_attr_used.rs:9:4 + | +LL | fn kaboom() { + | ^^^^^^ + | + = note: `-D clippy::cognitive-complexity` implied by `-D warnings` + = help: you could split it up into multiple smaller functions + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/collapsible_else_if.fixed b/src/tools/clippy/tests/ui/collapsible_else_if.fixed new file mode 100644 index 0000000000..c69a46f0a7 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_else_if.fixed @@ -0,0 +1,77 @@ +// run-rustfix +#![allow(clippy::assertions_on_constants)] + +#[rustfmt::skip] +#[warn(clippy::collapsible_if)] +#[warn(clippy::collapsible_else_if)] + +fn main() { + let x = "hello"; + let y = "world"; + // Collapse `else { if .. }` to `else if ..` + if x == "hello" { + print!("Hello "); + } else if y == "world" { + println!("world!") + } + + if x == "hello" { + print!("Hello "); + } else if let Some(42) = Some(42) { + println!("world!") + } + + if x == "hello" { + print!("Hello "); + } else if y == "world" { + println!("world") + } + else { + println!("!") + } + + if x == "hello" { + print!("Hello "); + } else if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else if x == "hello" { + println!("world") + } + else { + println!("!") + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + + if x == "hello" { + print!("Hello "); + } else { + #[cfg(not(roflol))] + if y == "world" { + println!("world!") + } + } +} diff --git a/src/tools/clippy/tests/ui/collapsible_else_if.rs b/src/tools/clippy/tests/ui/collapsible_else_if.rs new file mode 100644 index 0000000000..1359c7eb62 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_else_if.rs @@ -0,0 +1,91 @@ +// run-rustfix +#![allow(clippy::assertions_on_constants)] + +#[rustfmt::skip] +#[warn(clippy::collapsible_if)] +#[warn(clippy::collapsible_else_if)] + +fn main() { + let x = "hello"; + let y = "world"; + // Collapse `else { if .. }` to `else if ..` + if x == "hello" { + print!("Hello "); + } else { + if y == "world" { + println!("world!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + if let Some(42) = Some(42) { + println!("world!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + if y == "world" { + println!("world") + } + else { + println!("!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else { + if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else { + if x == "hello" { + println!("world") + } + else { + println!("!") + } + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else { + if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + #[cfg(not(roflol))] + if y == "world" { + println!("world!") + } + } +} diff --git a/src/tools/clippy/tests/ui/collapsible_else_if.stderr b/src/tools/clippy/tests/ui/collapsible_else_if.stderr new file mode 100644 index 0000000000..ee3e11ae56 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_else_if.stderr @@ -0,0 +1,154 @@ +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:14:12 + | +LL | } else { + | ____________^ +LL | | if y == "world" { +LL | | println!("world!") +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::collapsible-else-if` implied by `-D warnings` +help: collapse nested if block + | +LL | } else if y == "world" { +LL | println!("world!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:22:12 + | +LL | } else { + | ____________^ +LL | | if let Some(42) = Some(42) { +LL | | println!("world!") +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | } else if let Some(42) = Some(42) { +LL | println!("world!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:30:12 + | +LL | } else { + | ____________^ +LL | | if y == "world" { +LL | | println!("world") +LL | | } +... | +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | } else if y == "world" { +LL | println!("world") +LL | } +LL | else { +LL | println!("!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:41:12 + | +LL | } else { + | ____________^ +LL | | if let Some(42) = Some(42) { +LL | | println!("world") +LL | | } +... | +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | } else if let Some(42) = Some(42) { +LL | println!("world") +LL | } +LL | else { +LL | println!("!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:52:12 + | +LL | } else { + | ____________^ +LL | | if let Some(42) = Some(42) { +LL | | println!("world") +LL | | } +... | +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | } else if let Some(42) = Some(42) { +LL | println!("world") +LL | } +LL | else { +LL | println!("!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:63:12 + | +LL | } else { + | ____________^ +LL | | if x == "hello" { +LL | | println!("world") +LL | | } +... | +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | } else if x == "hello" { +LL | println!("world") +LL | } +LL | else { +LL | println!("!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:74:12 + | +LL | } else { + | ____________^ +LL | | if let Some(42) = Some(42) { +LL | | println!("world") +LL | | } +... | +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | } else if let Some(42) = Some(42) { +LL | println!("world") +LL | } +LL | else { +LL | println!("!") +LL | } + | + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/collapsible_if.fixed b/src/tools/clippy/tests/ui/collapsible_if.fixed new file mode 100644 index 0000000000..e4c088bf6f --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_if.fixed @@ -0,0 +1,148 @@ +// run-rustfix +#![allow(clippy::assertions_on_constants)] + +#[rustfmt::skip] +#[warn(clippy::collapsible_if)] +fn main() { + let x = "hello"; + let y = "world"; + if x == "hello" && y == "world" { + println!("Hello world!"); + } + + if (x == "hello" || x == "world") && (y == "world" || y == "hello") { + println!("Hello world!"); + } + + if x == "hello" && x == "world" && (y == "world" || y == "hello") { + println!("Hello world!"); + } + + if (x == "hello" || x == "world") && y == "world" && y == "hello" { + println!("Hello world!"); + } + + if x == "hello" && x == "world" && y == "world" && y == "hello" { + println!("Hello world!"); + } + + if 42 == 1337 && 'a' != 'A' { + println!("world!") + } + + // Works because any if with an else statement cannot be collapsed. + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } + } else { + println!("Not Hello world"); + } + + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } else { + println!("Hello something else"); + } + } + + if x == "hello" { + print!("Hello "); + if y == "world" { + println!("world!") + } + } + + if true { + } else { + assert!(true); // assert! is just an `if` + } + + + // The following tests check for the fix of https://github.com/rust-lang/rust-clippy/issues/798 + if x == "hello" {// Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { // Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { + // Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" && y == "world" { // Collapsible + println!("Hello world!"); + } + + if x == "hello" { + print!("Hello "); + } else { + // Not collapsible + if y == "world" { + println!("world!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + // Not collapsible + if let Some(42) = Some(42) { + println!("world!") + } + } + + if x == "hello" { + /* Not collapsible */ + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { /* Not collapsible */ + if y == "world" { + println!("Hello world!"); + } + } + + // Test behavior wrt. `let_chains`. + // None of the cases below should be collapsed. + fn truth() -> bool { true } + + // Prefix: + if let 0 = 1 { + if truth() {} + } + + // Suffix: + if truth() { + if let 0 = 1 {} + } + + // Midfix: + if truth() { + if let 0 = 1 { + if truth() {} + } + } + + // Fix #5962 + if matches!(true, true) && matches!(true, true) {} + + if true { + #[cfg(not(teehee))] + if true { + println!("Hello world!"); + } + } +} diff --git a/src/tools/clippy/tests/ui/collapsible_if.rs b/src/tools/clippy/tests/ui/collapsible_if.rs new file mode 100644 index 0000000000..d6cf01c831 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_if.rs @@ -0,0 +1,164 @@ +// run-rustfix +#![allow(clippy::assertions_on_constants)] + +#[rustfmt::skip] +#[warn(clippy::collapsible_if)] +fn main() { + let x = "hello"; + let y = "world"; + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" || x == "world" { + if y == "world" || y == "hello" { + println!("Hello world!"); + } + } + + if x == "hello" && x == "world" { + if y == "world" || y == "hello" { + println!("Hello world!"); + } + } + + if x == "hello" || x == "world" { + if y == "world" && y == "hello" { + println!("Hello world!"); + } + } + + if x == "hello" && x == "world" { + if y == "world" && y == "hello" { + println!("Hello world!"); + } + } + + if 42 == 1337 { + if 'a' != 'A' { + println!("world!") + } + } + + // Works because any if with an else statement cannot be collapsed. + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } + } else { + println!("Not Hello world"); + } + + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } else { + println!("Hello something else"); + } + } + + if x == "hello" { + print!("Hello "); + if y == "world" { + println!("world!") + } + } + + if true { + } else { + assert!(true); // assert! is just an `if` + } + + + // The following tests check for the fix of https://github.com/rust-lang/rust-clippy/issues/798 + if x == "hello" {// Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { // Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { + // Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { + if y == "world" { // Collapsible + println!("Hello world!"); + } + } + + if x == "hello" { + print!("Hello "); + } else { + // Not collapsible + if y == "world" { + println!("world!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + // Not collapsible + if let Some(42) = Some(42) { + println!("world!") + } + } + + if x == "hello" { + /* Not collapsible */ + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { /* Not collapsible */ + if y == "world" { + println!("Hello world!"); + } + } + + // Test behavior wrt. `let_chains`. + // None of the cases below should be collapsed. + fn truth() -> bool { true } + + // Prefix: + if let 0 = 1 { + if truth() {} + } + + // Suffix: + if truth() { + if let 0 = 1 {} + } + + // Midfix: + if truth() { + if let 0 = 1 { + if truth() {} + } + } + + // Fix #5962 + if matches!(true, true) { + if matches!(true, true) {} + } + + if true { + #[cfg(not(teehee))] + if true { + println!("Hello world!"); + } + } +} diff --git a/src/tools/clippy/tests/ui/collapsible_if.stderr b/src/tools/clippy/tests/ui/collapsible_if.stderr new file mode 100644 index 0000000000..acd1ec3f2c --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_if.stderr @@ -0,0 +1,130 @@ +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:9:5 + | +LL | / if x == "hello" { +LL | | if y == "world" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::collapsible-if` implied by `-D warnings` +help: collapse nested if block + | +LL | if x == "hello" && y == "world" { +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:15:5 + | +LL | / if x == "hello" || x == "world" { +LL | | if y == "world" || y == "hello" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | if (x == "hello" || x == "world") && (y == "world" || y == "hello") { +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:21:5 + | +LL | / if x == "hello" && x == "world" { +LL | | if y == "world" || y == "hello" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | if x == "hello" && x == "world" && (y == "world" || y == "hello") { +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:27:5 + | +LL | / if x == "hello" || x == "world" { +LL | | if y == "world" && y == "hello" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | if (x == "hello" || x == "world") && y == "world" && y == "hello" { +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:33:5 + | +LL | / if x == "hello" && x == "world" { +LL | | if y == "world" && y == "hello" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | if x == "hello" && x == "world" && y == "world" && y == "hello" { +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:39:5 + | +LL | / if 42 == 1337 { +LL | | if 'a' != 'A' { +LL | | println!("world!") +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | if 42 == 1337 && 'a' != 'A' { +LL | println!("world!") +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:95:5 + | +LL | / if x == "hello" { +LL | | if y == "world" { // Collapsible +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL | if x == "hello" && y == "world" { // Collapsible +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:154:5 + | +LL | / if matches!(true, true) { +LL | | if matches!(true, true) {} +LL | | } + | |_____^ help: collapse nested if block: `if matches!(true, true) && matches!(true, true) {}` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/collapsible_match.rs b/src/tools/clippy/tests/ui/collapsible_match.rs new file mode 100644 index 0000000000..55467cf422 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_match.rs @@ -0,0 +1,255 @@ +#![warn(clippy::collapsible_match)] +#![allow(clippy::needless_return, clippy::no_effect, clippy::single_match)] + +fn lint_cases(opt_opt: Option>, res_opt: Result, String>) { + // match without block + match res_opt { + Ok(val) => match val { + Some(n) => foo(n), + _ => return, + }, + _ => return, + } + + // match with block + match res_opt { + Ok(val) => match val { + Some(n) => foo(n), + _ => return, + }, + _ => return, + } + + // if let, if let + if let Ok(val) = res_opt { + if let Some(n) = val { + take(n); + } + } + + // if let else, if let else + if let Ok(val) = res_opt { + if let Some(n) = val { + take(n); + } else { + return; + } + } else { + return; + } + + // if let, match + if let Ok(val) = res_opt { + match val { + Some(n) => foo(n), + _ => (), + } + } + + // match, if let + match res_opt { + Ok(val) => { + if let Some(n) = val { + take(n); + } + }, + _ => {}, + } + + // if let else, match + if let Ok(val) = res_opt { + match val { + Some(n) => foo(n), + _ => return, + } + } else { + return; + } + + // match, if let else + match res_opt { + Ok(val) => { + if let Some(n) = val { + take(n); + } else { + return; + } + }, + _ => return, + } + + // None in inner match same as outer wild branch + match res_opt { + Ok(val) => match val { + Some(n) => foo(n), + None => return, + }, + _ => return, + } + + // None in outer match same as inner wild branch + match opt_opt { + Some(val) => match val { + Some(n) => foo(n), + _ => return, + }, + None => return, + } +} + +fn negative_cases(res_opt: Result, String>, res_res: Result, String>) { + // no wild pattern in outer match + match res_opt { + Ok(val) => match val { + Some(n) => foo(n), + _ => return, + }, + Err(_) => return, + } + + // inner branch is not wild or None + match res_res { + Ok(val) => match val { + Ok(n) => foo(n), + Err(_) => return, + }, + _ => return, + } + + // statement before inner match + match res_opt { + Ok(val) => { + "hi buddy"; + match val { + Some(n) => foo(n), + _ => return, + } + }, + _ => return, + } + + // statement after inner match + match res_opt { + Ok(val) => { + match val { + Some(n) => foo(n), + _ => return, + } + "hi buddy"; + }, + _ => return, + } + + // wild branches do not match + match res_opt { + Ok(val) => match val { + Some(n) => foo(n), + _ => { + "sup"; + return; + }, + }, + _ => return, + } + + // binding used in if guard + match res_opt { + Ok(val) if val.is_some() => match val { + Some(n) => foo(n), + _ => return, + }, + _ => return, + } + + // binding used in inner match body + match res_opt { + Ok(val) => match val { + Some(_) => take(val), + _ => return, + }, + _ => return, + } + + // if guard on inner match + { + match res_opt { + Ok(val) => match val { + Some(n) if make() => foo(n), + _ => return, + }, + _ => return, + } + match res_opt { + Ok(val) => match val { + _ => make(), + _ if make() => return, + }, + _ => return, + } + } + + // differing macro contexts + { + macro_rules! mac { + ($val:ident) => { + match $val { + Some(n) => foo(n), + _ => return, + } + }; + } + match res_opt { + Ok(val) => mac!(val), + _ => return, + } + } + + // OR pattern + enum E { + A(T), + B(T), + C(T), + }; + match make::>>() { + E::A(val) | E::B(val) => match val { + Some(n) => foo(n), + _ => return, + }, + _ => return, + } + match make::>>() { + Some(val) => match val { + E::A(val) | E::B(val) => foo(val), + _ => return, + }, + _ => return, + } + if let Ok(val) = res_opt { + if let Some(n) = val { + let _ = || { + // usage in closure + println!("{:?}", val); + }; + } + } + let _: &dyn std::any::Any = match &Some(Some(1)) { + Some(e) => match e { + Some(e) => e, + e => e, + }, + // else branch looks the same but the binding is different + e => e, + }; +} + +fn make() -> T { + unimplemented!() +} + +fn foo(t: T) -> U { + unimplemented!() +} + +fn take(t: T) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/collapsible_match.stderr b/src/tools/clippy/tests/ui/collapsible_match.stderr new file mode 100644 index 0000000000..7797888490 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_match.stderr @@ -0,0 +1,179 @@ +error: unnecessary nested match + --> $DIR/collapsible_match.rs:7:20 + | +LL | Ok(val) => match val { + | ____________________^ +LL | | Some(n) => foo(n), +LL | | _ => return, +LL | | }, + | |_________^ + | + = note: `-D clippy::collapsible-match` implied by `-D warnings` +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match.rs:7:12 + | +LL | Ok(val) => match val { + | ^^^ replace this binding +LL | Some(n) => foo(n), + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match.rs:16:20 + | +LL | Ok(val) => match val { + | ____________________^ +LL | | Some(n) => foo(n), +LL | | _ => return, +LL | | }, + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match.rs:16:12 + | +LL | Ok(val) => match val { + | ^^^ replace this binding +LL | Some(n) => foo(n), + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match.rs:25:9 + | +LL | / if let Some(n) = val { +LL | | take(n); +LL | | } + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match.rs:24:15 + | +LL | if let Ok(val) = res_opt { + | ^^^ replace this binding +LL | if let Some(n) = val { + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match.rs:32:9 + | +LL | / if let Some(n) = val { +LL | | take(n); +LL | | } else { +LL | | return; +LL | | } + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match.rs:31:15 + | +LL | if let Ok(val) = res_opt { + | ^^^ replace this binding +LL | if let Some(n) = val { + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match.rs:43:9 + | +LL | / match val { +LL | | Some(n) => foo(n), +LL | | _ => (), +LL | | } + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match.rs:42:15 + | +LL | if let Ok(val) = res_opt { + | ^^^ replace this binding +LL | match val { +LL | Some(n) => foo(n), + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match.rs:52:13 + | +LL | / if let Some(n) = val { +LL | | take(n); +LL | | } + | |_____________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match.rs:51:12 + | +LL | Ok(val) => { + | ^^^ replace this binding +LL | if let Some(n) = val { + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match.rs:61:9 + | +LL | / match val { +LL | | Some(n) => foo(n), +LL | | _ => return, +LL | | } + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match.rs:60:15 + | +LL | if let Ok(val) = res_opt { + | ^^^ replace this binding +LL | match val { +LL | Some(n) => foo(n), + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match.rs:72:13 + | +LL | / if let Some(n) = val { +LL | | take(n); +LL | | } else { +LL | | return; +LL | | } + | |_____________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match.rs:71:12 + | +LL | Ok(val) => { + | ^^^ replace this binding +LL | if let Some(n) = val { + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match.rs:83:20 + | +LL | Ok(val) => match val { + | ____________________^ +LL | | Some(n) => foo(n), +LL | | None => return, +LL | | }, + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match.rs:83:12 + | +LL | Ok(val) => match val { + | ^^^ replace this binding +LL | Some(n) => foo(n), + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match.rs:92:22 + | +LL | Some(val) => match val { + | ______________________^ +LL | | Some(n) => foo(n), +LL | | _ => return, +LL | | }, + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match.rs:92:14 + | +LL | Some(val) => match val { + | ^^^ replace this binding +LL | Some(n) => foo(n), + | ^^^^^^^ with this pattern + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/collapsible_match2.rs b/src/tools/clippy/tests/ui/collapsible_match2.rs new file mode 100644 index 0000000000..8372a21247 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_match2.rs @@ -0,0 +1,82 @@ +#![warn(clippy::collapsible_match)] +#![allow(clippy::needless_return, clippy::no_effect, clippy::single_match)] + +fn lint_cases(opt_opt: Option>, res_opt: Result, String>) { + // if guards on outer match + { + match res_opt { + Ok(val) if make() => match val { + Some(n) => foo(n), + _ => return, + }, + _ => return, + } + match res_opt { + Ok(val) => match val { + Some(n) => foo(n), + _ => return, + }, + _ if make() => return, + _ => return, + } + } + + // macro + { + macro_rules! mac { + ($outer:expr => $pat:pat, $e:expr => $inner_pat:pat, $then:expr) => { + match $outer { + $pat => match $e { + $inner_pat => $then, + _ => return, + }, + _ => return, + } + }; + } + // Lint this since the patterns are not defined by the macro. + // Allows the lint to work on if_chain! for example. + // Fixing the lint requires knowledge of the specific macro, but we optimistically assume that + // there is still a better way to write this. + mac!(res_opt => Ok(val), val => Some(n), foo(n)); + } + + // deref reference value + match Some(&[1]) { + Some(s) => match *s { + [n] => foo(n), + _ => (), + }, + _ => (), + } + + // ref pattern and deref + match Some(&[1]) { + Some(ref s) => match &*s { + [n] => foo(n), + _ => (), + }, + _ => (), + } +} + +fn no_lint() { + // deref inner value (cannot pattern match with Vec) + match Some(vec![1]) { + Some(s) => match *s { + [n] => foo(n), + _ => (), + }, + _ => (), + } +} + +fn make() -> T { + unimplemented!() +} + +fn foo(t: T) -> U { + unimplemented!() +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/collapsible_match2.stderr b/src/tools/clippy/tests/ui/collapsible_match2.stderr new file mode 100644 index 0000000000..c8a445ef36 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_match2.stderr @@ -0,0 +1,97 @@ +error: unnecessary nested match + --> $DIR/collapsible_match2.rs:8:34 + | +LL | Ok(val) if make() => match val { + | __________________________________^ +LL | | Some(n) => foo(n), +LL | | _ => return, +LL | | }, + | |_____________^ + | + = note: `-D clippy::collapsible-match` implied by `-D warnings` +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match2.rs:8:16 + | +LL | Ok(val) if make() => match val { + | ^^^ replace this binding +LL | Some(n) => foo(n), + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match2.rs:15:24 + | +LL | Ok(val) => match val { + | ________________________^ +LL | | Some(n) => foo(n), +LL | | _ => return, +LL | | }, + | |_____________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match2.rs:15:16 + | +LL | Ok(val) => match val { + | ^^^ replace this binding +LL | Some(n) => foo(n), + | ^^^^^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match2.rs:29:29 + | +LL | $pat => match $e { + | _____________________________^ +LL | | $inner_pat => $then, +LL | | _ => return, +LL | | }, + | |_____________________^ +... +LL | mac!(res_opt => Ok(val), val => Some(n), foo(n)); + | ------------------------------------------------- in this macro invocation + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match2.rs:41:28 + | +LL | mac!(res_opt => Ok(val), val => Some(n), foo(n)); + | ^^^ ^^^^^^^ with this pattern + | | + | replace this binding + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unnecessary nested match + --> $DIR/collapsible_match2.rs:46:20 + | +LL | Some(s) => match *s { + | ____________________^ +LL | | [n] => foo(n), +LL | | _ => (), +LL | | }, + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match2.rs:46:14 + | +LL | Some(s) => match *s { + | ^ replace this binding +LL | [n] => foo(n), + | ^^^ with this pattern + +error: unnecessary nested match + --> $DIR/collapsible_match2.rs:55:24 + | +LL | Some(ref s) => match &*s { + | ________________________^ +LL | | [n] => foo(n), +LL | | _ => (), +LL | | }, + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> $DIR/collapsible_match2.rs:55:14 + | +LL | Some(ref s) => match &*s { + | ^^^^^ replace this binding +LL | [n] => foo(n), + | ^^^ with this pattern + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/comparison_chain.rs b/src/tools/clippy/tests/ui/comparison_chain.rs new file mode 100644 index 0000000000..3b03f8c7df --- /dev/null +++ b/src/tools/clippy/tests/ui/comparison_chain.rs @@ -0,0 +1,206 @@ +#![allow(dead_code)] +#![warn(clippy::comparison_chain)] + +fn a() {} +fn b() {} +fn c() {} + +fn f(x: u8, y: u8, z: u8) { + // Ignored: Only one branch + if x > y { + a() + } + + if x > y { + a() + } else if x < y { + b() + } + + // Ignored: Only one explicit conditional + if x > y { + a() + } else { + b() + } + + if x > y { + a() + } else if x < y { + b() + } else { + c() + } + + if x > y { + a() + } else if y > x { + b() + } else { + c() + } + + if x > 1 { + a() + } else if x < 1 { + b() + } else if x == 1 { + c() + } + + // Ignored: Binop args are not equivalent + if x > 1 { + a() + } else if y > 1 { + b() + } else { + c() + } + + // Ignored: Binop args are not equivalent + if x > y { + a() + } else if x > z { + b() + } else if y > z { + c() + } + + // Ignored: Not binary comparisons + if true { + a() + } else if false { + b() + } else { + c() + } +} + +#[allow(clippy::float_cmp)] +fn g(x: f64, y: f64, z: f64) { + // Ignored: f64 doesn't implement Ord + if x > y { + a() + } else if x < y { + b() + } + + // Ignored: f64 doesn't implement Ord + if x > y { + a() + } else if x < y { + b() + } else { + c() + } + + // Ignored: f64 doesn't implement Ord + if x > y { + a() + } else if y > x { + b() + } else { + c() + } + + // Ignored: f64 doesn't implement Ord + if x > 1.0 { + a() + } else if x < 1.0 { + b() + } else if x == 1.0 { + c() + } +} + +fn h(x: T, y: T, z: T) { + if x > y { + a() + } else if x < y { + b() + } + + if x > y { + a() + } else if x < y { + b() + } else { + c() + } + + if x > y { + a() + } else if y > x { + b() + } else { + c() + } +} + +// The following uses should be ignored +mod issue_5212 { + use super::{a, b, c}; + fn foo() -> u8 { + 21 + } + + fn same_operation_equals() { + // operands are fixed + + if foo() == 42 { + a() + } else if foo() == 42 { + b() + } + + if foo() == 42 { + a() + } else if foo() == 42 { + b() + } else { + c() + } + + // operands are transposed + + if foo() == 42 { + a() + } else if 42 == foo() { + b() + } + } + + fn same_operation_not_equals() { + // operands are fixed + + if foo() > 42 { + a() + } else if foo() > 42 { + b() + } + + if foo() > 42 { + a() + } else if foo() > 42 { + b() + } else { + c() + } + + if foo() < 42 { + a() + } else if foo() < 42 { + b() + } + + if foo() < 42 { + a() + } else if foo() < 42 { + b() + } else { + c() + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/comparison_chain.stderr b/src/tools/clippy/tests/ui/comparison_chain.stderr new file mode 100644 index 0000000000..be25a80dde --- /dev/null +++ b/src/tools/clippy/tests/ui/comparison_chain.stderr @@ -0,0 +1,97 @@ +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:14:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if x < y { +LL | | b() +LL | | } + | |_____^ + | + = note: `-D clippy::comparison-chain` implied by `-D warnings` + = help: consider rewriting the `if` chain to use `cmp` and `match` + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:27:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if x < y { +LL | | b() +LL | | } else { +LL | | c() +LL | | } + | |_____^ + | + = help: consider rewriting the `if` chain to use `cmp` and `match` + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:35:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if y > x { +LL | | b() +LL | | } else { +LL | | c() +LL | | } + | |_____^ + | + = help: consider rewriting the `if` chain to use `cmp` and `match` + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:43:5 + | +LL | / if x > 1 { +LL | | a() +LL | | } else if x < 1 { +LL | | b() +LL | | } else if x == 1 { +LL | | c() +LL | | } + | |_____^ + | + = help: consider rewriting the `if` chain to use `cmp` and `match` + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:117:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if x < y { +LL | | b() +LL | | } + | |_____^ + | + = help: consider rewriting the `if` chain to use `cmp` and `match` + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:123:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if x < y { +LL | | b() +LL | | } else { +LL | | c() +LL | | } + | |_____^ + | + = help: consider rewriting the `if` chain to use `cmp` and `match` + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:131:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if y > x { +LL | | b() +LL | | } else { +LL | | c() +LL | | } + | |_____^ + | + = help: consider rewriting the `if` chain to use `cmp` and `match` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/comparison_to_empty.fixed b/src/tools/clippy/tests/ui/comparison_to_empty.fixed new file mode 100644 index 0000000000..261024caca --- /dev/null +++ b/src/tools/clippy/tests/ui/comparison_to_empty.fixed @@ -0,0 +1,23 @@ +// run-rustfix + +#![warn(clippy::comparison_to_empty)] + +fn main() { + // Disallow comparisons to empty + let s = String::new(); + let _ = s.is_empty(); + let _ = !s.is_empty(); + + let v = vec![0]; + let _ = v.is_empty(); + let _ = !v.is_empty(); + + // Allow comparisons to non-empty + let s = String::new(); + let _ = s == " "; + let _ = s != " "; + + let v = vec![0]; + let _ = v == [0]; + let _ = v != [0]; +} diff --git a/src/tools/clippy/tests/ui/comparison_to_empty.rs b/src/tools/clippy/tests/ui/comparison_to_empty.rs new file mode 100644 index 0000000000..98ddd97495 --- /dev/null +++ b/src/tools/clippy/tests/ui/comparison_to_empty.rs @@ -0,0 +1,23 @@ +// run-rustfix + +#![warn(clippy::comparison_to_empty)] + +fn main() { + // Disallow comparisons to empty + let s = String::new(); + let _ = s == ""; + let _ = s != ""; + + let v = vec![0]; + let _ = v == []; + let _ = v != []; + + // Allow comparisons to non-empty + let s = String::new(); + let _ = s == " "; + let _ = s != " "; + + let v = vec![0]; + let _ = v == [0]; + let _ = v != [0]; +} diff --git a/src/tools/clippy/tests/ui/comparison_to_empty.stderr b/src/tools/clippy/tests/ui/comparison_to_empty.stderr new file mode 100644 index 0000000000..f69d6bd525 --- /dev/null +++ b/src/tools/clippy/tests/ui/comparison_to_empty.stderr @@ -0,0 +1,28 @@ +error: comparison to empty slice + --> $DIR/comparison_to_empty.rs:8:13 + | +LL | let _ = s == ""; + | ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` + | + = note: `-D clippy::comparison-to-empty` implied by `-D warnings` + +error: comparison to empty slice + --> $DIR/comparison_to_empty.rs:9:13 + | +LL | let _ = s != ""; + | ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!s.is_empty()` + +error: comparison to empty slice + --> $DIR/comparison_to_empty.rs:12:13 + | +LL | let _ = v == []; + | ^^^^^^^ help: using `is_empty` is clearer and more explicit: `v.is_empty()` + +error: comparison to empty slice + --> $DIR/comparison_to_empty.rs:13:13 + | +LL | let _ = v != []; + | ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!v.is_empty()` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/complex_types.rs b/src/tools/clippy/tests/ui/complex_types.rs new file mode 100644 index 0000000000..383bbb49db --- /dev/null +++ b/src/tools/clippy/tests/ui/complex_types.rs @@ -0,0 +1,60 @@ +#![warn(clippy::all)] +#![allow(unused, clippy::needless_pass_by_value, clippy::vec_box)] +#![feature(associated_type_defaults)] + +type Alias = Vec>>; // no warning here + +const CST: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); +static ST: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); + +struct S { + f: Vec>>, +} + +struct Ts(Vec>>); + +enum E { + Tuple(Vec>>), + Struct { f: Vec>> }, +} + +impl S { + const A: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); + fn impl_method(&self, p: Vec>>) {} +} + +trait T { + const A: Vec>>; + type B = Vec>>; + fn method(&self, p: Vec>>); + fn def_method(&self, p: Vec>>) {} +} + +fn test1() -> Vec>> { + vec![] +} + +fn test2(_x: Vec>>) {} + +fn test3() { + let _y: Vec>> = vec![]; +} + +#[repr(C)] +struct D { + // should not warn, since we don't have control over the signature (#3222) + test4: extern "C" fn( + itself: &D, + a: usize, + b: usize, + c: usize, + d: usize, + e: usize, + f: usize, + g: usize, + h: usize, + i: usize, + ), +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/complex_types.stderr b/src/tools/clippy/tests/ui/complex_types.stderr new file mode 100644 index 0000000000..7fcbb4bce8 --- /dev/null +++ b/src/tools/clippy/tests/ui/complex_types.stderr @@ -0,0 +1,94 @@ +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:7:12 + | +LL | const CST: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::type-complexity` implied by `-D warnings` + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:8:12 + | +LL | static ST: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:11:8 + | +LL | f: Vec>>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:14:11 + | +LL | struct Ts(Vec>>); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:17:11 + | +LL | Tuple(Vec>>), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:18:17 + | +LL | Struct { f: Vec>> }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:22:14 + | +LL | const A: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:23:30 + | +LL | fn impl_method(&self, p: Vec>>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:27:14 + | +LL | const A: Vec>>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:28:14 + | +LL | type B = Vec>>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:29:25 + | +LL | fn method(&self, p: Vec>>); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:30:29 + | +LL | fn def_method(&self, p: Vec>>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:33:15 + | +LL | fn test1() -> Vec>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:37:14 + | +LL | fn test2(_x: Vec>>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:40:13 + | +LL | let _y: Vec>> = vec![]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/copy_iterator.rs b/src/tools/clippy/tests/ui/copy_iterator.rs new file mode 100644 index 0000000000..e3d5928be2 --- /dev/null +++ b/src/tools/clippy/tests/ui/copy_iterator.rs @@ -0,0 +1,23 @@ +#![warn(clippy::copy_iterator)] + +#[derive(Copy, Clone)] +struct Countdown(u8); + +impl Iterator for Countdown { + type Item = u8; + + fn next(&mut self) -> Option { + self.0.checked_sub(1).map(|c| { + self.0 = c; + c + }) + } +} + +fn main() { + let my_iterator = Countdown(5); + let a: Vec<_> = my_iterator.take(1).collect(); + assert_eq!(a.len(), 1); + let b: Vec<_> = my_iterator.collect(); + assert_eq!(b.len(), 5); +} diff --git a/src/tools/clippy/tests/ui/copy_iterator.stderr b/src/tools/clippy/tests/ui/copy_iterator.stderr new file mode 100644 index 0000000000..f8ce6af796 --- /dev/null +++ b/src/tools/clippy/tests/ui/copy_iterator.stderr @@ -0,0 +1,17 @@ +error: you are implementing `Iterator` on a `Copy` type + --> $DIR/copy_iterator.rs:6:1 + | +LL | / impl Iterator for Countdown { +LL | | type Item = u8; +LL | | +LL | | fn next(&mut self) -> Option { +... | +LL | | } +LL | | } + | |_^ + | + = note: `-D clippy::copy-iterator` implied by `-D warnings` + = note: consider implementing `IntoIterator` instead + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/associated-constant-ice.rs b/src/tools/clippy/tests/ui/crashes/associated-constant-ice.rs new file mode 100644 index 0000000000..948deba3ea --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/associated-constant-ice.rs @@ -0,0 +1,13 @@ +/// Test for https://github.com/rust-lang/rust-clippy/issues/1698 + +pub trait Trait { + const CONSTANT: u8; +} + +impl Trait for u8 { + const CONSTANT: u8 = 2; +} + +fn main() { + println!("{}", u8::CONSTANT * 10); +} diff --git a/src/tools/clippy/tests/ui/crashes/auxiliary/ice-4727-aux.rs b/src/tools/clippy/tests/ui/crashes/auxiliary/ice-4727-aux.rs new file mode 100644 index 0000000000..58a20caf6c --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/auxiliary/ice-4727-aux.rs @@ -0,0 +1,9 @@ +pub trait Trait { + fn fun(par: &str) -> &str; +} + +impl Trait for str { + fn fun(par: &str) -> &str { + &par[0..1] + } +} diff --git a/src/tools/clippy/tests/ui/crashes/auxiliary/proc_macro_crash.rs b/src/tools/clippy/tests/ui/crashes/auxiliary/proc_macro_crash.rs new file mode 100644 index 0000000000..ed8e7a708a --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/auxiliary/proc_macro_crash.rs @@ -0,0 +1,39 @@ +// compile-flags: --emit=link +// no-prefer-dynamic +// ^ compiletest by default builds all aux files as dylibs, but we don't want that for proc-macro +// crates. If we don't set this, compiletest will override the `crate_type` attribute below and +// compile this as dylib. Removing this then causes the test to fail because a `dylib` crate can't +// contain a proc-macro. + +#![feature(repr128)] +#![allow(incomplete_features)] +#![crate_type = "proc-macro"] + +extern crate proc_macro; + +use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; +use std::iter::FromIterator; + +#[proc_macro] +pub fn macro_test(input_stream: TokenStream) -> TokenStream { + let first_token = input_stream.into_iter().next().unwrap(); + let span = first_token.span(); + + TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new("fn", Span::call_site())), + TokenTree::Ident(Ident::new("code", Span::call_site())), + TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())), + TokenTree::Group(Group::new(Delimiter::Brace, { + let mut clause = Group::new(Delimiter::Brace, TokenStream::new()); + clause.set_span(span); + + TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new("if", Span::call_site())), + TokenTree::Ident(Ident::new("true", Span::call_site())), + TokenTree::Group(clause.clone()), + TokenTree::Ident(Ident::new("else", Span::call_site())), + TokenTree::Group(clause), + ]) + })), + ]) +} diff --git a/src/tools/clippy/tests/ui/crashes/auxiliary/use_self_macro.rs b/src/tools/clippy/tests/ui/crashes/auxiliary/use_self_macro.rs new file mode 100644 index 0000000000..a8a85b4bae --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/auxiliary/use_self_macro.rs @@ -0,0 +1,15 @@ +macro_rules! use_self { + ( + impl $ty:ident { + fn func(&$this:ident) { + [fields($($field:ident)*)] + } + } + ) => ( + impl $ty { + fn func(&$this) { + let $ty { $($field),* } = $this; + } + } + ) +} diff --git a/src/tools/clippy/tests/ui/crashes/cc_seme.rs b/src/tools/clippy/tests/ui/crashes/cc_seme.rs new file mode 100644 index 0000000000..98588be9cf --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/cc_seme.rs @@ -0,0 +1,27 @@ +#[allow(dead_code)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/478 + +enum Baz { + One, + Two, +} + +struct Test { + t: Option, + b: Baz, +} + +fn main() {} + +pub fn foo() { + use Baz::*; + let x = Test { t: Some(0), b: One }; + + match x { + Test { t: Some(_), b: One } => unreachable!(), + Test { t: Some(42), b: Two } => unreachable!(), + Test { t: None, .. } => unreachable!(), + Test { .. } => unreachable!(), + } +} diff --git a/src/tools/clippy/tests/ui/crashes/enum-glob-import-crate.rs b/src/tools/clippy/tests/ui/crashes/enum-glob-import-crate.rs new file mode 100644 index 0000000000..dca32aa3b5 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/enum-glob-import-crate.rs @@ -0,0 +1,6 @@ +#![deny(clippy::all)] +#![allow(unused_imports)] + +use std::*; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-1588.rs b/src/tools/clippy/tests/ui/crashes/ice-1588.rs new file mode 100644 index 0000000000..b0a3d11bce --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-1588.rs @@ -0,0 +1,13 @@ +#![allow(clippy::all)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/1588 + +fn main() { + match 1 { + 1 => {}, + 2 => { + [0; 1]; + }, + _ => {}, + } +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-1782.rs b/src/tools/clippy/tests/ui/crashes/ice-1782.rs new file mode 100644 index 0000000000..81af88962a --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-1782.rs @@ -0,0 +1,26 @@ +#![allow(dead_code, unused_variables)] + +/// Should not trigger an ICE in `SpanlessEq` / `consts::constant` +/// +/// Issue: https://github.com/rust-lang/rust-clippy/issues/1782 +use std::{mem, ptr}; + +fn spanless_eq_ice() { + let txt = "something"; + match txt { + "something" => unsafe { + ptr::write( + ptr::null_mut() as *mut u32, + mem::transmute::<[u8; 4], _>([0, 0, 0, 255]), + ) + }, + _ => unsafe { + ptr::write( + ptr::null_mut() as *mut u32, + mem::transmute::<[u8; 4], _>([13, 246, 24, 255]), + ) + }, + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-1969.rs b/src/tools/clippy/tests/ui/crashes/ice-1969.rs new file mode 100644 index 0000000000..96a8fe6c24 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-1969.rs @@ -0,0 +1,13 @@ +#![allow(clippy::all)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/1969 + +fn main() {} + +pub trait Convert { + type Action: From<*const f64>; + + fn convert(val: *const f64) -> Self::Action { + val.into() + } +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2499.rs b/src/tools/clippy/tests/ui/crashes/ice-2499.rs new file mode 100644 index 0000000000..45b3b1869d --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2499.rs @@ -0,0 +1,26 @@ +#![allow(dead_code, clippy::char_lit_as_u8, clippy::needless_bool)] + +/// Should not trigger an ICE in `SpanlessHash` / `consts::constant` +/// +/// Issue: https://github.com/rust-lang/rust-clippy/issues/2499 + +fn f(s: &[u8]) -> bool { + let t = s[0] as char; + + match t { + 'E' | 'W' => {}, + 'T' => { + if s[0..4] != ['0' as u8; 4] { + return false; + } else { + return true; + } + }, + _ => { + return false; + }, + } + true +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2594.rs b/src/tools/clippy/tests/ui/crashes/ice-2594.rs new file mode 100644 index 0000000000..3f3986b6fc --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2594.rs @@ -0,0 +1,20 @@ +#![allow(dead_code, unused_variables)] + +/// Should not trigger an ICE in `SpanlessHash` / `consts::constant` +/// +/// Issue: https://github.com/rust-lang/rust-clippy/issues/2594 + +fn spanless_hash_ice() { + let txt = "something"; + let empty_header: [u8; 1] = [1; 1]; + + match txt { + "something" => { + let mut headers = [empty_header; 1]; + }, + "" => (), + _ => (), + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2727.rs b/src/tools/clippy/tests/ui/crashes/ice-2727.rs new file mode 100644 index 0000000000..56024abc8f --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2727.rs @@ -0,0 +1,7 @@ +/// Test for https://github.com/rust-lang/rust-clippy/issues/2727 + +pub fn f(new: fn()) { + new(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2760.rs b/src/tools/clippy/tests/ui/crashes/ice-2760.rs new file mode 100644 index 0000000000..f1a229f3f4 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2760.rs @@ -0,0 +1,23 @@ +#![allow( + unused_variables, + clippy::blacklisted_name, + clippy::needless_pass_by_value, + dead_code +)] + +/// This should not compile-fail with: +/// +/// error[E0277]: the trait bound `T: Foo` is not satisfied +// See rust-lang/rust-clippy#2760. + +trait Foo { + type Bar; +} + +struct Baz { + bar: T::Bar, +} + +fn take(baz: Baz) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2774.rs b/src/tools/clippy/tests/ui/crashes/ice-2774.rs new file mode 100644 index 0000000000..d44b0fae82 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2774.rs @@ -0,0 +1,27 @@ +use std::collections::HashSet; + +// See rust-lang/rust-clippy#2774. + +#[derive(Eq, PartialEq, Debug, Hash)] +pub struct Bar { + foo: Foo, +} + +#[derive(Eq, PartialEq, Debug, Hash)] +pub struct Foo {} + +#[allow(clippy::implicit_hasher)] +// This should not cause a "cannot relate bound region" ICE. +pub fn add_barfoos_to_foos<'a>(bars: &HashSet<&'a Bar>) { + let mut foos = HashSet::new(); + foos.extend(bars.iter().map(|b| &b.foo)); +} + +#[allow(clippy::implicit_hasher)] +// Also, this should not cause a "cannot relate bound region" ICE. +pub fn add_barfoos_to_foos2(bars: &HashSet<&Bar>) { + let mut foos = HashSet::new(); + foos.extend(bars.iter().map(|b| &b.foo)); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2774.stderr b/src/tools/clippy/tests/ui/crashes/ice-2774.stderr new file mode 100644 index 0000000000..0c2d48f938 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2774.stderr @@ -0,0 +1,10 @@ +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/ice-2774.rs:15:1 + | +LL | pub fn add_barfoos_to_foos<'a>(bars: &HashSet<&'a Bar>) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::needless-lifetimes` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/ice-2862.rs b/src/tools/clippy/tests/ui/crashes/ice-2862.rs new file mode 100644 index 0000000000..8326e3663b --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2862.rs @@ -0,0 +1,16 @@ +/// Test for https://github.com/rust-lang/rust-clippy/issues/2862 + +pub trait FooMap { + fn map B>(&self, f: F) -> B; +} + +impl FooMap for bool { + fn map B>(&self, f: F) -> B { + f() + } +} + +fn main() { + let a = true; + a.map(|| false); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2865.rs b/src/tools/clippy/tests/ui/crashes/ice-2865.rs new file mode 100644 index 0000000000..6b1ceb5056 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2865.rs @@ -0,0 +1,16 @@ +#[allow(dead_code)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/2865 + +struct Ice { + size: String, +} + +impl<'a> From for Ice { + fn from(_: String) -> Self { + let text = || "iceberg".to_string(); + Self { size: text() } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3151.rs b/src/tools/clippy/tests/ui/crashes/ice-3151.rs new file mode 100644 index 0000000000..fef4d7db84 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3151.rs @@ -0,0 +1,15 @@ +/// Test for https://github.com/rust-lang/rust-clippy/issues/2865 + +#[derive(Clone)] +pub struct HashMap { + hash_builder: S, + table: RawTable, +} + +#[derive(Clone)] +pub struct RawTable { + size: usize, + val: V, +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3462.rs b/src/tools/clippy/tests/ui/crashes/ice-3462.rs new file mode 100644 index 0000000000..7d62e315da --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3462.rs @@ -0,0 +1,23 @@ +#![warn(clippy::all)] +#![allow(clippy::blacklisted_name)] +#![allow(unused)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/3462 + +enum Foo { + Bar, + Baz, +} + +fn bar(foo: Foo) { + macro_rules! baz { + () => { + if let Foo::Bar = foo {} + }; + } + + baz!(); + baz!(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-360.rs b/src/tools/clippy/tests/ui/crashes/ice-360.rs new file mode 100644 index 0000000000..6555c19ca6 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-360.rs @@ -0,0 +1,12 @@ +fn main() {} + +fn no_panic(slice: &[T]) { + let mut iter = slice.iter(); + loop { + let _ = match iter.next() { + Some(ele) => ele, + None => break, + }; + loop {} + } +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-360.stderr b/src/tools/clippy/tests/ui/crashes/ice-360.stderr new file mode 100644 index 0000000000..0eb7bb12b3 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-360.stderr @@ -0,0 +1,25 @@ +error: this loop could be written as a `while let` loop + --> $DIR/ice-360.rs:5:5 + | +LL | / loop { +LL | | let _ = match iter.next() { +LL | | Some(ele) => ele, +LL | | None => break, +LL | | }; +LL | | loop {} +LL | | } + | |_____^ help: try: `while let Some(ele) = iter.next() { .. }` + | + = note: `-D clippy::while-let-loop` implied by `-D warnings` + +error: empty `loop {}` wastes CPU cycles + --> $DIR/ice-360.rs:10:9 + | +LL | loop {} + | ^^^^^^^ + | + = note: `-D clippy::empty-loop` implied by `-D warnings` + = help: you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/crashes/ice-3717.rs b/src/tools/clippy/tests/ui/crashes/ice-3717.rs new file mode 100644 index 0000000000..f50714643f --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3717.rs @@ -0,0 +1,10 @@ +#![deny(clippy::implicit_hasher)] + +use std::collections::HashSet; + +fn main() {} + +pub fn ice_3717(_: &HashSet) { + let _ = [0u8; 0]; + let _: HashSet = HashSet::new(); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3717.stderr b/src/tools/clippy/tests/ui/crashes/ice-3717.stderr new file mode 100644 index 0000000000..296c95abb9 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3717.stderr @@ -0,0 +1,22 @@ +error: parameter of type `HashSet` should be generalized over different hashers + --> $DIR/ice-3717.rs:7:21 + | +LL | pub fn ice_3717(_: &HashSet) { + | ^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/ice-3717.rs:1:9 + | +LL | #![deny(clippy::implicit_hasher)] + | ^^^^^^^^^^^^^^^^^^^^^^^ +help: consider adding a type parameter + | +LL | pub fn ice_3717(_: &HashSet) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | let _: HashSet = HashSet::default(); + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/ice-3741.rs b/src/tools/clippy/tests/ui/crashes/ice-3741.rs new file mode 100644 index 0000000000..1253ddcfae --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3741.rs @@ -0,0 +1,10 @@ +// aux-build:proc_macro_crash.rs + +#![warn(clippy::suspicious_else_formatting)] + +extern crate proc_macro_crash; +use proc_macro_crash::macro_test; + +fn main() { + macro_test!(2); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3747.rs b/src/tools/clippy/tests/ui/crashes/ice-3747.rs new file mode 100644 index 0000000000..cdf018cbc8 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3747.rs @@ -0,0 +1,17 @@ +/// Test for https://github.com/rust-lang/rust-clippy/issues/3747 + +macro_rules! a { + ( $pub:tt $($attr:tt)* ) => { + $($attr)* $pub fn say_hello() {} + }; +} + +macro_rules! b { + () => { + a! { pub } + }; +} + +b! {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3891.rs b/src/tools/clippy/tests/ui/crashes/ice-3891.rs new file mode 100644 index 0000000000..05c5134c84 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3891.rs @@ -0,0 +1,3 @@ +fn main() { + 1x; +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3891.stderr b/src/tools/clippy/tests/ui/crashes/ice-3891.stderr new file mode 100644 index 0000000000..59469ec589 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3891.stderr @@ -0,0 +1,10 @@ +error: invalid suffix `x` for number literal + --> $DIR/ice-3891.rs:2:5 + | +LL | 1x; + | ^^ invalid suffix `x` + | + = help: the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/ice-3969.rs b/src/tools/clippy/tests/ui/crashes/ice-3969.rs new file mode 100644 index 0000000000..4feab7910b --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3969.rs @@ -0,0 +1,51 @@ +// https://github.com/rust-lang/rust-clippy/issues/3969 +// used to crash: error: internal compiler error: +// src/librustc_traits/normalize_erasing_regions.rs:43: could not fully normalize `::Item test from rustc ./ui/trivial-bounds/trivial-bounds-inconsistent.rs + +// Check that tautalogically false bounds are accepted, and are used +// in type inference. +#![feature(trivial_bounds)] +#![allow(unused)] + +trait A {} + +impl A for i32 {} + +struct Dst { + x: X, +} + +struct TwoStrs(str, str) +where + str: Sized; + +fn unsized_local() +where + for<'a> Dst: Sized, +{ + let x: Dst = *(Box::new(Dst { x: 1 }) as Box>); +} + +fn return_str() -> str +where + str: Sized, +{ + *"Sized".to_string().into_boxed_str() +} + +fn use_op(s: String) -> String +where + String: ::std::ops::Neg, +{ + -s +} + +fn use_for() +where + i32: Iterator, +{ + for _ in 2i32 {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3969.stderr b/src/tools/clippy/tests/ui/crashes/ice-3969.stderr new file mode 100644 index 0000000000..923db0664a --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3969.stderr @@ -0,0 +1,22 @@ +error: trait objects without an explicit `dyn` are deprecated + --> $DIR/ice-3969.rs:25:17 + | +LL | for<'a> Dst: Sized, + | ^^^^^^ help: use `dyn`: `dyn A + 'a` + | + = note: `-D bare-trait-objects` implied by `-D warnings` + +error: trait objects without an explicit `dyn` are deprecated + --> $DIR/ice-3969.rs:27:16 + | +LL | let x: Dst = *(Box::new(Dst { x: 1 }) as Box>); + | ^ help: use `dyn`: `dyn A` + +error: trait objects without an explicit `dyn` are deprecated + --> $DIR/ice-3969.rs:27:57 + | +LL | let x: Dst = *(Box::new(Dst { x: 1 }) as Box>); + | ^ help: use `dyn`: `dyn A` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/crashes/ice-4121.rs b/src/tools/clippy/tests/ui/crashes/ice-4121.rs new file mode 100644 index 0000000000..e1a142fdcb --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4121.rs @@ -0,0 +1,13 @@ +use std::mem; + +pub struct Foo(A, B); + +impl Foo { + const HOST_SIZE: usize = mem::size_of::(); + + pub fn crash() -> bool { + Self::HOST_SIZE == 0 + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4545.rs b/src/tools/clippy/tests/ui/crashes/ice-4545.rs new file mode 100644 index 0000000000..d9c9c2096d --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4545.rs @@ -0,0 +1,14 @@ +fn repro() { + trait Foo { + type Bar; + } + + #[allow(dead_code)] + struct Baz { + field: T::Bar, + } +} + +fn main() { + repro(); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4579.rs b/src/tools/clippy/tests/ui/crashes/ice-4579.rs new file mode 100644 index 0000000000..2e7e279f84 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4579.rs @@ -0,0 +1,13 @@ +#![allow(clippy::single_match)] + +use std::ptr; + +fn main() { + match Some(0_usize) { + Some(_) => { + let s = "012345"; + unsafe { ptr::read(s.as_ptr().offset(1) as *const [u8; 5]) }; + }, + _ => (), + }; +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4671.rs b/src/tools/clippy/tests/ui/crashes/ice-4671.rs new file mode 100644 index 0000000000..64e8e77694 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4671.rs @@ -0,0 +1,21 @@ +#![warn(clippy::use_self)] + +#[macro_use] +#[path = "auxiliary/use_self_macro.rs"] +mod use_self_macro; + +struct Foo { + a: u32, +} + +use_self! { + impl Foo { + fn func(&self) { + [fields( + a + )] + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4727.rs b/src/tools/clippy/tests/ui/crashes/ice-4727.rs new file mode 100644 index 0000000000..2a4bc83f58 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4727.rs @@ -0,0 +1,6 @@ +#![warn(clippy::use_self)] + +#[path = "auxiliary/ice-4727-aux.rs"] +mod aux; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4760.rs b/src/tools/clippy/tests/ui/crashes/ice-4760.rs new file mode 100644 index 0000000000..08b0696176 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4760.rs @@ -0,0 +1,9 @@ +const COUNT: usize = 2; +struct Thing; +trait Dummy {} + +const _: () = { + impl Dummy for Thing where [i32; COUNT]: Sized {} +}; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4775.rs b/src/tools/clippy/tests/ui/crashes/ice-4775.rs new file mode 100644 index 0000000000..31e53e846d --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4775.rs @@ -0,0 +1,14 @@ +#![feature(const_generics)] +#![allow(incomplete_features)] + +pub struct ArrayWrapper([usize; N]); + +impl ArrayWrapper<{ N }> { + pub fn ice(&self) { + for i in self.0.iter() { + println!("{}", i); + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4968.rs b/src/tools/clippy/tests/ui/crashes/ice-4968.rs new file mode 100644 index 0000000000..3822f17459 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4968.rs @@ -0,0 +1,20 @@ +// check-pass + +// Test for https://github.com/rust-lang/rust-clippy/issues/4968 + +#![warn(clippy::unsound_collection_transmute)] + +trait Trait { + type Assoc; +} + +use std::mem::{self, ManuallyDrop}; + +#[allow(unused)] +fn func(slice: Vec) { + unsafe { + let _: Vec> = mem::transmute(slice); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5207.rs b/src/tools/clippy/tests/ui/crashes/ice-5207.rs new file mode 100644 index 0000000000..1b20c9defa --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5207.rs @@ -0,0 +1,7 @@ +// edition:2018 + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/5207 + +pub async fn bar<'a, T: 'a>(_: T) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5223.rs b/src/tools/clippy/tests/ui/crashes/ice-5223.rs new file mode 100644 index 0000000000..9bb2e227fc --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5223.rs @@ -0,0 +1,18 @@ +// Regression test for #5233 + +#![feature(const_generics)] +#![allow(incomplete_features)] +#![warn(clippy::indexing_slicing, clippy::iter_cloned_collect)] + +pub struct KotomineArray { + arr: [T; N], +} + +impl KotomineArray { + pub fn ice(self) { + let _ = self.arr[..]; + let _ = self.arr.iter().cloned().collect::>(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5238.rs b/src/tools/clippy/tests/ui/crashes/ice-5238.rs new file mode 100644 index 0000000000..989eb6d448 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5238.rs @@ -0,0 +1,9 @@ +// Regression test for #5238 / https://github.com/rust-lang/rust/pull/69562 + +#![feature(generators, generator_trait)] + +fn main() { + let _ = || { + yield; + }; +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5389.rs b/src/tools/clippy/tests/ui/crashes/ice-5389.rs new file mode 100644 index 0000000000..de26219900 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5389.rs @@ -0,0 +1,13 @@ +#![allow(clippy::explicit_counter_loop)] + +fn main() { + let v = vec![1, 2, 3]; + let mut i = 0; + let max_storage_size = [0; 128 * 1024]; + for item in &v { + bar(i, *item); + i += 1; + } +} + +fn bar(_: usize, _: u32) {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5497.rs b/src/tools/clippy/tests/ui/crashes/ice-5497.rs new file mode 100644 index 0000000000..0769bce5fc --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5497.rs @@ -0,0 +1,11 @@ +// reduced from rustc issue-69020-assoc-const-arith-overflow.rs +pub fn main() {} + +pub trait Foo { + const OOB: i32; +} + +impl Foo for Vec { + const OOB: i32 = [1][1] + T::OOB; + //~^ ERROR operation will panic +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5579.rs b/src/tools/clippy/tests/ui/crashes/ice-5579.rs new file mode 100644 index 0000000000..e1842c73f0 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5579.rs @@ -0,0 +1,17 @@ +trait IsErr { + fn is_err(&self, err: &str) -> bool; +} + +impl IsErr for Option { + fn is_err(&self, _err: &str) -> bool { + true + } +} + +fn main() { + let t = Some(1); + + if t.is_err("") { + t.unwrap(); + } +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5872.rs b/src/tools/clippy/tests/ui/crashes/ice-5872.rs new file mode 100644 index 0000000000..68afa8f8c3 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5872.rs @@ -0,0 +1,5 @@ +#![warn(clippy::needless_collect)] + +fn main() { + let _ = vec![1, 2, 3].into_iter().collect::>().is_empty(); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5872.stderr b/src/tools/clippy/tests/ui/crashes/ice-5872.stderr new file mode 100644 index 0000000000..a60ca345cf --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5872.stderr @@ -0,0 +1,10 @@ +error: avoid using `collect()` when not needed + --> $DIR/ice-5872.rs:4:39 + | +LL | let _ = vec![1, 2, 3].into_iter().collect::>().is_empty(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` + | + = note: `-D clippy::needless-collect` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/ice-5944.rs b/src/tools/clippy/tests/ui/crashes/ice-5944.rs new file mode 100644 index 0000000000..5caf29c619 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5944.rs @@ -0,0 +1,13 @@ +#![warn(clippy::repeat_once)] + +trait Repeat { + fn repeat(&self) {} +} + +impl Repeat for usize { + fn repeat(&self) {} +} + +fn main() { + let _ = 42.repeat(); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6139.rs b/src/tools/clippy/tests/ui/crashes/ice-6139.rs new file mode 100644 index 0000000000..f3966e47f5 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6139.rs @@ -0,0 +1,7 @@ +trait T<'a> {} + +fn foo(_: Vec>>) {} + +fn main() { + foo(vec![]); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6153.rs b/src/tools/clippy/tests/ui/crashes/ice-6153.rs new file mode 100644 index 0000000000..9f73f39f10 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6153.rs @@ -0,0 +1,9 @@ +pub struct S<'a, 'e>(&'a str, &'e str); + +pub type T<'a, 'e> = std::collections::HashMap, ()>; + +impl<'e, 'a: 'e> S<'a, 'e> { + pub fn foo(_a: &str, _b: &str, _map: &T) {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6179.rs b/src/tools/clippy/tests/ui/crashes/ice-6179.rs new file mode 100644 index 0000000000..f8c866a49a --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6179.rs @@ -0,0 +1,21 @@ +//! This is a minimal reproducer for the ICE in https://github.com/rust-lang/rust-clippy/pull/6179. +//! The ICE is mainly caused by using `hir_ty_to_ty`. See the discussion in the PR for details. + +#![warn(clippy::use_self)] +#![allow(dead_code)] + +struct Foo {} + +impl Foo { + fn foo() -> Self { + impl Foo { + fn bar() {} + } + + let _: _ = 1; + + Self {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6250.rs b/src/tools/clippy/tests/ui/crashes/ice-6250.rs new file mode 100644 index 0000000000..c33580ff6a --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6250.rs @@ -0,0 +1,16 @@ +// originally from glacier/fixed/77218.rs +// ice while adjusting... + +pub struct Cache { + data: Vec, +} + +pub fn list_data(cache: &Cache, key: usize) { + for reference in vec![1, 2, 3] { + if + /* let */ + Some(reference) = cache.data.get(key) { + unimplemented!() + } + } +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6250.stderr b/src/tools/clippy/tests/ui/crashes/ice-6250.stderr new file mode 100644 index 0000000000..c38727316c --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6250.stderr @@ -0,0 +1,42 @@ +error[E0658]: destructuring assignments are unstable + --> $DIR/ice-6250.rs:12:25 + | +LL | Some(reference) = cache.data.get(key) { + | --------------- ^ + | | + | cannot assign to this expression + | + = note: see issue #71126 for more information + = help: add `#![feature(destructuring_assignment)]` to the crate attributes to enable + +error[E0601]: `main` function not found in crate `ice_6250` + --> $DIR/ice-6250.rs:4:1 + | +LL | / pub struct Cache { +LL | | data: Vec, +LL | | } +LL | | +... | +LL | | } +LL | | } + | |_^ consider adding a `main` function to `$DIR/ice-6250.rs` + +error[E0308]: mismatched types + --> $DIR/ice-6250.rs:12:14 + | +LL | Some(reference) = cache.data.get(key) { + | ^^^^^^^^^ + | | + | expected integer, found `&i32` + | help: consider dereferencing the borrow: `*reference` + +error[E0308]: mismatched types + --> $DIR/ice-6250.rs:12:9 + | +LL | Some(reference) = cache.data.get(key) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found `()` + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0308, E0601, E0658. +For more information about an error, try `rustc --explain E0308`. diff --git a/src/tools/clippy/tests/ui/crashes/ice-6251.rs b/src/tools/clippy/tests/ui/crashes/ice-6251.rs new file mode 100644 index 0000000000..6aa779aaeb --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6251.rs @@ -0,0 +1,6 @@ +// originally from glacier/fixed/77329.rs +// assertion failed: `(left == right) ; different DefIds + +fn bug() -> impl Iterator { + std::iter::empty() +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6251.stderr b/src/tools/clippy/tests/ui/crashes/ice-6251.stderr new file mode 100644 index 0000000000..9a7cf4b091 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6251.stderr @@ -0,0 +1,43 @@ +error[E0601]: `main` function not found in crate `ice_6251` + --> $DIR/ice-6251.rs:4:1 + | +LL | / fn bug() -> impl Iterator { +LL | | std::iter::empty() +LL | | } + | |_^ consider adding a `main` function to `$DIR/ice-6251.rs` + +error[E0277]: the size for values of type `[u8]` cannot be known at compilation time + --> $DIR/ice-6251.rs:4:45 + | +LL | fn bug() -> impl Iterator { + | ^ doesn't have a size known at compile-time + | + = help: the trait `std::marker::Sized` is not implemented for `[u8]` + = help: unsized fn params are gated as an unstable feature +help: function arguments must have a statically known size, borrowed types always have a known size + | +LL | fn bug() -> impl Iterator { + | ^ + +error[E0277]: the size for values of type `[u8]` cannot be known at compilation time + --> $DIR/ice-6251.rs:4:54 + | +LL | fn bug() -> impl Iterator { + | ^ doesn't have a size known at compile-time + | + = help: the trait `std::marker::Sized` is not implemented for `[u8]` + = note: the return type of a function must have a statically known size + +error[E0308]: mismatched types + --> $DIR/ice-6251.rs:4:44 + | +LL | fn bug() -> impl Iterator { + | ^^^^^^^^^^^ expected `usize`, found closure + | + = note: expected type `usize` + found closure `[closure@$DIR/ice-6251.rs:4:44: 4:55]` + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0277, E0308, E0601. +For more information about an error, try `rustc --explain E0277`. diff --git a/src/tools/clippy/tests/ui/crashes/ice-6252.rs b/src/tools/clippy/tests/ui/crashes/ice-6252.rs new file mode 100644 index 0000000000..2e3d9fd1e9 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6252.rs @@ -0,0 +1,15 @@ +// originally from glacier fixed/77919.rs +// encountered errors resolving bounds after type-checking + +trait TypeVal { + const VAL: T; +} +struct Five; +struct Multiply { + _n: PhantomData, +} +impl TypeVal for Multiply where N: TypeVal {} + +fn main() { + [1; >::VAL]; +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6252.stderr b/src/tools/clippy/tests/ui/crashes/ice-6252.stderr new file mode 100644 index 0000000000..eaa5e6f51c --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6252.stderr @@ -0,0 +1,32 @@ +error[E0412]: cannot find type `PhantomData` in this scope + --> $DIR/ice-6252.rs:9:9 + | +LL | _n: PhantomData, + | ^^^^^^^^^^^ not found in this scope + | +help: consider importing this struct + | +LL | use std::marker::PhantomData; + | + +error[E0412]: cannot find type `VAL` in this scope + --> $DIR/ice-6252.rs:11:63 + | +LL | impl TypeVal for Multiply where N: TypeVal {} + | - ^^^ not found in this scope + | | + | help: you might be missing a type parameter: `, VAL` + +error[E0046]: not all trait items implemented, missing: `VAL` + --> $DIR/ice-6252.rs:11:1 + | +LL | const VAL: T; + | ------------- `VAL` from trait +... +LL | impl TypeVal for Multiply where N: TypeVal {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `VAL` in implementation + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0046, E0412. +For more information about an error, try `rustc --explain E0046`. diff --git a/src/tools/clippy/tests/ui/crashes/ice-6254.rs b/src/tools/clippy/tests/ui/crashes/ice-6254.rs new file mode 100644 index 0000000000..c19eca4388 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6254.rs @@ -0,0 +1,15 @@ +// originally from ./src/test/ui/pattern/usefulness/consts-opaque.rs +// panicked at 'assertion failed: rows.iter().all(|r| r.len() == v.len())', +// compiler/rustc_mir_build/src/thir/pattern/_match.rs:2030:5 + +#[derive(PartialEq)] +struct Foo(i32); +const FOO_REF_REF: &&Foo = &&Foo(42); + +fn main() { + // This used to cause an ICE (https://github.com/rust-lang/rust/issues/78071) + match FOO_REF_REF { + FOO_REF_REF => {}, + Foo(_) => {}, + } +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6254.stderr b/src/tools/clippy/tests/ui/crashes/ice-6254.stderr new file mode 100644 index 0000000000..95ebf23d81 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6254.stderr @@ -0,0 +1,12 @@ +error: to use a constant of type `Foo` in a pattern, `Foo` must be annotated with `#[derive(PartialEq, Eq)]` + --> $DIR/ice-6254.rs:12:9 + | +LL | FOO_REF_REF => {}, + | ^^^^^^^^^^^ + | + = note: `-D indirect-structural-match` implied by `-D warnings` + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #62411 + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/ice-6255.rs b/src/tools/clippy/tests/ui/crashes/ice-6255.rs new file mode 100644 index 0000000000..bd4a81d98e --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6255.rs @@ -0,0 +1,15 @@ +// originally from rustc ./src/test/ui/macros/issue-78325-inconsistent-resolution.rs +// inconsistent resolution for a macro + +macro_rules! define_other_core { + ( ) => { + extern crate std as core; + //~^ ERROR macro-expanded `extern crate` items cannot shadow names passed with `--extern` + }; +} + +fn main() { + core::panic!(); +} + +define_other_core!(); diff --git a/src/tools/clippy/tests/ui/crashes/ice-6255.stderr b/src/tools/clippy/tests/ui/crashes/ice-6255.stderr new file mode 100644 index 0000000000..d973ea1e23 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6255.stderr @@ -0,0 +1,13 @@ +error: macro-expanded `extern crate` items cannot shadow names passed with `--extern` + --> $DIR/ice-6255.rs:6:9 + | +LL | extern crate std as core; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | define_other_core!(); + | --------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/ice-6256.rs b/src/tools/clippy/tests/ui/crashes/ice-6256.rs new file mode 100644 index 0000000000..67308263da --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6256.rs @@ -0,0 +1,15 @@ +// originally from rustc ./src/test/ui/regions/issue-78262.rs +// ICE: to get the signature of a closure, use substs.as_closure().sig() not fn_sig() +#![allow(clippy::upper_case_acronyms)] + +trait TT {} + +impl dyn TT { + fn func(&self) {} +} + +#[rustfmt::skip] +fn main() { + let f = |x: &dyn TT| x.func(); //[default]~ ERROR: mismatched types + //[nll]~^ ERROR: borrowed data escapes outside of closure +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6256.stderr b/src/tools/clippy/tests/ui/crashes/ice-6256.stderr new file mode 100644 index 0000000000..d35d459168 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6256.stderr @@ -0,0 +1,18 @@ +error[E0308]: mismatched types + --> $DIR/ice-6256.rs:13:28 + | +LL | let f = |x: &dyn TT| x.func(); //[default]~ ERROR: mismatched types + | ^^^^ lifetime mismatch + | + = note: expected reference `&(dyn TT + 'static)` + found reference `&dyn TT` +note: the anonymous lifetime #1 defined on the body at 13:13... + --> $DIR/ice-6256.rs:13:13 + | +LL | let f = |x: &dyn TT| x.func(); //[default]~ ERROR: mismatched types + | ^^^^^^^^^^^^^^^^^^^^^ + = note: ...does not necessarily outlive the static lifetime + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/tools/clippy/tests/ui/crashes/ice-6332.rs b/src/tools/clippy/tests/ui/crashes/ice-6332.rs new file mode 100644 index 0000000000..9dc92aa500 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6332.rs @@ -0,0 +1,11 @@ +fn cmark_check() { + let mut link_err = false; + macro_rules! cmark_error { + ($bad:expr) => { + *$bad = true; + }; + } + cmark_error!(&mut link_err); +} + +pub fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6539.rs b/src/tools/clippy/tests/ui/crashes/ice-6539.rs new file mode 100644 index 0000000000..ac6c3e4aba --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6539.rs @@ -0,0 +1,16 @@ +// The test for the ICE 6539: https://github.com/rust-lang/rust-clippy/issues/6539. +// The cause is that `zero_sized_map_values` used `layout_of` with types from type aliases, +// which is essentially the same as the ICE 4968. +// Note that only type aliases with associated types caused the crash this time, +// not others such as trait impls. + +use std::collections::{BTreeMap, HashMap}; + +pub trait Trait { + type Assoc; +} + +type TypeAlias = HashMap<(), ::Assoc>; +type TypeAlias2 = BTreeMap<(), ::Assoc>; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6792.rs b/src/tools/clippy/tests/ui/crashes/ice-6792.rs new file mode 100644 index 0000000000..0e2ab1a39b --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6792.rs @@ -0,0 +1,20 @@ +//! This is a reproducer for the ICE 6792: https://github.com/rust-lang/rust-clippy/issues/6792. +//! The ICE is caused by using `TyCtxt::type_of(assoc_type_id)`. + +trait Trait { + type Ty; + + fn broken() -> Self::Ty; +} + +struct Foo {} + +impl Trait for Foo { + type Ty = Foo; + + fn broken() -> Self::Ty { + Self::Ty {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6793.rs b/src/tools/clippy/tests/ui/crashes/ice-6793.rs new file mode 100644 index 0000000000..12a4a0d25e --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6793.rs @@ -0,0 +1,23 @@ +//! This is a reproducer for the ICE 6793: https://github.com/rust-lang/rust-clippy/issues/6793. +//! The ICE is caused by using `TyCtxt::type_of(assoc_type_id)`, which is the same as the ICE 6792. + +trait Trait { + type Ty: 'static + Clone; + + fn broken() -> Self::Ty; +} + +#[derive(Clone)] +struct MyType { + x: i32, +} + +impl Trait for MyType { + type Ty = MyType; + + fn broken() -> Self::Ty { + Self::Ty { x: 1 } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-6840.rs b/src/tools/clippy/tests/ui/crashes/ice-6840.rs new file mode 100644 index 0000000000..d789f60c5d --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6840.rs @@ -0,0 +1,31 @@ +//! This is a reproducer for the ICE 6840: https://github.com/rust-lang/rust-clippy/issues/6840. +//! The ICE is caused by `TyCtxt::layout_of` and `is_normalizable` not being strict enough +#![allow(dead_code)] +use std::collections::HashMap; + +pub trait Rule { + type DependencyKey; +} + +pub struct RuleEdges { + dependencies: R::DependencyKey, +} + +type RuleDependencyEdges = HashMap>; + +// reproducer from the GitHub issue ends here +// but check some additional variants +type RuleDependencyEdgesArray = HashMap; 8]>; +type RuleDependencyEdgesSlice = HashMap]>; +type RuleDependencyEdgesRef = HashMap>; +type RuleDependencyEdgesRaw = HashMap>; +type RuleDependencyEdgesTuple = HashMap, RuleEdges)>; + +// and an additional checks to make sure fix doesn't have stack-overflow issue +// on self-containing types +pub struct SelfContaining { + inner: Box, +} +type SelfContainingEdges = HashMap; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-700.rs b/src/tools/clippy/tests/ui/crashes/ice-700.rs new file mode 100644 index 0000000000..0cbceedbd6 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-700.rs @@ -0,0 +1,9 @@ +#![deny(clippy::all)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/700 + +fn core() {} + +fn main() { + core(); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice_exacte_size.rs b/src/tools/clippy/tests/ui/crashes/ice_exacte_size.rs new file mode 100644 index 0000000000..30e4b11ec0 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice_exacte_size.rs @@ -0,0 +1,19 @@ +#![deny(clippy::all)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/1336 + +#[allow(dead_code)] +struct Foo; + +impl Iterator for Foo { + type Item = (); + + fn next(&mut self) -> Option<()> { + let _ = self.len() == 0; + unimplemented!() + } +} + +impl ExactSizeIterator for Foo {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/if_same_then_else.rs b/src/tools/clippy/tests/ui/crashes/if_same_then_else.rs new file mode 100644 index 0000000000..2f91329299 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/if_same_then_else.rs @@ -0,0 +1,16 @@ +#![allow(clippy::comparison_chain)] +#![deny(clippy::if_same_then_else)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/2426 + +fn main() {} + +pub fn foo(a: i32, b: i32) -> Option<&'static str> { + if a == b { + None + } else if a > b { + Some("a pfeil b") + } else { + None + } +} diff --git a/src/tools/clippy/tests/ui/crashes/implements-trait.rs b/src/tools/clippy/tests/ui/crashes/implements-trait.rs new file mode 100644 index 0000000000..4502b0147a --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/implements-trait.rs @@ -0,0 +1,5 @@ +#[allow(clippy::needless_borrowed_reference)] +fn main() { + let mut v = Vec::::new(); + let _ = v.iter_mut().filter(|&ref a| a.is_empty()); +} diff --git a/src/tools/clippy/tests/ui/crashes/inherent_impl.rs b/src/tools/clippy/tests/ui/crashes/inherent_impl.rs new file mode 100644 index 0000000000..aeb27b5ba8 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/inherent_impl.rs @@ -0,0 +1,26 @@ +#![deny(clippy::multiple_inherent_impl)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/4578 + +macro_rules! impl_foo { + ($struct:ident) => { + impl $struct { + fn foo() {} + } + }; +} + +macro_rules! impl_bar { + ($struct:ident) => { + impl $struct { + fn bar() {} + } + }; +} + +struct MyStruct; + +impl_foo!(MyStruct); +impl_bar!(MyStruct); + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/issue-825.rs b/src/tools/clippy/tests/ui/crashes/issue-825.rs new file mode 100644 index 0000000000..05696e3d7d --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/issue-825.rs @@ -0,0 +1,25 @@ +#![allow(warnings)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/825 + +// this should compile in a reasonable amount of time +fn rust_type_id(name: &str) { + if "bool" == &name[..] + || "uint" == &name[..] + || "u8" == &name[..] + || "u16" == &name[..] + || "u32" == &name[..] + || "f32" == &name[..] + || "f64" == &name[..] + || "i8" == &name[..] + || "i16" == &name[..] + || "i32" == &name[..] + || "i64" == &name[..] + || "Self" == &name[..] + || "str" == &name[..] + { + unreachable!(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/issues_loop_mut_cond.rs b/src/tools/clippy/tests/ui/crashes/issues_loop_mut_cond.rs new file mode 100644 index 0000000000..bb238c81eb --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/issues_loop_mut_cond.rs @@ -0,0 +1,28 @@ +#![allow(dead_code)] + +/// Issue: https://github.com/rust-lang/rust-clippy/issues/2596 +pub fn loop_on_block_condition(u: &mut isize) { + while { *u < 0 } { + *u += 1; + } +} + +/// https://github.com/rust-lang/rust-clippy/issues/2584 +fn loop_with_unsafe_condition(ptr: *const u8) { + let mut len = 0; + while unsafe { *ptr.offset(len) } != 0 { + len += 1; + } +} + +/// https://github.com/rust-lang/rust-clippy/issues/2710 +static mut RUNNING: bool = true; +fn loop_on_static_condition() { + unsafe { + while RUNNING { + RUNNING = false; + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/match_same_arms_const.rs b/src/tools/clippy/tests/ui/crashes/match_same_arms_const.rs new file mode 100644 index 0000000000..94c939665e --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/match_same_arms_const.rs @@ -0,0 +1,18 @@ +#![deny(clippy::match_same_arms)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/2427 + +const PRICE_OF_SWEETS: u32 = 5; +const PRICE_OF_KINDNESS: u32 = 0; +const PRICE_OF_DRINKS: u32 = 5; + +pub fn price(thing: &str) -> u32 { + match thing { + "rolo" => PRICE_OF_SWEETS, + "advice" => PRICE_OF_KINDNESS, + "juice" => PRICE_OF_DRINKS, + _ => panic!(), + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/mut_mut_macro.rs b/src/tools/clippy/tests/ui/crashes/mut_mut_macro.rs new file mode 100644 index 0000000000..a238e7896f --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/mut_mut_macro.rs @@ -0,0 +1,34 @@ +#![deny(clippy::mut_mut, clippy::zero_ptr, clippy::cmp_nan)] +#![allow(dead_code)] + +// FIXME: compiletest + extern crates doesn't work together. To make this test work, it would need +// the following three lines and the lazy_static crate. +// +// #[macro_use] +// extern crate lazy_static; +// use std::collections::HashMap; + +/// ensure that we don't suggest `is_nan` and `is_null` inside constants +/// FIXME: once const fn is stable, suggest these functions again in constants + +const BAA: *const i32 = 0 as *const i32; +static mut BAR: *const i32 = BAA; +static mut FOO: *const i32 = 0 as *const i32; +static mut BUH: bool = 42.0 < f32::NAN; + +#[allow(unused_variables, unused_mut)] +fn main() { + /* + lazy_static! { + static ref MUT_MAP : HashMap = { + let mut m = HashMap::new(); + m.insert(0, "zero"); + m + }; + static ref MUT_COUNT : usize = MUT_MAP.len(); + } + assert_eq!(*MUT_COUNT, 1); + */ + // FIXME: don't lint in array length, requires `check_body` + //let _ = [""; (42.0 < f32::NAN) as usize]; +} diff --git a/src/tools/clippy/tests/ui/crashes/needless_borrow_fp.rs b/src/tools/clippy/tests/ui/crashes/needless_borrow_fp.rs new file mode 100644 index 0000000000..4f61c76828 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/needless_borrow_fp.rs @@ -0,0 +1,7 @@ +#[deny(clippy::all)] +#[derive(Debug)] +pub enum Error { + Type(&'static str), +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.rs b/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.rs new file mode 100644 index 0000000000..676564b244 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.rs @@ -0,0 +1,20 @@ +#![deny(clippy::needless_lifetimes)] +#![allow(dead_code)] + +trait Foo {} + +struct Bar {} + +struct Baz<'a> { + bar: &'a Bar, +} + +impl<'a> Foo for Baz<'a> {} + +impl Bar { + fn baz<'a>(&'a self) -> impl Foo + 'a { + Baz { bar: self } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.stderr b/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.stderr new file mode 100644 index 0000000000..d68bbe7880 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.stderr @@ -0,0 +1,14 @@ +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes_impl_trait.rs:15:5 + | +LL | fn baz<'a>(&'a self) -> impl Foo + 'a { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/needless_lifetimes_impl_trait.rs:1:9 + | +LL | #![deny(clippy::needless_lifetimes)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/procedural_macro.rs b/src/tools/clippy/tests/ui/crashes/procedural_macro.rs new file mode 100644 index 0000000000..c746849338 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/procedural_macro.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate clippy_mini_macro_test; + +#[deny(warnings)] +fn main() { + let x = Foo; + println!("{:?}", x); +} + +#[derive(ClippyMiniMacroTest, Debug)] +struct Foo; diff --git a/src/tools/clippy/tests/ui/crashes/regressions.rs b/src/tools/clippy/tests/ui/crashes/regressions.rs new file mode 100644 index 0000000000..a41bcb33b4 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/regressions.rs @@ -0,0 +1,11 @@ +#![allow(clippy::blacklisted_name)] + +pub fn foo(bar: *const u8) { + println!("{:#p}", bar); +} + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/4917 +/// i32 { + #[cfg(unix)] + return 1; + #[cfg(not(unix))] + return 2; +} + +#[deny(warnings)] +fn cfg_let_and_return() -> i32 { + #[cfg(unix)] + let x = 1; + #[cfg(not(unix))] + let x = 2; + x +} + +fn main() { + cfg_return(); + cfg_let_and_return(); +} diff --git a/src/tools/clippy/tests/ui/crashes/shadow.rs b/src/tools/clippy/tests/ui/crashes/shadow.rs new file mode 100644 index 0000000000..843e8ef64d --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/shadow.rs @@ -0,0 +1,6 @@ +fn main() { + let x: [i32; { + let u = 2; + 4 + }] = [2; { 4 }]; +} diff --git a/src/tools/clippy/tests/ui/crashes/single-match-else.rs b/src/tools/clippy/tests/ui/crashes/single-match-else.rs new file mode 100644 index 0000000000..1ba7ac0821 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/single-match-else.rs @@ -0,0 +1,11 @@ +#![warn(clippy::single_match_else)] + +//! Test for https://github.com/rust-lang/rust-clippy/issues/1588 + +fn main() { + let n = match (42, 43) { + (42, n) => n, + _ => panic!("typeck error"), + }; + assert_eq!(n, 43); +} diff --git a/src/tools/clippy/tests/ui/crashes/third-party/clippy.toml b/src/tools/clippy/tests/ui/crashes/third-party/clippy.toml new file mode 100644 index 0000000000..9f87de20ba --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/third-party/clippy.toml @@ -0,0 +1,3 @@ +# this is ignored by Clippy, but allowed for other tools like clippy-service +[third-party] +clippy-feature = "nightly" diff --git a/src/tools/clippy/tests/ui/crashes/third-party/conf_allowlisted.rs b/src/tools/clippy/tests/ui/crashes/third-party/conf_allowlisted.rs new file mode 100644 index 0000000000..f328e4d9d0 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/third-party/conf_allowlisted.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/trivial_bounds.rs b/src/tools/clippy/tests/ui/crashes/trivial_bounds.rs new file mode 100644 index 0000000000..60105a8213 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/trivial_bounds.rs @@ -0,0 +1,11 @@ +#![feature(trivial_bounds)] +#![allow(unused, trivial_bounds)] + +fn test_trivial_bounds() +where + i32: Iterator, +{ + for _ in 2i32 {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/used_underscore_binding_macro.rs b/src/tools/clippy/tests/ui/crashes/used_underscore_binding_macro.rs new file mode 100644 index 0000000000..c57a45dc7a --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/used_underscore_binding_macro.rs @@ -0,0 +1,18 @@ +// edition:2018 + +use serde::Deserialize; + +/// Tests that we do not lint for unused underscores in a `MacroAttribute` +/// expansion +#[deny(clippy::used_underscore_binding)] +#[derive(Deserialize)] +struct MacroAttributesTest { + _foo: u32, +} + +#[test] +fn macro_attributes_test() { + let _ = MacroAttributesTest { _foo: 0 }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.rs b/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.rs new file mode 100644 index 0000000000..995787c533 --- /dev/null +++ b/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.rs @@ -0,0 +1,12 @@ +// ignore-macos +// ignore-windows + +#![feature(main)] + +#[warn(clippy::main_recursion)] +#[allow(unconditional_recursion)] +#[main] +fn a() { + println!("Hello, World!"); + a(); +} diff --git a/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.stderr b/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.stderr new file mode 100644 index 0000000000..f52fc949f6 --- /dev/null +++ b/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.stderr @@ -0,0 +1,11 @@ +error: recursing into entrypoint `a` + --> $DIR/entrypoint_recursion.rs:11:5 + | +LL | a(); + | ^ + | + = note: `-D clippy::main-recursion` implied by `-D warnings` + = help: consider using another function for this recursion + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs b/src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs new file mode 100644 index 0000000000..25b1417be9 --- /dev/null +++ b/src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs @@ -0,0 +1,33 @@ +// ignore-macos +// ignore-windows + +#![feature(lang_items, link_args, start, libc)] +#![link_args = "-nostartfiles"] +#![no_std] + +use core::panic::PanicInfo; +use core::sync::atomic::{AtomicUsize, Ordering}; + +static N: AtomicUsize = AtomicUsize::new(0); + +#[warn(clippy::main_recursion)] +#[start] +fn main(argc: isize, argv: *const *const u8) -> isize { + let x = N.load(Ordering::Relaxed); + N.store(x + 1, Ordering::Relaxed); + + if x < 3 { + main(argc, argv); + } + + 0 +} + +#[allow(clippy::empty_loop)] +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[lang = "eh_personality"] +extern "C" fn eh_personality() {} diff --git a/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.rs b/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.rs new file mode 100644 index 0000000000..89ff660993 --- /dev/null +++ b/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.rs @@ -0,0 +1,6 @@ +#[warn(clippy::main_recursion)] +#[allow(unconditional_recursion)] +fn main() { + println!("Hello, World!"); + main(); +} diff --git a/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.stderr b/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.stderr new file mode 100644 index 0000000000..0a260f9d23 --- /dev/null +++ b/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.stderr @@ -0,0 +1,11 @@ +error: recursing into entrypoint `main` + --> $DIR/std_main_recursion.rs:5:5 + | +LL | main(); + | ^^^^ + | + = note: `-D clippy::main-recursion` implied by `-D warnings` + = help: consider using another function for this recursion + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/create_dir.fixed b/src/tools/clippy/tests/ui/create_dir.fixed new file mode 100644 index 0000000000..8ed53a56ac --- /dev/null +++ b/src/tools/clippy/tests/ui/create_dir.fixed @@ -0,0 +1,17 @@ +// run-rustfix +#![allow(unused_must_use)] +#![warn(clippy::create_dir)] + +use std::fs::create_dir_all; + +fn create_dir() {} + +fn main() { + // Should be warned + create_dir_all("foo"); + create_dir_all("bar").unwrap(); + + // Shouldn't be warned + create_dir(); + std::fs::create_dir_all("foobar"); +} diff --git a/src/tools/clippy/tests/ui/create_dir.rs b/src/tools/clippy/tests/ui/create_dir.rs new file mode 100644 index 0000000000..19c8fc24ba --- /dev/null +++ b/src/tools/clippy/tests/ui/create_dir.rs @@ -0,0 +1,17 @@ +// run-rustfix +#![allow(unused_must_use)] +#![warn(clippy::create_dir)] + +use std::fs::create_dir_all; + +fn create_dir() {} + +fn main() { + // Should be warned + std::fs::create_dir("foo"); + std::fs::create_dir("bar").unwrap(); + + // Shouldn't be warned + create_dir(); + std::fs::create_dir_all("foobar"); +} diff --git a/src/tools/clippy/tests/ui/create_dir.stderr b/src/tools/clippy/tests/ui/create_dir.stderr new file mode 100644 index 0000000000..67298fc470 --- /dev/null +++ b/src/tools/clippy/tests/ui/create_dir.stderr @@ -0,0 +1,16 @@ +error: calling `std::fs::create_dir` where there may be a better way + --> $DIR/create_dir.rs:11:5 + | +LL | std::fs::create_dir("foo"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `std::fs::create_dir_all` instead: `create_dir_all("foo")` + | + = note: `-D clippy::create-dir` implied by `-D warnings` + +error: calling `std::fs::create_dir` where there may be a better way + --> $DIR/create_dir.rs:12:5 + | +LL | std::fs::create_dir("bar").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `std::fs::create_dir_all` instead: `create_dir_all("bar")` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/dbg_macro.rs b/src/tools/clippy/tests/ui/dbg_macro.rs new file mode 100644 index 0000000000..d74e2611ee --- /dev/null +++ b/src/tools/clippy/tests/ui/dbg_macro.rs @@ -0,0 +1,19 @@ +#![warn(clippy::dbg_macro)] + +fn foo(n: u32) -> u32 { + if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n } +} + +fn factorial(n: u32) -> u32 { + if dbg!(n <= 1) { + dbg!(1) + } else { + dbg!(n * factorial(n - 1)) + } +} + +fn main() { + dbg!(42); + dbg!(dbg!(dbg!(42))); + foo(3) + dbg!(factorial(4)); +} diff --git a/src/tools/clippy/tests/ui/dbg_macro.stderr b/src/tools/clippy/tests/ui/dbg_macro.stderr new file mode 100644 index 0000000000..bdf372af29 --- /dev/null +++ b/src/tools/clippy/tests/ui/dbg_macro.stderr @@ -0,0 +1,80 @@ +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:4:22 + | +LL | if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n } + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::dbg-macro` implied by `-D warnings` +help: ensure to avoid having uses of it in version control + | +LL | if let Some(n) = n.checked_sub(4) { n } else { n } + | ^^^^^^^^^^^^^^^^ + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:8:8 + | +LL | if dbg!(n <= 1) { + | ^^^^^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | if n <= 1 { + | ^^^^^^ + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:9:9 + | +LL | dbg!(1) + | ^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | 1 + | + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:11:9 + | +LL | dbg!(n * factorial(n - 1)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | n * factorial(n - 1) + | + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:16:5 + | +LL | dbg!(42); + | ^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | 42; + | ^^ + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:17:5 + | +LL | dbg!(dbg!(dbg!(42))); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | dbg!(dbg!(42)); + | ^^^^^^^^^^^^^^ + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:18:14 + | +LL | foo(3) + dbg!(factorial(4)); + | ^^^^^^^^^^^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | foo(3) + factorial(4); + | ^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/debug_assert_with_mut_call.rs b/src/tools/clippy/tests/ui/debug_assert_with_mut_call.rs new file mode 100644 index 0000000000..477a47118d --- /dev/null +++ b/src/tools/clippy/tests/ui/debug_assert_with_mut_call.rs @@ -0,0 +1,133 @@ +// compile-flags: --edition=2018 +#![feature(custom_inner_attributes)] +#![rustfmt::skip] +#![warn(clippy::debug_assert_with_mut_call)] +#![allow(clippy::redundant_closure_call)] + +struct S; + +impl S { + fn bool_self_ref(&self) -> bool { false } + fn bool_self_mut(&mut self) -> bool { false } + fn bool_self_ref_arg_ref(&self, _: &u32) -> bool { false } + fn bool_self_ref_arg_mut(&self, _: &mut u32) -> bool { false } + fn bool_self_mut_arg_ref(&mut self, _: &u32) -> bool { false } + fn bool_self_mut_arg_mut(&mut self, _: &mut u32) -> bool { false } + + fn u32_self_ref(&self) -> u32 { 0 } + fn u32_self_mut(&mut self) -> u32 { 0 } + fn u32_self_ref_arg_ref(&self, _: &u32) -> u32 { 0 } + fn u32_self_ref_arg_mut(&self, _: &mut u32) -> u32 { 0 } + fn u32_self_mut_arg_ref(&mut self, _: &u32) -> u32 { 0 } + fn u32_self_mut_arg_mut(&mut self, _: &mut u32) -> u32 { 0 } +} + +fn bool_ref(_: &u32) -> bool { false } +fn bool_mut(_: &mut u32) -> bool { false } +fn u32_ref(_: &u32) -> u32 { 0 } +fn u32_mut(_: &mut u32) -> u32 { 0 } + +fn func_non_mutable() { + debug_assert!(bool_ref(&3)); + debug_assert!(!bool_ref(&3)); + + debug_assert_eq!(0, u32_ref(&3)); + debug_assert_eq!(u32_ref(&3), 0); + + debug_assert_ne!(1, u32_ref(&3)); + debug_assert_ne!(u32_ref(&3), 1); +} + +fn func_mutable() { + debug_assert!(bool_mut(&mut 3)); + debug_assert!(!bool_mut(&mut 3)); + + debug_assert_eq!(0, u32_mut(&mut 3)); + debug_assert_eq!(u32_mut(&mut 3), 0); + + debug_assert_ne!(1, u32_mut(&mut 3)); + debug_assert_ne!(u32_mut(&mut 3), 1); +} + +fn method_non_mutable() { + debug_assert!(S.bool_self_ref()); + debug_assert!(S.bool_self_ref_arg_ref(&3)); + + debug_assert_eq!(S.u32_self_ref(), 0); + debug_assert_eq!(S.u32_self_ref_arg_ref(&3), 0); + + debug_assert_ne!(S.u32_self_ref(), 1); + debug_assert_ne!(S.u32_self_ref_arg_ref(&3), 1); +} + +fn method_mutable() { + debug_assert!(S.bool_self_mut()); + debug_assert!(!S.bool_self_mut()); + debug_assert!(S.bool_self_ref_arg_mut(&mut 3)); + debug_assert!(S.bool_self_mut_arg_ref(&3)); + debug_assert!(S.bool_self_mut_arg_mut(&mut 3)); + + debug_assert_eq!(S.u32_self_mut(), 0); + debug_assert_eq!(S.u32_self_mut_arg_ref(&3), 0); + debug_assert_eq!(S.u32_self_ref_arg_mut(&mut 3), 0); + debug_assert_eq!(S.u32_self_mut_arg_mut(&mut 3), 0); + + debug_assert_ne!(S.u32_self_mut(), 1); + debug_assert_ne!(S.u32_self_mut_arg_ref(&3), 1); + debug_assert_ne!(S.u32_self_ref_arg_mut(&mut 3), 1); + debug_assert_ne!(S.u32_self_mut_arg_mut(&mut 3), 1); +} + +fn misc() { + // with variable + let mut v: Vec = vec![1, 2, 3, 4]; + debug_assert_eq!(v.get(0), Some(&1)); + debug_assert_ne!(v[0], 2); + debug_assert_eq!(v.pop(), Some(1)); + debug_assert_ne!(Some(3), v.pop()); + + let a = &mut 3; + debug_assert!(bool_mut(a)); + + // nested + debug_assert!(!(bool_ref(&u32_mut(&mut 3)))); + + // chained + debug_assert_eq!(v.pop().unwrap(), 3); + + // format args + debug_assert!(bool_ref(&3), "w/o format"); + debug_assert!(bool_mut(&mut 3), "w/o format"); + debug_assert!(bool_ref(&3), "{} format", "w/"); + debug_assert!(bool_mut(&mut 3), "{} format", "w/"); + + // sub block + let mut x = 42_u32; + debug_assert!({ + bool_mut(&mut x); + x > 10 + }); + + // closures + debug_assert!((|| { + let mut x = 42; + bool_mut(&mut x); + x > 10 + })()); +} + +async fn debug_await() { + debug_assert!(async { + true + }.await); +} + +fn main() { + func_non_mutable(); + func_mutable(); + method_non_mutable(); + method_mutable(); + + misc(); + debug_await(); +} diff --git a/src/tools/clippy/tests/ui/debug_assert_with_mut_call.stderr b/src/tools/clippy/tests/ui/debug_assert_with_mut_call.stderr new file mode 100644 index 0000000000..a2ca71b57a --- /dev/null +++ b/src/tools/clippy/tests/ui/debug_assert_with_mut_call.stderr @@ -0,0 +1,172 @@ +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:42:19 + | +LL | debug_assert!(bool_mut(&mut 3)); + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::debug-assert-with-mut-call` implied by `-D warnings` + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:43:20 + | +LL | debug_assert!(!bool_mut(&mut 3)); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:45:25 + | +LL | debug_assert_eq!(0, u32_mut(&mut 3)); + | ^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:46:22 + | +LL | debug_assert_eq!(u32_mut(&mut 3), 0); + | ^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:48:25 + | +LL | debug_assert_ne!(1, u32_mut(&mut 3)); + | ^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:49:22 + | +LL | debug_assert_ne!(u32_mut(&mut 3), 1); + | ^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:64:19 + | +LL | debug_assert!(S.bool_self_mut()); + | ^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:65:20 + | +LL | debug_assert!(!S.bool_self_mut()); + | ^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:66:19 + | +LL | debug_assert!(S.bool_self_ref_arg_mut(&mut 3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:67:19 + | +LL | debug_assert!(S.bool_self_mut_arg_ref(&3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:68:19 + | +LL | debug_assert!(S.bool_self_mut_arg_mut(&mut 3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:70:22 + | +LL | debug_assert_eq!(S.u32_self_mut(), 0); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:71:22 + | +LL | debug_assert_eq!(S.u32_self_mut_arg_ref(&3), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:72:22 + | +LL | debug_assert_eq!(S.u32_self_ref_arg_mut(&mut 3), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:73:22 + | +LL | debug_assert_eq!(S.u32_self_mut_arg_mut(&mut 3), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:75:22 + | +LL | debug_assert_ne!(S.u32_self_mut(), 1); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:76:22 + | +LL | debug_assert_ne!(S.u32_self_mut_arg_ref(&3), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:77:22 + | +LL | debug_assert_ne!(S.u32_self_ref_arg_mut(&mut 3), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:78:22 + | +LL | debug_assert_ne!(S.u32_self_mut_arg_mut(&mut 3), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:86:22 + | +LL | debug_assert_eq!(v.pop(), Some(1)); + | ^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:87:31 + | +LL | debug_assert_ne!(Some(3), v.pop()); + | ^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:90:19 + | +LL | debug_assert!(bool_mut(a)); + | ^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:93:31 + | +LL | debug_assert!(!(bool_ref(&u32_mut(&mut 3)))); + | ^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:96:22 + | +LL | debug_assert_eq!(v.pop().unwrap(), 3); + | ^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:100:19 + | +LL | debug_assert!(bool_mut(&mut 3), "w/o format"); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:102:19 + | +LL | debug_assert!(bool_mut(&mut 3), "{} format", "w/"); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:107:9 + | +LL | bool_mut(&mut x); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:114:9 + | +LL | bool_mut(&mut x); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 28 previous errors + diff --git a/src/tools/clippy/tests/ui/decimal_literal_representation.fixed b/src/tools/clippy/tests/ui/decimal_literal_representation.fixed new file mode 100644 index 0000000000..de39146512 --- /dev/null +++ b/src/tools/clippy/tests/ui/decimal_literal_representation.fixed @@ -0,0 +1,27 @@ +// run-rustfix + +#[warn(clippy::decimal_literal_representation)] +#[allow(unused_variables)] +#[rustfmt::skip] +fn main() { + let good = ( // Hex: + 127, // 0x7F + 256, // 0x100 + 511, // 0x1FF + 2048, // 0x800 + 4090, // 0xFFA + 16_371, // 0x3FF3 + 61_683, // 0xF0F3 + 2_131_750_925, // 0x7F0F_F00D + ); + let bad = ( // Hex: + 0x8005, // 0x8005 + 0xFF00, // 0xFF00 + 0x7F0F_F00F, // 0x7F0F_F00F + 0x7FFF_FFFF, // 0x7FFF_FFFF + #[allow(overflowing_literals)] + 0xF0F0_F0F0, // 0xF0F0_F0F0 + 0x8005_usize, // 0x8005_usize + 0x7F0F_F00F_isize, // 0x7F0F_F00F_isize + ); +} diff --git a/src/tools/clippy/tests/ui/decimal_literal_representation.rs b/src/tools/clippy/tests/ui/decimal_literal_representation.rs new file mode 100644 index 0000000000..55d07698e7 --- /dev/null +++ b/src/tools/clippy/tests/ui/decimal_literal_representation.rs @@ -0,0 +1,27 @@ +// run-rustfix + +#[warn(clippy::decimal_literal_representation)] +#[allow(unused_variables)] +#[rustfmt::skip] +fn main() { + let good = ( // Hex: + 127, // 0x7F + 256, // 0x100 + 511, // 0x1FF + 2048, // 0x800 + 4090, // 0xFFA + 16_371, // 0x3FF3 + 61_683, // 0xF0F3 + 2_131_750_925, // 0x7F0F_F00D + ); + let bad = ( // Hex: + 32_773, // 0x8005 + 65_280, // 0xFF00 + 2_131_750_927, // 0x7F0F_F00F + 2_147_483_647, // 0x7FFF_FFFF + #[allow(overflowing_literals)] + 4_042_322_160, // 0xF0F0_F0F0 + 32_773usize, // 0x8005_usize + 2_131_750_927isize, // 0x7F0F_F00F_isize + ); +} diff --git a/src/tools/clippy/tests/ui/decimal_literal_representation.stderr b/src/tools/clippy/tests/ui/decimal_literal_representation.stderr new file mode 100644 index 0000000000..8d50c8f83d --- /dev/null +++ b/src/tools/clippy/tests/ui/decimal_literal_representation.stderr @@ -0,0 +1,46 @@ +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:18:9 + | +LL | 32_773, // 0x8005 + | ^^^^^^ help: consider: `0x8005` + | + = note: `-D clippy::decimal-literal-representation` implied by `-D warnings` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:19:9 + | +LL | 65_280, // 0xFF00 + | ^^^^^^ help: consider: `0xFF00` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:20:9 + | +LL | 2_131_750_927, // 0x7F0F_F00F + | ^^^^^^^^^^^^^ help: consider: `0x7F0F_F00F` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:21:9 + | +LL | 2_147_483_647, // 0x7FFF_FFFF + | ^^^^^^^^^^^^^ help: consider: `0x7FFF_FFFF` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:23:9 + | +LL | 4_042_322_160, // 0xF0F0_F0F0 + | ^^^^^^^^^^^^^ help: consider: `0xF0F0_F0F0` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:24:9 + | +LL | 32_773usize, // 0x8005_usize + | ^^^^^^^^^^^ help: consider: `0x8005_usize` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:25:9 + | +LL | 2_131_750_927isize, // 0x7F0F_F00F_isize + | ^^^^^^^^^^^^^^^^^^ help: consider: `0x7F0F_F00F_isize` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/declare_interior_mutable_const/enums.rs b/src/tools/clippy/tests/ui/declare_interior_mutable_const/enums.rs new file mode 100644 index 0000000000..f44518694b --- /dev/null +++ b/src/tools/clippy/tests/ui/declare_interior_mutable_const/enums.rs @@ -0,0 +1,123 @@ +#![warn(clippy::declare_interior_mutable_const)] + +use std::cell::Cell; +use std::sync::atomic::AtomicUsize; + +enum OptionalCell { + Unfrozen(Cell), + Frozen, +} + +// a constant with enums should be linted only when the used variant is unfrozen (#3962). +const UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(true)); //~ ERROR interior mutable +const FROZEN_VARIANT: OptionalCell = OptionalCell::Frozen; + +const fn unfrozen_variant() -> OptionalCell { + OptionalCell::Unfrozen(Cell::new(false)) +} + +const fn frozen_variant() -> OptionalCell { + OptionalCell::Frozen +} + +const UNFROZEN_VARIANT_FROM_FN: OptionalCell = unfrozen_variant(); //~ ERROR interior mutable +const FROZEN_VARIANT_FROM_FN: OptionalCell = frozen_variant(); + +enum NestedInnermost { + Unfrozen(AtomicUsize), + Frozen, +} + +struct NestedInner { + inner: NestedInnermost, +} + +enum NestedOuter { + NestedInner(NestedInner), + NotNested(usize), +} + +struct NestedOutermost { + outer: NestedOuter, +} + +// a constant with enums should be linted according to its value, no matter how structs involve. +const NESTED_UNFROZEN_VARIANT: NestedOutermost = NestedOutermost { + outer: NestedOuter::NestedInner(NestedInner { + inner: NestedInnermost::Unfrozen(AtomicUsize::new(2)), + }), +}; //~ ERROR interior mutable +const NESTED_FROZEN_VARIANT: NestedOutermost = NestedOutermost { + outer: NestedOuter::NestedInner(NestedInner { + inner: NestedInnermost::Frozen, + }), +}; + +trait AssocConsts { + // When there's no default value, lint it only according to its type. + // Further details are on the corresponding code (`NonCopyConst::check_trait_item`). + const TO_BE_UNFROZEN_VARIANT: OptionalCell; //~ ERROR interior mutable + const TO_BE_FROZEN_VARIANT: OptionalCell; //~ ERROR interior mutable + + // Lint default values accordingly. + const DEFAULTED_ON_UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(false)); //~ ERROR interior mutable + const DEFAULTED_ON_FROZEN_VARIANT: OptionalCell = OptionalCell::Frozen; +} + +// The lint doesn't trigger for an assoc constant in a trait impl with an unfrozen type even if it +// has enums. Further details are on the corresponding code in 'NonCopyConst::check_impl_item'. +impl AssocConsts for u64 { + const TO_BE_UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(false)); + const TO_BE_FROZEN_VARIANT: OptionalCell = OptionalCell::Frozen; + + // even if this sets an unfrozen variant, the lint ignores it. + const DEFAULTED_ON_FROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(false)); +} + +// At first, I thought I'd need to check every patterns in `trait.rs`; but, what matters +// here are values; and I think substituted generics at definitions won't appear in MIR. +trait AssocTypes { + type ToBeUnfrozen; + + const TO_BE_UNFROZEN_VARIANT: Option; + const TO_BE_FROZEN_VARIANT: Option; +} + +impl AssocTypes for u64 { + type ToBeUnfrozen = AtomicUsize; + + const TO_BE_UNFROZEN_VARIANT: Option = Some(Self::ToBeUnfrozen::new(4)); //~ ERROR interior mutable + const TO_BE_FROZEN_VARIANT: Option = None; +} + +// Use raw pointers since direct generics have a false negative at the type level. +enum BothOfCellAndGeneric { + Unfrozen(Cell<*const T>), + Generic(*const T), + Frozen(usize), +} + +impl BothOfCellAndGeneric { + const UNFROZEN_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Unfrozen(Cell::new(std::ptr::null())); //~ ERROR interior mutable + + // This is a false positive. The argument about this is on `is_value_unfrozen_raw` + const GENERIC_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Generic(std::ptr::null()); //~ ERROR interior mutable + + const FROZEN_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Frozen(5); + + // This is what is likely to be a false negative when one tries to fix + // the `GENERIC_VARIANT` false positive. + const NO_ENUM: Cell<*const T> = Cell::new(std::ptr::null()); //~ ERROR interior mutable +} + +// associated types here is basically the same as the one above. +trait BothOfCellAndGenericWithAssocType { + type AssocType; + + const UNFROZEN_VARIANT: BothOfCellAndGeneric = + BothOfCellAndGeneric::Unfrozen(Cell::new(std::ptr::null())); //~ ERROR interior mutable + const GENERIC_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Generic(std::ptr::null()); //~ ERROR interior mutable + const FROZEN_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Frozen(5); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/declare_interior_mutable_const/enums.stderr b/src/tools/clippy/tests/ui/declare_interior_mutable_const/enums.stderr new file mode 100644 index 0000000000..84198d5461 --- /dev/null +++ b/src/tools/clippy/tests/ui/declare_interior_mutable_const/enums.stderr @@ -0,0 +1,89 @@ +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:12:1 + | +LL | const UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(true)); //~ ERROR interior mutable + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | make this a static item (maybe with lazy_static) + | + = note: `-D clippy::declare-interior-mutable-const` implied by `-D warnings` + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:23:1 + | +LL | const UNFROZEN_VARIANT_FROM_FN: OptionalCell = unfrozen_variant(); //~ ERROR interior mutable + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | make this a static item (maybe with lazy_static) + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:45:1 + | +LL | const NESTED_UNFROZEN_VARIANT: NestedOutermost = NestedOutermost { + | ^---- + | | + | _make this a static item (maybe with lazy_static) + | | +LL | | outer: NestedOuter::NestedInner(NestedInner { +LL | | inner: NestedInnermost::Unfrozen(AtomicUsize::new(2)), +LL | | }), +LL | | }; //~ ERROR interior mutable + | |__^ + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:59:5 + | +LL | const TO_BE_UNFROZEN_VARIANT: OptionalCell; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:60:5 + | +LL | const TO_BE_FROZEN_VARIANT: OptionalCell; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:63:5 + | +LL | const DEFAULTED_ON_UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(false)); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:89:5 + | +LL | const TO_BE_UNFROZEN_VARIANT: Option = Some(Self::ToBeUnfrozen::new(4)); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:101:5 + | +LL | const UNFROZEN_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Unfrozen(Cell::new(std::ptr::null())); //~ ERROR interior mut... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:104:5 + | +LL | const GENERIC_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Generic(std::ptr::null()); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:110:5 + | +LL | const NO_ENUM: Cell<*const T> = Cell::new(std::ptr::null()); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:117:5 + | +LL | / const UNFROZEN_VARIANT: BothOfCellAndGeneric = +LL | | BothOfCellAndGeneric::Unfrozen(Cell::new(std::ptr::null())); //~ ERROR interior mutable + | |____________________________________________________________________^ + +error: a `const` item should never be interior mutable + --> $DIR/enums.rs:119:5 + | +LL | const GENERIC_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Generic(std::ptr::null()); //~ ERROR interior mu... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/declare_interior_mutable_const/others.rs b/src/tools/clippy/tests/ui/declare_interior_mutable_const/others.rs new file mode 100644 index 0000000000..48c5e9537d --- /dev/null +++ b/src/tools/clippy/tests/ui/declare_interior_mutable_const/others.rs @@ -0,0 +1,34 @@ +#![warn(clippy::declare_interior_mutable_const)] + +use std::borrow::Cow; +use std::cell::Cell; +use std::fmt::Display; +use std::sync::atomic::AtomicUsize; +use std::sync::Once; + +const ATOMIC: AtomicUsize = AtomicUsize::new(5); //~ ERROR interior mutable +const CELL: Cell = Cell::new(6); //~ ERROR interior mutable +const ATOMIC_TUPLE: ([AtomicUsize; 1], Vec, u8) = ([ATOMIC], Vec::new(), 7); +//~^ ERROR interior mutable + +macro_rules! declare_const { + ($name:ident: $ty:ty = $e:expr) => { + const $name: $ty = $e; + }; +} +declare_const!(_ONCE: Once = Once::new()); //~ ERROR interior mutable + +// const ATOMIC_REF: &AtomicUsize = &AtomicUsize::new(7); // This will simply trigger E0492. + +const INTEGER: u8 = 8; +const STRING: String = String::new(); +const STR: &str = "012345"; +const COW: Cow = Cow::Borrowed("abcdef"); +//^ note: a const item of Cow is used in the `postgres` package. + +const NO_ANN: &dyn Display = &70; + +static STATIC_TUPLE: (AtomicUsize, String) = (ATOMIC, STRING); +//^ there should be no lints on this line + +fn main() {} diff --git a/src/tools/clippy/tests/ui/declare_interior_mutable_const/others.stderr b/src/tools/clippy/tests/ui/declare_interior_mutable_const/others.stderr new file mode 100644 index 0000000000..6153c96edc --- /dev/null +++ b/src/tools/clippy/tests/ui/declare_interior_mutable_const/others.stderr @@ -0,0 +1,39 @@ +error: a `const` item should never be interior mutable + --> $DIR/others.rs:9:1 + | +LL | const ATOMIC: AtomicUsize = AtomicUsize::new(5); //~ ERROR interior mutable + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | make this a static item (maybe with lazy_static) + | + = note: `-D clippy::declare-interior-mutable-const` implied by `-D warnings` + +error: a `const` item should never be interior mutable + --> $DIR/others.rs:10:1 + | +LL | const CELL: Cell = Cell::new(6); //~ ERROR interior mutable + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | make this a static item (maybe with lazy_static) + +error: a `const` item should never be interior mutable + --> $DIR/others.rs:11:1 + | +LL | const ATOMIC_TUPLE: ([AtomicUsize; 1], Vec, u8) = ([ATOMIC], Vec::new(), 7); + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | make this a static item (maybe with lazy_static) + +error: a `const` item should never be interior mutable + --> $DIR/others.rs:16:9 + | +LL | const $name: $ty = $e; + | ^^^^^^^^^^^^^^^^^^^^^^ +... +LL | declare_const!(_ONCE: Once = Once::new()); //~ ERROR interior mutable + | ------------------------------------------ in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/declare_interior_mutable_const/traits.rs b/src/tools/clippy/tests/ui/declare_interior_mutable_const/traits.rs new file mode 100644 index 0000000000..535147ccc6 --- /dev/null +++ b/src/tools/clippy/tests/ui/declare_interior_mutable_const/traits.rs @@ -0,0 +1,150 @@ +#![warn(clippy::declare_interior_mutable_const)] + +use std::borrow::Cow; +use std::cell::Cell; +use std::sync::atomic::AtomicUsize; + +macro_rules! declare_const { + ($name:ident: $ty:ty = $e:expr) => { + const $name: $ty = $e; + }; +} + +// a constant whose type is a concrete type should be linted at the definition site. +trait ConcreteTypes { + const ATOMIC: AtomicUsize; //~ ERROR interior mutable + const INTEGER: u64; + const STRING: String; + declare_const!(ANOTHER_ATOMIC: AtomicUsize = Self::ATOMIC); //~ ERROR interior mutable +} + +impl ConcreteTypes for u64 { + const ATOMIC: AtomicUsize = AtomicUsize::new(9); + const INTEGER: u64 = 10; + const STRING: String = String::new(); +} + +// a helper trait used below +trait ConstDefault { + const DEFAULT: Self; +} + +// a constant whose type is a generic type should be linted at the implementation site. +trait GenericTypes { + const TO_REMAIN_GENERIC: T; + const TO_BE_CONCRETE: U; + + const HAVING_DEFAULT: T = Self::TO_REMAIN_GENERIC; + declare_const!(IN_MACRO: T = Self::TO_REMAIN_GENERIC); +} + +impl GenericTypes for u64 { + const TO_REMAIN_GENERIC: T = T::DEFAULT; + const TO_BE_CONCRETE: AtomicUsize = AtomicUsize::new(11); //~ ERROR interior mutable +} + +// a helper type used below +struct Wrapper(T); + +// a constant whose type is an associated type should be linted at the implementation site, too. +trait AssocTypes { + type ToBeFrozen; + type ToBeUnfrozen; + type ToBeGenericParam; + + const TO_BE_FROZEN: Self::ToBeFrozen; + const TO_BE_UNFROZEN: Self::ToBeUnfrozen; + const WRAPPED_TO_BE_UNFROZEN: Wrapper; + // to ensure it can handle things when a generic type remains after normalization. + const WRAPPED_TO_BE_GENERIC_PARAM: Wrapper; +} + +impl AssocTypes for Vec { + type ToBeFrozen = u16; + type ToBeUnfrozen = AtomicUsize; + type ToBeGenericParam = T; + + const TO_BE_FROZEN: Self::ToBeFrozen = 12; + const TO_BE_UNFROZEN: Self::ToBeUnfrozen = AtomicUsize::new(13); //~ ERROR interior mutable + const WRAPPED_TO_BE_UNFROZEN: Wrapper = Wrapper(AtomicUsize::new(14)); //~ ERROR interior mutable + const WRAPPED_TO_BE_GENERIC_PARAM: Wrapper = Wrapper(T::DEFAULT); +} + +// a helper trait used below +trait AssocTypesHelper { + type NotToBeBounded; + type ToBeBounded; + + const NOT_TO_BE_BOUNDED: Self::NotToBeBounded; +} + +// a constant whose type is an assoc type originated from a generic param bounded at the definition +// site should be linted at there. +trait AssocTypesFromGenericParam +where + T: AssocTypesHelper, +{ + const NOT_BOUNDED: T::NotToBeBounded; + const BOUNDED: T::ToBeBounded; //~ ERROR interior mutable +} + +impl AssocTypesFromGenericParam for u64 +where + T: AssocTypesHelper, +{ + // an associated type could remain unknown in a trait impl. + const NOT_BOUNDED: T::NotToBeBounded = T::NOT_TO_BE_BOUNDED; + const BOUNDED: T::ToBeBounded = AtomicUsize::new(15); +} + +// a constant whose type is `Self` should be linted at the implementation site as well. +// (`Option` requires `Sized` bound.) +trait SelfType: Sized { + const SELF: Self; + // this was the one in the original issue (#5050). + const WRAPPED_SELF: Option; +} + +impl SelfType for u64 { + const SELF: Self = 16; + const WRAPPED_SELF: Option = Some(20); +} + +impl SelfType for AtomicUsize { + // this (interior mutable `Self` const) exists in `parking_lot`. + // `const_trait_impl` will replace it in the future, hopefully. + const SELF: Self = AtomicUsize::new(17); //~ ERROR interior mutable + const WRAPPED_SELF: Option = Some(AtomicUsize::new(21)); //~ ERROR interior mutable +} + +// Even though a constant contains a generic type, if it also have a interior mutable type, +// it should be linted at the definition site. +trait BothOfCellAndGeneric { + // this is a false negative in the current implementation. + const DIRECT: Cell; + const INDIRECT: Cell<*const T>; //~ ERROR interior mutable +} + +impl BothOfCellAndGeneric for u64 { + const DIRECT: Cell = Cell::new(T::DEFAULT); + const INDIRECT: Cell<*const T> = Cell::new(std::ptr::null()); +} + +struct Local(T); + +// a constant in an inherent impl are essentially the same as a normal const item +// except there can be a generic or associated type. +impl Local +where + T: ConstDefault + AssocTypesHelper, +{ + const ATOMIC: AtomicUsize = AtomicUsize::new(18); //~ ERROR interior mutable + const COW: Cow<'static, str> = Cow::Borrowed("tuvwxy"); + + const GENERIC_TYPE: T = T::DEFAULT; + + const ASSOC_TYPE: T::NotToBeBounded = T::NOT_TO_BE_BOUNDED; + const BOUNDED_ASSOC_TYPE: T::ToBeBounded = AtomicUsize::new(19); //~ ERROR interior mutable +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/declare_interior_mutable_const/traits.stderr b/src/tools/clippy/tests/ui/declare_interior_mutable_const/traits.stderr new file mode 100644 index 0000000000..bb77f39b62 --- /dev/null +++ b/src/tools/clippy/tests/ui/declare_interior_mutable_const/traits.stderr @@ -0,0 +1,75 @@ +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:15:5 + | +LL | const ATOMIC: AtomicUsize; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::declare-interior-mutable-const` implied by `-D warnings` + +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:9:9 + | +LL | const $name: $ty = $e; + | ^^^^^^^^^^^^^^^^^^^^^^ +... +LL | declare_const!(ANOTHER_ATOMIC: AtomicUsize = Self::ATOMIC); //~ ERROR interior mutable + | ----------------------------------------------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:43:5 + | +LL | const TO_BE_CONCRETE: AtomicUsize = AtomicUsize::new(11); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:68:5 + | +LL | const TO_BE_UNFROZEN: Self::ToBeUnfrozen = AtomicUsize::new(13); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:69:5 + | +LL | const WRAPPED_TO_BE_UNFROZEN: Wrapper = Wrapper(AtomicUsize::new(14)); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:88:5 + | +LL | const BOUNDED: T::ToBeBounded; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:116:5 + | +LL | const SELF: Self = AtomicUsize::new(17); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:117:5 + | +LL | const WRAPPED_SELF: Option = Some(AtomicUsize::new(21)); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:125:5 + | +LL | const INDIRECT: Cell<*const T>; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:141:5 + | +LL | const ATOMIC: AtomicUsize = AtomicUsize::new(18); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/traits.rs:147:5 + | +LL | const BOUNDED_ASSOC_TYPE: T::ToBeBounded = AtomicUsize::new(19); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/def_id_nocore.rs b/src/tools/clippy/tests/ui/def_id_nocore.rs new file mode 100644 index 0000000000..2a948d60b1 --- /dev/null +++ b/src/tools/clippy/tests/ui/def_id_nocore.rs @@ -0,0 +1,29 @@ +// ignore-windows +// ignore-macos + +#![feature(no_core, lang_items, start)] +#![no_core] + +#[link(name = "c")] +extern "C" {} + +#[lang = "sized"] +pub trait Sized {} +#[lang = "copy"] +pub trait Copy {} +#[lang = "freeze"] +pub unsafe trait Freeze {} + +#[lang = "start"] +#[start] +fn start(_argc: isize, _argv: *const *const u8) -> isize { + 0 +} + +pub struct A; + +impl A { + pub fn as_ref(self) -> &'static str { + "A" + } +} diff --git a/src/tools/clippy/tests/ui/def_id_nocore.stderr b/src/tools/clippy/tests/ui/def_id_nocore.stderr new file mode 100644 index 0000000000..ed87a50547 --- /dev/null +++ b/src/tools/clippy/tests/ui/def_id_nocore.stderr @@ -0,0 +1,10 @@ +error: methods called `as_*` usually take self by reference or self by mutable reference; consider choosing a less ambiguous name + --> $DIR/def_id_nocore.rs:26:19 + | +LL | pub fn as_ref(self) -> &'static str { + | ^^^^ + | + = note: `-D clippy::wrong-self-convention` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/default_numeric_fallback.rs b/src/tools/clippy/tests/ui/default_numeric_fallback.rs new file mode 100644 index 0000000000..0b3758952a --- /dev/null +++ b/src/tools/clippy/tests/ui/default_numeric_fallback.rs @@ -0,0 +1,135 @@ +#![warn(clippy::default_numeric_fallback)] +#![allow(unused)] +#![allow(clippy::never_loop)] +#![allow(clippy::no_effect)] +#![allow(clippy::unnecessary_operation)] + +mod basic_expr { + fn test() { + // Should lint unsuffixed literals typed `i32`. + let x = 22; + let x = [1, 2, 3]; + let x = if true { (1, 2) } else { (3, 4) }; + let x = match 1 { + 1 => 1, + _ => 2, + }; + + // Should lint unsuffixed literals typed `f64`. + let x = 0.12; + + // Should NOT lint suffixed literals. + let x = 22_i32; + let x = 0.12_f64; + + // Should NOT lint literals in init expr if `Local` has a type annotation. + let x: f64 = 0.1; + let x: [i32; 3] = [1, 2, 3]; + let x: (i32, i32) = if true { (1, 2) } else { (3, 4) }; + let x: _ = 1; + } +} + +mod nested_local { + fn test() { + let x: _ = { + // Should lint this because this literal is not bound to any types. + let y = 1; + + // Should NOT lint this because this literal is bound to `_` of outer `Local`. + 1 + }; + + let x: _ = if true { + // Should lint this because this literal is not bound to any types. + let y = 1; + + // Should NOT lint this because this literal is bound to `_` of outer `Local`. + 1 + } else { + // Should lint this because this literal is not bound to any types. + let y = 1; + + // Should NOT lint this because this literal is bound to `_` of outer `Local`. + 2 + }; + } +} + +mod function_def { + fn ret_i32() -> i32 { + // Even though the output type is specified, + // this unsuffixed literal is linted to reduce heuristics and keep codebase simple. + 1 + } + + fn test() { + // Should lint this because return type is inferred to `i32` and NOT bound to a concrete + // type. + let f = || -> _ { 1 }; + + // Even though the output type is specified, + // this unsuffixed literal is linted to reduce heuristics and keep codebase simple. + let f = || -> i32 { 1 }; + } +} + +mod function_calls { + fn concrete_arg(x: i32) {} + + fn generic_arg(t: T) {} + + fn test() { + // Should NOT lint this because the argument type is bound to a concrete type. + concrete_arg(1); + + // Should lint this because the argument type is inferred to `i32` and NOT bound to a concrete type. + generic_arg(1); + + // Should lint this because the argument type is inferred to `i32` and NOT bound to a concrete type. + let x: _ = generic_arg(1); + } +} + +mod struct_ctor { + struct ConcreteStruct { + x: i32, + } + + struct GenericStruct { + x: T, + } + + fn test() { + // Should NOT lint this because the field type is bound to a concrete type. + ConcreteStruct { x: 1 }; + + // Should lint this because the field type is inferred to `i32` and NOT bound to a concrete type. + GenericStruct { x: 1 }; + + // Should lint this because the field type is inferred to `i32` and NOT bound to a concrete type. + let _ = GenericStruct { x: 1 }; + } +} + +mod method_calls { + struct StructForMethodCallTest {} + + impl StructForMethodCallTest { + fn concrete_arg(&self, x: i32) {} + + fn generic_arg(&self, t: T) {} + } + + fn test() { + let s = StructForMethodCallTest {}; + + // Should NOT lint this because the argument type is bound to a concrete type. + s.concrete_arg(1); + + // Should lint this because the argument type is bound to a concrete type. + s.generic_arg(1); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/default_numeric_fallback.stderr b/src/tools/clippy/tests/ui/default_numeric_fallback.stderr new file mode 100644 index 0000000000..b31aa4ebcf --- /dev/null +++ b/src/tools/clippy/tests/ui/default_numeric_fallback.stderr @@ -0,0 +1,148 @@ +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:10:17 + | +LL | let x = 22; + | ^^ help: consider adding suffix: `22_i32` + | + = note: `-D clippy::default-numeric-fallback` implied by `-D warnings` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:11:18 + | +LL | let x = [1, 2, 3]; + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:11:21 + | +LL | let x = [1, 2, 3]; + | ^ help: consider adding suffix: `2_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:11:24 + | +LL | let x = [1, 2, 3]; + | ^ help: consider adding suffix: `3_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:12:28 + | +LL | let x = if true { (1, 2) } else { (3, 4) }; + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:12:31 + | +LL | let x = if true { (1, 2) } else { (3, 4) }; + | ^ help: consider adding suffix: `2_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:12:44 + | +LL | let x = if true { (1, 2) } else { (3, 4) }; + | ^ help: consider adding suffix: `3_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:12:47 + | +LL | let x = if true { (1, 2) } else { (3, 4) }; + | ^ help: consider adding suffix: `4_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:13:23 + | +LL | let x = match 1 { + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:14:13 + | +LL | 1 => 1, + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:14:18 + | +LL | 1 => 1, + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:15:18 + | +LL | _ => 2, + | ^ help: consider adding suffix: `2_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:19:17 + | +LL | let x = 0.12; + | ^^^^ help: consider adding suffix: `0.12_f64` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:37:21 + | +LL | let y = 1; + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:45:21 + | +LL | let y = 1; + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:51:21 + | +LL | let y = 1; + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:63:9 + | +LL | 1 + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:69:27 + | +LL | let f = || -> _ { 1 }; + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:73:29 + | +LL | let f = || -> i32 { 1 }; + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:87:21 + | +LL | generic_arg(1); + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:90:32 + | +LL | let x: _ = generic_arg(1); + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:108:28 + | +LL | GenericStruct { x: 1 }; + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:111:36 + | +LL | let _ = GenericStruct { x: 1 }; + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback.rs:131:23 + | +LL | s.generic_arg(1); + | ^ help: consider adding suffix: `1_i32` + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/default_trait_access.fixed b/src/tools/clippy/tests/ui/default_trait_access.fixed new file mode 100644 index 0000000000..4c80cabc72 --- /dev/null +++ b/src/tools/clippy/tests/ui/default_trait_access.fixed @@ -0,0 +1,88 @@ +// run-rustfix + +#![allow(unused_imports)] +#![deny(clippy::default_trait_access)] + +use std::default; +use std::default::Default as D2; +use std::string; + +fn main() { + let s1: String = std::string::String::default(); + + let s2 = String::default(); + + let s3: String = std::string::String::default(); + + let s4: String = std::string::String::default(); + + let s5 = string::String::default(); + + let s6: String = std::string::String::default(); + + let s7 = std::string::String::default(); + + let s8: String = DefaultFactory::make_t_badly(); + + let s9: String = DefaultFactory::make_t_nicely(); + + let s10 = DerivedDefault::default(); + + let s11: GenericDerivedDefault = GenericDerivedDefault::default(); + + let s12 = GenericDerivedDefault::::default(); + + let s13 = TupleDerivedDefault::default(); + + let s14: TupleDerivedDefault = TupleDerivedDefault::default(); + + let s15: ArrayDerivedDefault = ArrayDerivedDefault::default(); + + let s16 = ArrayDerivedDefault::default(); + + let s17: TupleStructDerivedDefault = TupleStructDerivedDefault::default(); + + let s18 = TupleStructDerivedDefault::default(); + + let s19 = ::default(); + + println!( + "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}], [{:?}]", + s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, + ); +} + +struct DefaultFactory; + +impl DefaultFactory { + pub fn make_t_badly() -> T { + Default::default() + } + + pub fn make_t_nicely() -> T { + T::default() + } +} + +#[derive(Debug, Default)] +struct DerivedDefault { + pub s: String, +} + +#[derive(Debug, Default)] +struct GenericDerivedDefault { + pub s: T, +} + +#[derive(Debug, Default)] +struct TupleDerivedDefault { + pub s: (String, String), +} + +#[derive(Debug, Default)] +struct ArrayDerivedDefault { + pub s: [String; 10], +} + +#[derive(Debug, Default)] +struct TupleStructDerivedDefault(String); diff --git a/src/tools/clippy/tests/ui/default_trait_access.rs b/src/tools/clippy/tests/ui/default_trait_access.rs new file mode 100644 index 0000000000..a68b6455c0 --- /dev/null +++ b/src/tools/clippy/tests/ui/default_trait_access.rs @@ -0,0 +1,88 @@ +// run-rustfix + +#![allow(unused_imports)] +#![deny(clippy::default_trait_access)] + +use std::default; +use std::default::Default as D2; +use std::string; + +fn main() { + let s1: String = Default::default(); + + let s2 = String::default(); + + let s3: String = D2::default(); + + let s4: String = std::default::Default::default(); + + let s5 = string::String::default(); + + let s6: String = default::Default::default(); + + let s7 = std::string::String::default(); + + let s8: String = DefaultFactory::make_t_badly(); + + let s9: String = DefaultFactory::make_t_nicely(); + + let s10 = DerivedDefault::default(); + + let s11: GenericDerivedDefault = Default::default(); + + let s12 = GenericDerivedDefault::::default(); + + let s13 = TupleDerivedDefault::default(); + + let s14: TupleDerivedDefault = Default::default(); + + let s15: ArrayDerivedDefault = Default::default(); + + let s16 = ArrayDerivedDefault::default(); + + let s17: TupleStructDerivedDefault = Default::default(); + + let s18 = TupleStructDerivedDefault::default(); + + let s19 = ::default(); + + println!( + "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}], [{:?}]", + s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, + ); +} + +struct DefaultFactory; + +impl DefaultFactory { + pub fn make_t_badly() -> T { + Default::default() + } + + pub fn make_t_nicely() -> T { + T::default() + } +} + +#[derive(Debug, Default)] +struct DerivedDefault { + pub s: String, +} + +#[derive(Debug, Default)] +struct GenericDerivedDefault { + pub s: T, +} + +#[derive(Debug, Default)] +struct TupleDerivedDefault { + pub s: (String, String), +} + +#[derive(Debug, Default)] +struct ArrayDerivedDefault { + pub s: [String; 10], +} + +#[derive(Debug, Default)] +struct TupleStructDerivedDefault(String); diff --git a/src/tools/clippy/tests/ui/default_trait_access.stderr b/src/tools/clippy/tests/ui/default_trait_access.stderr new file mode 100644 index 0000000000..df8a5b94dd --- /dev/null +++ b/src/tools/clippy/tests/ui/default_trait_access.stderr @@ -0,0 +1,56 @@ +error: calling `std::string::String::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:11:22 + | +LL | let s1: String = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `std::string::String::default()` + | +note: the lint level is defined here + --> $DIR/default_trait_access.rs:4:9 + | +LL | #![deny(clippy::default_trait_access)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: calling `std::string::String::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:15:22 + | +LL | let s3: String = D2::default(); + | ^^^^^^^^^^^^^ help: try: `std::string::String::default()` + +error: calling `std::string::String::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:17:22 + | +LL | let s4: String = std::default::Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::string::String::default()` + +error: calling `std::string::String::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:21:22 + | +LL | let s6: String = default::Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::string::String::default()` + +error: calling `GenericDerivedDefault::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:31:46 + | +LL | let s11: GenericDerivedDefault = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `GenericDerivedDefault::default()` + +error: calling `TupleDerivedDefault::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:37:36 + | +LL | let s14: TupleDerivedDefault = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `TupleDerivedDefault::default()` + +error: calling `ArrayDerivedDefault::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:39:36 + | +LL | let s15: ArrayDerivedDefault = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `ArrayDerivedDefault::default()` + +error: calling `TupleStructDerivedDefault::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:43:42 + | +LL | let s17: TupleStructDerivedDefault = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `TupleStructDerivedDefault::default()` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/deprecated.rs b/src/tools/clippy/tests/ui/deprecated.rs new file mode 100644 index 0000000000..fc444c0bea --- /dev/null +++ b/src/tools/clippy/tests/ui/deprecated.rs @@ -0,0 +1,15 @@ +#[warn(clippy::unstable_as_slice)] +#[warn(clippy::unstable_as_mut_slice)] +#[warn(clippy::misaligned_transmute)] +#[warn(clippy::unused_collect)] +#[warn(clippy::invalid_ref)] +#[warn(clippy::into_iter_on_array)] +#[warn(clippy::unused_label)] +#[warn(clippy::regex_macro)] +#[warn(clippy::drop_bounds)] +#[warn(clippy::temporary_cstring_as_ptr)] +#[warn(clippy::panic_params)] +#[warn(clippy::unknown_clippy_lints)] +#[warn(clippy::find_map)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/deprecated.stderr b/src/tools/clippy/tests/ui/deprecated.stderr new file mode 100644 index 0000000000..64efcd18f8 --- /dev/null +++ b/src/tools/clippy/tests/ui/deprecated.stderr @@ -0,0 +1,88 @@ +error: lint `clippy::unstable_as_slice` has been removed: `Vec::as_slice` has been stabilized in 1.7 + --> $DIR/deprecated.rs:1:8 + | +LL | #[warn(clippy::unstable_as_slice)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D renamed-and-removed-lints` implied by `-D warnings` + +error: lint `clippy::unstable_as_mut_slice` has been removed: `Vec::as_mut_slice` has been stabilized in 1.7 + --> $DIR/deprecated.rs:2:8 + | +LL | #[warn(clippy::unstable_as_mut_slice)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::misaligned_transmute` has been removed: this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr + --> $DIR/deprecated.rs:3:8 + | +LL | #[warn(clippy::misaligned_transmute)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::unused_collect` has been removed: `collect` has been marked as #[must_use] in rustc and that covers all cases of this lint + --> $DIR/deprecated.rs:4:8 + | +LL | #[warn(clippy::unused_collect)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::invalid_ref` has been removed: superseded by rustc lint `invalid_value` + --> $DIR/deprecated.rs:5:8 + | +LL | #[warn(clippy::invalid_ref)] + | ^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::into_iter_on_array` has been removed: this lint has been uplifted to rustc and is now called `array_into_iter` + --> $DIR/deprecated.rs:6:8 + | +LL | #[warn(clippy::into_iter_on_array)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::unused_label` has been removed: this lint has been uplifted to rustc and is now called `unused_labels` + --> $DIR/deprecated.rs:7:8 + | +LL | #[warn(clippy::unused_label)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::regex_macro` has been removed: the regex! macro has been removed from the regex crate in 2018 + --> $DIR/deprecated.rs:8:8 + | +LL | #[warn(clippy::regex_macro)] + | ^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::drop_bounds` has been removed: this lint has been uplifted to rustc and is now called `drop_bounds` + --> $DIR/deprecated.rs:9:8 + | +LL | #[warn(clippy::drop_bounds)] + | ^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::temporary_cstring_as_ptr` has been removed: this lint has been uplifted to rustc and is now called `temporary_cstring_as_ptr` + --> $DIR/deprecated.rs:10:8 + | +LL | #[warn(clippy::temporary_cstring_as_ptr)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::panic_params` has been removed: this lint has been uplifted to rustc and is now called `panic_fmt` + --> $DIR/deprecated.rs:11:8 + | +LL | #[warn(clippy::panic_params)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::unknown_clippy_lints` has been removed: this lint has been integrated into the `unknown_lints` rustc lint + --> $DIR/deprecated.rs:12:8 + | +LL | #[warn(clippy::unknown_clippy_lints)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::find_map` has been removed: this lint has been replaced by `manual_find_map`, a more specific lint + --> $DIR/deprecated.rs:13:8 + | +LL | #[warn(clippy::find_map)] + | ^^^^^^^^^^^^^^^^ + +error: lint `clippy::unstable_as_slice` has been removed: `Vec::as_slice` has been stabilized in 1.7 + --> $DIR/deprecated.rs:1:8 + | +LL | #[warn(clippy::unstable_as_slice)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/deprecated_old.rs b/src/tools/clippy/tests/ui/deprecated_old.rs new file mode 100644 index 0000000000..e89dca4fcf --- /dev/null +++ b/src/tools/clippy/tests/ui/deprecated_old.rs @@ -0,0 +1,5 @@ +#[warn(unstable_as_slice)] +#[warn(unstable_as_mut_slice)] +#[warn(misaligned_transmute)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/deprecated_old.stderr b/src/tools/clippy/tests/ui/deprecated_old.stderr new file mode 100644 index 0000000000..b8550078c4 --- /dev/null +++ b/src/tools/clippy/tests/ui/deprecated_old.stderr @@ -0,0 +1,28 @@ +error: lint `unstable_as_slice` has been removed: `Vec::as_slice` has been stabilized in 1.7 + --> $DIR/deprecated_old.rs:1:8 + | +LL | #[warn(unstable_as_slice)] + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D renamed-and-removed-lints` implied by `-D warnings` + +error: lint `unstable_as_mut_slice` has been removed: `Vec::as_mut_slice` has been stabilized in 1.7 + --> $DIR/deprecated_old.rs:2:8 + | +LL | #[warn(unstable_as_mut_slice)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: lint `misaligned_transmute` has been removed: this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr + --> $DIR/deprecated_old.rs:3:8 + | +LL | #[warn(misaligned_transmute)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: lint `unstable_as_slice` has been removed: `Vec::as_slice` has been stabilized in 1.7 + --> $DIR/deprecated_old.rs:1:8 + | +LL | #[warn(unstable_as_slice)] + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/deref_addrof.fixed b/src/tools/clippy/tests/ui/deref_addrof.fixed new file mode 100644 index 0000000000..0795900558 --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof.fixed @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::deref_addrof)] + +fn get_number() -> usize { + 10 +} + +fn get_reference(n: &usize) -> &usize { + n +} + +#[allow(clippy::many_single_char_names, clippy::double_parens)] +#[allow(unused_variables, unused_parens)] +fn main() { + let a = 10; + let aref = &a; + + let b = a; + + let b = get_number(); + + let b = *get_reference(&a); + + let bytes: Vec = vec![1, 2, 3, 4]; + let b = bytes[1..2][0]; + + //This produces a suggestion of 'let b = (a);' which + //will trigger the 'unused_parens' lint + let b = (a); + + let b = a; + + #[rustfmt::skip] + let b = a; + + let b = &a; + + let b = *aref; +} + +#[rustfmt::skip] +macro_rules! m { + ($visitor: expr) => { + $visitor + }; +} + +#[rustfmt::skip] +macro_rules! m_mut { + ($visitor: expr) => { + $visitor + }; +} + +pub struct S; +impl S { + pub fn f(&self) -> &Self { + m!(self) + } + pub fn f_mut(&self) -> &Self { + m_mut!(self) + } +} diff --git a/src/tools/clippy/tests/ui/deref_addrof.rs b/src/tools/clippy/tests/ui/deref_addrof.rs new file mode 100644 index 0000000000..60c4318601 --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof.rs @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::deref_addrof)] + +fn get_number() -> usize { + 10 +} + +fn get_reference(n: &usize) -> &usize { + n +} + +#[allow(clippy::many_single_char_names, clippy::double_parens)] +#[allow(unused_variables, unused_parens)] +fn main() { + let a = 10; + let aref = &a; + + let b = *&a; + + let b = *&get_number(); + + let b = *get_reference(&a); + + let bytes: Vec = vec![1, 2, 3, 4]; + let b = *&bytes[1..2][0]; + + //This produces a suggestion of 'let b = (a);' which + //will trigger the 'unused_parens' lint + let b = *&(a); + + let b = *(&a); + + #[rustfmt::skip] + let b = *((&a)); + + let b = *&&a; + + let b = **&aref; +} + +#[rustfmt::skip] +macro_rules! m { + ($visitor: expr) => { + *& $visitor + }; +} + +#[rustfmt::skip] +macro_rules! m_mut { + ($visitor: expr) => { + *& mut $visitor + }; +} + +pub struct S; +impl S { + pub fn f(&self) -> &Self { + m!(self) + } + pub fn f_mut(&self) -> &Self { + m_mut!(self) + } +} diff --git a/src/tools/clippy/tests/ui/deref_addrof.stderr b/src/tools/clippy/tests/ui/deref_addrof.stderr new file mode 100644 index 0000000000..e85b30fa56 --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof.stderr @@ -0,0 +1,74 @@ +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:18:13 + | +LL | let b = *&a; + | ^^^ help: try this: `a` + | + = note: `-D clippy::deref-addrof` implied by `-D warnings` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:20:13 + | +LL | let b = *&get_number(); + | ^^^^^^^^^^^^^^ help: try this: `get_number()` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:25:13 + | +LL | let b = *&bytes[1..2][0]; + | ^^^^^^^^^^^^^^^^ help: try this: `bytes[1..2][0]` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:29:13 + | +LL | let b = *&(a); + | ^^^^^ help: try this: `(a)` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:31:13 + | +LL | let b = *(&a); + | ^^^^^ help: try this: `a` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:34:13 + | +LL | let b = *((&a)); + | ^^^^^^^ help: try this: `a` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:36:13 + | +LL | let b = *&&a; + | ^^^^ help: try this: `&a` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:38:14 + | +LL | let b = **&aref; + | ^^^^^^ help: try this: `aref` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:44:9 + | +LL | *& $visitor + | ^^^^^^^^^^^ help: try this: `$visitor` +... +LL | m!(self) + | -------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:51:9 + | +LL | *& mut $visitor + | ^^^^^^^^^^^^^^^ help: try this: `$visitor` +... +LL | m_mut!(self) + | ------------ in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/deref_addrof_double_trigger.rs b/src/tools/clippy/tests/ui/deref_addrof_double_trigger.rs new file mode 100644 index 0000000000..4531943299 --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof_double_trigger.rs @@ -0,0 +1,23 @@ +// This test can't work with run-rustfix because it needs two passes of test+fix + +#[warn(clippy::deref_addrof)] +#[allow(unused_variables, unused_mut)] +fn main() { + let a = 10; + + //This produces a suggestion of 'let b = *&a;' which + //will trigger the 'clippy::deref_addrof' lint again + let b = **&&a; + + { + let mut x = 10; + let y = *&mut x; + } + + { + //This produces a suggestion of 'let y = *&mut x' which + //will trigger the 'clippy::deref_addrof' lint again + let mut x = 10; + let y = **&mut &mut x; + } +} diff --git a/src/tools/clippy/tests/ui/deref_addrof_double_trigger.stderr b/src/tools/clippy/tests/ui/deref_addrof_double_trigger.stderr new file mode 100644 index 0000000000..2c55a4ed6a --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof_double_trigger.stderr @@ -0,0 +1,22 @@ +error: immediately dereferencing a reference + --> $DIR/deref_addrof_double_trigger.rs:10:14 + | +LL | let b = **&&a; + | ^^^^ help: try this: `&a` + | + = note: `-D clippy::deref-addrof` implied by `-D warnings` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof_double_trigger.rs:14:17 + | +LL | let y = *&mut x; + | ^^^^^^^ help: try this: `x` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof_double_trigger.rs:21:18 + | +LL | let y = **&mut &mut x; + | ^^^^^^^^^^^^ help: try this: `&mut x` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/deref_addrof_macro.rs b/src/tools/clippy/tests/ui/deref_addrof_macro.rs new file mode 100644 index 0000000000..dcebd6c6e2 --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof_macro.rs @@ -0,0 +1,10 @@ +macro_rules! m { + ($($x:tt),*) => { &[$(($x, stringify!(x)),)*] }; +} + +#[warn(clippy::deref_addrof)] +fn f() -> [(i32, &'static str); 3] { + *m![1, 2, 3] // should be fine +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/dereference.fixed b/src/tools/clippy/tests/ui/dereference.fixed new file mode 100644 index 0000000000..459ca91b93 --- /dev/null +++ b/src/tools/clippy/tests/ui/dereference.fixed @@ -0,0 +1,93 @@ +// run-rustfix + +#![allow(unused_variables, clippy::many_single_char_names, clippy::clone_double_ref)] +#![warn(clippy::explicit_deref_methods)] + +use std::ops::{Deref, DerefMut}; + +fn concat(deref_str: &str) -> String { + format!("{}bar", deref_str) +} + +fn just_return(deref_str: &str) -> &str { + deref_str +} + +struct CustomVec(Vec); +impl Deref for CustomVec { + type Target = Vec; + + fn deref(&self) -> &Vec { + &self.0 + } +} + +fn main() { + let a: &mut String = &mut String::from("foo"); + + // these should require linting + + let b: &str = &*a; + + let b: &mut str = &mut *a; + + // both derefs should get linted here + let b: String = format!("{}, {}", &*a, &*a); + + println!("{}", &*a); + + #[allow(clippy::match_single_binding)] + match &*a { + _ => (), + } + + let b: String = concat(&*a); + + let b = &*just_return(a); + + let b: String = concat(&*just_return(a)); + + let b: &str = &*a.deref(); + + let opt_a = Some(a.clone()); + let b = &*opt_a.unwrap(); + + // following should not require linting + + let cv = CustomVec(vec![0, 42]); + let c = cv.deref()[0]; + + let b: &str = &*a.deref(); + + let b: String = a.deref().clone(); + + let b: usize = a.deref_mut().len(); + + let b: &usize = &a.deref().len(); + + let b: &str = &*a; + + let b: &mut str = &mut *a; + + macro_rules! expr_deref { + ($body:expr) => { + $body.deref() + }; + } + let b: &str = expr_deref!(a); + + // The struct does not implement Deref trait + #[derive(Copy, Clone)] + struct NoLint(u32); + impl NoLint { + pub fn deref(self) -> u32 { + self.0 + } + pub fn deref_mut(self) -> u32 { + self.0 + } + } + let no_lint = NoLint(42); + let b = no_lint.deref(); + let b = no_lint.deref_mut(); +} diff --git a/src/tools/clippy/tests/ui/dereference.rs b/src/tools/clippy/tests/ui/dereference.rs new file mode 100644 index 0000000000..8dc5272e67 --- /dev/null +++ b/src/tools/clippy/tests/ui/dereference.rs @@ -0,0 +1,93 @@ +// run-rustfix + +#![allow(unused_variables, clippy::many_single_char_names, clippy::clone_double_ref)] +#![warn(clippy::explicit_deref_methods)] + +use std::ops::{Deref, DerefMut}; + +fn concat(deref_str: &str) -> String { + format!("{}bar", deref_str) +} + +fn just_return(deref_str: &str) -> &str { + deref_str +} + +struct CustomVec(Vec); +impl Deref for CustomVec { + type Target = Vec; + + fn deref(&self) -> &Vec { + &self.0 + } +} + +fn main() { + let a: &mut String = &mut String::from("foo"); + + // these should require linting + + let b: &str = a.deref(); + + let b: &mut str = a.deref_mut(); + + // both derefs should get linted here + let b: String = format!("{}, {}", a.deref(), a.deref()); + + println!("{}", a.deref()); + + #[allow(clippy::match_single_binding)] + match a.deref() { + _ => (), + } + + let b: String = concat(a.deref()); + + let b = just_return(a).deref(); + + let b: String = concat(just_return(a).deref()); + + let b: &str = a.deref().deref(); + + let opt_a = Some(a.clone()); + let b = opt_a.unwrap().deref(); + + // following should not require linting + + let cv = CustomVec(vec![0, 42]); + let c = cv.deref()[0]; + + let b: &str = &*a.deref(); + + let b: String = a.deref().clone(); + + let b: usize = a.deref_mut().len(); + + let b: &usize = &a.deref().len(); + + let b: &str = &*a; + + let b: &mut str = &mut *a; + + macro_rules! expr_deref { + ($body:expr) => { + $body.deref() + }; + } + let b: &str = expr_deref!(a); + + // The struct does not implement Deref trait + #[derive(Copy, Clone)] + struct NoLint(u32); + impl NoLint { + pub fn deref(self) -> u32 { + self.0 + } + pub fn deref_mut(self) -> u32 { + self.0 + } + } + let no_lint = NoLint(42); + let b = no_lint.deref(); + let b = no_lint.deref_mut(); +} diff --git a/src/tools/clippy/tests/ui/dereference.stderr b/src/tools/clippy/tests/ui/dereference.stderr new file mode 100644 index 0000000000..d26b462a43 --- /dev/null +++ b/src/tools/clippy/tests/ui/dereference.stderr @@ -0,0 +1,70 @@ +error: explicit deref method call + --> $DIR/dereference.rs:30:19 + | +LL | let b: &str = a.deref(); + | ^^^^^^^^^ help: try this: `&*a` + | + = note: `-D clippy::explicit-deref-methods` implied by `-D warnings` + +error: explicit deref_mut method call + --> $DIR/dereference.rs:32:23 + | +LL | let b: &mut str = a.deref_mut(); + | ^^^^^^^^^^^^^ help: try this: `&mut *a` + +error: explicit deref method call + --> $DIR/dereference.rs:35:39 + | +LL | let b: String = format!("{}, {}", a.deref(), a.deref()); + | ^^^^^^^^^ help: try this: `&*a` + +error: explicit deref method call + --> $DIR/dereference.rs:35:50 + | +LL | let b: String = format!("{}, {}", a.deref(), a.deref()); + | ^^^^^^^^^ help: try this: `&*a` + +error: explicit deref method call + --> $DIR/dereference.rs:37:20 + | +LL | println!("{}", a.deref()); + | ^^^^^^^^^ help: try this: `&*a` + +error: explicit deref method call + --> $DIR/dereference.rs:40:11 + | +LL | match a.deref() { + | ^^^^^^^^^ help: try this: `&*a` + +error: explicit deref method call + --> $DIR/dereference.rs:44:28 + | +LL | let b: String = concat(a.deref()); + | ^^^^^^^^^ help: try this: `&*a` + +error: explicit deref method call + --> $DIR/dereference.rs:46:13 + | +LL | let b = just_return(a).deref(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&*just_return(a)` + +error: explicit deref method call + --> $DIR/dereference.rs:48:28 + | +LL | let b: String = concat(just_return(a).deref()); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&*just_return(a)` + +error: explicit deref method call + --> $DIR/dereference.rs:50:19 + | +LL | let b: &str = a.deref().deref(); + | ^^^^^^^^^^^^^^^^^ help: try this: `&*a.deref()` + +error: explicit deref method call + --> $DIR/dereference.rs:53:13 + | +LL | let b = opt_a.unwrap().deref(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&*opt_a.unwrap()` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/derive.rs b/src/tools/clippy/tests/ui/derive.rs new file mode 100644 index 0000000000..8fcb0e8b28 --- /dev/null +++ b/src/tools/clippy/tests/ui/derive.rs @@ -0,0 +1,74 @@ +#![feature(untagged_unions)] +#![allow(dead_code)] +#![warn(clippy::expl_impl_clone_on_copy)] + +#[derive(Copy)] +struct Qux; + +impl Clone for Qux { + fn clone(&self) -> Self { + Qux + } +} + +// looks like unions don't support deriving Clone for now +#[derive(Copy)] +union Union { + a: u8, +} + +impl Clone for Union { + fn clone(&self) -> Self { + Union { a: 42 } + } +} + +// See #666 +#[derive(Copy)] +struct Lt<'a> { + a: &'a u8, +} + +impl<'a> Clone for Lt<'a> { + fn clone(&self) -> Self { + unimplemented!() + } +} + +// Ok, `Clone` cannot be derived because of the big array +#[derive(Copy)] +struct BigArray { + a: [u8; 65], +} + +impl Clone for BigArray { + fn clone(&self) -> Self { + unimplemented!() + } +} + +// Ok, function pointers are not always Clone +#[derive(Copy)] +struct FnPtr { + a: fn() -> !, +} + +impl Clone for FnPtr { + fn clone(&self) -> Self { + unimplemented!() + } +} + +// Ok, generics +#[derive(Copy)] +struct Generic { + a: T, +} + +impl Clone for Generic { + fn clone(&self) -> Self { + unimplemented!() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/derive.stderr b/src/tools/clippy/tests/ui/derive.stderr new file mode 100644 index 0000000000..1328a9b310 --- /dev/null +++ b/src/tools/clippy/tests/ui/derive.stderr @@ -0,0 +1,83 @@ +error: you are implementing `Clone` explicitly on a `Copy` type + --> $DIR/derive.rs:8:1 + | +LL | / impl Clone for Qux { +LL | | fn clone(&self) -> Self { +LL | | Qux +LL | | } +LL | | } + | |_^ + | + = note: `-D clippy::expl-impl-clone-on-copy` implied by `-D warnings` +note: consider deriving `Clone` or removing `Copy` + --> $DIR/derive.rs:8:1 + | +LL | / impl Clone for Qux { +LL | | fn clone(&self) -> Self { +LL | | Qux +LL | | } +LL | | } + | |_^ + +error: you are implementing `Clone` explicitly on a `Copy` type + --> $DIR/derive.rs:32:1 + | +LL | / impl<'a> Clone for Lt<'a> { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + | +note: consider deriving `Clone` or removing `Copy` + --> $DIR/derive.rs:32:1 + | +LL | / impl<'a> Clone for Lt<'a> { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + +error: you are implementing `Clone` explicitly on a `Copy` type + --> $DIR/derive.rs:44:1 + | +LL | / impl Clone for BigArray { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + | +note: consider deriving `Clone` or removing `Copy` + --> $DIR/derive.rs:44:1 + | +LL | / impl Clone for BigArray { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + +error: you are implementing `Clone` explicitly on a `Copy` type + --> $DIR/derive.rs:56:1 + | +LL | / impl Clone for FnPtr { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + | +note: consider deriving `Clone` or removing `Copy` + --> $DIR/derive.rs:56:1 + | +LL | / impl Clone for FnPtr { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/derive_hash_xor_eq.rs b/src/tools/clippy/tests/ui/derive_hash_xor_eq.rs new file mode 100644 index 0000000000..10abe22d53 --- /dev/null +++ b/src/tools/clippy/tests/ui/derive_hash_xor_eq.rs @@ -0,0 +1,54 @@ +#[derive(PartialEq, Hash)] +struct Foo; + +impl PartialEq for Foo { + fn eq(&self, _: &u64) -> bool { + true + } +} + +#[derive(Hash)] +struct Bar; + +impl PartialEq for Bar { + fn eq(&self, _: &Bar) -> bool { + true + } +} + +#[derive(Hash)] +struct Baz; + +impl PartialEq for Baz { + fn eq(&self, _: &Baz) -> bool { + true + } +} + +#[derive(PartialEq)] +struct Bah; + +impl std::hash::Hash for Bah { + fn hash(&self, _: &mut H) {} +} + +#[derive(PartialEq)] +struct Foo2; + +trait Hash {} + +// We don't want to lint on user-defined traits called `Hash` +impl Hash for Foo2 {} + +mod use_hash { + use std::hash::{Hash, Hasher}; + + #[derive(PartialEq)] + struct Foo3; + + impl Hash for Foo3 { + fn hash(&self, _: &mut H) {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/derive_hash_xor_eq.stderr b/src/tools/clippy/tests/ui/derive_hash_xor_eq.stderr new file mode 100644 index 0000000000..2287a548fe --- /dev/null +++ b/src/tools/clippy/tests/ui/derive_hash_xor_eq.stderr @@ -0,0 +1,67 @@ +error: you are deriving `Hash` but have implemented `PartialEq` explicitly + --> $DIR/derive_hash_xor_eq.rs:10:10 + | +LL | #[derive(Hash)] + | ^^^^ + | + = note: `#[deny(clippy::derive_hash_xor_eq)]` on by default +note: `PartialEq` implemented here + --> $DIR/derive_hash_xor_eq.rs:13:1 + | +LL | / impl PartialEq for Bar { +LL | | fn eq(&self, _: &Bar) -> bool { +LL | | true +LL | | } +LL | | } + | |_^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are deriving `Hash` but have implemented `PartialEq` explicitly + --> $DIR/derive_hash_xor_eq.rs:19:10 + | +LL | #[derive(Hash)] + | ^^^^ + | +note: `PartialEq` implemented here + --> $DIR/derive_hash_xor_eq.rs:22:1 + | +LL | / impl PartialEq for Baz { +LL | | fn eq(&self, _: &Baz) -> bool { +LL | | true +LL | | } +LL | | } + | |_^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are implementing `Hash` explicitly but have derived `PartialEq` + --> $DIR/derive_hash_xor_eq.rs:31:1 + | +LL | / impl std::hash::Hash for Bah { +LL | | fn hash(&self, _: &mut H) {} +LL | | } + | |_^ + | +note: `PartialEq` implemented here + --> $DIR/derive_hash_xor_eq.rs:28:10 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are implementing `Hash` explicitly but have derived `PartialEq` + --> $DIR/derive_hash_xor_eq.rs:49:5 + | +LL | / impl Hash for Foo3 { +LL | | fn hash(&self, _: &mut H) {} +LL | | } + | |_____^ + | +note: `PartialEq` implemented here + --> $DIR/derive_hash_xor_eq.rs:46:14 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/derive_ord_xor_partial_ord.rs b/src/tools/clippy/tests/ui/derive_ord_xor_partial_ord.rs new file mode 100644 index 0000000000..6f12d36d77 --- /dev/null +++ b/src/tools/clippy/tests/ui/derive_ord_xor_partial_ord.rs @@ -0,0 +1,69 @@ +#![warn(clippy::derive_ord_xor_partial_ord)] +#![allow(clippy::unnecessary_wraps)] + +use std::cmp::Ordering; + +#[derive(PartialOrd, Ord, PartialEq, Eq)] +struct DeriveBoth; + +impl PartialEq for DeriveBoth { + fn eq(&self, _: &u64) -> bool { + true + } +} + +impl PartialOrd for DeriveBoth { + fn partial_cmp(&self, _: &u64) -> Option { + Some(Ordering::Equal) + } +} + +#[derive(Ord, PartialEq, Eq)] +struct DeriveOrd; + +impl PartialOrd for DeriveOrd { + fn partial_cmp(&self, other: &Self) -> Option { + Some(other.cmp(self)) + } +} + +#[derive(Ord, PartialEq, Eq)] +struct DeriveOrdWithExplicitTypeVariable; + +impl PartialOrd for DeriveOrdWithExplicitTypeVariable { + fn partial_cmp(&self, other: &Self) -> Option { + Some(other.cmp(self)) + } +} + +#[derive(PartialOrd, PartialEq, Eq)] +struct DerivePartialOrd; + +impl std::cmp::Ord for DerivePartialOrd { + fn cmp(&self, other: &Self) -> Ordering { + Ordering::Less + } +} + +#[derive(PartialOrd, PartialEq, Eq)] +struct ImplUserOrd; + +trait Ord {} + +// We don't want to lint on user-defined traits called `Ord` +impl Ord for ImplUserOrd {} + +mod use_ord { + use std::cmp::{Ord, Ordering}; + + #[derive(PartialOrd, PartialEq, Eq)] + struct DerivePartialOrdInUseOrd; + + impl Ord for DerivePartialOrdInUseOrd { + fn cmp(&self, other: &Self) -> Ordering { + Ordering::Less + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/derive_ord_xor_partial_ord.stderr b/src/tools/clippy/tests/ui/derive_ord_xor_partial_ord.stderr new file mode 100644 index 0000000000..97b46a4aa8 --- /dev/null +++ b/src/tools/clippy/tests/ui/derive_ord_xor_partial_ord.stderr @@ -0,0 +1,71 @@ +error: you are deriving `Ord` but have implemented `PartialOrd` explicitly + --> $DIR/derive_ord_xor_partial_ord.rs:21:10 + | +LL | #[derive(Ord, PartialEq, Eq)] + | ^^^ + | + = note: `-D clippy::derive-ord-xor-partial-ord` implied by `-D warnings` +note: `PartialOrd` implemented here + --> $DIR/derive_ord_xor_partial_ord.rs:24:1 + | +LL | / impl PartialOrd for DeriveOrd { +LL | | fn partial_cmp(&self, other: &Self) -> Option { +LL | | Some(other.cmp(self)) +LL | | } +LL | | } + | |_^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are deriving `Ord` but have implemented `PartialOrd` explicitly + --> $DIR/derive_ord_xor_partial_ord.rs:30:10 + | +LL | #[derive(Ord, PartialEq, Eq)] + | ^^^ + | +note: `PartialOrd` implemented here + --> $DIR/derive_ord_xor_partial_ord.rs:33:1 + | +LL | / impl PartialOrd for DeriveOrdWithExplicitTypeVariable { +LL | | fn partial_cmp(&self, other: &Self) -> Option { +LL | | Some(other.cmp(self)) +LL | | } +LL | | } + | |_^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are implementing `Ord` explicitly but have derived `PartialOrd` + --> $DIR/derive_ord_xor_partial_ord.rs:42:1 + | +LL | / impl std::cmp::Ord for DerivePartialOrd { +LL | | fn cmp(&self, other: &Self) -> Ordering { +LL | | Ordering::Less +LL | | } +LL | | } + | |_^ + | +note: `PartialOrd` implemented here + --> $DIR/derive_ord_xor_partial_ord.rs:39:10 + | +LL | #[derive(PartialOrd, PartialEq, Eq)] + | ^^^^^^^^^^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are implementing `Ord` explicitly but have derived `PartialOrd` + --> $DIR/derive_ord_xor_partial_ord.rs:62:5 + | +LL | / impl Ord for DerivePartialOrdInUseOrd { +LL | | fn cmp(&self, other: &Self) -> Ordering { +LL | | Ordering::Less +LL | | } +LL | | } + | |_____^ + | +note: `PartialOrd` implemented here + --> $DIR/derive_ord_xor_partial_ord.rs:59:14 + | +LL | #[derive(PartialOrd, PartialEq, Eq)] + | ^^^^^^^^^^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/diverging_sub_expression.rs b/src/tools/clippy/tests/ui/diverging_sub_expression.rs new file mode 100644 index 0000000000..4df241c9fc --- /dev/null +++ b/src/tools/clippy/tests/ui/diverging_sub_expression.rs @@ -0,0 +1,42 @@ +#![warn(clippy::diverging_sub_expression)] +#![allow(clippy::match_same_arms, clippy::logic_bug)] + +#[allow(clippy::empty_loop)] +fn diverge() -> ! { + loop {} +} + +struct A; + +impl A { + fn foo(&self) -> ! { + diverge() + } +} + +#[allow(unused_variables, clippy::unnecessary_operation, clippy::short_circuit_statement)] +fn main() { + let b = true; + b || diverge(); + b || A.foo(); +} + +#[allow(dead_code, unused_variables)] +fn foobar() { + loop { + let x = match 5 { + 4 => return, + 5 => continue, + 6 => true || return, + 7 => true || continue, + 8 => break, + 9 => diverge(), + 3 => true || diverge(), + 10 => match 42 { + 99 => return, + _ => true || panic!("boo"), + }, + _ => true || break, + }; + } +} diff --git a/src/tools/clippy/tests/ui/diverging_sub_expression.stderr b/src/tools/clippy/tests/ui/diverging_sub_expression.stderr new file mode 100644 index 0000000000..170e7d92de --- /dev/null +++ b/src/tools/clippy/tests/ui/diverging_sub_expression.stderr @@ -0,0 +1,40 @@ +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:20:10 + | +LL | b || diverge(); + | ^^^^^^^^^ + | + = note: `-D clippy::diverging-sub-expression` implied by `-D warnings` + +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:21:10 + | +LL | b || A.foo(); + | ^^^^^^^ + +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:30:26 + | +LL | 6 => true || return, + | ^^^^^^ + +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:31:26 + | +LL | 7 => true || continue, + | ^^^^^^^^ + +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:34:26 + | +LL | 3 => true || diverge(), + | ^^^^^^^^^ + +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:39:26 + | +LL | _ => true || break, + | ^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/dlist.rs b/src/tools/clippy/tests/ui/dlist.rs new file mode 100644 index 0000000000..2940d2d290 --- /dev/null +++ b/src/tools/clippy/tests/ui/dlist.rs @@ -0,0 +1,40 @@ +#![feature(associated_type_defaults)] +#![warn(clippy::linkedlist)] +#![allow(dead_code, clippy::needless_pass_by_value)] + +extern crate alloc; +use alloc::collections::linked_list::LinkedList; + +trait Foo { + type Baz = LinkedList; + fn foo(_: LinkedList); + const BAR: Option>; +} + +// Ok, we don’t want to warn for implementations; see issue #605. +impl Foo for LinkedList { + fn foo(_: LinkedList) {} + const BAR: Option> = None; +} + +struct Bar; +impl Bar { + fn foo(_: LinkedList) {} +} + +pub fn test(my_favourite_linked_list: LinkedList) { + println!("{:?}", my_favourite_linked_list) +} + +pub fn test_ret() -> Option> { + unimplemented!(); +} + +pub fn test_local_not_linted() { + let _: LinkedList; +} + +fn main() { + test(LinkedList::new()); + test_local_not_linted(); +} diff --git a/src/tools/clippy/tests/ui/dlist.stderr b/src/tools/clippy/tests/ui/dlist.stderr new file mode 100644 index 0000000000..234db33ba1 --- /dev/null +++ b/src/tools/clippy/tests/ui/dlist.stderr @@ -0,0 +1,51 @@ +error: you seem to be using a `LinkedList`! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:9:16 + | +LL | type Baz = LinkedList; + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::linkedlist` implied by `-D warnings` + = help: a `VecDeque` might work + +error: you seem to be using a `LinkedList`! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:10:15 + | +LL | fn foo(_: LinkedList); + | ^^^^^^^^^^^^^^ + | + = help: a `VecDeque` might work + +error: you seem to be using a `LinkedList`! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:11:23 + | +LL | const BAR: Option>; + | ^^^^^^^^^^^^^^ + | + = help: a `VecDeque` might work + +error: you seem to be using a `LinkedList`! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:22:15 + | +LL | fn foo(_: LinkedList) {} + | ^^^^^^^^^^^^^^ + | + = help: a `VecDeque` might work + +error: you seem to be using a `LinkedList`! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:25:39 + | +LL | pub fn test(my_favourite_linked_list: LinkedList) { + | ^^^^^^^^^^^^^^ + | + = help: a `VecDeque` might work + +error: you seem to be using a `LinkedList`! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:29:29 + | +LL | pub fn test_ret() -> Option> { + | ^^^^^^^^^^^^^^ + | + = help: a `VecDeque` might work + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/doc.rs b/src/tools/clippy/tests/ui/doc.rs new file mode 100644 index 0000000000..d2c666bd29 --- /dev/null +++ b/src/tools/clippy/tests/ui/doc.rs @@ -0,0 +1,204 @@ +//! This file tests for the `DOC_MARKDOWN` lint. + +#![allow(dead_code)] +#![warn(clippy::doc_markdown)] +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +/// The foo_bar function does _nothing_. See also foo::bar. (note the dot there) +/// Markdown is _weird_. I mean _really weird_. This \_ is ok. So is `_`. But not Foo::some_fun +/// which should be reported only once despite being __doubly bad__. +/// Here be ::a::global:path. +/// That's not code ~NotInCodeBlock~. +/// be_sure_we_got_to_the_end_of_it +fn foo_bar() { +} + +/// That one tests multiline ticks. +/// ```rust +/// foo_bar FOO_BAR +/// _foo bar_ +/// ``` +/// +/// ~~~rust +/// foo_bar FOO_BAR +/// _foo bar_ +/// ~~~ +/// be_sure_we_got_to_the_end_of_it +fn multiline_codeblock() { +} + +/// This _is a test for +/// multiline +/// emphasis_. +/// be_sure_we_got_to_the_end_of_it +fn test_emphasis() { +} + +/// This tests units. See also #835. +/// kiB MiB GiB TiB PiB EiB +/// kib Mib Gib Tib Pib Eib +/// kB MB GB TB PB EB +/// kb Mb Gb Tb Pb Eb +/// 32kiB 32MiB 32GiB 32TiB 32PiB 32EiB +/// 32kib 32Mib 32Gib 32Tib 32Pib 32Eib +/// 32kB 32MB 32GB 32TB 32PB 32EB +/// 32kb 32Mb 32Gb 32Tb 32Pb 32Eb +/// NaN +/// be_sure_we_got_to_the_end_of_it +fn test_units() { +} + +/// This tests allowed identifiers. +/// KiB MiB GiB TiB PiB EiB +/// DirectX +/// ECMAScript +/// GPLv2 GPLv3 +/// GitHub GitLab +/// IPv4 IPv6 +/// ClojureScript CoffeeScript JavaScript PureScript TypeScript +/// NaN NaNs +/// OAuth GraphQL +/// OCaml +/// OpenGL OpenMP OpenSSH OpenSSL OpenStreetMap OpenDNS +/// WebGL +/// TensorFlow +/// TrueType +/// iOS macOS +/// TeX LaTeX BibTeX BibLaTeX +/// MinGW +/// CamelCase (see also #2395) +/// be_sure_we_got_to_the_end_of_it +fn test_allowed() { +} + +/// This test has [a link_with_underscores][chunked-example] inside it. See #823. +/// See also [the issue tracker](https://github.com/rust-lang/rust-clippy/search?q=clippy::doc_markdown&type=Issues) +/// on GitHub (which is a camel-cased word, but is OK). And here is another [inline link][inline_link]. +/// It can also be [inline_link2]. +/// +/// [chunked-example]: https://en.wikipedia.org/wiki/Chunked_transfer_encoding#Example +/// [inline_link]: https://foobar +/// [inline_link2]: https://foobar +/// The `main` function is the entry point of the program. Here it only calls the `foo_bar` and +/// `multiline_ticks` functions. +/// +/// expression of the type `_ m c` (where `` +/// is one of {`&`, '|'} and `` is one of {`!=`, `>=`, `>` , +/// be_sure_we_got_to_the_end_of_it +fn main() { + foo_bar(); + multiline_codeblock(); + test_emphasis(); + test_units(); +} + +/// ## CamelCaseThing +/// Talks about `CamelCaseThing`. Titles should be ignored; see issue #897. +/// +/// # CamelCaseThing +/// +/// Not a title #897 CamelCaseThing +/// be_sure_we_got_to_the_end_of_it +fn issue897() { +} + +/// I am confused by brackets? (`x_y`) +/// I am confused by brackets? (foo `x_y`) +/// I am confused by brackets? (`x_y` foo) +/// be_sure_we_got_to_the_end_of_it +fn issue900() { +} + +/// Diesel queries also have a similar problem to [Iterator][iterator], where +/// /// More talking +/// returning them from a function requires exposing the implementation of that +/// function. The [`helper_types`][helper_types] module exists to help with this, +/// but you might want to hide the return type or have it conditionally change. +/// Boxing can achieve both. +/// +/// [iterator]: https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html +/// [helper_types]: ../helper_types/index.html +/// be_sure_we_got_to_the_end_of_it +fn issue883() { +} + +/// `foo_bar +/// baz_quz` +/// [foo +/// bar](https://doc.rust-lang.org/stable/std/iter/trait.IteratorFooBar.html) +fn multiline() { +} + +/** E.g., serialization of an empty list: FooBar +``` +That's in a code block: `PackedNode` +``` + +And BarQuz too. +be_sure_we_got_to_the_end_of_it +*/ +fn issue1073() { +} + +/** E.g., serialization of an empty list: FooBar +``` +That's in a code block: PackedNode +``` + +And BarQuz too. +be_sure_we_got_to_the_end_of_it +*/ +fn issue1073_alt() { +} + +/// Tests more than three quotes: +/// ```` +/// DoNotWarn +/// ``` +/// StillDont +/// ```` +/// be_sure_we_got_to_the_end_of_it +fn four_quotes() { +} + +/// See [NIST SP 800-56A, revision 2]. +/// +/// [NIST SP 800-56A, revision 2]: +/// https://github.com/rust-lang/rust-clippy/issues/902#issuecomment-261919419 +fn issue_902_comment() {} + +#[cfg_attr(feature = "a", doc = " ```")] +#[cfg_attr(not(feature = "a"), doc = " ```ignore")] +/// fn main() { +/// let s = "localhost:10000".to_string(); +/// println!("{}", s); +/// } +/// ``` +fn issue_1469() {} + +/** + * This is a doc comment that should not be a list + *This would also be an error under a strict common mark interpretation + */ +fn issue_1920() {} + +/// Ok: +/// +/// Not ok: http://www.unicode.org +/// Not ok: https://www.unicode.org +/// Not ok: http://www.unicode.org/ +/// Not ok: http://www.unicode.org/reports/tr9/#Reordering_Resolved_Levels +fn issue_1832() {} + +/// An iterator over mycrate::Collection's values. +/// It should not lint a `'static` lifetime in ticks. +fn issue_2210() {} + +/// This should not cause the lint to trigger: +/// #REQ-data-family.lint_partof_exists +fn issue_2343() {} + +/// This should not cause an ICE: +/// __|_ _|__||_| +fn pulldown_cmark_crash() {} diff --git a/src/tools/clippy/tests/ui/doc.stderr b/src/tools/clippy/tests/ui/doc.stderr new file mode 100644 index 0000000000..7eab8a85f0 --- /dev/null +++ b/src/tools/clippy/tests/ui/doc.stderr @@ -0,0 +1,190 @@ +error: you should put `foo_bar` between ticks in the documentation + --> $DIR/doc.rs:8:9 + | +LL | /// The foo_bar function does _nothing_. See also foo::bar. (note the dot there) + | ^^^^^^^ + | + = note: `-D clippy::doc-markdown` implied by `-D warnings` + +error: you should put `foo::bar` between ticks in the documentation + --> $DIR/doc.rs:8:51 + | +LL | /// The foo_bar function does _nothing_. See also foo::bar. (note the dot there) + | ^^^^^^^^ + +error: you should put `Foo::some_fun` between ticks in the documentation + --> $DIR/doc.rs:9:83 + | +LL | /// Markdown is _weird_. I mean _really weird_. This /_ is ok. So is `_`. But not Foo::some_fun + | ^^^^^^^^^^^^^ + +error: you should put `a::global:path` between ticks in the documentation + --> $DIR/doc.rs:11:15 + | +LL | /// Here be ::a::global:path. + | ^^^^^^^^^^^^^^ + +error: you should put `NotInCodeBlock` between ticks in the documentation + --> $DIR/doc.rs:12:22 + | +LL | /// That's not code ~NotInCodeBlock~. + | ^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:13:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:27:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:34:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:48:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:71:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `link_with_underscores` between ticks in the documentation + --> $DIR/doc.rs:75:22 + | +LL | /// This test has [a link_with_underscores][chunked-example] inside it. See #823. + | ^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `inline_link2` between ticks in the documentation + --> $DIR/doc.rs:78:21 + | +LL | /// It can also be [inline_link2]. + | ^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:88:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `CamelCaseThing` between ticks in the documentation + --> $DIR/doc.rs:96:8 + | +LL | /// ## CamelCaseThing + | ^^^^^^^^^^^^^^ + +error: you should put `CamelCaseThing` between ticks in the documentation + --> $DIR/doc.rs:99:7 + | +LL | /// # CamelCaseThing + | ^^^^^^^^^^^^^^ + +error: you should put `CamelCaseThing` between ticks in the documentation + --> $DIR/doc.rs:101:22 + | +LL | /// Not a title #897 CamelCaseThing + | ^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:102:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:109:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:122:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `FooBar` between ticks in the documentation + --> $DIR/doc.rs:133:43 + | +LL | /** E.g., serialization of an empty list: FooBar + | ^^^^^^ + +error: you should put `BarQuz` between ticks in the documentation + --> $DIR/doc.rs:138:5 + | +LL | And BarQuz too. + | ^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:139:1 + | +LL | be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `FooBar` between ticks in the documentation + --> $DIR/doc.rs:144:43 + | +LL | /** E.g., serialization of an empty list: FooBar + | ^^^^^^ + +error: you should put `BarQuz` between ticks in the documentation + --> $DIR/doc.rs:149:5 + | +LL | And BarQuz too. + | ^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:150:1 + | +LL | be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:161:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> $DIR/doc.rs:188:13 + | +LL | /// Not ok: http://www.unicode.org + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> $DIR/doc.rs:189:13 + | +LL | /// Not ok: https://www.unicode.org + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> $DIR/doc.rs:190:13 + | +LL | /// Not ok: http://www.unicode.org/ + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> $DIR/doc.rs:191:13 + | +LL | /// Not ok: http://www.unicode.org/reports/tr9/#Reordering_Resolved_Levels + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `mycrate::Collection` between ticks in the documentation + --> $DIR/doc.rs:194:22 + | +LL | /// An iterator over mycrate::Collection's values. + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 31 previous errors + diff --git a/src/tools/clippy/tests/ui/doc_errors.rs b/src/tools/clippy/tests/ui/doc_errors.rs new file mode 100644 index 0000000000..c77a74a58f --- /dev/null +++ b/src/tools/clippy/tests/ui/doc_errors.rs @@ -0,0 +1,105 @@ +// edition:2018 +#![warn(clippy::missing_errors_doc)] +#![allow(clippy::result_unit_err)] +#![allow(clippy::unnecessary_wraps)] + +use std::io; + +pub fn pub_fn_missing_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +pub async fn async_pub_fn_missing_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +/// This is not sufficiently documented. +pub fn pub_fn_returning_io_result() -> io::Result<()> { + unimplemented!(); +} + +/// This is not sufficiently documented. +pub async fn async_pub_fn_returning_io_result() -> io::Result<()> { + unimplemented!(); +} + +/// # Errors +/// A description of the errors goes here. +pub fn pub_fn_with_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +/// # Errors +/// A description of the errors goes here. +pub async fn async_pub_fn_with_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +/// This function doesn't require the documentation because it is private +fn priv_fn_missing_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +/// This function doesn't require the documentation because it is private +async fn async_priv_fn_missing_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +pub struct Struct1; + +impl Struct1 { + /// This is not sufficiently documented. + pub fn pub_method_missing_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + /// This is not sufficiently documented. + pub async fn async_pub_method_missing_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + /// # Errors + /// A description of the errors goes here. + pub fn pub_method_with_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + /// # Errors + /// A description of the errors goes here. + pub async fn async_pub_method_with_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + /// This function doesn't require the documentation because it is private. + fn priv_method_missing_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + /// This function doesn't require the documentation because it is private. + async fn async_priv_method_missing_errors_header() -> Result<(), ()> { + unimplemented!(); + } +} + +pub trait Trait1 { + /// This is not sufficiently documented. + fn trait_method_missing_errors_header() -> Result<(), ()>; + + /// # Errors + /// A description of the errors goes here. + fn trait_method_with_errors_header() -> Result<(), ()>; +} + +impl Trait1 for Struct1 { + fn trait_method_missing_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + fn trait_method_with_errors_header() -> Result<(), ()> { + unimplemented!(); + } +} + +fn main() -> Result<(), ()> { + Ok(()) +} diff --git a/src/tools/clippy/tests/ui/doc_errors.stderr b/src/tools/clippy/tests/ui/doc_errors.stderr new file mode 100644 index 0000000000..b5a81419da --- /dev/null +++ b/src/tools/clippy/tests/ui/doc_errors.stderr @@ -0,0 +1,58 @@ +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:8:1 + | +LL | / pub fn pub_fn_missing_errors_header() -> Result<(), ()> { +LL | | unimplemented!(); +LL | | } + | |_^ + | + = note: `-D clippy::missing-errors-doc` implied by `-D warnings` + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:12:1 + | +LL | / pub async fn async_pub_fn_missing_errors_header() -> Result<(), ()> { +LL | | unimplemented!(); +LL | | } + | |_^ + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:17:1 + | +LL | / pub fn pub_fn_returning_io_result() -> io::Result<()> { +LL | | unimplemented!(); +LL | | } + | |_^ + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:22:1 + | +LL | / pub async fn async_pub_fn_returning_io_result() -> io::Result<()> { +LL | | unimplemented!(); +LL | | } + | |_^ + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:52:5 + | +LL | / pub fn pub_method_missing_errors_header() -> Result<(), ()> { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:57:5 + | +LL | / pub async fn async_pub_method_missing_errors_header() -> Result<(), ()> { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:86:5 + | +LL | fn trait_method_missing_errors_header() -> Result<(), ()>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/doc_panics.rs b/src/tools/clippy/tests/ui/doc_panics.rs new file mode 100644 index 0000000000..17e72353f8 --- /dev/null +++ b/src/tools/clippy/tests/ui/doc_panics.rs @@ -0,0 +1,114 @@ +#![warn(clippy::missing_panics_doc)] +#![allow(clippy::option_map_unit_fn)] + +fn main() {} + +/// This needs to be documented +pub fn unwrap() { + let result = Err("Hi"); + result.unwrap() +} + +/// This needs to be documented +pub fn panic() { + panic!("This function panics") +} + +/// This needs to be documented +pub fn todo() { + todo!() +} + +/// This needs to be documented +pub fn inner_body(opt: Option) { + opt.map(|x| { + if x == 10 { + panic!() + } + }); +} + +/// This needs to be documented +pub fn unreachable_and_panic() { + if true { unreachable!() } else { panic!() } +} + +/// This is documented +/// +/// # Panics +/// +/// Panics if `result` if an error +pub fn unwrap_documented() { + let result = Err("Hi"); + result.unwrap() +} + +/// This is documented +/// +/// # Panics +/// +/// Panics just because +pub fn panic_documented() { + panic!("This function panics") +} + +/// This is documented +/// +/// # Panics +/// +/// Panics if `opt` is Just(10) +pub fn inner_body_documented(opt: Option) { + opt.map(|x| { + if x == 10 { + panic!() + } + }); +} + +/// This is documented +/// +/// # Panics +/// +/// We still need to do this part +pub fn todo_documented() { + todo!() +} + +/// This is documented +/// +/// # Panics +/// +/// We still need to do this part +pub fn unreachable_amd_panic_documented() { + if true { unreachable!() } else { panic!() } +} + +/// This is okay because it is private +fn unwrap_private() { + let result = Err("Hi"); + result.unwrap() +} + +/// This is okay because it is private +fn panic_private() { + panic!("This function panics") +} + +/// This is okay because it is private +fn todo_private() { + todo!() +} + +/// This is okay because it is private +fn inner_body_private(opt: Option) { + opt.map(|x| { + if x == 10 { + panic!() + } + }); +} + +/// This is okay because unreachable +pub fn unreachable() { + unreachable!("This function panics") +} diff --git a/src/tools/clippy/tests/ui/doc_panics.stderr b/src/tools/clippy/tests/ui/doc_panics.stderr new file mode 100644 index 0000000000..2fa88a2f6e --- /dev/null +++ b/src/tools/clippy/tests/ui/doc_panics.stderr @@ -0,0 +1,82 @@ +error: docs for function which may panic missing `# Panics` section + --> $DIR/doc_panics.rs:7:1 + | +LL | / pub fn unwrap() { +LL | | let result = Err("Hi"); +LL | | result.unwrap() +LL | | } + | |_^ + | + = note: `-D clippy::missing-panics-doc` implied by `-D warnings` +note: first possible panic found here + --> $DIR/doc_panics.rs:9:5 + | +LL | result.unwrap() + | ^^^^^^^^^^^^^^^ + +error: docs for function which may panic missing `# Panics` section + --> $DIR/doc_panics.rs:13:1 + | +LL | / pub fn panic() { +LL | | panic!("This function panics") +LL | | } + | |_^ + | +note: first possible panic found here + --> $DIR/doc_panics.rs:14:5 + | +LL | panic!("This function panics") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: docs for function which may panic missing `# Panics` section + --> $DIR/doc_panics.rs:18:1 + | +LL | / pub fn todo() { +LL | | todo!() +LL | | } + | |_^ + | +note: first possible panic found here + --> $DIR/doc_panics.rs:19:5 + | +LL | todo!() + | ^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: docs for function which may panic missing `# Panics` section + --> $DIR/doc_panics.rs:23:1 + | +LL | / pub fn inner_body(opt: Option) { +LL | | opt.map(|x| { +LL | | if x == 10 { +LL | | panic!() +LL | | } +LL | | }); +LL | | } + | |_^ + | +note: first possible panic found here + --> $DIR/doc_panics.rs:26:13 + | +LL | panic!() + | ^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: docs for function which may panic missing `# Panics` section + --> $DIR/doc_panics.rs:32:1 + | +LL | / pub fn unreachable_and_panic() { +LL | | if true { unreachable!() } else { panic!() } +LL | | } + | |_^ + | +note: first possible panic found here + --> $DIR/doc_panics.rs:33:39 + | +LL | if true { unreachable!() } else { panic!() } + | ^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/doc_unsafe.rs b/src/tools/clippy/tests/ui/doc_unsafe.rs new file mode 100644 index 0000000000..484aa72d59 --- /dev/null +++ b/src/tools/clippy/tests/ui/doc_unsafe.rs @@ -0,0 +1,100 @@ +// aux-build:doc_unsafe_macros.rs + +#[macro_use] +extern crate doc_unsafe_macros; + +/// This is not sufficiently documented +pub unsafe fn destroy_the_planet() { + unimplemented!(); +} + +/// This one is +/// +/// # Safety +/// +/// This function shouldn't be called unless the horsemen are ready +pub unsafe fn apocalypse(universe: &mut ()) { + unimplemented!(); +} + +/// This is a private function, so docs aren't necessary +unsafe fn you_dont_see_me() { + unimplemented!(); +} + +mod private_mod { + pub unsafe fn only_crate_wide_accessible() { + unimplemented!(); + } + + pub unsafe fn republished() { + unimplemented!(); + } +} + +pub use private_mod::republished; + +pub trait UnsafeTrait { + unsafe fn woefully_underdocumented(self); + + /// # Safety + unsafe fn at_least_somewhat_documented(self); +} + +pub struct Struct; + +impl UnsafeTrait for Struct { + unsafe fn woefully_underdocumented(self) { + // all is well + } + + unsafe fn at_least_somewhat_documented(self) { + // all is still well + } +} + +impl Struct { + pub unsafe fn more_undocumented_unsafe() -> Self { + unimplemented!(); + } + + /// # Safety + pub unsafe fn somewhat_documented(&self) { + unimplemented!(); + } + + unsafe fn private(&self) { + unimplemented!(); + } +} + +macro_rules! very_unsafe { + () => { + pub unsafe fn whee() { + unimplemented!() + } + + /// # Safety + /// + /// Please keep the seat belt fastened + pub unsafe fn drive() { + whee() + } + }; +} + +very_unsafe!(); + +// we don't lint code from external macros +undocd_unsafe!(); + +fn main() { + unsafe { + you_dont_see_me(); + destroy_the_planet(); + let mut universe = (); + apocalypse(&mut universe); + private_mod::only_crate_wide_accessible(); + drive(); + } +} diff --git a/src/tools/clippy/tests/ui/doc_unsafe.stderr b/src/tools/clippy/tests/ui/doc_unsafe.stderr new file mode 100644 index 0000000000..c784d41ba1 --- /dev/null +++ b/src/tools/clippy/tests/ui/doc_unsafe.stderr @@ -0,0 +1,47 @@ +error: unsafe function's docs miss `# Safety` section + --> $DIR/doc_unsafe.rs:7:1 + | +LL | / pub unsafe fn destroy_the_planet() { +LL | | unimplemented!(); +LL | | } + | |_^ + | + = note: `-D clippy::missing-safety-doc` implied by `-D warnings` + +error: unsafe function's docs miss `# Safety` section + --> $DIR/doc_unsafe.rs:30:5 + | +LL | / pub unsafe fn republished() { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: unsafe function's docs miss `# Safety` section + --> $DIR/doc_unsafe.rs:38:5 + | +LL | unsafe fn woefully_underdocumented(self); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unsafe function's docs miss `# Safety` section + --> $DIR/doc_unsafe.rs:57:5 + | +LL | / pub unsafe fn more_undocumented_unsafe() -> Self { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: unsafe function's docs miss `# Safety` section + --> $DIR/doc_unsafe.rs:73:9 + | +LL | / pub unsafe fn whee() { +LL | | unimplemented!() +LL | | } + | |_________^ +... +LL | very_unsafe!(); + | --------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/double_comparison.fixed b/src/tools/clippy/tests/ui/double_comparison.fixed new file mode 100644 index 0000000000..bb6cdaa667 --- /dev/null +++ b/src/tools/clippy/tests/ui/double_comparison.fixed @@ -0,0 +1,30 @@ +// run-rustfix + +fn main() { + let x = 1; + let y = 2; + if x <= y { + // do something + } + if x <= y { + // do something + } + if x >= y { + // do something + } + if x >= y { + // do something + } + if x != y { + // do something + } + if x != y { + // do something + } + if x == y { + // do something + } + if x == y { + // do something + } +} diff --git a/src/tools/clippy/tests/ui/double_comparison.rs b/src/tools/clippy/tests/ui/double_comparison.rs new file mode 100644 index 0000000000..9a2a9068a2 --- /dev/null +++ b/src/tools/clippy/tests/ui/double_comparison.rs @@ -0,0 +1,30 @@ +// run-rustfix + +fn main() { + let x = 1; + let y = 2; + if x == y || x < y { + // do something + } + if x < y || x == y { + // do something + } + if x == y || x > y { + // do something + } + if x > y || x == y { + // do something + } + if x < y || x > y { + // do something + } + if x > y || x < y { + // do something + } + if x <= y && x >= y { + // do something + } + if x >= y && x <= y { + // do something + } +} diff --git a/src/tools/clippy/tests/ui/double_comparison.stderr b/src/tools/clippy/tests/ui/double_comparison.stderr new file mode 100644 index 0000000000..05ef4e25f7 --- /dev/null +++ b/src/tools/clippy/tests/ui/double_comparison.stderr @@ -0,0 +1,52 @@ +error: this binary expression can be simplified + --> $DIR/double_comparison.rs:6:8 + | +LL | if x == y || x < y { + | ^^^^^^^^^^^^^^^ help: try: `x <= y` + | + = note: `-D clippy::double-comparisons` implied by `-D warnings` + +error: this binary expression can be simplified + --> $DIR/double_comparison.rs:9:8 + | +LL | if x < y || x == y { + | ^^^^^^^^^^^^^^^ help: try: `x <= y` + +error: this binary expression can be simplified + --> $DIR/double_comparison.rs:12:8 + | +LL | if x == y || x > y { + | ^^^^^^^^^^^^^^^ help: try: `x >= y` + +error: this binary expression can be simplified + --> $DIR/double_comparison.rs:15:8 + | +LL | if x > y || x == y { + | ^^^^^^^^^^^^^^^ help: try: `x >= y` + +error: this binary expression can be simplified + --> $DIR/double_comparison.rs:18:8 + | +LL | if x < y || x > y { + | ^^^^^^^^^^^^^^ help: try: `x != y` + +error: this binary expression can be simplified + --> $DIR/double_comparison.rs:21:8 + | +LL | if x > y || x < y { + | ^^^^^^^^^^^^^^ help: try: `x != y` + +error: this binary expression can be simplified + --> $DIR/double_comparison.rs:24:8 + | +LL | if x <= y && x >= y { + | ^^^^^^^^^^^^^^^^ help: try: `x == y` + +error: this binary expression can be simplified + --> $DIR/double_comparison.rs:27:8 + | +LL | if x >= y && x <= y { + | ^^^^^^^^^^^^^^^^ help: try: `x == y` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/double_must_use.rs b/src/tools/clippy/tests/ui/double_must_use.rs new file mode 100644 index 0000000000..05e087b08b --- /dev/null +++ b/src/tools/clippy/tests/ui/double_must_use.rs @@ -0,0 +1,28 @@ +#![warn(clippy::double_must_use)] +#![allow(clippy::result_unit_err)] + +#[must_use] +pub fn must_use_result() -> Result<(), ()> { + unimplemented!(); +} + +#[must_use] +pub fn must_use_tuple() -> (Result<(), ()>, u8) { + unimplemented!(); +} + +#[must_use] +pub fn must_use_array() -> [Result<(), ()>; 1] { + unimplemented!(); +} + +#[must_use = "With note"] +pub fn must_use_with_note() -> Result<(), ()> { + unimplemented!(); +} + +fn main() { + must_use_result(); + must_use_tuple(); + must_use_with_note(); +} diff --git a/src/tools/clippy/tests/ui/double_must_use.stderr b/src/tools/clippy/tests/ui/double_must_use.stderr new file mode 100644 index 0000000000..8290ece1ca --- /dev/null +++ b/src/tools/clippy/tests/ui/double_must_use.stderr @@ -0,0 +1,27 @@ +error: this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]` + --> $DIR/double_must_use.rs:5:1 + | +LL | pub fn must_use_result() -> Result<(), ()> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::double-must-use` implied by `-D warnings` + = help: either add some descriptive text or remove the attribute + +error: this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]` + --> $DIR/double_must_use.rs:10:1 + | +LL | pub fn must_use_tuple() -> (Result<(), ()>, u8) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: either add some descriptive text or remove the attribute + +error: this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]` + --> $DIR/double_must_use.rs:15:1 + | +LL | pub fn must_use_array() -> [Result<(), ()>; 1] { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: either add some descriptive text or remove the attribute + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/double_neg.rs b/src/tools/clippy/tests/ui/double_neg.rs new file mode 100644 index 0000000000..d47dfcb5ba --- /dev/null +++ b/src/tools/clippy/tests/ui/double_neg.rs @@ -0,0 +1,7 @@ +#[warn(clippy::double_neg)] +fn main() { + let x = 1; + -x; + -(-x); + --x; +} diff --git a/src/tools/clippy/tests/ui/double_neg.stderr b/src/tools/clippy/tests/ui/double_neg.stderr new file mode 100644 index 0000000000..d82ed05f05 --- /dev/null +++ b/src/tools/clippy/tests/ui/double_neg.stderr @@ -0,0 +1,10 @@ +error: `--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op + --> $DIR/double_neg.rs:6:5 + | +LL | --x; + | ^^^ + | + = note: `-D clippy::double-neg` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/double_parens.rs b/src/tools/clippy/tests/ui/double_parens.rs new file mode 100644 index 0000000000..ff1dc76ab6 --- /dev/null +++ b/src/tools/clippy/tests/ui/double_parens.rs @@ -0,0 +1,56 @@ +#![warn(clippy::double_parens)] +#![allow(dead_code, clippy::eq_op)] +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +fn dummy_fn(_: T) {} + +struct DummyStruct; + +impl DummyStruct { + fn dummy_method(self, _: T) {} +} + +fn simple_double_parens() -> i32 { + ((0)) +} + +fn fn_double_parens() { + dummy_fn((0)); +} + +fn method_double_parens(x: DummyStruct) { + x.dummy_method((0)); +} + +fn tuple_double_parens() -> (i32, i32) { + ((1, 2)) +} + +fn unit_double_parens() { + (()) +} + +fn fn_tuple_ok() { + dummy_fn((1, 2)); +} + +fn method_tuple_ok(x: DummyStruct) { + x.dummy_method((1, 2)); +} + +fn fn_unit_ok() { + dummy_fn(()); +} + +fn method_unit_ok(x: DummyStruct) { + x.dummy_method(()); +} + +// Issue #3206 +fn inside_macro() { + assert_eq!((1, 2), (1, 2), "Error"); + assert_eq!(((1, 2)), (1, 2), "Error"); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/double_parens.stderr b/src/tools/clippy/tests/ui/double_parens.stderr new file mode 100644 index 0000000000..40fcad2ab1 --- /dev/null +++ b/src/tools/clippy/tests/ui/double_parens.stderr @@ -0,0 +1,40 @@ +error: consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:15:5 + | +LL | ((0)) + | ^^^^^ + | + = note: `-D clippy::double-parens` implied by `-D warnings` + +error: consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:19:14 + | +LL | dummy_fn((0)); + | ^^^ + +error: consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:23:20 + | +LL | x.dummy_method((0)); + | ^^^ + +error: consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:27:5 + | +LL | ((1, 2)) + | ^^^^^^^^ + +error: consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:31:5 + | +LL | (()) + | ^^^^ + +error: consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:53:16 + | +LL | assert_eq!(((1, 2)), (1, 2), "Error"); + | ^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/drop_forget_copy.rs b/src/tools/clippy/tests/ui/drop_forget_copy.rs new file mode 100644 index 0000000000..9ddd6d6470 --- /dev/null +++ b/src/tools/clippy/tests/ui/drop_forget_copy.rs @@ -0,0 +1,66 @@ +#![warn(clippy::drop_copy, clippy::forget_copy)] +#![allow(clippy::toplevel_ref_arg, clippy::drop_ref, clippy::forget_ref, unused_mut)] + +use std::mem::{drop, forget}; +use std::vec::Vec; + +#[derive(Copy, Clone)] +struct SomeStruct {} + +struct AnotherStruct { + x: u8, + y: u8, + z: Vec, +} + +impl Clone for AnotherStruct { + fn clone(&self) -> AnotherStruct { + AnotherStruct { + x: self.x, + y: self.y, + z: self.z.clone(), + } + } +} + +fn main() { + let s1 = SomeStruct {}; + let s2 = s1; + let s3 = &s1; + let mut s4 = s1; + let ref s5 = s1; + + drop(s1); + drop(s2); + drop(s3); + drop(s4); + drop(s5); + + forget(s1); + forget(s2); + forget(s3); + forget(s4); + forget(s5); + + let a1 = AnotherStruct { + x: 255, + y: 0, + z: vec![1, 2, 3], + }; + let a2 = &a1; + let mut a3 = a1.clone(); + let ref a4 = a1; + let a5 = a1.clone(); + + drop(a2); + drop(a3); + drop(a4); + drop(a5); + + forget(a2); + let a3 = &a1; + forget(a3); + forget(a4); + let a5 = a1.clone(); + forget(a5); +} diff --git a/src/tools/clippy/tests/ui/drop_forget_copy.stderr b/src/tools/clippy/tests/ui/drop_forget_copy.stderr new file mode 100644 index 0000000000..01de0be7ca --- /dev/null +++ b/src/tools/clippy/tests/ui/drop_forget_copy.stderr @@ -0,0 +1,76 @@ +error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact + --> $DIR/drop_forget_copy.rs:33:5 + | +LL | drop(s1); + | ^^^^^^^^ + | + = note: `-D clippy::drop-copy` implied by `-D warnings` +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:33:10 + | +LL | drop(s1); + | ^^ + +error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact + --> $DIR/drop_forget_copy.rs:34:5 + | +LL | drop(s2); + | ^^^^^^^^ + | +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:34:10 + | +LL | drop(s2); + | ^^ + +error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact + --> $DIR/drop_forget_copy.rs:36:5 + | +LL | drop(s4); + | ^^^^^^^^ + | +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:36:10 + | +LL | drop(s4); + | ^^ + +error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetting a copy leaves the original intact + --> $DIR/drop_forget_copy.rs:39:5 + | +LL | forget(s1); + | ^^^^^^^^^^ + | + = note: `-D clippy::forget-copy` implied by `-D warnings` +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:39:12 + | +LL | forget(s1); + | ^^ + +error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetting a copy leaves the original intact + --> $DIR/drop_forget_copy.rs:40:5 + | +LL | forget(s2); + | ^^^^^^^^^^ + | +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:40:12 + | +LL | forget(s2); + | ^^ + +error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetting a copy leaves the original intact + --> $DIR/drop_forget_copy.rs:42:5 + | +LL | forget(s4); + | ^^^^^^^^^^ + | +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:42:12 + | +LL | forget(s4); + | ^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/drop_ref.rs b/src/tools/clippy/tests/ui/drop_ref.rs new file mode 100644 index 0000000000..e1a15c609f --- /dev/null +++ b/src/tools/clippy/tests/ui/drop_ref.rs @@ -0,0 +1,74 @@ +#![warn(clippy::drop_ref)] +#![allow(clippy::toplevel_ref_arg)] +#![allow(clippy::map_err_ignore)] +#![allow(clippy::unnecessary_wraps)] + +use std::mem::drop; + +struct SomeStruct; + +fn main() { + drop(&SomeStruct); + + let mut owned1 = SomeStruct; + drop(&owned1); + drop(&&owned1); + drop(&mut owned1); + drop(owned1); //OK + + let reference1 = &SomeStruct; + drop(reference1); + + let reference2 = &mut SomeStruct; + drop(reference2); + + let ref reference3 = SomeStruct; + drop(reference3); +} + +#[allow(dead_code)] +fn test_generic_fn_drop(val: T) { + drop(&val); + drop(val); //OK +} + +#[allow(dead_code)] +fn test_similarly_named_function() { + fn drop(_val: T) {} + drop(&SomeStruct); //OK; call to unrelated function which happens to have the same name + std::mem::drop(&SomeStruct); +} + +#[derive(Copy, Clone)] +pub struct Error; +fn produce_half_owl_error() -> Result<(), Error> { + Ok(()) +} + +fn produce_half_owl_ok() -> Result { + Ok(true) +} + +#[allow(dead_code)] +fn test_owl_result() -> Result<(), ()> { + produce_half_owl_error().map_err(|_| ())?; + produce_half_owl_ok().map(|_| ())?; + // the following should not be linted, + // we should not force users to use toilet closures + // to produce owl results when drop is more convenient + produce_half_owl_error().map_err(drop)?; + produce_half_owl_ok().map_err(drop)?; + Ok(()) +} + +#[allow(dead_code)] +fn test_owl_result_2() -> Result { + produce_half_owl_error().map_err(|_| ())?; + produce_half_owl_ok().map(|_| ())?; + // the following should not be linted, + // we should not force users to use toilet closures + // to produce owl results when drop is more convenient + produce_half_owl_error().map_err(drop)?; + produce_half_owl_ok().map(drop)?; + Ok(1) +} diff --git a/src/tools/clippy/tests/ui/drop_ref.stderr b/src/tools/clippy/tests/ui/drop_ref.stderr new file mode 100644 index 0000000000..531849f068 --- /dev/null +++ b/src/tools/clippy/tests/ui/drop_ref.stderr @@ -0,0 +1,111 @@ +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing + --> $DIR/drop_ref.rs:11:5 + | +LL | drop(&SomeStruct); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::drop-ref` implied by `-D warnings` +note: argument has type `&SomeStruct` + --> $DIR/drop_ref.rs:11:10 + | +LL | drop(&SomeStruct); + | ^^^^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing + --> $DIR/drop_ref.rs:14:5 + | +LL | drop(&owned1); + | ^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/drop_ref.rs:14:10 + | +LL | drop(&owned1); + | ^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing + --> $DIR/drop_ref.rs:15:5 + | +LL | drop(&&owned1); + | ^^^^^^^^^^^^^^ + | +note: argument has type `&&SomeStruct` + --> $DIR/drop_ref.rs:15:10 + | +LL | drop(&&owned1); + | ^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing + --> $DIR/drop_ref.rs:16:5 + | +LL | drop(&mut owned1); + | ^^^^^^^^^^^^^^^^^ + | +note: argument has type `&mut SomeStruct` + --> $DIR/drop_ref.rs:16:10 + | +LL | drop(&mut owned1); + | ^^^^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing + --> $DIR/drop_ref.rs:20:5 + | +LL | drop(reference1); + | ^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/drop_ref.rs:20:10 + | +LL | drop(reference1); + | ^^^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing + --> $DIR/drop_ref.rs:23:5 + | +LL | drop(reference2); + | ^^^^^^^^^^^^^^^^ + | +note: argument has type `&mut SomeStruct` + --> $DIR/drop_ref.rs:23:10 + | +LL | drop(reference2); + | ^^^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing + --> $DIR/drop_ref.rs:26:5 + | +LL | drop(reference3); + | ^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/drop_ref.rs:26:10 + | +LL | drop(reference3); + | ^^^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing + --> $DIR/drop_ref.rs:31:5 + | +LL | drop(&val); + | ^^^^^^^^^^ + | +note: argument has type `&T` + --> $DIR/drop_ref.rs:31:10 + | +LL | drop(&val); + | ^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing + --> $DIR/drop_ref.rs:39:5 + | +LL | std::mem::drop(&SomeStruct); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/drop_ref.rs:39:20 + | +LL | std::mem::drop(&SomeStruct); + | ^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/duplicate_underscore_argument.rs b/src/tools/clippy/tests/ui/duplicate_underscore_argument.rs new file mode 100644 index 0000000000..54d748c7ce --- /dev/null +++ b/src/tools/clippy/tests/ui/duplicate_underscore_argument.rs @@ -0,0 +1,10 @@ +#![warn(clippy::duplicate_underscore_argument)] +#[allow(dead_code, unused)] + +fn join_the_dark_side(darth: i32, _darth: i32) {} +fn join_the_light_side(knight: i32, _master: i32) {} // the Force is strong with this one + +fn main() { + join_the_dark_side(0, 0); + join_the_light_side(0, 0); +} diff --git a/src/tools/clippy/tests/ui/duplicate_underscore_argument.stderr b/src/tools/clippy/tests/ui/duplicate_underscore_argument.stderr new file mode 100644 index 0000000000..f71614a5fd --- /dev/null +++ b/src/tools/clippy/tests/ui/duplicate_underscore_argument.stderr @@ -0,0 +1,10 @@ +error: `darth` already exists, having another argument having almost the same name makes code comprehension and documentation more difficult + --> $DIR/duplicate_underscore_argument.rs:4:23 + | +LL | fn join_the_dark_side(darth: i32, _darth: i32) {} + | ^^^^^ + | + = note: `-D clippy::duplicate-underscore-argument` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/duration_subsec.fixed b/src/tools/clippy/tests/ui/duration_subsec.fixed new file mode 100644 index 0000000000..ee5c7863ef --- /dev/null +++ b/src/tools/clippy/tests/ui/duration_subsec.fixed @@ -0,0 +1,29 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::duration_subsec)] + +use std::time::Duration; + +fn main() { + let dur = Duration::new(5, 0); + + let bad_millis_1 = dur.subsec_millis(); + let bad_millis_2 = dur.subsec_millis(); + let good_millis = dur.subsec_millis(); + assert_eq!(bad_millis_1, good_millis); + assert_eq!(bad_millis_2, good_millis); + + let bad_micros = dur.subsec_micros(); + let good_micros = dur.subsec_micros(); + assert_eq!(bad_micros, good_micros); + + // Handle refs + let _ = (&dur).subsec_micros(); + + // Handle constants + const NANOS_IN_MICRO: u32 = 1_000; + let _ = dur.subsec_micros(); + + // Other literals aren't linted + let _ = dur.subsec_nanos() / 699; +} diff --git a/src/tools/clippy/tests/ui/duration_subsec.rs b/src/tools/clippy/tests/ui/duration_subsec.rs new file mode 100644 index 0000000000..3c9d2a2862 --- /dev/null +++ b/src/tools/clippy/tests/ui/duration_subsec.rs @@ -0,0 +1,29 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::duration_subsec)] + +use std::time::Duration; + +fn main() { + let dur = Duration::new(5, 0); + + let bad_millis_1 = dur.subsec_micros() / 1_000; + let bad_millis_2 = dur.subsec_nanos() / 1_000_000; + let good_millis = dur.subsec_millis(); + assert_eq!(bad_millis_1, good_millis); + assert_eq!(bad_millis_2, good_millis); + + let bad_micros = dur.subsec_nanos() / 1_000; + let good_micros = dur.subsec_micros(); + assert_eq!(bad_micros, good_micros); + + // Handle refs + let _ = (&dur).subsec_nanos() / 1_000; + + // Handle constants + const NANOS_IN_MICRO: u32 = 1_000; + let _ = dur.subsec_nanos() / NANOS_IN_MICRO; + + // Other literals aren't linted + let _ = dur.subsec_nanos() / 699; +} diff --git a/src/tools/clippy/tests/ui/duration_subsec.stderr b/src/tools/clippy/tests/ui/duration_subsec.stderr new file mode 100644 index 0000000000..cdbeff6a03 --- /dev/null +++ b/src/tools/clippy/tests/ui/duration_subsec.stderr @@ -0,0 +1,34 @@ +error: calling `subsec_millis()` is more concise than this calculation + --> $DIR/duration_subsec.rs:10:24 + | +LL | let bad_millis_1 = dur.subsec_micros() / 1_000; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_millis()` + | + = note: `-D clippy::duration-subsec` implied by `-D warnings` + +error: calling `subsec_millis()` is more concise than this calculation + --> $DIR/duration_subsec.rs:11:24 + | +LL | let bad_millis_2 = dur.subsec_nanos() / 1_000_000; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_millis()` + +error: calling `subsec_micros()` is more concise than this calculation + --> $DIR/duration_subsec.rs:16:22 + | +LL | let bad_micros = dur.subsec_nanos() / 1_000; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_micros()` + +error: calling `subsec_micros()` is more concise than this calculation + --> $DIR/duration_subsec.rs:21:13 + | +LL | let _ = (&dur).subsec_nanos() / 1_000; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(&dur).subsec_micros()` + +error: calling `subsec_micros()` is more concise than this calculation + --> $DIR/duration_subsec.rs:25:13 + | +LL | let _ = dur.subsec_nanos() / NANOS_IN_MICRO; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_micros()` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/else_if_without_else.rs b/src/tools/clippy/tests/ui/else_if_without_else.rs new file mode 100644 index 0000000000..879b3ac398 --- /dev/null +++ b/src/tools/clippy/tests/ui/else_if_without_else.rs @@ -0,0 +1,58 @@ +#![warn(clippy::all)] +#![warn(clippy::else_if_without_else)] + +fn bla1() -> bool { + unimplemented!() +} +fn bla2() -> bool { + unimplemented!() +} +fn bla3() -> bool { + unimplemented!() +} + +fn main() { + if bla1() { + println!("if"); + } + + if bla1() { + println!("if"); + } else { + println!("else"); + } + + if bla1() { + println!("if"); + } else if bla2() { + println!("else if"); + } else { + println!("else") + } + + if bla1() { + println!("if"); + } else if bla2() { + println!("else if 1"); + } else if bla3() { + println!("else if 2"); + } else { + println!("else") + } + + if bla1() { + println!("if"); + } else if bla2() { + //~ ERROR else if without else + println!("else if"); + } + + if bla1() { + println!("if"); + } else if bla2() { + println!("else if 1"); + } else if bla3() { + //~ ERROR else if without else + println!("else if 2"); + } +} diff --git a/src/tools/clippy/tests/ui/else_if_without_else.stderr b/src/tools/clippy/tests/ui/else_if_without_else.stderr new file mode 100644 index 0000000000..6f47658cfb --- /dev/null +++ b/src/tools/clippy/tests/ui/else_if_without_else.stderr @@ -0,0 +1,27 @@ +error: `if` expression with an `else if`, but without a final `else` + --> $DIR/else_if_without_else.rs:45:12 + | +LL | } else if bla2() { + | ____________^ +LL | | //~ ERROR else if without else +LL | | println!("else if"); +LL | | } + | |_____^ + | + = note: `-D clippy::else-if-without-else` implied by `-D warnings` + = help: add an `else` block here + +error: `if` expression with an `else if`, but without a final `else` + --> $DIR/else_if_without_else.rs:54:12 + | +LL | } else if bla3() { + | ____________^ +LL | | //~ ERROR else if without else +LL | | println!("else if 2"); +LL | | } + | |_____^ + | + = help: add an `else` block here + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/empty_enum.rs b/src/tools/clippy/tests/ui/empty_enum.rs new file mode 100644 index 0000000000..a2e5c13c45 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_enum.rs @@ -0,0 +1,7 @@ +#![allow(dead_code)] +#![warn(clippy::empty_enum)] +// Enable never type to test empty enum lint +#![feature(never_type)] +enum Empty {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_enum.stderr b/src/tools/clippy/tests/ui/empty_enum.stderr new file mode 100644 index 0000000000..7125e5f602 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_enum.stderr @@ -0,0 +1,11 @@ +error: enum with no variants + --> $DIR/empty_enum.rs:5:1 + | +LL | enum Empty {} + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::empty-enum` implied by `-D warnings` + = help: consider using the uninhabited type `!` (never type) or a wrapper around it to introduce a type which can't be instantiated + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/empty_enum_without_never_type.rs b/src/tools/clippy/tests/ui/empty_enum_without_never_type.rs new file mode 100644 index 0000000000..386677352e --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_enum_without_never_type.rs @@ -0,0 +1,7 @@ +#![allow(dead_code)] +#![warn(clippy::empty_enum)] + +// `never_type` is not enabled; this test has no stderr file +enum Empty {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.rs b/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.rs new file mode 100644 index 0000000000..3e92bca986 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.rs @@ -0,0 +1,113 @@ +// aux-build:proc_macro_attr.rs +#![warn(clippy::empty_line_after_outer_attr)] +#![allow(clippy::assertions_on_constants)] +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +#[macro_use] +extern crate proc_macro_attr; + +// This should produce a warning +#[crate_type = "lib"] + +/// some comment +fn with_one_newline_and_comment() { assert!(true) } + +// This should not produce a warning +#[crate_type = "lib"] +/// some comment +fn with_no_newline_and_comment() { assert!(true) } + + +// This should produce a warning +#[crate_type = "lib"] + +fn with_one_newline() { assert!(true) } + +// This should produce a warning, too +#[crate_type = "lib"] + + +fn with_two_newlines() { assert!(true) } + + +// This should produce a warning +#[crate_type = "lib"] + +enum Baz { + One, + Two +} + +// This should produce a warning +#[crate_type = "lib"] + +struct Foo { + one: isize, + two: isize +} + +// This should produce a warning +#[crate_type = "lib"] + +mod foo { +} + +/// This doc comment should not produce a warning + +/** This is also a doc comment and should not produce a warning + */ + +// This should not produce a warning +#[allow(non_camel_case_types)] +#[allow(missing_docs)] +#[allow(missing_docs)] +fn three_attributes() { assert!(true) } + +// This should not produce a warning +#[doc = " +Returns the escaped value of the textual representation of + +"] +pub fn function() -> bool { + true +} + +// This should not produce a warning +#[derive(Clone, Copy)] +pub enum FooFighter { + Bar1, + + Bar2, + + Bar3, + + Bar4 +} + +// This should not produce a warning because the empty line is inside a block comment +#[crate_type = "lib"] +/* + +*/ +pub struct S; + +// This should not produce a warning +#[crate_type = "lib"] +/* test */ +pub struct T; + +// This should not produce a warning +// See https://github.com/rust-lang/rust-clippy/issues/5567 +#[fake_async_trait] +pub trait Bazz { + fn foo() -> Vec { + let _i = ""; + + + + vec![] + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.stderr b/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.stderr new file mode 100644 index 0000000000..594fca44a3 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.stderr @@ -0,0 +1,54 @@ +error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:11:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | /// some comment +LL | | fn with_one_newline_and_comment() { assert!(true) } + | |_ + | + = note: `-D clippy::empty-line-after-outer-attr` implied by `-D warnings` + +error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:23:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | fn with_one_newline() { assert!(true) } + | |_ + +error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:28:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | +LL | | fn with_two_newlines() { assert!(true) } + | |_ + +error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:35:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | enum Baz { + | |_ + +error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:43:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | struct Foo { + | |_ + +error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:51:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | mod foo { + | |_ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/empty_loop.rs b/src/tools/clippy/tests/ui/empty_loop.rs new file mode 100644 index 0000000000..8fd7697eb3 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_loop.rs @@ -0,0 +1,51 @@ +// aux-build:macro_rules.rs + +#![warn(clippy::empty_loop)] + +#[macro_use] +extern crate macro_rules; + +fn should_trigger() { + loop {} + loop { + loop {} + } + + 'outer: loop { + 'inner: loop {} + } +} + +fn should_not_trigger() { + loop { + panic!("This is fine") + } + let ten_millis = std::time::Duration::from_millis(10); + loop { + std::thread::sleep(ten_millis) + } + + #[allow(clippy::never_loop)] + 'outer: loop { + 'inner: loop { + break 'inner; + } + break 'outer; + } + + // Make sure `allow` works for this lint + #[allow(clippy::empty_loop)] + loop {} + + // We don't lint loops inside macros + macro_rules! foo { + () => { + loop {} + }; + } + + // We don't lint external macros + foofoo!() +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_loop.stderr b/src/tools/clippy/tests/ui/empty_loop.stderr new file mode 100644 index 0000000000..555f3d3d88 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_loop.stderr @@ -0,0 +1,27 @@ +error: empty `loop {}` wastes CPU cycles + --> $DIR/empty_loop.rs:9:5 + | +LL | loop {} + | ^^^^^^^ + | + = note: `-D clippy::empty-loop` implied by `-D warnings` + = help: you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body + +error: empty `loop {}` wastes CPU cycles + --> $DIR/empty_loop.rs:11:9 + | +LL | loop {} + | ^^^^^^^ + | + = help: you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body + +error: empty `loop {}` wastes CPU cycles + --> $DIR/empty_loop.rs:15:9 + | +LL | 'inner: loop {} + | ^^^^^^^^^^^^^^^ + | + = help: you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/empty_loop_no_std.rs b/src/tools/clippy/tests/ui/empty_loop_no_std.rs new file mode 100644 index 0000000000..4553d3ec50 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_loop_no_std.rs @@ -0,0 +1,27 @@ +// ignore-macos +// ignore-windows + +#![warn(clippy::empty_loop)] +#![feature(lang_items, link_args, start, libc)] +#![link_args = "-nostartfiles"] +#![no_std] + +use core::panic::PanicInfo; + +#[start] +fn main(argc: isize, argv: *const *const u8) -> isize { + // This should trigger the lint + loop {} +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + // This should NOT trigger the lint + loop {} +} + +#[lang = "eh_personality"] +extern "C" fn eh_personality() { + // This should also trigger the lint + loop {} +} diff --git a/src/tools/clippy/tests/ui/empty_loop_no_std.stderr b/src/tools/clippy/tests/ui/empty_loop_no_std.stderr new file mode 100644 index 0000000000..520248fcb6 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_loop_no_std.stderr @@ -0,0 +1,19 @@ +error: empty `loop {}` wastes CPU cycles + --> $DIR/empty_loop_no_std.rs:14:5 + | +LL | loop {} + | ^^^^^^^ + | + = note: `-D clippy::empty-loop` implied by `-D warnings` + = help: you should either use `panic!()` or add a call pausing or sleeping the thread to the loop body + +error: empty `loop {}` wastes CPU cycles + --> $DIR/empty_loop_no_std.rs:26:5 + | +LL | loop {} + | ^^^^^^^ + | + = help: you should either use `panic!()` or add a call pausing or sleeping the thread to the loop body + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/entry_fixable.fixed b/src/tools/clippy/tests/ui/entry_fixable.fixed new file mode 100644 index 0000000000..dcdaae7e72 --- /dev/null +++ b/src/tools/clippy/tests/ui/entry_fixable.fixed @@ -0,0 +1,15 @@ +// run-rustfix + +#![allow(unused, clippy::needless_pass_by_value)] +#![warn(clippy::map_entry)] + +use std::collections::{BTreeMap, HashMap}; +use std::hash::Hash; + +fn foo() {} + +fn insert_if_absent0(m: &mut HashMap, k: K, v: V) { + m.entry(k).or_insert(v); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/entry_fixable.rs b/src/tools/clippy/tests/ui/entry_fixable.rs new file mode 100644 index 0000000000..55d5b21568 --- /dev/null +++ b/src/tools/clippy/tests/ui/entry_fixable.rs @@ -0,0 +1,17 @@ +// run-rustfix + +#![allow(unused, clippy::needless_pass_by_value)] +#![warn(clippy::map_entry)] + +use std::collections::{BTreeMap, HashMap}; +use std::hash::Hash; + +fn foo() {} + +fn insert_if_absent0(m: &mut HashMap, k: K, v: V) { + if !m.contains_key(&k) { + m.insert(k, v); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/entry_fixable.stderr b/src/tools/clippy/tests/ui/entry_fixable.stderr new file mode 100644 index 0000000000..87403200ce --- /dev/null +++ b/src/tools/clippy/tests/ui/entry_fixable.stderr @@ -0,0 +1,12 @@ +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> $DIR/entry_fixable.rs:12:5 + | +LL | / if !m.contains_key(&k) { +LL | | m.insert(k, v); +LL | | } + | |_____^ help: consider using: `m.entry(k).or_insert(v);` + | + = note: `-D clippy::map-entry` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/entry_unfixable.rs b/src/tools/clippy/tests/ui/entry_unfixable.rs new file mode 100644 index 0000000000..f530fc023c --- /dev/null +++ b/src/tools/clippy/tests/ui/entry_unfixable.rs @@ -0,0 +1,73 @@ +#![allow(unused, clippy::needless_pass_by_value)] +#![warn(clippy::map_entry)] + +use std::collections::{BTreeMap, HashMap}; +use std::hash::Hash; + +fn foo() {} + +fn insert_if_absent2(m: &mut HashMap, k: K, v: V) { + if !m.contains_key(&k) { + m.insert(k, v) + } else { + None + }; +} + +fn insert_if_present2(m: &mut HashMap, k: K, v: V) { + if m.contains_key(&k) { + None + } else { + m.insert(k, v) + }; +} + +fn insert_if_absent3(m: &mut HashMap, k: K, v: V) { + if !m.contains_key(&k) { + foo(); + m.insert(k, v) + } else { + None + }; +} + +fn insert_if_present3(m: &mut HashMap, k: K, v: V) { + if m.contains_key(&k) { + None + } else { + foo(); + m.insert(k, v) + }; +} + +fn insert_in_btreemap(m: &mut BTreeMap, k: K, v: V) { + if !m.contains_key(&k) { + foo(); + m.insert(k, v) + } else { + None + }; +} + +// should not trigger +fn insert_other_if_absent(m: &mut HashMap, k: K, o: K, v: V) { + if !m.contains_key(&k) { + m.insert(o, v); + } +} + +// should not trigger, because the one uses different HashMap from another one +fn insert_from_different_map(m: HashMap, n: &mut HashMap, k: K, v: V) { + if !m.contains_key(&k) { + n.insert(k, v); + } +} + +// should not trigger, because the one uses different HashMap from another one +fn insert_from_different_map2(m: &mut HashMap, n: &mut HashMap, k: K, v: V) { + if !m.contains_key(&k) { + n.insert(k, v); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/entry_unfixable.stderr b/src/tools/clippy/tests/ui/entry_unfixable.stderr new file mode 100644 index 0000000000..e58c8d22dc --- /dev/null +++ b/src/tools/clippy/tests/ui/entry_unfixable.stderr @@ -0,0 +1,57 @@ +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> $DIR/entry_unfixable.rs:10:5 + | +LL | / if !m.contains_key(&k) { +LL | | m.insert(k, v) +LL | | } else { +LL | | None +LL | | }; + | |_____^ consider using `m.entry(k)` + | + = note: `-D clippy::map-entry` implied by `-D warnings` + +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> $DIR/entry_unfixable.rs:18:5 + | +LL | / if m.contains_key(&k) { +LL | | None +LL | | } else { +LL | | m.insert(k, v) +LL | | }; + | |_____^ consider using `m.entry(k)` + +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> $DIR/entry_unfixable.rs:26:5 + | +LL | / if !m.contains_key(&k) { +LL | | foo(); +LL | | m.insert(k, v) +LL | | } else { +LL | | None +LL | | }; + | |_____^ consider using `m.entry(k)` + +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> $DIR/entry_unfixable.rs:35:5 + | +LL | / if m.contains_key(&k) { +LL | | None +LL | | } else { +LL | | foo(); +LL | | m.insert(k, v) +LL | | }; + | |_____^ consider using `m.entry(k)` + +error: usage of `contains_key` followed by `insert` on a `BTreeMap` + --> $DIR/entry_unfixable.rs:44:5 + | +LL | / if !m.contains_key(&k) { +LL | | foo(); +LL | | m.insert(k, v) +LL | | } else { +LL | | None +LL | | }; + | |_____^ consider using `m.entry(k)` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/enum_clike_unportable_variant.rs b/src/tools/clippy/tests/ui/enum_clike_unportable_variant.rs new file mode 100644 index 0000000000..7d6842f5b5 --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_clike_unportable_variant.rs @@ -0,0 +1,50 @@ +// ignore-x86 + +#![warn(clippy::enum_clike_unportable_variant)] +#![allow(unused, non_upper_case_globals)] + +#[repr(usize)] +enum NonPortable { + X = 0x1_0000_0000, + Y = 0, + Z = 0x7FFF_FFFF, + A = 0xFFFF_FFFF, +} + +enum NonPortableNoHint { + X = 0x1_0000_0000, + Y = 0, + Z = 0x7FFF_FFFF, + A = 0xFFFF_FFFF, +} + +#[repr(isize)] +enum NonPortableSigned { + X = -1, + Y = 0x7FFF_FFFF, + Z = 0xFFFF_FFFF, + A = 0x1_0000_0000, + B = i32::MIN as isize, + C = (i32::MIN as isize) - 1, +} + +enum NonPortableSignedNoHint { + X = -1, + Y = 0x7FFF_FFFF, + Z = 0xFFFF_FFFF, + A = 0x1_0000_0000, +} + +#[repr(usize)] +enum NonPortable2 { + X = ::Number, + Y = 0, +} + +trait Trait { + const Number: usize = 0x1_0000_0000; +} + +impl Trait for usize {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/enum_clike_unportable_variant.stderr b/src/tools/clippy/tests/ui/enum_clike_unportable_variant.stderr new file mode 100644 index 0000000000..5935eea5e0 --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_clike_unportable_variant.stderr @@ -0,0 +1,58 @@ +error: C-like enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:8:5 + | +LL | X = 0x1_0000_0000, + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::enum-clike-unportable-variant` implied by `-D warnings` + +error: C-like enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:15:5 + | +LL | X = 0x1_0000_0000, + | ^^^^^^^^^^^^^^^^^ + +error: C-like enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:18:5 + | +LL | A = 0xFFFF_FFFF, + | ^^^^^^^^^^^^^^^ + +error: C-like enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:25:5 + | +LL | Z = 0xFFFF_FFFF, + | ^^^^^^^^^^^^^^^ + +error: C-like enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:26:5 + | +LL | A = 0x1_0000_0000, + | ^^^^^^^^^^^^^^^^^ + +error: C-like enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:28:5 + | +LL | C = (i32::MIN as isize) - 1, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: C-like enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:34:5 + | +LL | Z = 0xFFFF_FFFF, + | ^^^^^^^^^^^^^^^ + +error: C-like enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:35:5 + | +LL | A = 0x1_0000_0000, + | ^^^^^^^^^^^^^^^^^ + +error: C-like enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:40:5 + | +LL | X = ::Number, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/enum_glob_use.fixed b/src/tools/clippy/tests/ui/enum_glob_use.fixed new file mode 100644 index 0000000000..a98216758b --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_glob_use.fixed @@ -0,0 +1,30 @@ +// run-rustfix + +#![warn(clippy::enum_glob_use)] +#![allow(unused)] +#![warn(unused_imports)] + +use std::cmp::Ordering::Less; + +enum Enum { + Foo, +} + +use self::Enum::Foo; + +mod in_fn_test { + fn blarg() { + use crate::Enum::Foo; + + let _ = Foo; + } +} + +mod blurg { + pub use std::cmp::Ordering::*; // ok, re-export +} + +fn main() { + let _ = Foo; + let _ = Less; +} diff --git a/src/tools/clippy/tests/ui/enum_glob_use.rs b/src/tools/clippy/tests/ui/enum_glob_use.rs new file mode 100644 index 0000000000..5d929c9731 --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_glob_use.rs @@ -0,0 +1,30 @@ +// run-rustfix + +#![warn(clippy::enum_glob_use)] +#![allow(unused)] +#![warn(unused_imports)] + +use std::cmp::Ordering::*; + +enum Enum { + Foo, +} + +use self::Enum::*; + +mod in_fn_test { + fn blarg() { + use crate::Enum::*; + + let _ = Foo; + } +} + +mod blurg { + pub use std::cmp::Ordering::*; // ok, re-export +} + +fn main() { + let _ = Foo; + let _ = Less; +} diff --git a/src/tools/clippy/tests/ui/enum_glob_use.stderr b/src/tools/clippy/tests/ui/enum_glob_use.stderr new file mode 100644 index 0000000000..69531aed39 --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_glob_use.stderr @@ -0,0 +1,22 @@ +error: usage of wildcard import for enum variants + --> $DIR/enum_glob_use.rs:7:5 + | +LL | use std::cmp::Ordering::*; + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `std::cmp::Ordering::Less` + | + = note: `-D clippy::enum-glob-use` implied by `-D warnings` + +error: usage of wildcard import for enum variants + --> $DIR/enum_glob_use.rs:13:5 + | +LL | use self::Enum::*; + | ^^^^^^^^^^^^^ help: try: `self::Enum::Foo` + +error: usage of wildcard import for enum variants + --> $DIR/enum_glob_use.rs:17:13 + | +LL | use crate::Enum::*; + | ^^^^^^^^^^^^^^ help: try: `crate::Enum::Foo` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/enum_variants.rs b/src/tools/clippy/tests/ui/enum_variants.rs new file mode 100644 index 0000000000..4fefc0b43f --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_variants.rs @@ -0,0 +1,149 @@ +#![feature(non_ascii_idents)] +#![warn(clippy::enum_variant_names, clippy::pub_enum_variant_names)] +#![allow(non_camel_case_types, clippy::upper_case_acronyms)] + +enum FakeCallType { + CALL, + CREATE, +} + +enum FakeCallType2 { + CALL, + CREATELL, +} + +enum Foo { + cFoo, + cBar, + cBaz, +} + +enum Fooo { + cFoo, // no error, threshold is 3 variants by default + cBar, +} + +enum Food { + FoodGood, + FoodMiddle, + FoodBad, +} + +enum Stuff { + StuffBad, // no error +} + +enum BadCallType { + CallTypeCall, + CallTypeCreate, + CallTypeDestroy, +} + +enum TwoCallType { + // no error + CallTypeCall, + CallTypeCreate, +} + +enum Consts { + ConstantInt, + ConstantCake, + ConstantLie, +} + +enum Two { + // no error here + ConstantInt, + ConstantInfer, +} + +enum Something { + CCall, + CCreate, + CCryogenize, +} + +enum Seal { + With, + Without, +} + +enum Seall { + With, + WithOut, + Withbroken, +} + +enum Sealll { + With, + WithOut, +} + +enum Seallll { + WithOutCake, + WithOutTea, + WithOut, +} + +enum NonCaps { + Prefix的, + PrefixTea, + PrefixCake, +} + +pub enum PubSeall { + WithOutCake, + WithOutTea, + WithOut, +} + +#[allow(clippy::pub_enum_variant_names)] +mod allowed { + pub enum PubAllowed { + SomeThis, + SomeThat, + SomeOtherWhat, + } +} + +// should not lint +enum Pat { + Foo, + Bar, + Path, +} + +// should not lint +enum N { + Pos, + Neg, + Float, +} + +// should not lint +enum Peek { + Peek1, + Peek2, + Peek3, +} + +// should not lint +pub enum NetworkLayer { + Layer2, + Layer3, +} + +// should lint suggesting `IData`, not only `Data` (see #4639) +enum IDataRequest { + PutIData(String), + GetIData(String), + DeleteUnpubIData(String), +} + +enum HIDataRequest { + PutHIData(String), + GetHIData(String), + DeleteUnpubHIData(String), +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/enum_variants.stderr b/src/tools/clippy/tests/ui/enum_variants.stderr new file mode 100644 index 0000000000..ab7fff4507 --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_variants.stderr @@ -0,0 +1,125 @@ +error: variant name ends with the enum's name + --> $DIR/enum_variants.rs:16:5 + | +LL | cFoo, + | ^^^^ + | + = note: `-D clippy::enum-variant-names` implied by `-D warnings` + +error: variant name starts with the enum's name + --> $DIR/enum_variants.rs:27:5 + | +LL | FoodGood, + | ^^^^^^^^ + +error: variant name starts with the enum's name + --> $DIR/enum_variants.rs:28:5 + | +LL | FoodMiddle, + | ^^^^^^^^^^ + +error: variant name starts with the enum's name + --> $DIR/enum_variants.rs:29:5 + | +LL | FoodBad, + | ^^^^^^^ + +error: all variants have the same prefix: `Food` + --> $DIR/enum_variants.rs:26:1 + | +LL | / enum Food { +LL | | FoodGood, +LL | | FoodMiddle, +LL | | FoodBad, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: all variants have the same prefix: `CallType` + --> $DIR/enum_variants.rs:36:1 + | +LL | / enum BadCallType { +LL | | CallTypeCall, +LL | | CallTypeCreate, +LL | | CallTypeDestroy, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: all variants have the same prefix: `Constant` + --> $DIR/enum_variants.rs:48:1 + | +LL | / enum Consts { +LL | | ConstantInt, +LL | | ConstantCake, +LL | | ConstantLie, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: all variants have the same prefix: `With` + --> $DIR/enum_variants.rs:82:1 + | +LL | / enum Seallll { +LL | | WithOutCake, +LL | | WithOutTea, +LL | | WithOut, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: all variants have the same prefix: `Prefix` + --> $DIR/enum_variants.rs:88:1 + | +LL | / enum NonCaps { +LL | | Prefix的, +LL | | PrefixTea, +LL | | PrefixCake, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: all variants have the same prefix: `With` + --> $DIR/enum_variants.rs:94:1 + | +LL | / pub enum PubSeall { +LL | | WithOutCake, +LL | | WithOutTea, +LL | | WithOut, +LL | | } + | |_^ + | + = note: `-D clippy::pub-enum-variant-names` implied by `-D warnings` + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: all variants have the same postfix: `IData` + --> $DIR/enum_variants.rs:137:1 + | +LL | / enum IDataRequest { +LL | | PutIData(String), +LL | | GetIData(String), +LL | | DeleteUnpubIData(String), +LL | | } + | |_^ + | + = help: remove the postfixes and use full paths to the variants instead of glob imports + +error: all variants have the same postfix: `HIData` + --> $DIR/enum_variants.rs:143:1 + | +LL | / enum HIDataRequest { +LL | | PutHIData(String), +LL | | GetHIData(String), +LL | | DeleteUnpubHIData(String), +LL | | } + | |_^ + | + = help: remove the postfixes and use full paths to the variants instead of glob imports + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/eprint_with_newline.rs b/src/tools/clippy/tests/ui/eprint_with_newline.rs new file mode 100644 index 0000000000..8df32649ad --- /dev/null +++ b/src/tools/clippy/tests/ui/eprint_with_newline.rs @@ -0,0 +1,49 @@ +#![allow(clippy::print_literal)] +#![warn(clippy::print_with_newline)] + +fn main() { + eprint!("Hello\n"); + eprint!("Hello {}\n", "world"); + eprint!("Hello {} {}\n", "world", "#2"); + eprint!("{}\n", 1265); + eprint!("\n"); + + // these are all fine + eprint!(""); + eprint!("Hello"); + eprintln!("Hello"); + eprintln!("Hello\n"); + eprintln!("Hello {}\n", "world"); + eprint!("Issue\n{}", 1265); + eprint!("{}", 1265); + eprint!("\n{}", 1275); + eprint!("\n\n"); + eprint!("like eof\n\n"); + eprint!("Hello {} {}\n\n", "world", "#2"); + eprintln!("\ndon't\nwarn\nfor\nmultiple\nnewlines\n"); // #3126 + eprintln!("\nbla\n\n"); // #3126 + + // Escaping + eprint!("\\n"); // #3514 + eprint!("\\\n"); // should fail + eprint!("\\\\n"); + + // Raw strings + eprint!(r"\n"); // #3778 + + // Literal newlines should also fail + eprint!( + " +" + ); + eprint!( + r" +" + ); + + // Don't warn on CRLF (#4208) + eprint!("\r\n"); + eprint!("foo\r\n"); + eprint!("\\r\n"); //~ ERROR + eprint!("foo\rbar\n") // ~ ERROR +} diff --git a/src/tools/clippy/tests/ui/eprint_with_newline.stderr b/src/tools/clippy/tests/ui/eprint_with_newline.stderr new file mode 100644 index 0000000000..31811d1d92 --- /dev/null +++ b/src/tools/clippy/tests/ui/eprint_with_newline.stderr @@ -0,0 +1,121 @@ +error: using `eprint!()` with a format string that ends in a single newline + --> $DIR/eprint_with_newline.rs:5:5 + | +LL | eprint!("Hello/n"); + | ^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::print-with-newline` implied by `-D warnings` +help: use `eprintln!` instead + | +LL | eprintln!("Hello"); + | ^^^^^^^^ -- + +error: using `eprint!()` with a format string that ends in a single newline + --> $DIR/eprint_with_newline.rs:6:5 + | +LL | eprint!("Hello {}/n", "world"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `eprintln!` instead + | +LL | eprintln!("Hello {}", "world"); + | ^^^^^^^^ -- + +error: using `eprint!()` with a format string that ends in a single newline + --> $DIR/eprint_with_newline.rs:7:5 + | +LL | eprint!("Hello {} {}/n", "world", "#2"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `eprintln!` instead + | +LL | eprintln!("Hello {} {}", "world", "#2"); + | ^^^^^^^^ -- + +error: using `eprint!()` with a format string that ends in a single newline + --> $DIR/eprint_with_newline.rs:8:5 + | +LL | eprint!("{}/n", 1265); + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: use `eprintln!` instead + | +LL | eprintln!("{}", 1265); + | ^^^^^^^^ -- + +error: using `eprint!()` with a format string that ends in a single newline + --> $DIR/eprint_with_newline.rs:9:5 + | +LL | eprint!("/n"); + | ^^^^^^^^^^^^^ + | +help: use `eprintln!` instead + | +LL | eprintln!(); + | ^^^^^^^^ -- + +error: using `eprint!()` with a format string that ends in a single newline + --> $DIR/eprint_with_newline.rs:28:5 + | +LL | eprint!("//n"); // should fail + | ^^^^^^^^^^^^^^^ + | +help: use `eprintln!` instead + | +LL | eprintln!("/"); // should fail + | ^^^^^^^^ -- + +error: using `eprint!()` with a format string that ends in a single newline + --> $DIR/eprint_with_newline.rs:35:5 + | +LL | / eprint!( +LL | | " +LL | | " +LL | | ); + | |_____^ + | +help: use `eprintln!` instead + | +LL | eprintln!( +LL | "" + | + +error: using `eprint!()` with a format string that ends in a single newline + --> $DIR/eprint_with_newline.rs:39:5 + | +LL | / eprint!( +LL | | r" +LL | | " +LL | | ); + | |_____^ + | +help: use `eprintln!` instead + | +LL | eprintln!( +LL | r"" + | + +error: using `eprint!()` with a format string that ends in a single newline + --> $DIR/eprint_with_newline.rs:47:5 + | +LL | eprint!("/r/n"); //~ ERROR + | ^^^^^^^^^^^^^^^^ + | +help: use `eprintln!` instead + | +LL | eprintln!("/r"); //~ ERROR + | ^^^^^^^^ -- + +error: using `eprint!()` with a format string that ends in a single newline + --> $DIR/eprint_with_newline.rs:48:5 + | +LL | eprint!("foo/rbar/n") // ~ ERROR + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: use `eprintln!` instead + | +LL | eprintln!("foo/rbar") // ~ ERROR + | ^^^^^^^^ -- + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/eq_op.rs b/src/tools/clippy/tests/ui/eq_op.rs new file mode 100644 index 0000000000..7ab23320db --- /dev/null +++ b/src/tools/clippy/tests/ui/eq_op.rs @@ -0,0 +1,97 @@ +// does not test any rustfixable lints + +#[rustfmt::skip] +#[warn(clippy::eq_op)] +#[allow(clippy::identity_op, clippy::double_parens, clippy::many_single_char_names)] +#[allow(clippy::no_effect, unused_variables, clippy::unnecessary_operation, clippy::short_circuit_statement)] +#[allow(clippy::nonminimal_bool)] +#[allow(unused)] +#[allow(clippy::unnecessary_cast)] +fn main() { + // simple values and comparisons + 1 == 1; + "no" == "no"; + // even though I agree that no means no ;-) + false != false; + 1.5 < 1.5; + 1u64 >= 1u64; + + // casts, methods, parentheses + (1 as u64) & (1 as u64); + 1 ^ ((((((1)))))); + + // unary and binary operators + (-(2) < -(2)); + ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4; + + // various other things + ([1] != [1]); + ((1, 2) != (1, 2)); + vec![1, 2, 3] == vec![1, 2, 3]; //no error yet, as we don't match macros + + // const folding + 1 + 1 == 2; + 1 - 1 == 0; + + 1 - 1; + 1 / 1; + true && true; + + true || true; + + + let a: u32 = 0; + let b: u32 = 0; + + a == b && b == a; + a != b && b != a; + a < b && b > a; + a <= b && b >= a; + + let mut a = vec![1]; + a == a; + 2*a.len() == 2*a.len(); // ok, functions + a.pop() == a.pop(); // ok, functions + + check_ignore_macro(); + + // named constants + const A: u32 = 10; + const B: u32 = 10; + const C: u32 = A / B; // ok, different named constants + const D: u32 = A / A; +} + +#[rustfmt::skip] +macro_rules! check_if_named_foo { + ($expression:expr) => ( + if stringify!($expression) == "foo" { + println!("foo!"); + } else { + println!("not foo."); + } + ) +} + +macro_rules! bool_macro { + ($expression:expr) => { + true + }; +} + +#[allow(clippy::short_circuit_statement)] +fn check_ignore_macro() { + check_if_named_foo!(foo); + // checks if the lint ignores macros with `!` operator + !bool_macro!(1) && !bool_macro!(""); +} + +struct Nested { + inner: ((i32,), (i32,), (i32,)), +} + +fn check_nested(n1: &Nested, n2: &Nested) -> bool { + // `n2.inner.0.0` mistyped as `n1.inner.0.0` + (n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0 +} diff --git a/src/tools/clippy/tests/ui/eq_op.stderr b/src/tools/clippy/tests/ui/eq_op.stderr new file mode 100644 index 0000000000..8ef658af8d --- /dev/null +++ b/src/tools/clippy/tests/ui/eq_op.stderr @@ -0,0 +1,174 @@ +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:12:5 + | +LL | 1 == 1; + | ^^^^^^ + | + = note: `-D clippy::eq-op` implied by `-D warnings` + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:13:5 + | +LL | "no" == "no"; + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `!=` + --> $DIR/eq_op.rs:15:5 + | +LL | false != false; + | ^^^^^^^^^^^^^^ + +error: equal expressions as operands to `<` + --> $DIR/eq_op.rs:16:5 + | +LL | 1.5 < 1.5; + | ^^^^^^^^^ + +error: equal expressions as operands to `>=` + --> $DIR/eq_op.rs:17:5 + | +LL | 1u64 >= 1u64; + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `&` + --> $DIR/eq_op.rs:20:5 + | +LL | (1 as u64) & (1 as u64); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `^` + --> $DIR/eq_op.rs:21:5 + | +LL | 1 ^ ((((((1)))))); + | ^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `<` + --> $DIR/eq_op.rs:24:5 + | +LL | (-(2) < -(2)); + | ^^^^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:25:5 + | +LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `&` + --> $DIR/eq_op.rs:25:6 + | +LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + | ^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `&` + --> $DIR/eq_op.rs:25:27 + | +LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + | ^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:26:5 + | +LL | (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `!=` + --> $DIR/eq_op.rs:29:5 + | +LL | ([1] != [1]); + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `!=` + --> $DIR/eq_op.rs:30:5 + | +LL | ((1, 2) != (1, 2)); + | ^^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:34:5 + | +LL | 1 + 1 == 2; + | ^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:35:5 + | +LL | 1 - 1 == 0; + | ^^^^^^^^^^ + +error: equal expressions as operands to `-` + --> $DIR/eq_op.rs:35:5 + | +LL | 1 - 1 == 0; + | ^^^^^ + +error: equal expressions as operands to `-` + --> $DIR/eq_op.rs:37:5 + | +LL | 1 - 1; + | ^^^^^ + +error: equal expressions as operands to `/` + --> $DIR/eq_op.rs:38:5 + | +LL | 1 / 1; + | ^^^^^ + +error: equal expressions as operands to `&&` + --> $DIR/eq_op.rs:39:5 + | +LL | true && true; + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `||` + --> $DIR/eq_op.rs:41:5 + | +LL | true || true; + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `&&` + --> $DIR/eq_op.rs:47:5 + | +LL | a == b && b == a; + | ^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `&&` + --> $DIR/eq_op.rs:48:5 + | +LL | a != b && b != a; + | ^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `&&` + --> $DIR/eq_op.rs:49:5 + | +LL | a < b && b > a; + | ^^^^^^^^^^^^^^ + +error: equal expressions as operands to `&&` + --> $DIR/eq_op.rs:50:5 + | +LL | a <= b && b >= a; + | ^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:53:5 + | +LL | a == a; + | ^^^^^^ + +error: equal expressions as operands to `/` + --> $DIR/eq_op.rs:63:20 + | +LL | const D: u32 = A / A; + | ^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:96:5 + | +LL | (n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::eq_op)]` on by default + +error: aborting due to 28 previous errors + diff --git a/src/tools/clippy/tests/ui/eq_op_macros.rs b/src/tools/clippy/tests/ui/eq_op_macros.rs new file mode 100644 index 0000000000..6b5b31a1a2 --- /dev/null +++ b/src/tools/clippy/tests/ui/eq_op_macros.rs @@ -0,0 +1,56 @@ +#![warn(clippy::eq_op)] + +// lint also in macro definition +macro_rules! assert_in_macro_def { + () => { + let a = 42; + assert_eq!(a, a); + assert_ne!(a, a); + debug_assert_eq!(a, a); + debug_assert_ne!(a, a); + }; +} + +// lint identical args in assert-like macro invocations (see #3574) +fn main() { + assert_in_macro_def!(); + + let a = 1; + let b = 2; + + // lint identical args in `assert_eq!` + assert_eq!(a, a); + assert_eq!(a + 1, a + 1); + // ok + assert_eq!(a, b); + assert_eq!(a, a + 1); + assert_eq!(a + 1, b + 1); + + // lint identical args in `assert_ne!` + assert_ne!(a, a); + assert_ne!(a + 1, a + 1); + // ok + assert_ne!(a, b); + assert_ne!(a, a + 1); + assert_ne!(a + 1, b + 1); + + // lint identical args in `debug_assert_eq!` + debug_assert_eq!(a, a); + debug_assert_eq!(a + 1, a + 1); + // ok + debug_assert_eq!(a, b); + debug_assert_eq!(a, a + 1); + debug_assert_eq!(a + 1, b + 1); + + // lint identical args in `debug_assert_ne!` + debug_assert_ne!(a, a); + debug_assert_ne!(a + 1, a + 1); + // ok + debug_assert_ne!(a, b); + debug_assert_ne!(a, a + 1); + debug_assert_ne!(a + 1, b + 1); + + let my_vec = vec![1; 5]; + let mut my_iter = my_vec.iter(); + assert_ne!(my_iter.next(), my_iter.next()); +} diff --git a/src/tools/clippy/tests/ui/eq_op_macros.stderr b/src/tools/clippy/tests/ui/eq_op_macros.stderr new file mode 100644 index 0000000000..fb9378108b --- /dev/null +++ b/src/tools/clippy/tests/ui/eq_op_macros.stderr @@ -0,0 +1,95 @@ +error: identical args used in this `assert_eq!` macro call + --> $DIR/eq_op_macros.rs:7:20 + | +LL | assert_eq!(a, a); + | ^^^^ +... +LL | assert_in_macro_def!(); + | ----------------------- in this macro invocation + | + = note: `-D clippy::eq-op` implied by `-D warnings` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: identical args used in this `assert_ne!` macro call + --> $DIR/eq_op_macros.rs:8:20 + | +LL | assert_ne!(a, a); + | ^^^^ +... +LL | assert_in_macro_def!(); + | ----------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: identical args used in this `assert_eq!` macro call + --> $DIR/eq_op_macros.rs:22:16 + | +LL | assert_eq!(a, a); + | ^^^^ + +error: identical args used in this `assert_eq!` macro call + --> $DIR/eq_op_macros.rs:23:16 + | +LL | assert_eq!(a + 1, a + 1); + | ^^^^^^^^^^^^ + +error: identical args used in this `assert_ne!` macro call + --> $DIR/eq_op_macros.rs:30:16 + | +LL | assert_ne!(a, a); + | ^^^^ + +error: identical args used in this `assert_ne!` macro call + --> $DIR/eq_op_macros.rs:31:16 + | +LL | assert_ne!(a + 1, a + 1); + | ^^^^^^^^^^^^ + +error: identical args used in this `debug_assert_eq!` macro call + --> $DIR/eq_op_macros.rs:9:26 + | +LL | debug_assert_eq!(a, a); + | ^^^^ +... +LL | assert_in_macro_def!(); + | ----------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: identical args used in this `debug_assert_ne!` macro call + --> $DIR/eq_op_macros.rs:10:26 + | +LL | debug_assert_ne!(a, a); + | ^^^^ +... +LL | assert_in_macro_def!(); + | ----------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: identical args used in this `debug_assert_eq!` macro call + --> $DIR/eq_op_macros.rs:38:22 + | +LL | debug_assert_eq!(a, a); + | ^^^^ + +error: identical args used in this `debug_assert_eq!` macro call + --> $DIR/eq_op_macros.rs:39:22 + | +LL | debug_assert_eq!(a + 1, a + 1); + | ^^^^^^^^^^^^ + +error: identical args used in this `debug_assert_ne!` macro call + --> $DIR/eq_op_macros.rs:46:22 + | +LL | debug_assert_ne!(a, a); + | ^^^^ + +error: identical args used in this `debug_assert_ne!` macro call + --> $DIR/eq_op_macros.rs:47:22 + | +LL | debug_assert_ne!(a + 1, a + 1); + | ^^^^^^^^^^^^ + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/erasing_op.rs b/src/tools/clippy/tests/ui/erasing_op.rs new file mode 100644 index 0000000000..1540062a4b --- /dev/null +++ b/src/tools/clippy/tests/ui/erasing_op.rs @@ -0,0 +1,9 @@ +#[allow(clippy::no_effect)] +#[warn(clippy::erasing_op)] +fn main() { + let x: u8 = 0; + + x * 0; + 0 & x; + 0 / x; +} diff --git a/src/tools/clippy/tests/ui/erasing_op.stderr b/src/tools/clippy/tests/ui/erasing_op.stderr new file mode 100644 index 0000000000..e54ce85f98 --- /dev/null +++ b/src/tools/clippy/tests/ui/erasing_op.stderr @@ -0,0 +1,22 @@ +error: this operation will always return zero. This is likely not the intended outcome + --> $DIR/erasing_op.rs:6:5 + | +LL | x * 0; + | ^^^^^ + | + = note: `-D clippy::erasing-op` implied by `-D warnings` + +error: this operation will always return zero. This is likely not the intended outcome + --> $DIR/erasing_op.rs:7:5 + | +LL | 0 & x; + | ^^^^^ + +error: this operation will always return zero. This is likely not the intended outcome + --> $DIR/erasing_op.rs:8:5 + | +LL | 0 / x; + | ^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/escape_analysis.rs b/src/tools/clippy/tests/ui/escape_analysis.rs new file mode 100644 index 0000000000..d26f48fc68 --- /dev/null +++ b/src/tools/clippy/tests/ui/escape_analysis.rs @@ -0,0 +1,204 @@ +#![feature(box_syntax)] +#![allow( + clippy::borrowed_box, + clippy::needless_pass_by_value, + clippy::unused_unit, + clippy::redundant_clone, + clippy::match_single_binding +)] +#![warn(clippy::boxed_local)] + +#[derive(Clone)] +struct A; + +impl A { + fn foo(&self) {} +} + +trait Z { + fn bar(&self); +} + +impl Z for A { + fn bar(&self) { + //nothing + } +} + +fn main() {} + +fn ok_box_trait(boxed_trait: &Box) { + let boxed_local = boxed_trait; + // done +} + +fn warn_call() { + let x = box A; + x.foo(); +} + +fn warn_arg(x: Box) { + x.foo(); +} + +fn nowarn_closure_arg() { + let x = Some(box A); + x.map_or((), |x| take_ref(&x)); +} + +fn warn_rename_call() { + let x = box A; + + let y = x; + y.foo(); // via autoderef +} + +fn warn_notuse() { + let bz = box A; +} + +fn warn_pass() { + let bz = box A; + take_ref(&bz); // via deref coercion +} + +fn nowarn_return() -> Box { + box A // moved out, "escapes" +} + +fn nowarn_move() { + let bx = box A; + drop(bx) // moved in, "escapes" +} +fn nowarn_call() { + let bx = box A; + bx.clone(); // method only available to Box, not via autoderef +} + +fn nowarn_pass() { + let bx = box A; + take_box(&bx); // fn needs &Box +} + +fn take_box(x: &Box) {} +fn take_ref(x: &A) {} + +fn nowarn_ref_take() { + // false positive, should actually warn + let x = box A; + let y = &x; + take_box(y); +} + +fn nowarn_match() { + let x = box A; // moved into a match + match x { + y => drop(y), + } +} + +fn warn_match() { + let x = box A; + match &x { + // not moved + ref y => (), + } +} + +fn nowarn_large_array() { + // should not warn, is large array + // and should not be on stack + let x = box [1; 10000]; + match &x { + // not moved + ref y => (), + } +} + +/// ICE regression test +pub trait Foo { + type Item; +} + +impl<'a> Foo for &'a () { + type Item = (); +} + +pub struct PeekableSeekable { + _peeked: I::Item, +} + +pub fn new(_needs_name: Box>) -> () {} + +/// Regression for #916, #1123 +/// +/// This shouldn't warn for `boxed_local`as the implementation of a trait +/// can't change much about the trait definition. +trait BoxedAction { + fn do_sth(self: Box); +} + +impl BoxedAction for u64 { + fn do_sth(self: Box) { + println!("{}", *self) + } +} + +/// Regression for #1478 +/// +/// This shouldn't warn for `boxed_local`as self itself is a box type. +trait MyTrait { + fn do_sth(self); +} + +impl MyTrait for Box { + fn do_sth(self) {} +} + +// Issue #3739 - capture in closures +mod issue_3739 { + use super::A; + + fn consume(_: T) {} + fn borrow(_: &T) {} + + fn closure_consume(x: Box) { + let _ = move || { + consume(x); + }; + } + + fn closure_borrow(x: Box) { + let _ = || { + borrow(&x); + }; + } +} + +/// Issue #5542 +/// +/// This shouldn't warn for `boxed_local` as it is intended to called from non-Rust code. +pub extern "C" fn do_not_warn_me(_c_pointer: Box) -> () {} + +#[rustfmt::skip] // Forces rustfmt to not add ABI +pub extern fn do_not_warn_me_no_abi(_c_pointer: Box) -> () {} + +// Issue #4804 - default implementation in trait +mod issue4804 { + trait DefaultTraitImplTest { + // don't warn on `self` + fn default_impl(self: Box) -> u32 { + 5 + } + + // warn on `x: Box` + fn default_impl_x(self: Box, x: Box) -> u32 { + 4 + } + } + + trait WarnTrait { + // warn on `x: Box` + fn foo(x: Box) {} + } +} diff --git a/src/tools/clippy/tests/ui/escape_analysis.stderr b/src/tools/clippy/tests/ui/escape_analysis.stderr new file mode 100644 index 0000000000..4a82b4419f --- /dev/null +++ b/src/tools/clippy/tests/ui/escape_analysis.stderr @@ -0,0 +1,28 @@ +error: local variable doesn't need to be boxed here + --> $DIR/escape_analysis.rs:40:13 + | +LL | fn warn_arg(x: Box) { + | ^ + | + = note: `-D clippy::boxed-local` implied by `-D warnings` + +error: local variable doesn't need to be boxed here + --> $DIR/escape_analysis.rs:131:12 + | +LL | pub fn new(_needs_name: Box>) -> () {} + | ^^^^^^^^^^^ + +error: local variable doesn't need to be boxed here + --> $DIR/escape_analysis.rs:195:44 + | +LL | fn default_impl_x(self: Box, x: Box) -> u32 { + | ^ + +error: local variable doesn't need to be boxed here + --> $DIR/escape_analysis.rs:202:16 + | +LL | fn foo(x: Box) {} + | ^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/eta.fixed b/src/tools/clippy/tests/ui/eta.fixed new file mode 100644 index 0000000000..2be2283e3f --- /dev/null +++ b/src/tools/clippy/tests/ui/eta.fixed @@ -0,0 +1,219 @@ +// run-rustfix + +#![allow( + unused, + clippy::no_effect, + clippy::redundant_closure_call, + clippy::many_single_char_names, + clippy::needless_pass_by_value, + clippy::option_map_unit_fn +)] +#![warn( + clippy::redundant_closure, + clippy::redundant_closure_for_method_calls, + clippy::needless_borrow +)] + +use std::path::PathBuf; + +macro_rules! mac { + () => { + foobar() + }; +} + +macro_rules! closure_mac { + () => { + |n| foo(n) + }; +} + +fn main() { + let a = Some(1u8).map(foo); + meta(foo); + let c = Some(1u8).map(|a| {1+2; foo}(a)); + true.then(|| mac!()); // don't lint function in macro expansion + Some(1).map(closure_mac!()); // don't lint closure in macro expansion + let _: Option> = true.then(std::vec::Vec::new); // special case vec! + let d = Some(1u8).map(|a| foo((|b| foo2(b))(a))); //is adjusted? + all(&[1, 2, 3], &2, |x, y| below(x, y)); //is adjusted + unsafe { + Some(1u8).map(|a| unsafe_fn(a)); // unsafe fn + } + + // See #815 + let e = Some(1u8).map(|a| divergent(a)); + let e = Some(1u8).map(generic); + let e = Some(1u8).map(generic); + // See #515 + let a: Option)>> = + Some(vec![1i32, 2]).map(|v| -> Box)> { Box::new(v) }); +} + +trait TestTrait { + fn trait_foo(self) -> bool; + fn trait_foo_ref(&self) -> bool; +} + +struct TestStruct<'a> { + some_ref: &'a i32, +} + +impl<'a> TestStruct<'a> { + fn foo(self) -> bool { + false + } + unsafe fn foo_unsafe(self) -> bool { + true + } +} + +impl<'a> TestTrait for TestStruct<'a> { + fn trait_foo(self) -> bool { + false + } + fn trait_foo_ref(&self) -> bool { + false + } +} + +impl<'a> std::ops::Deref for TestStruct<'a> { + type Target = char; + fn deref(&self) -> &char { + &'a' + } +} + +fn test_redundant_closures_containing_method_calls() { + let i = 10; + let e = Some(TestStruct { some_ref: &i }).map(TestStruct::foo); + let e = Some(TestStruct { some_ref: &i }).map(TestStruct::foo); + let e = Some(TestStruct { some_ref: &i }).map(TestTrait::trait_foo); + let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo_ref()); + let e = Some(TestStruct { some_ref: &i }).map(TestTrait::trait_foo); + let e = Some(&mut vec![1, 2, 3]).map(std::vec::Vec::clear); + let e = Some(&mut vec![1, 2, 3]).map(std::vec::Vec::clear); + unsafe { + let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo_unsafe()); + } + let e = Some("str").map(std::string::ToString::to_string); + let e = Some("str").map(str::to_string); + let e = Some('a').map(char::to_uppercase); + let e = Some('a').map(char::to_uppercase); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.len_utf8()).collect(); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(char::to_ascii_uppercase).collect(); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(char::to_ascii_uppercase).collect(); + let p = Some(PathBuf::new()); + let e = p.as_ref().and_then(|s| s.to_str()); + let c = Some(TestStruct { some_ref: &i }) + .as_ref() + .map(|c| c.to_ascii_uppercase()); + + fn test_different_borrow_levels(t: &[&T]) + where + T: TestTrait, + { + t.iter().filter(|x| x.trait_foo_ref()); + t.iter().map(|x| x.trait_foo_ref()); + } + + let mut some = Some(|x| x * x); + let arr = [Ok(1), Err(2)]; + let _: Vec<_> = arr.iter().map(|x| x.map_err(|e| some.take().unwrap()(e))).collect(); +} + +struct Thunk(Box T>); + +impl Thunk { + fn new T>(f: F) -> Thunk { + let mut option = Some(f); + // This should not trigger redundant_closure (#1439) + Thunk(Box::new(move || option.take().unwrap()())) + } + + fn unwrap(self) -> T { + let Thunk(mut f) = self; + f() + } +} + +fn foobar() { + let thunk = Thunk::new(|| println!("Hello, world!")); + thunk.unwrap() +} + +fn meta(f: F) +where + F: Fn(u8), +{ + f(1u8) +} + +fn foo(_: u8) {} + +fn foo2(_: u8) -> u8 { + 1u8 +} + +fn all(x: &[X], y: &X, f: F) -> bool +where + F: Fn(&X, &X) -> bool, +{ + x.iter().all(|e| f(e, y)) +} + +fn below(x: &u8, y: &u8) -> bool { + x < y +} + +unsafe fn unsafe_fn(_: u8) {} + +fn divergent(_: u8) -> ! { + unimplemented!() +} + +fn generic(_: T) -> u8 { + 0 +} + +fn passes_fn_mut(mut x: Box) { + requires_fn_once(|| x()); +} +fn requires_fn_once(_: T) {} + +fn test_redundant_closure_with_function_pointer() { + type FnPtrType = fn(u8); + let foo_ptr: FnPtrType = foo; + let a = Some(1u8).map(foo_ptr); +} + +fn test_redundant_closure_with_another_closure() { + let closure = |a| println!("{}", a); + let a = Some(1u8).map(closure); +} + +fn make_lazy(f: impl Fn() -> fn(u8) -> u8) -> impl Fn(u8) -> u8 { + // Currently f is called when result of make_lazy is called. + // If the closure is removed, f will be called when make_lazy itself is + // called. This changes semantics, so the closure must stay. + Box::new(move |x| f()(x)) +} + +fn call String>(f: F) -> String { + f(&mut "Hello".to_owned()) +} +fn test_difference_in_mutability() { + call(|s| s.clone()); +} + +struct Bar; +impl std::ops::Deref for Bar { + type Target = str; + fn deref(&self) -> &str { + "hi" + } +} + +fn test_deref_with_trait_method() { + let _ = [Bar].iter().map(|s| s.to_string()).collect::>(); +} diff --git a/src/tools/clippy/tests/ui/eta.rs b/src/tools/clippy/tests/ui/eta.rs new file mode 100644 index 0000000000..f0373f9ccf --- /dev/null +++ b/src/tools/clippy/tests/ui/eta.rs @@ -0,0 +1,219 @@ +// run-rustfix + +#![allow( + unused, + clippy::no_effect, + clippy::redundant_closure_call, + clippy::many_single_char_names, + clippy::needless_pass_by_value, + clippy::option_map_unit_fn +)] +#![warn( + clippy::redundant_closure, + clippy::redundant_closure_for_method_calls, + clippy::needless_borrow +)] + +use std::path::PathBuf; + +macro_rules! mac { + () => { + foobar() + }; +} + +macro_rules! closure_mac { + () => { + |n| foo(n) + }; +} + +fn main() { + let a = Some(1u8).map(|a| foo(a)); + meta(|a| foo(a)); + let c = Some(1u8).map(|a| {1+2; foo}(a)); + true.then(|| mac!()); // don't lint function in macro expansion + Some(1).map(closure_mac!()); // don't lint closure in macro expansion + let _: Option> = true.then(|| vec![]); // special case vec! + let d = Some(1u8).map(|a| foo((|b| foo2(b))(a))); //is adjusted? + all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted + unsafe { + Some(1u8).map(|a| unsafe_fn(a)); // unsafe fn + } + + // See #815 + let e = Some(1u8).map(|a| divergent(a)); + let e = Some(1u8).map(|a| generic(a)); + let e = Some(1u8).map(generic); + // See #515 + let a: Option)>> = + Some(vec![1i32, 2]).map(|v| -> Box)> { Box::new(v) }); +} + +trait TestTrait { + fn trait_foo(self) -> bool; + fn trait_foo_ref(&self) -> bool; +} + +struct TestStruct<'a> { + some_ref: &'a i32, +} + +impl<'a> TestStruct<'a> { + fn foo(self) -> bool { + false + } + unsafe fn foo_unsafe(self) -> bool { + true + } +} + +impl<'a> TestTrait for TestStruct<'a> { + fn trait_foo(self) -> bool { + false + } + fn trait_foo_ref(&self) -> bool { + false + } +} + +impl<'a> std::ops::Deref for TestStruct<'a> { + type Target = char; + fn deref(&self) -> &char { + &'a' + } +} + +fn test_redundant_closures_containing_method_calls() { + let i = 10; + let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo()); + let e = Some(TestStruct { some_ref: &i }).map(TestStruct::foo); + let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo()); + let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo_ref()); + let e = Some(TestStruct { some_ref: &i }).map(TestTrait::trait_foo); + let e = Some(&mut vec![1, 2, 3]).map(|v| v.clear()); + let e = Some(&mut vec![1, 2, 3]).map(std::vec::Vec::clear); + unsafe { + let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo_unsafe()); + } + let e = Some("str").map(|s| s.to_string()); + let e = Some("str").map(str::to_string); + let e = Some('a').map(|s| s.to_uppercase()); + let e = Some('a').map(char::to_uppercase); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.len_utf8()).collect(); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.to_ascii_uppercase()).collect(); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(char::to_ascii_uppercase).collect(); + let p = Some(PathBuf::new()); + let e = p.as_ref().and_then(|s| s.to_str()); + let c = Some(TestStruct { some_ref: &i }) + .as_ref() + .map(|c| c.to_ascii_uppercase()); + + fn test_different_borrow_levels(t: &[&T]) + where + T: TestTrait, + { + t.iter().filter(|x| x.trait_foo_ref()); + t.iter().map(|x| x.trait_foo_ref()); + } + + let mut some = Some(|x| x * x); + let arr = [Ok(1), Err(2)]; + let _: Vec<_> = arr.iter().map(|x| x.map_err(|e| some.take().unwrap()(e))).collect(); +} + +struct Thunk(Box T>); + +impl Thunk { + fn new T>(f: F) -> Thunk { + let mut option = Some(f); + // This should not trigger redundant_closure (#1439) + Thunk(Box::new(move || option.take().unwrap()())) + } + + fn unwrap(self) -> T { + let Thunk(mut f) = self; + f() + } +} + +fn foobar() { + let thunk = Thunk::new(|| println!("Hello, world!")); + thunk.unwrap() +} + +fn meta(f: F) +where + F: Fn(u8), +{ + f(1u8) +} + +fn foo(_: u8) {} + +fn foo2(_: u8) -> u8 { + 1u8 +} + +fn all(x: &[X], y: &X, f: F) -> bool +where + F: Fn(&X, &X) -> bool, +{ + x.iter().all(|e| f(e, y)) +} + +fn below(x: &u8, y: &u8) -> bool { + x < y +} + +unsafe fn unsafe_fn(_: u8) {} + +fn divergent(_: u8) -> ! { + unimplemented!() +} + +fn generic(_: T) -> u8 { + 0 +} + +fn passes_fn_mut(mut x: Box) { + requires_fn_once(|| x()); +} +fn requires_fn_once(_: T) {} + +fn test_redundant_closure_with_function_pointer() { + type FnPtrType = fn(u8); + let foo_ptr: FnPtrType = foo; + let a = Some(1u8).map(|a| foo_ptr(a)); +} + +fn test_redundant_closure_with_another_closure() { + let closure = |a| println!("{}", a); + let a = Some(1u8).map(|a| closure(a)); +} + +fn make_lazy(f: impl Fn() -> fn(u8) -> u8) -> impl Fn(u8) -> u8 { + // Currently f is called when result of make_lazy is called. + // If the closure is removed, f will be called when make_lazy itself is + // called. This changes semantics, so the closure must stay. + Box::new(move |x| f()(x)) +} + +fn call String>(f: F) -> String { + f(&mut "Hello".to_owned()) +} +fn test_difference_in_mutability() { + call(|s| s.clone()); +} + +struct Bar; +impl std::ops::Deref for Bar { + type Target = str; + fn deref(&self) -> &str { + "hi" + } +} + +fn test_deref_with_trait_method() { + let _ = [Bar].iter().map(|s| s.to_string()).collect::>(); +} diff --git a/src/tools/clippy/tests/ui/eta.stderr b/src/tools/clippy/tests/ui/eta.stderr new file mode 100644 index 0000000000..57ed652796 --- /dev/null +++ b/src/tools/clippy/tests/ui/eta.stderr @@ -0,0 +1,86 @@ +error: redundant closure + --> $DIR/eta.rs:32:27 + | +LL | let a = Some(1u8).map(|a| foo(a)); + | ^^^^^^^^^^ help: replace the closure with the function itself: `foo` + | + = note: `-D clippy::redundant-closure` implied by `-D warnings` + +error: redundant closure + --> $DIR/eta.rs:33:10 + | +LL | meta(|a| foo(a)); + | ^^^^^^^^^^ help: replace the closure with the function itself: `foo` + +error: redundant closure + --> $DIR/eta.rs:37:40 + | +LL | let _: Option> = true.then(|| vec![]); // special case vec! + | ^^^^^^^^^ help: replace the closure with `Vec::new`: `std::vec::Vec::new` + +error: this expression borrows a reference (`&u8`) that is immediately dereferenced by the compiler + --> $DIR/eta.rs:39:21 + | +LL | all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted + | ^^^ help: change this to: `&2` + | + = note: `-D clippy::needless-borrow` implied by `-D warnings` + +error: redundant closure + --> $DIR/eta.rs:46:27 + | +LL | let e = Some(1u8).map(|a| generic(a)); + | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `generic` + +error: redundant closure + --> $DIR/eta.rs:89:51 + | +LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo()); + | ^^^^^^^^^^^ help: replace the closure with the method itself: `TestStruct::foo` + | + = note: `-D clippy::redundant-closure-for-method-calls` implied by `-D warnings` + +error: redundant closure + --> $DIR/eta.rs:91:51 + | +LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo()); + | ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `TestTrait::trait_foo` + +error: redundant closure + --> $DIR/eta.rs:94:42 + | +LL | let e = Some(&mut vec![1, 2, 3]).map(|v| v.clear()); + | ^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::vec::Vec::clear` + +error: redundant closure + --> $DIR/eta.rs:99:29 + | +LL | let e = Some("str").map(|s| s.to_string()); + | ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::string::ToString::to_string` + +error: redundant closure + --> $DIR/eta.rs:101:27 + | +LL | let e = Some('a').map(|s| s.to_uppercase()); + | ^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_uppercase` + +error: redundant closure + --> $DIR/eta.rs:104:65 + | +LL | let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.to_ascii_uppercase()).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_ascii_uppercase` + +error: redundant closure + --> $DIR/eta.rs:187:27 + | +LL | let a = Some(1u8).map(|a| foo_ptr(a)); + | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo_ptr` + +error: redundant closure + --> $DIR/eta.rs:192:27 + | +LL | let a = Some(1u8).map(|a| closure(a)); + | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `closure` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/eval_order_dependence.rs b/src/tools/clippy/tests/ui/eval_order_dependence.rs new file mode 100644 index 0000000000..d806bc6d40 --- /dev/null +++ b/src/tools/clippy/tests/ui/eval_order_dependence.rs @@ -0,0 +1,109 @@ +#[warn(clippy::eval_order_dependence)] +#[allow( + unused_assignments, + unused_variables, + clippy::many_single_char_names, + clippy::no_effect, + dead_code, + clippy::blacklisted_name +)] +fn main() { + let mut x = 0; + let a = { + x = 1; + 1 + } + x; + + // Example from iss#277 + x += { + x = 20; + 2 + }; + + // Does it work in weird places? + // ...in the base for a struct expression? + struct Foo { + a: i32, + b: i32, + }; + let base = Foo { a: 4, b: 5 }; + let foo = Foo { + a: x, + ..{ + x = 6; + base + } + }; + // ...inside a closure? + let closure = || { + let mut x = 0; + x += { + x = 20; + 2 + }; + }; + // ...not across a closure? + let mut y = 0; + let b = (y, || y = 1); + + // && and || evaluate left-to-right. + let a = { + x = 1; + true + } && (x == 3); + let a = { + x = 1; + true + } || (x == 3); + + // Make sure we don't get confused by alpha conversion. + let a = { + let mut x = 1; + x = 2; + 1 + } + x; + + // No warning if we don't read the variable... + x = { + x = 20; + 2 + }; + // ...if the assignment is in a closure... + let b = { + || { + x = 1; + }; + 1 + } + x; + // ... or the access is under an address. + let b = ( + { + let p = &x; + 1 + }, + { + x = 1; + x + }, + ); + + // Limitation: l-values other than simple variables don't trigger + // the warning. + let mut tup = (0, 0); + let c = { + tup.0 = 1; + 1 + } + tup.0; + // Limitation: you can get away with a read under address-of. + let mut z = 0; + let b = ( + &{ + z = x; + x + }, + { + x = 3; + x + }, + ); +} diff --git a/src/tools/clippy/tests/ui/eval_order_dependence.stderr b/src/tools/clippy/tests/ui/eval_order_dependence.stderr new file mode 100644 index 0000000000..8f4fa2228f --- /dev/null +++ b/src/tools/clippy/tests/ui/eval_order_dependence.stderr @@ -0,0 +1,51 @@ +error: unsequenced read of a variable + --> $DIR/eval_order_dependence.rs:15:9 + | +LL | } + x; + | ^ + | + = note: `-D clippy::eval-order-dependence` implied by `-D warnings` +note: whether read occurs before this write depends on evaluation order + --> $DIR/eval_order_dependence.rs:13:9 + | +LL | x = 1; + | ^^^^^ + +error: unsequenced read of a variable + --> $DIR/eval_order_dependence.rs:18:5 + | +LL | x += { + | ^ + | +note: whether read occurs before this write depends on evaluation order + --> $DIR/eval_order_dependence.rs:19:9 + | +LL | x = 20; + | ^^^^^^ + +error: unsequenced read of a variable + --> $DIR/eval_order_dependence.rs:31:12 + | +LL | a: x, + | ^ + | +note: whether read occurs before this write depends on evaluation order + --> $DIR/eval_order_dependence.rs:33:13 + | +LL | x = 6; + | ^^^^^ + +error: unsequenced read of a variable + --> $DIR/eval_order_dependence.rs:40:9 + | +LL | x += { + | ^ + | +note: whether read occurs before this write depends on evaluation order + --> $DIR/eval_order_dependence.rs:41:13 + | +LL | x = 20; + | ^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/excessive_precision.fixed b/src/tools/clippy/tests/ui/excessive_precision.fixed new file mode 100644 index 0000000000..bf0325fec7 --- /dev/null +++ b/src/tools/clippy/tests/ui/excessive_precision.fixed @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::excessive_precision)] +#![allow(dead_code, unused_variables, clippy::print_literal)] + +fn main() { + // Consts + const GOOD32: f32 = 0.123_456; + const GOOD32_SM: f32 = 0.000_000_000_1; + const GOOD32_DOT: f32 = 10_000_000_000.0; + const GOOD32_EDGE: f32 = 1.000_000_8; + const GOOD64: f64 = 0.123_456_789_012; + const GOOD64_SM: f32 = 0.000_000_000_000_000_1; + const GOOD64_DOT: f32 = 10_000_000_000_000_000.0; + + const BAD32_1: f32 = 0.123_456_79_f32; + const BAD32_2: f32 = 0.123_456_79; + const BAD32_3: f32 = 0.1; + const BAD32_EDGE: f32 = 1.000_001; + + const BAD64_1: f64 = 0.123_456_789_012_345_66_f64; + const BAD64_2: f64 = 0.123_456_789_012_345_66; + const BAD64_3: f64 = 0.1; + + // Literal as param + println!("{:?}", 8.888_888_888_888_89); + + // // TODO add inferred type tests for f32 + // Locals + let good32: f32 = 0.123_456_f32; + let good32_2: f32 = 0.123_456; + + let good64: f64 = 0.123_456_789_012; + let good64_suf: f64 = 0.123_456_789_012f64; + let good64_inf = 0.123_456_789_012; + + let bad32: f32 = 1.123_456_8; + let bad32_suf: f32 = 1.123_456_8_f32; + let bad32_inf = 1.123_456_8_f32; + + let bad64: f64 = 0.123_456_789_012_345_66; + let bad64_suf: f64 = 0.123_456_789_012_345_66_f64; + let bad64_inf = 0.123_456_789_012_345_66; + + // Vectors + let good_vec32: Vec = vec![0.123_456]; + let good_vec64: Vec = vec![0.123_456_789]; + + let bad_vec32: Vec = vec![0.123_456_79]; + let bad_vec64: Vec = vec![0.123_456_789_123_456_78]; + + // Exponential float notation + let good_e32: f32 = 1e-10; + let bad_e32: f32 = 1.123_456_8e-10; + + let good_bige32: f32 = 1E-10; + let bad_bige32: f32 = 1.123_456_8E-10; + + // Inferred type + let good_inferred: f32 = 1f32 * 1_000_000_000.; + + // issue #2840 + let num = 0.000_000_000_01e-10f64; +} diff --git a/src/tools/clippy/tests/ui/excessive_precision.rs b/src/tools/clippy/tests/ui/excessive_precision.rs new file mode 100644 index 0000000000..ce4722a90f --- /dev/null +++ b/src/tools/clippy/tests/ui/excessive_precision.rs @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::excessive_precision)] +#![allow(dead_code, unused_variables, clippy::print_literal)] + +fn main() { + // Consts + const GOOD32: f32 = 0.123_456; + const GOOD32_SM: f32 = 0.000_000_000_1; + const GOOD32_DOT: f32 = 10_000_000_000.0; + const GOOD32_EDGE: f32 = 1.000_000_8; + const GOOD64: f64 = 0.123_456_789_012; + const GOOD64_SM: f32 = 0.000_000_000_000_000_1; + const GOOD64_DOT: f32 = 10_000_000_000_000_000.0; + + const BAD32_1: f32 = 0.123_456_789_f32; + const BAD32_2: f32 = 0.123_456_789; + const BAD32_3: f32 = 0.100_000_000_000_1; + const BAD32_EDGE: f32 = 1.000_000_9; + + const BAD64_1: f64 = 0.123_456_789_012_345_67f64; + const BAD64_2: f64 = 0.123_456_789_012_345_67; + const BAD64_3: f64 = 0.100_000_000_000_000_000_1; + + // Literal as param + println!("{:?}", 8.888_888_888_888_888_888_888); + + // // TODO add inferred type tests for f32 + // Locals + let good32: f32 = 0.123_456_f32; + let good32_2: f32 = 0.123_456; + + let good64: f64 = 0.123_456_789_012; + let good64_suf: f64 = 0.123_456_789_012f64; + let good64_inf = 0.123_456_789_012; + + let bad32: f32 = 1.123_456_789; + let bad32_suf: f32 = 1.123_456_789_f32; + let bad32_inf = 1.123_456_789_f32; + + let bad64: f64 = 0.123_456_789_012_345_67; + let bad64_suf: f64 = 0.123_456_789_012_345_67f64; + let bad64_inf = 0.123_456_789_012_345_67; + + // Vectors + let good_vec32: Vec = vec![0.123_456]; + let good_vec64: Vec = vec![0.123_456_789]; + + let bad_vec32: Vec = vec![0.123_456_789]; + let bad_vec64: Vec = vec![0.123_456_789_123_456_789]; + + // Exponential float notation + let good_e32: f32 = 1e-10; + let bad_e32: f32 = 1.123_456_788_888e-10; + + let good_bige32: f32 = 1E-10; + let bad_bige32: f32 = 1.123_456_788_888E-10; + + // Inferred type + let good_inferred: f32 = 1f32 * 1_000_000_000.; + + // issue #2840 + let num = 0.000_000_000_01e-10f64; +} diff --git a/src/tools/clippy/tests/ui/excessive_precision.stderr b/src/tools/clippy/tests/ui/excessive_precision.stderr new file mode 100644 index 0000000000..599773f2f7 --- /dev/null +++ b/src/tools/clippy/tests/ui/excessive_precision.stderr @@ -0,0 +1,112 @@ +error: float has excessive precision + --> $DIR/excessive_precision.rs:15:26 + | +LL | const BAD32_1: f32 = 0.123_456_789_f32; + | ^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_79_f32` + | + = note: `-D clippy::excessive-precision` implied by `-D warnings` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:16:26 + | +LL | const BAD32_2: f32 = 0.123_456_789; + | ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_79` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:17:26 + | +LL | const BAD32_3: f32 = 0.100_000_000_000_1; + | ^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.1` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:18:29 + | +LL | const BAD32_EDGE: f32 = 1.000_000_9; + | ^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.000_001` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:20:26 + | +LL | const BAD64_1: f64 = 0.123_456_789_012_345_67f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_012_345_66_f64` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:21:26 + | +LL | const BAD64_2: f64 = 0.123_456_789_012_345_67; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_012_345_66` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:22:26 + | +LL | const BAD64_3: f64 = 0.100_000_000_000_000_000_1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.1` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:25:22 + | +LL | println!("{:?}", 8.888_888_888_888_888_888_888); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `8.888_888_888_888_89` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:36:22 + | +LL | let bad32: f32 = 1.123_456_789; + | ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.123_456_8` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:37:26 + | +LL | let bad32_suf: f32 = 1.123_456_789_f32; + | ^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.123_456_8_f32` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:38:21 + | +LL | let bad32_inf = 1.123_456_789_f32; + | ^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.123_456_8_f32` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:40:22 + | +LL | let bad64: f64 = 0.123_456_789_012_345_67; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_012_345_66` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:41:26 + | +LL | let bad64_suf: f64 = 0.123_456_789_012_345_67f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_012_345_66_f64` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:42:21 + | +LL | let bad64_inf = 0.123_456_789_012_345_67; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_012_345_66` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:48:36 + | +LL | let bad_vec32: Vec = vec![0.123_456_789]; + | ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_79` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:49:36 + | +LL | let bad_vec64: Vec = vec![0.123_456_789_123_456_789]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_123_456_78` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:53:24 + | +LL | let bad_e32: f32 = 1.123_456_788_888e-10; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.123_456_8e-10` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:56:27 + | +LL | let bad_bige32: f32 = 1.123_456_788_888E-10; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.123_456_8E-10` + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/exhaustive_items.fixed b/src/tools/clippy/tests/ui/exhaustive_items.fixed new file mode 100644 index 0000000000..c209f5b4b7 --- /dev/null +++ b/src/tools/clippy/tests/ui/exhaustive_items.fixed @@ -0,0 +1,91 @@ +// run-rustfix + +#![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)] +#![allow(unused)] + +fn main() { + // nop +} + +pub mod enums { + #[non_exhaustive] + pub enum Exhaustive { + Foo, + Bar, + Baz, + Quux(String), + } + + /// Some docs + #[repr(C)] + #[non_exhaustive] + pub enum ExhaustiveWithAttrs { + Foo, + Bar, + Baz, + Quux(String), + } + + // no warning, already non_exhaustive + #[non_exhaustive] + pub enum NonExhaustive { + Foo, + Bar, + Baz, + Quux(String), + } + + // no warning, private + enum ExhaustivePrivate { + Foo, + Bar, + Baz, + Quux(String), + } + + // no warning, private + #[non_exhaustive] + enum NonExhaustivePrivate { + Foo, + Bar, + Baz, + Quux(String), + } +} + +pub mod structs { + #[non_exhaustive] + pub struct Exhaustive { + pub foo: u8, + pub bar: String, + } + + // no warning, already non_exhaustive + #[non_exhaustive] + pub struct NonExhaustive { + pub foo: u8, + pub bar: String, + } + + // no warning, private fields + pub struct ExhaustivePrivateFieldTuple(u8); + + // no warning, private fields + pub struct ExhaustivePrivateField { + pub foo: u8, + bar: String, + } + + // no warning, private + struct ExhaustivePrivate { + pub foo: u8, + pub bar: String, + } + + // no warning, private + #[non_exhaustive] + struct NonExhaustivePrivate { + pub foo: u8, + pub bar: String, + } +} diff --git a/src/tools/clippy/tests/ui/exhaustive_items.rs b/src/tools/clippy/tests/ui/exhaustive_items.rs new file mode 100644 index 0000000000..6f59dbf2da --- /dev/null +++ b/src/tools/clippy/tests/ui/exhaustive_items.rs @@ -0,0 +1,88 @@ +// run-rustfix + +#![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)] +#![allow(unused)] + +fn main() { + // nop +} + +pub mod enums { + pub enum Exhaustive { + Foo, + Bar, + Baz, + Quux(String), + } + + /// Some docs + #[repr(C)] + pub enum ExhaustiveWithAttrs { + Foo, + Bar, + Baz, + Quux(String), + } + + // no warning, already non_exhaustive + #[non_exhaustive] + pub enum NonExhaustive { + Foo, + Bar, + Baz, + Quux(String), + } + + // no warning, private + enum ExhaustivePrivate { + Foo, + Bar, + Baz, + Quux(String), + } + + // no warning, private + #[non_exhaustive] + enum NonExhaustivePrivate { + Foo, + Bar, + Baz, + Quux(String), + } +} + +pub mod structs { + pub struct Exhaustive { + pub foo: u8, + pub bar: String, + } + + // no warning, already non_exhaustive + #[non_exhaustive] + pub struct NonExhaustive { + pub foo: u8, + pub bar: String, + } + + // no warning, private fields + pub struct ExhaustivePrivateFieldTuple(u8); + + // no warning, private fields + pub struct ExhaustivePrivateField { + pub foo: u8, + bar: String, + } + + // no warning, private + struct ExhaustivePrivate { + pub foo: u8, + pub bar: String, + } + + // no warning, private + #[non_exhaustive] + struct NonExhaustivePrivate { + pub foo: u8, + pub bar: String, + } +} diff --git a/src/tools/clippy/tests/ui/exhaustive_items.stderr b/src/tools/clippy/tests/ui/exhaustive_items.stderr new file mode 100644 index 0000000000..8fbab535a9 --- /dev/null +++ b/src/tools/clippy/tests/ui/exhaustive_items.stderr @@ -0,0 +1,61 @@ +error: exported enums should not be exhaustive + --> $DIR/exhaustive_items.rs:11:5 + | +LL | / pub enum Exhaustive { +LL | | Foo, +LL | | Bar, +LL | | Baz, +LL | | Quux(String), +LL | | } + | |_____^ + | +note: the lint level is defined here + --> $DIR/exhaustive_items.rs:3:9 + | +LL | #![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ +help: try adding #[non_exhaustive] + | +LL | #[non_exhaustive] +LL | pub enum Exhaustive { + | + +error: exported enums should not be exhaustive + --> $DIR/exhaustive_items.rs:20:5 + | +LL | / pub enum ExhaustiveWithAttrs { +LL | | Foo, +LL | | Bar, +LL | | Baz, +LL | | Quux(String), +LL | | } + | |_____^ + | +help: try adding #[non_exhaustive] + | +LL | #[non_exhaustive] +LL | pub enum ExhaustiveWithAttrs { + | + +error: exported structs should not be exhaustive + --> $DIR/exhaustive_items.rs:55:5 + | +LL | / pub struct Exhaustive { +LL | | pub foo: u8, +LL | | pub bar: String, +LL | | } + | |_____^ + | +note: the lint level is defined here + --> $DIR/exhaustive_items.rs:3:35 + | +LL | #![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try adding #[non_exhaustive] + | +LL | #[non_exhaustive] +LL | pub struct Exhaustive { + | + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/exit1.rs b/src/tools/clippy/tests/ui/exit1.rs new file mode 100644 index 0000000000..4eac6eb746 --- /dev/null +++ b/src/tools/clippy/tests/ui/exit1.rs @@ -0,0 +1,15 @@ +#[warn(clippy::exit)] + +fn not_main() { + if true { + std::process::exit(4); + } +} + +fn main() { + if true { + std::process::exit(2); + }; + not_main(); + std::process::exit(1); +} diff --git a/src/tools/clippy/tests/ui/exit1.stderr b/src/tools/clippy/tests/ui/exit1.stderr new file mode 100644 index 0000000000..a8d3956aa2 --- /dev/null +++ b/src/tools/clippy/tests/ui/exit1.stderr @@ -0,0 +1,10 @@ +error: usage of `process::exit` + --> $DIR/exit1.rs:5:9 + | +LL | std::process::exit(4); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::exit` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/exit2.rs b/src/tools/clippy/tests/ui/exit2.rs new file mode 100644 index 0000000000..4b693ed708 --- /dev/null +++ b/src/tools/clippy/tests/ui/exit2.rs @@ -0,0 +1,13 @@ +#[warn(clippy::exit)] + +fn also_not_main() { + std::process::exit(3); +} + +fn main() { + if true { + std::process::exit(2); + }; + also_not_main(); + std::process::exit(1); +} diff --git a/src/tools/clippy/tests/ui/exit2.stderr b/src/tools/clippy/tests/ui/exit2.stderr new file mode 100644 index 0000000000..7263e156a9 --- /dev/null +++ b/src/tools/clippy/tests/ui/exit2.stderr @@ -0,0 +1,10 @@ +error: usage of `process::exit` + --> $DIR/exit2.rs:4:5 + | +LL | std::process::exit(3); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::exit` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/exit3.rs b/src/tools/clippy/tests/ui/exit3.rs new file mode 100644 index 0000000000..9dc0e1015a --- /dev/null +++ b/src/tools/clippy/tests/ui/exit3.rs @@ -0,0 +1,8 @@ +#[warn(clippy::exit)] + +fn main() { + if true { + std::process::exit(2); + }; + std::process::exit(1); +} diff --git a/src/tools/clippy/tests/ui/expect.rs b/src/tools/clippy/tests/ui/expect.rs new file mode 100644 index 0000000000..1073acf6f0 --- /dev/null +++ b/src/tools/clippy/tests/ui/expect.rs @@ -0,0 +1,16 @@ +#![warn(clippy::expect_used)] + +fn expect_option() { + let opt = Some(0); + let _ = opt.expect(""); +} + +fn expect_result() { + let res: Result = Ok(0); + let _ = res.expect(""); +} + +fn main() { + expect_option(); + expect_result(); +} diff --git a/src/tools/clippy/tests/ui/expect.stderr b/src/tools/clippy/tests/ui/expect.stderr new file mode 100644 index 0000000000..9d3fc7df15 --- /dev/null +++ b/src/tools/clippy/tests/ui/expect.stderr @@ -0,0 +1,19 @@ +error: used `expect()` on `an Option` value + --> $DIR/expect.rs:5:13 + | +LL | let _ = opt.expect(""); + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::expect-used` implied by `-D warnings` + = help: if this value is an `None`, it will panic + +error: used `expect()` on `a Result` value + --> $DIR/expect.rs:10:13 + | +LL | let _ = res.expect(""); + | ^^^^^^^^^^^^^^ + | + = help: if this value is an `Err`, it will panic + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/expect_fun_call.fixed b/src/tools/clippy/tests/ui/expect_fun_call.fixed new file mode 100644 index 0000000000..a756d1cf50 --- /dev/null +++ b/src/tools/clippy/tests/ui/expect_fun_call.fixed @@ -0,0 +1,94 @@ +// run-rustfix + +#![warn(clippy::expect_fun_call)] + +/// Checks implementation of the `EXPECT_FUN_CALL` lint + +fn main() { + struct Foo; + + impl Foo { + fn new() -> Self { + Foo + } + + fn expect(&self, msg: &str) { + panic!("{}", msg) + } + } + + let with_some = Some("value"); + with_some.expect("error"); + + let with_none: Option = None; + with_none.expect("error"); + + let error_code = 123_i32; + let with_none_and_format: Option = None; + with_none_and_format.unwrap_or_else(|| panic!("Error {}: fake error", error_code)); + + let with_none_and_as_str: Option = None; + with_none_and_as_str.unwrap_or_else(|| panic!("Error {}: fake error", error_code)); + + let with_ok: Result<(), ()> = Ok(()); + with_ok.expect("error"); + + let with_err: Result<(), ()> = Err(()); + with_err.expect("error"); + + let error_code = 123_i32; + let with_err_and_format: Result<(), ()> = Err(()); + with_err_and_format.unwrap_or_else(|_| panic!("Error {}: fake error", error_code)); + + let with_err_and_as_str: Result<(), ()> = Err(()); + with_err_and_as_str.unwrap_or_else(|_| panic!("Error {}: fake error", error_code)); + + let with_dummy_type = Foo::new(); + with_dummy_type.expect("another test string"); + + let with_dummy_type_and_format = Foo::new(); + with_dummy_type_and_format.expect(&format!("Error {}: fake error", error_code)); + + let with_dummy_type_and_as_str = Foo::new(); + with_dummy_type_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + + //Issue #2937 + Some("foo").unwrap_or_else(|| panic!("{} {}", 1, 2)); + + //Issue #2979 - this should not lint + { + let msg = "bar"; + Some("foo").expect(msg); + } + + { + fn get_string() -> String { + "foo".to_string() + } + + fn get_static_str() -> &'static str { + "foo" + } + + fn get_non_static_str(_: &u32) -> &str { + "foo" + } + + Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) }); + Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) }); + Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) }); + + Some("foo").unwrap_or_else(|| { panic!("{}", get_static_str()) }); + Some("foo").unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) }); + } + + //Issue #3839 + Some(true).unwrap_or_else(|| panic!("key {}, {}", 1, 2)); + + //Issue #4912 - the receiver is a &Option + { + let opt = Some(1); + let opt_ref = &opt; + opt_ref.unwrap_or_else(|| panic!("{:?}", opt_ref)); + } +} diff --git a/src/tools/clippy/tests/ui/expect_fun_call.rs b/src/tools/clippy/tests/ui/expect_fun_call.rs new file mode 100644 index 0000000000..60bbaa89d4 --- /dev/null +++ b/src/tools/clippy/tests/ui/expect_fun_call.rs @@ -0,0 +1,94 @@ +// run-rustfix + +#![warn(clippy::expect_fun_call)] + +/// Checks implementation of the `EXPECT_FUN_CALL` lint + +fn main() { + struct Foo; + + impl Foo { + fn new() -> Self { + Foo + } + + fn expect(&self, msg: &str) { + panic!("{}", msg) + } + } + + let with_some = Some("value"); + with_some.expect("error"); + + let with_none: Option = None; + with_none.expect("error"); + + let error_code = 123_i32; + let with_none_and_format: Option = None; + with_none_and_format.expect(&format!("Error {}: fake error", error_code)); + + let with_none_and_as_str: Option = None; + with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + + let with_ok: Result<(), ()> = Ok(()); + with_ok.expect("error"); + + let with_err: Result<(), ()> = Err(()); + with_err.expect("error"); + + let error_code = 123_i32; + let with_err_and_format: Result<(), ()> = Err(()); + with_err_and_format.expect(&format!("Error {}: fake error", error_code)); + + let with_err_and_as_str: Result<(), ()> = Err(()); + with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + + let with_dummy_type = Foo::new(); + with_dummy_type.expect("another test string"); + + let with_dummy_type_and_format = Foo::new(); + with_dummy_type_and_format.expect(&format!("Error {}: fake error", error_code)); + + let with_dummy_type_and_as_str = Foo::new(); + with_dummy_type_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + + //Issue #2937 + Some("foo").expect(format!("{} {}", 1, 2).as_ref()); + + //Issue #2979 - this should not lint + { + let msg = "bar"; + Some("foo").expect(msg); + } + + { + fn get_string() -> String { + "foo".to_string() + } + + fn get_static_str() -> &'static str { + "foo" + } + + fn get_non_static_str(_: &u32) -> &str { + "foo" + } + + Some("foo").expect(&get_string()); + Some("foo").expect(get_string().as_ref()); + Some("foo").expect(get_string().as_str()); + + Some("foo").expect(get_static_str()); + Some("foo").expect(get_non_static_str(&0)); + } + + //Issue #3839 + Some(true).expect(&format!("key {}, {}", 1, 2)); + + //Issue #4912 - the receiver is a &Option + { + let opt = Some(1); + let opt_ref = &opt; + opt_ref.expect(&format!("{:?}", opt_ref)); + } +} diff --git a/src/tools/clippy/tests/ui/expect_fun_call.stderr b/src/tools/clippy/tests/ui/expect_fun_call.stderr new file mode 100644 index 0000000000..6dc796f5ce --- /dev/null +++ b/src/tools/clippy/tests/ui/expect_fun_call.stderr @@ -0,0 +1,76 @@ +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:28:26 + | +LL | with_none_and_format.expect(&format!("Error {}: fake error", error_code)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))` + | + = note: `-D clippy::expect-fun-call` implied by `-D warnings` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:31:26 + | +LL | with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:41:25 + | +LL | with_err_and_format.expect(&format!("Error {}: fake error", error_code)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:44:25 + | +LL | with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:56:17 + | +LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{} {}", 1, 2))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:77:21 + | +LL | Some("foo").expect(&get_string()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:78:21 + | +LL | Some("foo").expect(get_string().as_ref()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:79:21 + | +LL | Some("foo").expect(get_string().as_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:81:21 + | +LL | Some("foo").expect(get_static_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_static_str()) })` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:82:21 + | +LL | Some("foo").expect(get_non_static_str(&0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:86:16 + | +LL | Some(true).expect(&format!("key {}, {}", 1, 2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:92:17 + | +LL | opt_ref.expect(&format!("{:?}", opt_ref)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{:?}", opt_ref))` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/explicit_counter_loop.rs b/src/tools/clippy/tests/ui/explicit_counter_loop.rs new file mode 100644 index 0000000000..81d8221bd1 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_counter_loop.rs @@ -0,0 +1,160 @@ +#![warn(clippy::explicit_counter_loop)] + +fn main() { + let mut vec = vec![1, 2, 3, 4]; + let mut _index = 0; + for _v in &vec { + _index += 1 + } + + let mut _index = 1; + _index = 0; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + for _v in &mut vec { + _index += 1; + } + + let mut _index = 0; + for _v in vec { + _index += 1; + } +} + +mod issue_1219 { + pub fn test() { + // should not trigger the lint because variable is used after the loop #473 + let vec = vec![1, 2, 3]; + let mut index = 0; + for _v in &vec { + index += 1 + } + println!("index: {}", index); + + // should not trigger the lint because the count is conditional #1219 + let text = "banana"; + let mut count = 0; + for ch in text.chars() { + println!("{}", count); + if ch == 'a' { + continue; + } + count += 1; + } + + // should not trigger the lint because the count is conditional + let text = "banana"; + let mut count = 0; + for ch in text.chars() { + println!("{}", count); + if ch == 'a' { + count += 1; + } + } + + // should trigger the lint because the count is not conditional + let text = "banana"; + let mut count = 0; + for ch in text.chars() { + println!("{}", count); + count += 1; + if ch == 'a' { + continue; + } + } + + // should trigger the lint because the count is not conditional + let text = "banana"; + let mut count = 0; + for ch in text.chars() { + println!("{}", count); + count += 1; + for i in 0..2 { + let _ = 123; + } + } + + // should not trigger the lint because the count is incremented multiple times + let text = "banana"; + let mut count = 0; + for ch in text.chars() { + println!("{}", count); + count += 1; + for i in 0..2 { + count += 1; + } + } + } +} + +mod issue_3308 { + pub fn test() { + // should not trigger the lint because the count is incremented multiple times + let mut skips = 0; + let erasures = vec![]; + for i in 0..10 { + println!("{}", skips); + while erasures.contains(&(i + skips)) { + skips += 1; + } + } + + // should not trigger the lint because the count is incremented multiple times + let mut skips = 0; + for i in 0..10 { + println!("{}", skips); + let mut j = 0; + while j < 5 { + skips += 1; + j += 1; + } + } + + // should not trigger the lint because the count is incremented multiple times + let mut skips = 0; + for i in 0..10 { + println!("{}", skips); + for j in 0..5 { + skips += 1; + } + } + } +} + +mod issue_1670 { + pub fn test() { + let mut count = 0; + for _i in 3..10 { + count += 1; + } + } +} + +mod issue_4732 { + pub fn test() { + let slice = &[1, 2, 3]; + let mut index = 0; + + // should not trigger the lint because the count is used after the loop + for _v in slice { + index += 1 + } + let _closure = || println!("index: {}", index); + } +} + +mod issue_4677 { + pub fn test() { + let slice = &[1, 2, 3]; + + // should not trigger the lint because the count is used after incremented + let mut count = 0; + for _i in slice { + count += 1; + println!("{}", count); + } + } +} diff --git a/src/tools/clippy/tests/ui/explicit_counter_loop.stderr b/src/tools/clippy/tests/ui/explicit_counter_loop.stderr new file mode 100644 index 0000000000..4cbacffe87 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_counter_loop.stderr @@ -0,0 +1,46 @@ +error: the variable `_index` is used as a loop counter + --> $DIR/explicit_counter_loop.rs:6:5 + | +LL | for _v in &vec { + | ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()` + | + = note: `-D clippy::explicit-counter-loop` implied by `-D warnings` + +error: the variable `_index` is used as a loop counter + --> $DIR/explicit_counter_loop.rs:12:5 + | +LL | for _v in &vec { + | ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()` + +error: the variable `_index` is used as a loop counter + --> $DIR/explicit_counter_loop.rs:17:5 + | +LL | for _v in &mut vec { + | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter_mut().enumerate()` + +error: the variable `_index` is used as a loop counter + --> $DIR/explicit_counter_loop.rs:22:5 + | +LL | for _v in vec { + | ^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.into_iter().enumerate()` + +error: the variable `count` is used as a loop counter + --> $DIR/explicit_counter_loop.rs:61:9 + | +LL | for ch in text.chars() { + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()` + +error: the variable `count` is used as a loop counter + --> $DIR/explicit_counter_loop.rs:72:9 + | +LL | for ch in text.chars() { + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()` + +error: the variable `count` is used as a loop counter + --> $DIR/explicit_counter_loop.rs:130:9 + | +LL | for _i in 3..10 { + | ^^^^^^^^^^^^^^^ help: consider using: `for (count, _i) in (3..10).enumerate()` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/explicit_write.fixed b/src/tools/clippy/tests/ui/explicit_write.fixed new file mode 100644 index 0000000000..692d2ca675 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_write.fixed @@ -0,0 +1,51 @@ +// run-rustfix +#![allow(unused_imports)] +#![warn(clippy::explicit_write)] + +fn stdout() -> String { + String::new() +} + +fn stderr() -> String { + String::new() +} + +fn main() { + // these should warn + { + use std::io::Write; + print!("test"); + eprint!("test"); + println!("test"); + eprintln!("test"); + print!("test"); + eprint!("test"); + + // including newlines + println!("test\ntest"); + eprintln!("test\ntest"); + } + // these should not warn, different destination + { + use std::fmt::Write; + let mut s = String::new(); + write!(s, "test").unwrap(); + write!(s, "test").unwrap(); + writeln!(s, "test").unwrap(); + writeln!(s, "test").unwrap(); + s.write_fmt(format_args!("test")).unwrap(); + s.write_fmt(format_args!("test")).unwrap(); + write!(stdout(), "test").unwrap(); + write!(stderr(), "test").unwrap(); + writeln!(stdout(), "test").unwrap(); + writeln!(stderr(), "test").unwrap(); + stdout().write_fmt(format_args!("test")).unwrap(); + stderr().write_fmt(format_args!("test")).unwrap(); + } + // these should not warn, no unwrap + { + use std::io::Write; + std::io::stdout().write_fmt(format_args!("test")).expect("no stdout"); + std::io::stderr().write_fmt(format_args!("test")).expect("no stderr"); + } +} diff --git a/src/tools/clippy/tests/ui/explicit_write.rs b/src/tools/clippy/tests/ui/explicit_write.rs new file mode 100644 index 0000000000..455c5ef55d --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_write.rs @@ -0,0 +1,51 @@ +// run-rustfix +#![allow(unused_imports)] +#![warn(clippy::explicit_write)] + +fn stdout() -> String { + String::new() +} + +fn stderr() -> String { + String::new() +} + +fn main() { + // these should warn + { + use std::io::Write; + write!(std::io::stdout(), "test").unwrap(); + write!(std::io::stderr(), "test").unwrap(); + writeln!(std::io::stdout(), "test").unwrap(); + writeln!(std::io::stderr(), "test").unwrap(); + std::io::stdout().write_fmt(format_args!("test")).unwrap(); + std::io::stderr().write_fmt(format_args!("test")).unwrap(); + + // including newlines + writeln!(std::io::stdout(), "test\ntest").unwrap(); + writeln!(std::io::stderr(), "test\ntest").unwrap(); + } + // these should not warn, different destination + { + use std::fmt::Write; + let mut s = String::new(); + write!(s, "test").unwrap(); + write!(s, "test").unwrap(); + writeln!(s, "test").unwrap(); + writeln!(s, "test").unwrap(); + s.write_fmt(format_args!("test")).unwrap(); + s.write_fmt(format_args!("test")).unwrap(); + write!(stdout(), "test").unwrap(); + write!(stderr(), "test").unwrap(); + writeln!(stdout(), "test").unwrap(); + writeln!(stderr(), "test").unwrap(); + stdout().write_fmt(format_args!("test")).unwrap(); + stderr().write_fmt(format_args!("test")).unwrap(); + } + // these should not warn, no unwrap + { + use std::io::Write; + std::io::stdout().write_fmt(format_args!("test")).expect("no stdout"); + std::io::stderr().write_fmt(format_args!("test")).expect("no stderr"); + } +} diff --git a/src/tools/clippy/tests/ui/explicit_write.stderr b/src/tools/clippy/tests/ui/explicit_write.stderr new file mode 100644 index 0000000000..9feef9c0dc --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_write.stderr @@ -0,0 +1,52 @@ +error: use of `write!(stdout(), ...).unwrap()` + --> $DIR/explicit_write.rs:17:9 + | +LL | write!(std::io::stdout(), "test").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `print!("test")` + | + = note: `-D clippy::explicit-write` implied by `-D warnings` + +error: use of `write!(stderr(), ...).unwrap()` + --> $DIR/explicit_write.rs:18:9 + | +LL | write!(std::io::stderr(), "test").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprint!("test")` + +error: use of `writeln!(stdout(), ...).unwrap()` + --> $DIR/explicit_write.rs:19:9 + | +LL | writeln!(std::io::stdout(), "test").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `println!("test")` + +error: use of `writeln!(stderr(), ...).unwrap()` + --> $DIR/explicit_write.rs:20:9 + | +LL | writeln!(std::io::stderr(), "test").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("test")` + +error: use of `stdout().write_fmt(...).unwrap()` + --> $DIR/explicit_write.rs:21:9 + | +LL | std::io::stdout().write_fmt(format_args!("test")).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `print!("test")` + +error: use of `stderr().write_fmt(...).unwrap()` + --> $DIR/explicit_write.rs:22:9 + | +LL | std::io::stderr().write_fmt(format_args!("test")).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprint!("test")` + +error: use of `writeln!(stdout(), ...).unwrap()` + --> $DIR/explicit_write.rs:25:9 + | +LL | writeln!(std::io::stdout(), "test/ntest").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `println!("test/ntest")` + +error: use of `writeln!(stderr(), ...).unwrap()` + --> $DIR/explicit_write.rs:26:9 + | +LL | writeln!(std::io::stderr(), "test/ntest").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("test/ntest")` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/explicit_write_non_rustfix.rs b/src/tools/clippy/tests/ui/explicit_write_non_rustfix.rs new file mode 100644 index 0000000000..f21e8ef935 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_write_non_rustfix.rs @@ -0,0 +1,8 @@ +#![allow(unused_imports, clippy::blacklisted_name)] +#![warn(clippy::explicit_write)] + +fn main() { + use std::io::Write; + let bar = "bar"; + writeln!(std::io::stderr(), "foo {}", bar).unwrap(); +} diff --git a/src/tools/clippy/tests/ui/explicit_write_non_rustfix.stderr b/src/tools/clippy/tests/ui/explicit_write_non_rustfix.stderr new file mode 100644 index 0000000000..77cadb99bb --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_write_non_rustfix.stderr @@ -0,0 +1,10 @@ +error: use of `writeln!(stderr(), ...).unwrap()`. Consider using `eprintln!` instead + --> $DIR/explicit_write_non_rustfix.rs:7:5 + | +LL | writeln!(std::io::stderr(), "foo {}", bar).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::explicit-write` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/extra_unused_lifetimes.rs b/src/tools/clippy/tests/ui/extra_unused_lifetimes.rs new file mode 100644 index 0000000000..150acfbfee --- /dev/null +++ b/src/tools/clippy/tests/ui/extra_unused_lifetimes.rs @@ -0,0 +1,75 @@ +#![allow( + unused, + dead_code, + clippy::needless_lifetimes, + clippy::needless_pass_by_value, + clippy::needless_arbitrary_self_type +)] +#![warn(clippy::extra_unused_lifetimes)] + +fn empty() {} + +fn used_lt<'a>(x: &'a u8) {} + +fn unused_lt<'a>(x: u8) {} + +fn unused_lt_transitive<'a, 'b: 'a>(x: &'b u8) { + // 'a is useless here since it's not directly bound +} + +fn lt_return<'a, 'b: 'a>(x: &'b u8) -> &'a u8 { + panic!() +} + +fn lt_return_only<'a>() -> &'a u8 { + panic!() +} + +fn unused_lt_blergh<'a>(x: Option>) {} + +trait Foo<'a> { + fn x(&self, a: &'a u8); +} + +impl<'a> Foo<'a> for u8 { + fn x(&self, a: &'a u8) {} +} + +struct Bar; + +impl Bar { + fn x<'a>(&self) {} +} + +// test for #489 (used lifetimes in bounds) +pub fn parse<'a, I: Iterator>(_it: &mut I) { + unimplemented!() +} +pub fn parse2<'a, I>(_it: &mut I) +where + I: Iterator, +{ + unimplemented!() +} + +struct X { + x: u32, +} + +impl X { + fn self_ref_with_lifetime<'a>(&'a self) {} + fn explicit_self_with_lifetime<'a>(self: &'a Self) {} +} + +// Methods implementing traits must have matching lifetimes +mod issue4291 { + trait BadTrait { + fn unused_lt<'a>(x: u8) {} + } + + impl BadTrait for () { + fn unused_lt<'a>(_x: u8) {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/extra_unused_lifetimes.stderr b/src/tools/clippy/tests/ui/extra_unused_lifetimes.stderr new file mode 100644 index 0000000000..ebdb8e7495 --- /dev/null +++ b/src/tools/clippy/tests/ui/extra_unused_lifetimes.stderr @@ -0,0 +1,28 @@ +error: this lifetime isn't used in the function definition + --> $DIR/extra_unused_lifetimes.rs:14:14 + | +LL | fn unused_lt<'a>(x: u8) {} + | ^^ + | + = note: `-D clippy::extra-unused-lifetimes` implied by `-D warnings` + +error: this lifetime isn't used in the function definition + --> $DIR/extra_unused_lifetimes.rs:16:25 + | +LL | fn unused_lt_transitive<'a, 'b: 'a>(x: &'b u8) { + | ^^ + +error: this lifetime isn't used in the function definition + --> $DIR/extra_unused_lifetimes.rs:41:10 + | +LL | fn x<'a>(&self) {} + | ^^ + +error: this lifetime isn't used in the function definition + --> $DIR/extra_unused_lifetimes.rs:67:22 + | +LL | fn unused_lt<'a>(x: u8) {} + | ^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/fallible_impl_from.rs b/src/tools/clippy/tests/ui/fallible_impl_from.rs new file mode 100644 index 0000000000..5d5af4e463 --- /dev/null +++ b/src/tools/clippy/tests/ui/fallible_impl_from.rs @@ -0,0 +1,76 @@ +#![deny(clippy::fallible_impl_from)] + +// docs example +struct Foo(i32); +impl From for Foo { + fn from(s: String) -> Self { + Foo(s.parse().unwrap()) + } +} + +struct Valid(Vec); + +impl<'a> From<&'a str> for Valid { + fn from(s: &'a str) -> Valid { + Valid(s.to_owned().into_bytes()) + } +} +impl From for Valid { + fn from(i: usize) -> Valid { + Valid(Vec::with_capacity(i)) + } +} + +struct Invalid; + +impl From for Invalid { + fn from(i: usize) -> Invalid { + if i != 42 { + panic!(); + } + Invalid + } +} + +impl From> for Invalid { + fn from(s: Option) -> Invalid { + let s = s.unwrap(); + if !s.is_empty() { + panic!("42"); + } else if s.parse::().unwrap() != 42 { + panic!("{:?}", s); + } + Invalid + } +} + +trait ProjStrTrait { + type ProjString; +} +impl ProjStrTrait for Box { + type ProjString = String; +} +impl<'a> From<&'a mut as ProjStrTrait>::ProjString> for Invalid { + fn from(s: &'a mut as ProjStrTrait>::ProjString) -> Invalid { + if s.parse::().ok().unwrap() != 42 { + panic!("{:?}", s); + } + Invalid + } +} + +struct Unreachable; + +impl From for Unreachable { + fn from(s: String) -> Unreachable { + if s.is_empty() { + return Unreachable; + } + match s.chars().next() { + Some(_) => Unreachable, + None => unreachable!(), // do not lint the unreachable macro + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/fallible_impl_from.stderr b/src/tools/clippy/tests/ui/fallible_impl_from.stderr new file mode 100644 index 0000000000..a938d234fa --- /dev/null +++ b/src/tools/clippy/tests/ui/fallible_impl_from.stderr @@ -0,0 +1,93 @@ +error: consider implementing `TryFrom` instead + --> $DIR/fallible_impl_from.rs:5:1 + | +LL | / impl From for Foo { +LL | | fn from(s: String) -> Self { +LL | | Foo(s.parse().unwrap()) +LL | | } +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/fallible_impl_from.rs:1:9 + | +LL | #![deny(clippy::fallible_impl_from)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail +note: potential failure(s) + --> $DIR/fallible_impl_from.rs:7:13 + | +LL | Foo(s.parse().unwrap()) + | ^^^^^^^^^^^^^^^^^^ + +error: consider implementing `TryFrom` instead + --> $DIR/fallible_impl_from.rs:26:1 + | +LL | / impl From for Invalid { +LL | | fn from(i: usize) -> Invalid { +LL | | if i != 42 { +LL | | panic!(); +... | +LL | | } +LL | | } + | |_^ + | + = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail +note: potential failure(s) + --> $DIR/fallible_impl_from.rs:29:13 + | +LL | panic!(); + | ^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: consider implementing `TryFrom` instead + --> $DIR/fallible_impl_from.rs:35:1 + | +LL | / impl From> for Invalid { +LL | | fn from(s: Option) -> Invalid { +LL | | let s = s.unwrap(); +LL | | if !s.is_empty() { +... | +LL | | } +LL | | } + | |_^ + | + = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail +note: potential failure(s) + --> $DIR/fallible_impl_from.rs:37:17 + | +LL | let s = s.unwrap(); + | ^^^^^^^^^^ +LL | if !s.is_empty() { +LL | panic!("42"); + | ^^^^^^^^^^^^^ +LL | } else if s.parse::().unwrap() != 42 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | panic!("{:?}", s); + | ^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: consider implementing `TryFrom` instead + --> $DIR/fallible_impl_from.rs:53:1 + | +LL | / impl<'a> From<&'a mut as ProjStrTrait>::ProjString> for Invalid { +LL | | fn from(s: &'a mut as ProjStrTrait>::ProjString) -> Invalid { +LL | | if s.parse::().ok().unwrap() != 42 { +LL | | panic!("{:?}", s); +... | +LL | | } +LL | | } + | |_^ + | + = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail +note: potential failure(s) + --> $DIR/fallible_impl_from.rs:55:12 + | +LL | if s.parse::().ok().unwrap() != 42 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | panic!("{:?}", s); + | ^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/field_reassign_with_default.rs b/src/tools/clippy/tests/ui/field_reassign_with_default.rs new file mode 100644 index 0000000000..9fc208f533 --- /dev/null +++ b/src/tools/clippy/tests/ui/field_reassign_with_default.rs @@ -0,0 +1,147 @@ +// aux-build:proc_macro_derive.rs +// aux-build:macro_rules.rs + +#![warn(clippy::field_reassign_with_default)] + +#[macro_use] +extern crate proc_macro_derive; +#[macro_use] +extern crate macro_rules; + +// Don't lint on derives that derive `Default` +// See https://github.com/rust-lang/rust-clippy/issues/6545 +#[derive(FieldReassignWithDefault)] +struct DerivedStruct; + +#[derive(Default)] +struct A { + i: i32, + j: i64, +} + +struct B { + i: i32, + j: i64, +} + +#[derive(Default)] +struct C { + i: Vec, + j: i64, +} +/// Implements .next() that returns a different number each time. +struct SideEffect(i32); + +impl SideEffect { + fn new() -> SideEffect { + SideEffect(0) + } + fn next(&mut self) -> i32 { + self.0 += 1; + self.0 + } +} + +fn main() { + // wrong, produces first error in stderr + let mut a: A = Default::default(); + a.i = 42; + + // right + let mut a: A = Default::default(); + + // right + let a = A { + i: 42, + ..Default::default() + }; + + // right + let mut a: A = Default::default(); + if a.i == 0 { + a.j = 12; + } + + // right + let mut a: A = Default::default(); + let b = 5; + + // right + let mut b = 32; + let mut a: A = Default::default(); + b = 2; + + // right + let b: B = B { i: 42, j: 24 }; + + // right + let mut b: B = B { i: 42, j: 24 }; + b.i = 52; + + // right + let mut b = B { i: 15, j: 16 }; + let mut a: A = Default::default(); + b.i = 2; + + // wrong, produces second error in stderr + let mut a: A = Default::default(); + a.j = 43; + a.i = 42; + + // wrong, produces third error in stderr + let mut a: A = Default::default(); + a.i = 42; + a.j = 43; + a.j = 44; + + // wrong, produces fourth error in stderr + let mut a = A::default(); + a.i = 42; + + // wrong, but does not produce an error in stderr, because we can't produce a correct kind of + // suggestion with current implementation + let mut c: (i32, i32) = Default::default(); + c.0 = 42; + c.1 = 21; + + // wrong, produces the fifth error in stderr + let mut a: A = Default::default(); + a.i = Default::default(); + + // wrong, produces the sixth error in stderr + let mut a: A = Default::default(); + a.i = Default::default(); + a.j = 45; + + // right, because an assignment refers to another field + let mut x = A::default(); + x.i = 42; + x.j = 21 + x.i as i64; + + // right, we bail out if there's a reassignment to the same variable, since there is a risk of + // side-effects affecting the outcome + let mut x = A::default(); + let mut side_effect = SideEffect::new(); + x.i = side_effect.next(); + x.j = 2; + x.i = side_effect.next(); + + // don't lint - some private fields + let mut x = m::F::default(); + x.a = 1; + + // don't expand macros in the suggestion (#6522) + let mut a: C = C::default(); + a.i = vec![1]; + + // Don't lint in external macros + field_reassign_with_default!(); +} + +mod m { + #[derive(Default)] + pub struct F { + pub a: u64, + b: u64, + } +} diff --git a/src/tools/clippy/tests/ui/field_reassign_with_default.stderr b/src/tools/clippy/tests/ui/field_reassign_with_default.stderr new file mode 100644 index 0000000000..2f0f28f7bb --- /dev/null +++ b/src/tools/clippy/tests/ui/field_reassign_with_default.stderr @@ -0,0 +1,87 @@ +error: field assignment outside of initializer for an instance created with Default::default() + --> $DIR/field_reassign_with_default.rs:48:5 + | +LL | a.i = 42; + | ^^^^^^^^^ + | + = note: `-D clippy::field-reassign-with-default` implied by `-D warnings` +note: consider initializing the variable with `main::A { i: 42, ..Default::default() }` and removing relevant reassignments + --> $DIR/field_reassign_with_default.rs:47:5 + | +LL | let mut a: A = Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: field assignment outside of initializer for an instance created with Default::default() + --> $DIR/field_reassign_with_default.rs:88:5 + | +LL | a.j = 43; + | ^^^^^^^^^ + | +note: consider initializing the variable with `main::A { j: 43, i: 42 }` and removing relevant reassignments + --> $DIR/field_reassign_with_default.rs:87:5 + | +LL | let mut a: A = Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: field assignment outside of initializer for an instance created with Default::default() + --> $DIR/field_reassign_with_default.rs:93:5 + | +LL | a.i = 42; + | ^^^^^^^^^ + | +note: consider initializing the variable with `main::A { i: 42, j: 44 }` and removing relevant reassignments + --> $DIR/field_reassign_with_default.rs:92:5 + | +LL | let mut a: A = Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: field assignment outside of initializer for an instance created with Default::default() + --> $DIR/field_reassign_with_default.rs:99:5 + | +LL | a.i = 42; + | ^^^^^^^^^ + | +note: consider initializing the variable with `main::A { i: 42, ..Default::default() }` and removing relevant reassignments + --> $DIR/field_reassign_with_default.rs:98:5 + | +LL | let mut a = A::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: field assignment outside of initializer for an instance created with Default::default() + --> $DIR/field_reassign_with_default.rs:109:5 + | +LL | a.i = Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider initializing the variable with `main::A { i: Default::default(), ..Default::default() }` and removing relevant reassignments + --> $DIR/field_reassign_with_default.rs:108:5 + | +LL | let mut a: A = Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: field assignment outside of initializer for an instance created with Default::default() + --> $DIR/field_reassign_with_default.rs:113:5 + | +LL | a.i = Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider initializing the variable with `main::A { i: Default::default(), j: 45 }` and removing relevant reassignments + --> $DIR/field_reassign_with_default.rs:112:5 + | +LL | let mut a: A = Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: field assignment outside of initializer for an instance created with Default::default() + --> $DIR/field_reassign_with_default.rs:135:5 + | +LL | a.i = vec![1]; + | ^^^^^^^^^^^^^^ + | +note: consider initializing the variable with `C { i: vec![1], ..Default::default() }` and removing relevant reassignments + --> $DIR/field_reassign_with_default.rs:134:5 + | +LL | let mut a: C = C::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/filetype_is_file.rs b/src/tools/clippy/tests/ui/filetype_is_file.rs new file mode 100644 index 0000000000..5de8fe8cdd --- /dev/null +++ b/src/tools/clippy/tests/ui/filetype_is_file.rs @@ -0,0 +1,23 @@ +#![warn(clippy::filetype_is_file)] + +fn main() -> std::io::Result<()> { + use std::fs; + use std::ops::BitOr; + + // !filetype.is_dir() + if fs::metadata("foo.txt")?.file_type().is_file() { + // read file + } + + // positive of filetype.is_dir() + if !fs::metadata("foo.txt")?.file_type().is_file() { + // handle dir + } + + // false positive of filetype.is_dir() + if !fs::metadata("foo.txt")?.file_type().is_file().bitor(true) { + // ... + } + + Ok(()) +} diff --git a/src/tools/clippy/tests/ui/filetype_is_file.stderr b/src/tools/clippy/tests/ui/filetype_is_file.stderr new file mode 100644 index 0000000000..cd1e3ac37f --- /dev/null +++ b/src/tools/clippy/tests/ui/filetype_is_file.stderr @@ -0,0 +1,27 @@ +error: `FileType::is_file()` only covers regular files + --> $DIR/filetype_is_file.rs:8:8 + | +LL | if fs::metadata("foo.txt")?.file_type().is_file() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::filetype-is-file` implied by `-D warnings` + = help: use `!FileType::is_dir()` instead + +error: `!FileType::is_file()` only denies regular files + --> $DIR/filetype_is_file.rs:13:8 + | +LL | if !fs::metadata("foo.txt")?.file_type().is_file() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `FileType::is_dir()` instead + +error: `FileType::is_file()` only covers regular files + --> $DIR/filetype_is_file.rs:18:9 + | +LL | if !fs::metadata("foo.txt")?.file_type().is_file().bitor(true) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `!FileType::is_dir()` instead + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/filter_map_identity.fixed b/src/tools/clippy/tests/ui/filter_map_identity.fixed new file mode 100644 index 0000000000..23ce28d8e9 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_identity.fixed @@ -0,0 +1,16 @@ +// run-rustfix + +#![allow(unused_imports)] +#![warn(clippy::filter_map_identity)] + +fn main() { + let iterator = vec![Some(1), None, Some(2)].into_iter(); + let _ = iterator.flatten(); + + let iterator = vec![Some(1), None, Some(2)].into_iter(); + let _ = iterator.flatten(); + + use std::convert::identity; + let iterator = vec![Some(1), None, Some(2)].into_iter(); + let _ = iterator.flatten(); +} diff --git a/src/tools/clippy/tests/ui/filter_map_identity.rs b/src/tools/clippy/tests/ui/filter_map_identity.rs new file mode 100644 index 0000000000..e698df13ee --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_identity.rs @@ -0,0 +1,16 @@ +// run-rustfix + +#![allow(unused_imports)] +#![warn(clippy::filter_map_identity)] + +fn main() { + let iterator = vec![Some(1), None, Some(2)].into_iter(); + let _ = iterator.filter_map(|x| x); + + let iterator = vec![Some(1), None, Some(2)].into_iter(); + let _ = iterator.filter_map(std::convert::identity); + + use std::convert::identity; + let iterator = vec![Some(1), None, Some(2)].into_iter(); + let _ = iterator.filter_map(identity); +} diff --git a/src/tools/clippy/tests/ui/filter_map_identity.stderr b/src/tools/clippy/tests/ui/filter_map_identity.stderr new file mode 100644 index 0000000000..596a632060 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_identity.stderr @@ -0,0 +1,22 @@ +error: called `filter_map(|x| x)` on an `Iterator` + --> $DIR/filter_map_identity.rs:8:22 + | +LL | let _ = iterator.filter_map(|x| x); + | ^^^^^^^^^^^^^^^^^ help: try: `flatten()` + | + = note: `-D clippy::filter-map-identity` implied by `-D warnings` + +error: called `filter_map(std::convert::identity)` on an `Iterator` + --> $DIR/filter_map_identity.rs:11:22 + | +LL | let _ = iterator.filter_map(std::convert::identity); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` + +error: called `filter_map(std::convert::identity)` on an `Iterator` + --> $DIR/filter_map_identity.rs:15:22 + | +LL | let _ = iterator.filter_map(identity); + | ^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/filter_map_next.rs b/src/tools/clippy/tests/ui/filter_map_next.rs new file mode 100644 index 0000000000..dbeb235430 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_next.rs @@ -0,0 +1,17 @@ +#![warn(clippy::all, clippy::pedantic)] + +fn main() { + let a = ["1", "lol", "3", "NaN", "5"]; + + #[rustfmt::skip] + let _: Option = vec![1, 2, 3, 4, 5, 6] + .into_iter() + .filter_map(|x| { + if x == 2 { + Some(x * 2) + } else { + None + } + }) + .next(); +} diff --git a/src/tools/clippy/tests/ui/filter_map_next.stderr b/src/tools/clippy/tests/ui/filter_map_next.stderr new file mode 100644 index 0000000000..ddc982c93f --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_next.stderr @@ -0,0 +1,17 @@ +error: called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling `.find_map(..)` instead + --> $DIR/filter_map_next.rs:7:26 + | +LL | let _: Option = vec![1, 2, 3, 4, 5, 6] + | __________________________^ +LL | | .into_iter() +LL | | .filter_map(|x| { +LL | | if x == 2 { +... | +LL | | }) +LL | | .next(); + | |_______________^ + | + = note: `-D clippy::filter-map-next` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/filter_map_next_fixable.fixed b/src/tools/clippy/tests/ui/filter_map_next_fixable.fixed new file mode 100644 index 0000000000..c3992d7e92 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_next_fixable.fixed @@ -0,0 +1,10 @@ +// run-rustfix + +#![warn(clippy::all, clippy::pedantic)] + +fn main() { + let a = ["1", "lol", "3", "NaN", "5"]; + + let element: Option = a.iter().find_map(|s| s.parse().ok()); + assert_eq!(element, Some(1)); +} diff --git a/src/tools/clippy/tests/ui/filter_map_next_fixable.rs b/src/tools/clippy/tests/ui/filter_map_next_fixable.rs new file mode 100644 index 0000000000..447219a968 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_next_fixable.rs @@ -0,0 +1,10 @@ +// run-rustfix + +#![warn(clippy::all, clippy::pedantic)] + +fn main() { + let a = ["1", "lol", "3", "NaN", "5"]; + + let element: Option = a.iter().filter_map(|s| s.parse().ok()).next(); + assert_eq!(element, Some(1)); +} diff --git a/src/tools/clippy/tests/ui/filter_map_next_fixable.stderr b/src/tools/clippy/tests/ui/filter_map_next_fixable.stderr new file mode 100644 index 0000000000..3bb062ffd7 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_next_fixable.stderr @@ -0,0 +1,10 @@ +error: called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling `.find_map(..)` instead + --> $DIR/filter_map_next_fixable.rs:8:32 + | +LL | let element: Option = a.iter().filter_map(|s| s.parse().ok()).next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `a.iter().find_map(|s| s.parse().ok())` + | + = note: `-D clippy::filter-map-next` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/filter_methods.rs b/src/tools/clippy/tests/ui/filter_methods.rs new file mode 100644 index 0000000000..5145024161 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_methods.rs @@ -0,0 +1,25 @@ +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::clippy::let_underscore_drop)] +#![allow(clippy::missing_docs_in_private_items)] + +fn main() { + let _: Vec<_> = vec![5; 6].into_iter().filter(|&x| x == 0).map(|x| x * 2).collect(); + + let _: Vec<_> = vec![5_i8; 6] + .into_iter() + .filter(|&x| x == 0) + .flat_map(|x| x.checked_mul(2)) + .collect(); + + let _: Vec<_> = vec![5_i8; 6] + .into_iter() + .filter_map(|x| x.checked_mul(2)) + .flat_map(|x| x.checked_mul(2)) + .collect(); + + let _: Vec<_> = vec![5_i8; 6] + .into_iter() + .filter_map(|x| x.checked_mul(2)) + .map(|x| x.checked_mul(2)) + .collect(); +} diff --git a/src/tools/clippy/tests/ui/filter_methods.stderr b/src/tools/clippy/tests/ui/filter_methods.stderr new file mode 100644 index 0000000000..c7b4f28be3 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_methods.stderr @@ -0,0 +1,39 @@ +error: called `filter(..).flat_map(..)` on an `Iterator` + --> $DIR/filter_methods.rs:8:21 + | +LL | let _: Vec<_> = vec![5_i8; 6] + | _____________________^ +LL | | .into_iter() +LL | | .filter(|&x| x == 0) +LL | | .flat_map(|x| x.checked_mul(2)) + | |_______________________________________^ + | + = note: `-D clippy::filter-map` implied by `-D warnings` + = help: this is more succinctly expressed by calling `.flat_map(..)` and filtering by returning `iter::empty()` + +error: called `filter_map(..).flat_map(..)` on an `Iterator` + --> $DIR/filter_methods.rs:14:21 + | +LL | let _: Vec<_> = vec![5_i8; 6] + | _____________________^ +LL | | .into_iter() +LL | | .filter_map(|x| x.checked_mul(2)) +LL | | .flat_map(|x| x.checked_mul(2)) + | |_______________________________________^ + | + = help: this is more succinctly expressed by calling `.flat_map(..)` and filtering by returning `iter::empty()` + +error: called `filter_map(..).map(..)` on an `Iterator` + --> $DIR/filter_methods.rs:20:21 + | +LL | let _: Vec<_> = vec![5_i8; 6] + | _____________________^ +LL | | .into_iter() +LL | | .filter_map(|x| x.checked_mul(2)) +LL | | .map(|x| x.checked_mul(2)) + | |__________________________________^ + | + = help: this is more succinctly expressed by only calling `.filter_map(..)` instead + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/find_map.rs b/src/tools/clippy/tests/ui/find_map.rs new file mode 100644 index 0000000000..88d3b0e749 --- /dev/null +++ b/src/tools/clippy/tests/ui/find_map.rs @@ -0,0 +1,33 @@ +#![warn(clippy::all, clippy::pedantic)] + +#[derive(Debug, Copy, Clone)] +enum Flavor { + Chocolate, +} + +#[derive(Debug, Copy, Clone)] +enum Dessert { + Banana, + Pudding, + Cake(Flavor), +} + +fn main() { + let desserts_of_the_week = vec![Dessert::Banana, Dessert::Cake(Flavor::Chocolate), Dessert::Pudding]; + + let a = ["lol", "NaN", "2", "5", "Xunda"]; + + let _: Option = a.iter().find(|s| s.parse::().is_ok()).map(|s| s.parse().unwrap()); + + #[allow(clippy::match_like_matches_macro)] + let _: Option = desserts_of_the_week + .iter() + .find(|dessert| match *dessert { + Dessert::Cake(_) => true, + _ => false, + }) + .map(|dessert| match *dessert { + Dessert::Cake(ref flavor) => *flavor, + _ => unreachable!(), + }); +} diff --git a/src/tools/clippy/tests/ui/flat_map_identity.fixed b/src/tools/clippy/tests/ui/flat_map_identity.fixed new file mode 100644 index 0000000000..dfe3bd47e1 --- /dev/null +++ b/src/tools/clippy/tests/ui/flat_map_identity.fixed @@ -0,0 +1,14 @@ +// run-rustfix + +#![allow(unused_imports)] +#![warn(clippy::flat_map_identity)] + +use std::convert; + +fn main() { + let iterator = [[0, 1], [2, 3], [4, 5]].iter(); + let _ = iterator.flatten(); + + let iterator = [[0, 1], [2, 3], [4, 5]].iter(); + let _ = iterator.flatten(); +} diff --git a/src/tools/clippy/tests/ui/flat_map_identity.rs b/src/tools/clippy/tests/ui/flat_map_identity.rs new file mode 100644 index 0000000000..393b956925 --- /dev/null +++ b/src/tools/clippy/tests/ui/flat_map_identity.rs @@ -0,0 +1,14 @@ +// run-rustfix + +#![allow(unused_imports)] +#![warn(clippy::flat_map_identity)] + +use std::convert; + +fn main() { + let iterator = [[0, 1], [2, 3], [4, 5]].iter(); + let _ = iterator.flat_map(|x| x); + + let iterator = [[0, 1], [2, 3], [4, 5]].iter(); + let _ = iterator.flat_map(convert::identity); +} diff --git a/src/tools/clippy/tests/ui/flat_map_identity.stderr b/src/tools/clippy/tests/ui/flat_map_identity.stderr new file mode 100644 index 0000000000..e4686ae5a5 --- /dev/null +++ b/src/tools/clippy/tests/ui/flat_map_identity.stderr @@ -0,0 +1,16 @@ +error: called `flat_map(|x| x)` on an `Iterator` + --> $DIR/flat_map_identity.rs:10:22 + | +LL | let _ = iterator.flat_map(|x| x); + | ^^^^^^^^^^^^^^^ help: try: `flatten()` + | + = note: `-D clippy::flat-map-identity` implied by `-D warnings` + +error: called `flat_map(std::convert::identity)` on an `Iterator` + --> $DIR/flat_map_identity.rs:13:22 + | +LL | let _ = iterator.flat_map(convert::identity); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/float_arithmetic.rs b/src/tools/clippy/tests/ui/float_arithmetic.rs new file mode 100644 index 0000000000..60fa7569eb --- /dev/null +++ b/src/tools/clippy/tests/ui/float_arithmetic.rs @@ -0,0 +1,52 @@ +#![warn(clippy::integer_arithmetic, clippy::float_arithmetic)] +#![allow( + unused, + clippy::shadow_reuse, + clippy::shadow_unrelated, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::op_ref +)] + +#[rustfmt::skip] +fn main() { + let mut f = 1.0f32; + + f * 2.0; + + 1.0 + f; + f * 2.0; + f / 2.0; + f - 2.0 * 4.2; + -f; + + f += 1.0; + f -= 1.0; + f *= 2.0; + f /= 2.0; +} + +// also warn about floating point arith with references involved + +pub fn float_arith_ref() { + 3.1_f32 + &1.2_f32; + &3.4_f32 + 1.5_f32; + &3.5_f32 + &1.3_f32; +} + +pub fn float_foo(f: &f32) -> f32 { + let a = 5.1; + a + f +} + +pub fn float_bar(f1: &f32, f2: &f32) -> f32 { + f1 + f2 +} + +pub fn float_baz(f1: f32, f2: &f32) -> f32 { + f1 + f2 +} + +pub fn float_qux(f1: f32, f2: f32) -> f32 { + (&f1 + &f2) +} diff --git a/src/tools/clippy/tests/ui/float_arithmetic.stderr b/src/tools/clippy/tests/ui/float_arithmetic.stderr new file mode 100644 index 0000000000..1ceffb35be --- /dev/null +++ b/src/tools/clippy/tests/ui/float_arithmetic.stderr @@ -0,0 +1,106 @@ +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:15:5 + | +LL | f * 2.0; + | ^^^^^^^ + | + = note: `-D clippy::float-arithmetic` implied by `-D warnings` + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:17:5 + | +LL | 1.0 + f; + | ^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:18:5 + | +LL | f * 2.0; + | ^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:19:5 + | +LL | f / 2.0; + | ^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:20:5 + | +LL | f - 2.0 * 4.2; + | ^^^^^^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:21:5 + | +LL | -f; + | ^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:23:5 + | +LL | f += 1.0; + | ^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:24:5 + | +LL | f -= 1.0; + | ^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:25:5 + | +LL | f *= 2.0; + | ^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:26:5 + | +LL | f /= 2.0; + | ^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:32:5 + | +LL | 3.1_f32 + &1.2_f32; + | ^^^^^^^^^^^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:33:5 + | +LL | &3.4_f32 + 1.5_f32; + | ^^^^^^^^^^^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:34:5 + | +LL | &3.5_f32 + &1.3_f32; + | ^^^^^^^^^^^^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:39:5 + | +LL | a + f + | ^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:43:5 + | +LL | f1 + f2 + | ^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:47:5 + | +LL | f1 + f2 + | ^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:51:5 + | +LL | (&f1 + &f2) + | ^^^^^^^^^^^ + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/float_cmp.rs b/src/tools/clippy/tests/ui/float_cmp.rs new file mode 100644 index 0000000000..ad5d1a09c0 --- /dev/null +++ b/src/tools/clippy/tests/ui/float_cmp.rs @@ -0,0 +1,116 @@ +#![warn(clippy::float_cmp)] +#![allow( + unused, + clippy::no_effect, + clippy::op_ref, + clippy::unnecessary_operation, + clippy::cast_lossless, + clippy::many_single_char_names +)] + +use std::ops::Add; + +const ZERO: f32 = 0.0; +const ONE: f32 = ZERO + 1.0; + +fn twice(x: T) -> T +where + T: Add + Copy, +{ + x + x +} + +fn eq_fl(x: f32, y: f32) -> bool { + if x.is_nan() { y.is_nan() } else { x == y } // no error, inside "eq" fn +} + +fn fl_eq(x: f32, y: f32) -> bool { + if x.is_nan() { y.is_nan() } else { x == y } // no error, inside "eq" fn +} + +struct X { + val: f32, +} + +impl PartialEq for X { + fn eq(&self, o: &X) -> bool { + if self.val.is_nan() { + o.val.is_nan() + } else { + self.val == o.val // no error, inside "eq" fn + } + } +} + +fn main() { + ZERO == 0f32; //no error, comparison with zero is ok + 1.0f32 != f32::INFINITY; // also comparison with infinity + 1.0f32 != f32::NEG_INFINITY; // and negative infinity + ZERO == 0.0; //no error, comparison with zero is ok + ZERO + ZERO != 1.0; //no error, comparison with zero is ok + + ONE == 1f32; + ONE == 1.0 + 0.0; + ONE + ONE == ZERO + ONE + ONE; + ONE != 2.0; + ONE != 0.0; // no error, comparison with zero is ok + twice(ONE) != ONE; + ONE as f64 != 2.0; + ONE as f64 != 0.0; // no error, comparison with zero is ok + + let x: f64 = 1.0; + + x == 1.0; + x != 0f64; // no error, comparison with zero is ok + + twice(x) != twice(ONE as f64); + + x < 0.0; // no errors, lower or greater comparisons need no fuzzyness + x > 0.0; + x <= 0.0; + x >= 0.0; + + let xs: [f32; 1] = [0.0]; + let a: *const f32 = xs.as_ptr(); + let b: *const f32 = xs.as_ptr(); + + assert_eq!(a, b); // no errors + + const ZERO_ARRAY: [f32; 2] = [0.0, 0.0]; + const NON_ZERO_ARRAY: [f32; 2] = [0.0, 0.1]; + + let i = 0; + let j = 1; + + ZERO_ARRAY[i] == NON_ZERO_ARRAY[j]; // ok, because lhs is zero regardless of i + NON_ZERO_ARRAY[i] == NON_ZERO_ARRAY[j]; + + let a1: [f32; 1] = [0.0]; + let a2: [f32; 1] = [1.1]; + + a1 == a2; + a1[0] == a2[0]; + + // no errors - comparing signums is ok + let x32 = 3.21f32; + 1.23f32.signum() == x32.signum(); + 1.23f32.signum() == -(x32.signum()); + 1.23f32.signum() == 3.21f32.signum(); + + 1.23f32.signum() != x32.signum(); + 1.23f32.signum() != -(x32.signum()); + 1.23f32.signum() != 3.21f32.signum(); + + let x64 = 3.21f64; + 1.23f64.signum() == x64.signum(); + 1.23f64.signum() == -(x64.signum()); + 1.23f64.signum() == 3.21f64.signum(); + + 1.23f64.signum() != x64.signum(); + 1.23f64.signum() != -(x64.signum()); + 1.23f64.signum() != 3.21f64.signum(); + + // the comparison should also look through references + &0.0 == &ZERO; + &&&&0.0 == &&&&ZERO; +} diff --git a/src/tools/clippy/tests/ui/float_cmp.stderr b/src/tools/clippy/tests/ui/float_cmp.stderr new file mode 100644 index 0000000000..cb5b68b2e9 --- /dev/null +++ b/src/tools/clippy/tests/ui/float_cmp.stderr @@ -0,0 +1,51 @@ +error: strict comparison of `f32` or `f64` + --> $DIR/float_cmp.rs:58:5 + | +LL | ONE as f64 != 2.0; + | ^^^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(ONE as f64 - 2.0).abs() > error_margin` + | + = note: `-D clippy::float-cmp` implied by `-D warnings` + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` + --> $DIR/float_cmp.rs:63:5 + | +LL | x == 1.0; + | ^^^^^^^^ help: consider comparing them within some margin of error: `(x - 1.0).abs() < error_margin` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` + --> $DIR/float_cmp.rs:66:5 + | +LL | twice(x) != twice(ONE as f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(twice(x) - twice(ONE as f64)).abs() > error_margin` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` + --> $DIR/float_cmp.rs:86:5 + | +LL | NON_ZERO_ARRAY[i] == NON_ZERO_ARRAY[j]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(NON_ZERO_ARRAY[i] - NON_ZERO_ARRAY[j]).abs() < error_margin` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` arrays + --> $DIR/float_cmp.rs:91:5 + | +LL | a1 == a2; + | ^^^^^^^^ + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` + --> $DIR/float_cmp.rs:92:5 + | +LL | a1[0] == a2[0]; + | ^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(a1[0] - a2[0]).abs() < error_margin` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/float_cmp_const.rs b/src/tools/clippy/tests/ui/float_cmp_const.rs new file mode 100644 index 0000000000..86ce3bf3bd --- /dev/null +++ b/src/tools/clippy/tests/ui/float_cmp_const.rs @@ -0,0 +1,58 @@ +// does not test any rustfixable lints + +#![warn(clippy::float_cmp_const)] +#![allow(clippy::float_cmp)] +#![allow(unused, clippy::no_effect, clippy::unnecessary_operation)] + +const ONE: f32 = 1.0; +const TWO: f32 = 2.0; + +fn eq_one(x: f32) -> bool { + if x.is_nan() { false } else { x == ONE } // no error, inside "eq" fn +} + +fn main() { + // has errors + 1f32 == ONE; + TWO == ONE; + TWO != ONE; + ONE + ONE == TWO; + let x = 1; + x as f32 == ONE; + + let v = 0.9; + v == ONE; + v != ONE; + + // no errors, lower than or greater than comparisons + v < ONE; + v > ONE; + v <= ONE; + v >= ONE; + + // no errors, zero and infinity values + ONE != 0f32; + TWO == 0f32; + ONE != f32::INFINITY; + ONE == f32::NEG_INFINITY; + + // no errors, but will warn clippy::float_cmp if '#![allow(float_cmp)]' above is removed + let w = 1.1; + v == w; + v != w; + v == 1.0; + v != 1.0; + + const ZERO_ARRAY: [f32; 3] = [0.0, 0.0, 0.0]; + const ZERO_INF_ARRAY: [f32; 3] = [0.0, f32::INFINITY, f32::NEG_INFINITY]; + const NON_ZERO_ARRAY: [f32; 3] = [0.0, 0.1, 0.2]; + const NON_ZERO_ARRAY2: [f32; 3] = [0.2, 0.1, 0.0]; + + // no errors, zero and infinity values + NON_ZERO_ARRAY[0] == NON_ZERO_ARRAY2[1]; // lhs is 0.0 + ZERO_ARRAY == NON_ZERO_ARRAY; // lhs is all zeros + ZERO_INF_ARRAY == NON_ZERO_ARRAY; // lhs is all zeros or infinities + + // has errors + NON_ZERO_ARRAY == NON_ZERO_ARRAY2; +} diff --git a/src/tools/clippy/tests/ui/float_cmp_const.stderr b/src/tools/clippy/tests/ui/float_cmp_const.stderr new file mode 100644 index 0000000000..d8182cf855 --- /dev/null +++ b/src/tools/clippy/tests/ui/float_cmp_const.stderr @@ -0,0 +1,67 @@ +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:16:5 + | +LL | 1f32 == ONE; + | ^^^^^^^^^^^ help: consider comparing them within some margin of error: `(1f32 - ONE).abs() < error_margin` + | + = note: `-D clippy::float-cmp-const` implied by `-D warnings` + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:17:5 + | +LL | TWO == ONE; + | ^^^^^^^^^^ help: consider comparing them within some margin of error: `(TWO - ONE).abs() < error_margin` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:18:5 + | +LL | TWO != ONE; + | ^^^^^^^^^^ help: consider comparing them within some margin of error: `(TWO - ONE).abs() > error_margin` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:19:5 + | +LL | ONE + ONE == TWO; + | ^^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(ONE + ONE - TWO).abs() < error_margin` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:21:5 + | +LL | x as f32 == ONE; + | ^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(x as f32 - ONE).abs() < error_margin` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:24:5 + | +LL | v == ONE; + | ^^^^^^^^ help: consider comparing them within some margin of error: `(v - ONE).abs() < error_margin` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:25:5 + | +LL | v != ONE; + | ^^^^^^^^ help: consider comparing them within some margin of error: `(v - ONE).abs() > error_margin` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: strict comparison of `f32` or `f64` constant arrays + --> $DIR/float_cmp_const.rs:57:5 + | +LL | NON_ZERO_ARRAY == NON_ZERO_ARRAY2; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/float_equality_without_abs.rs b/src/tools/clippy/tests/ui/float_equality_without_abs.rs new file mode 100644 index 0000000000..d40fa00c31 --- /dev/null +++ b/src/tools/clippy/tests/ui/float_equality_without_abs.rs @@ -0,0 +1,31 @@ +#![warn(clippy::float_equality_without_abs)] + +pub fn is_roughly_equal(a: f32, b: f32) -> bool { + (a - b) < f32::EPSILON +} + +pub fn main() { + // all errors + is_roughly_equal(1.0, 2.0); + let a = 0.05; + let b = 0.0500001; + + let _ = (a - b) < f32::EPSILON; + let _ = a - b < f32::EPSILON; + let _ = a - b.abs() < f32::EPSILON; + let _ = (a as f64 - b as f64) < f64::EPSILON; + let _ = 1.0 - 2.0 < f32::EPSILON; + + let _ = f32::EPSILON > (a - b); + let _ = f32::EPSILON > a - b; + let _ = f32::EPSILON > a - b.abs(); + let _ = f64::EPSILON > (a as f64 - b as f64); + let _ = f32::EPSILON > 1.0 - 2.0; + + // those are correct + let _ = (a - b).abs() < f32::EPSILON; + let _ = (a as f64 - b as f64).abs() < f64::EPSILON; + + let _ = f32::EPSILON > (a - b).abs(); + let _ = f64::EPSILON > (a as f64 - b as f64).abs(); +} diff --git a/src/tools/clippy/tests/ui/float_equality_without_abs.stderr b/src/tools/clippy/tests/ui/float_equality_without_abs.stderr new file mode 100644 index 0000000000..b34c8159da --- /dev/null +++ b/src/tools/clippy/tests/ui/float_equality_without_abs.stderr @@ -0,0 +1,92 @@ +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:4:5 + | +LL | (a - b) < f32::EPSILON + | -------^^^^^^^^^^^^^^^ + | | + | help: add `.abs()`: `(a - b).abs()` + | + = note: `-D clippy::float-equality-without-abs` implied by `-D warnings` + +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:13:13 + | +LL | let _ = (a - b) < f32::EPSILON; + | -------^^^^^^^^^^^^^^^ + | | + | help: add `.abs()`: `(a - b).abs()` + +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:14:13 + | +LL | let _ = a - b < f32::EPSILON; + | -----^^^^^^^^^^^^^^^ + | | + | help: add `.abs()`: `(a - b).abs()` + +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:15:13 + | +LL | let _ = a - b.abs() < f32::EPSILON; + | -----------^^^^^^^^^^^^^^^ + | | + | help: add `.abs()`: `(a - b.abs()).abs()` + +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:16:13 + | +LL | let _ = (a as f64 - b as f64) < f64::EPSILON; + | ---------------------^^^^^^^^^^^^^^^ + | | + | help: add `.abs()`: `(a as f64 - b as f64).abs()` + +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:17:13 + | +LL | let _ = 1.0 - 2.0 < f32::EPSILON; + | ---------^^^^^^^^^^^^^^^ + | | + | help: add `.abs()`: `(1.0 - 2.0).abs()` + +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:19:13 + | +LL | let _ = f32::EPSILON > (a - b); + | ^^^^^^^^^^^^^^^------- + | | + | help: add `.abs()`: `(a - b).abs()` + +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:20:13 + | +LL | let _ = f32::EPSILON > a - b; + | ^^^^^^^^^^^^^^^----- + | | + | help: add `.abs()`: `(a - b).abs()` + +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:21:13 + | +LL | let _ = f32::EPSILON > a - b.abs(); + | ^^^^^^^^^^^^^^^----------- + | | + | help: add `.abs()`: `(a - b.abs()).abs()` + +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:22:13 + | +LL | let _ = f64::EPSILON > (a as f64 - b as f64); + | ^^^^^^^^^^^^^^^--------------------- + | | + | help: add `.abs()`: `(a as f64 - b as f64).abs()` + +error: float equality check without `.abs()` + --> $DIR/float_equality_without_abs.rs:23:13 + | +LL | let _ = f32::EPSILON > 1.0 - 2.0; + | ^^^^^^^^^^^^^^^--------- + | | + | help: add `.abs()`: `(1.0 - 2.0).abs()` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_abs.fixed b/src/tools/clippy/tests/ui/floating_point_abs.fixed new file mode 100644 index 0000000000..cea727257c --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_abs.fixed @@ -0,0 +1,78 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +struct A { + a: f64, + b: f64, +} + +fn fake_abs1(num: f64) -> f64 { + num.abs() +} + +fn fake_abs2(num: f64) -> f64 { + num.abs() +} + +fn fake_abs3(a: A) -> f64 { + a.a.abs() +} + +fn fake_abs4(num: f64) -> f64 { + num.abs() +} + +fn fake_abs5(a: A) -> f64 { + a.a.abs() +} + +fn fake_nabs1(num: f64) -> f64 { + -num.abs() +} + +fn fake_nabs2(num: f64) -> f64 { + -num.abs() +} + +fn fake_nabs3(a: A) -> A { + A { + a: -a.a.abs(), + b: a.b, + } +} + +fn not_fake_abs1(num: f64) -> f64 { + if num > 0.0 { num } else { -num - 1f64 } +} + +fn not_fake_abs2(num: f64) -> f64 { + if num > 0.0 { num + 1.0 } else { -(num + 1.0) } +} + +fn not_fake_abs3(num1: f64, num2: f64) -> f64 { + if num1 > 0.0 { num2 } else { -num2 } +} + +fn not_fake_abs4(a: A) -> f64 { + if a.a > 0.0 { a.b } else { -a.b } +} + +fn not_fake_abs5(a: A) -> f64 { + if a.a > 0.0 { a.a } else { -a.b } +} + +fn main() { + fake_abs1(5.0); + fake_abs2(5.0); + fake_abs3(A { a: 5.0, b: 5.0 }); + fake_abs4(5.0); + fake_abs5(A { a: 5.0, b: 5.0 }); + fake_nabs1(5.0); + fake_nabs2(5.0); + fake_nabs3(A { a: 5.0, b: 5.0 }); + not_fake_abs1(5.0); + not_fake_abs2(5.0); + not_fake_abs3(5.0, 5.0); + not_fake_abs4(A { a: 5.0, b: 5.0 }); + not_fake_abs5(A { a: 5.0, b: 5.0 }); +} diff --git a/src/tools/clippy/tests/ui/floating_point_abs.rs b/src/tools/clippy/tests/ui/floating_point_abs.rs new file mode 100644 index 0000000000..ba8a8f18fa --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_abs.rs @@ -0,0 +1,78 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +struct A { + a: f64, + b: f64, +} + +fn fake_abs1(num: f64) -> f64 { + if num >= 0.0 { num } else { -num } +} + +fn fake_abs2(num: f64) -> f64 { + if 0.0 < num { num } else { -num } +} + +fn fake_abs3(a: A) -> f64 { + if a.a > 0.0 { a.a } else { -a.a } +} + +fn fake_abs4(num: f64) -> f64 { + if 0.0 >= num { -num } else { num } +} + +fn fake_abs5(a: A) -> f64 { + if a.a < 0.0 { -a.a } else { a.a } +} + +fn fake_nabs1(num: f64) -> f64 { + if num < 0.0 { num } else { -num } +} + +fn fake_nabs2(num: f64) -> f64 { + if 0.0 >= num { num } else { -num } +} + +fn fake_nabs3(a: A) -> A { + A { + a: if a.a >= 0.0 { -a.a } else { a.a }, + b: a.b, + } +} + +fn not_fake_abs1(num: f64) -> f64 { + if num > 0.0 { num } else { -num - 1f64 } +} + +fn not_fake_abs2(num: f64) -> f64 { + if num > 0.0 { num + 1.0 } else { -(num + 1.0) } +} + +fn not_fake_abs3(num1: f64, num2: f64) -> f64 { + if num1 > 0.0 { num2 } else { -num2 } +} + +fn not_fake_abs4(a: A) -> f64 { + if a.a > 0.0 { a.b } else { -a.b } +} + +fn not_fake_abs5(a: A) -> f64 { + if a.a > 0.0 { a.a } else { -a.b } +} + +fn main() { + fake_abs1(5.0); + fake_abs2(5.0); + fake_abs3(A { a: 5.0, b: 5.0 }); + fake_abs4(5.0); + fake_abs5(A { a: 5.0, b: 5.0 }); + fake_nabs1(5.0); + fake_nabs2(5.0); + fake_nabs3(A { a: 5.0, b: 5.0 }); + not_fake_abs1(5.0); + not_fake_abs2(5.0); + not_fake_abs3(5.0, 5.0); + not_fake_abs4(A { a: 5.0, b: 5.0 }); + not_fake_abs5(A { a: 5.0, b: 5.0 }); +} diff --git a/src/tools/clippy/tests/ui/floating_point_abs.stderr b/src/tools/clippy/tests/ui/floating_point_abs.stderr new file mode 100644 index 0000000000..35af70201f --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_abs.stderr @@ -0,0 +1,52 @@ +error: manual implementation of `abs` method + --> $DIR/floating_point_abs.rs:10:5 + | +LL | if num >= 0.0 { num } else { -num } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.abs()` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: manual implementation of `abs` method + --> $DIR/floating_point_abs.rs:14:5 + | +LL | if 0.0 < num { num } else { -num } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.abs()` + +error: manual implementation of `abs` method + --> $DIR/floating_point_abs.rs:18:5 + | +LL | if a.a > 0.0 { a.a } else { -a.a } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.a.abs()` + +error: manual implementation of `abs` method + --> $DIR/floating_point_abs.rs:22:5 + | +LL | if 0.0 >= num { -num } else { num } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.abs()` + +error: manual implementation of `abs` method + --> $DIR/floating_point_abs.rs:26:5 + | +LL | if a.a < 0.0 { -a.a } else { a.a } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.a.abs()` + +error: manual implementation of negation of `abs` method + --> $DIR/floating_point_abs.rs:30:5 + | +LL | if num < 0.0 { num } else { -num } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-num.abs()` + +error: manual implementation of negation of `abs` method + --> $DIR/floating_point_abs.rs:34:5 + | +LL | if 0.0 >= num { num } else { -num } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-num.abs()` + +error: manual implementation of negation of `abs` method + --> $DIR/floating_point_abs.rs:39:12 + | +LL | a: if a.a >= 0.0 { -a.a } else { a.a }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-a.a.abs()` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_exp.fixed b/src/tools/clippy/tests/ui/floating_point_exp.fixed new file mode 100644 index 0000000000..ae7805fdf0 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_exp.fixed @@ -0,0 +1,18 @@ +// run-rustfix +#![warn(clippy::imprecise_flops)] + +fn main() { + let x = 2f32; + let _ = x.exp_m1(); + let _ = x.exp_m1() + 2.0; + // Cases where the lint shouldn't be applied + let _ = x.exp() - 2.0; + let _ = x.exp() - 1.0 * 2.0; + + let x = 2f64; + let _ = x.exp_m1(); + let _ = x.exp_m1() + 2.0; + // Cases where the lint shouldn't be applied + let _ = x.exp() - 2.0; + let _ = x.exp() - 1.0 * 2.0; +} diff --git a/src/tools/clippy/tests/ui/floating_point_exp.rs b/src/tools/clippy/tests/ui/floating_point_exp.rs new file mode 100644 index 0000000000..27e0b9bcbc --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_exp.rs @@ -0,0 +1,18 @@ +// run-rustfix +#![warn(clippy::imprecise_flops)] + +fn main() { + let x = 2f32; + let _ = x.exp() - 1.0; + let _ = x.exp() - 1.0 + 2.0; + // Cases where the lint shouldn't be applied + let _ = x.exp() - 2.0; + let _ = x.exp() - 1.0 * 2.0; + + let x = 2f64; + let _ = x.exp() - 1.0; + let _ = x.exp() - 1.0 + 2.0; + // Cases where the lint shouldn't be applied + let _ = x.exp() - 2.0; + let _ = x.exp() - 1.0 * 2.0; +} diff --git a/src/tools/clippy/tests/ui/floating_point_exp.stderr b/src/tools/clippy/tests/ui/floating_point_exp.stderr new file mode 100644 index 0000000000..5cd999ad47 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_exp.stderr @@ -0,0 +1,28 @@ +error: (e.pow(x) - 1) can be computed more accurately + --> $DIR/floating_point_exp.rs:6:13 + | +LL | let _ = x.exp() - 1.0; + | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` + | + = note: `-D clippy::imprecise-flops` implied by `-D warnings` + +error: (e.pow(x) - 1) can be computed more accurately + --> $DIR/floating_point_exp.rs:7:13 + | +LL | let _ = x.exp() - 1.0 + 2.0; + | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` + +error: (e.pow(x) - 1) can be computed more accurately + --> $DIR/floating_point_exp.rs:13:13 + | +LL | let _ = x.exp() - 1.0; + | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` + +error: (e.pow(x) - 1) can be computed more accurately + --> $DIR/floating_point_exp.rs:14:13 + | +LL | let _ = x.exp() - 1.0 + 2.0; + | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_hypot.fixed b/src/tools/clippy/tests/ui/floating_point_hypot.fixed new file mode 100644 index 0000000000..bbe411b3f4 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_hypot.fixed @@ -0,0 +1,14 @@ +// run-rustfix +#![warn(clippy::imprecise_flops)] + +fn main() { + let x = 3f32; + let y = 4f32; + let _ = x.hypot(y); + let _ = (x + 1f32).hypot(y); + let _ = x.hypot(y); + // Cases where the lint shouldn't be applied + // TODO: linting this adds some complexity, but could be done + let _ = x.mul_add(x, y * y).sqrt(); + let _ = (x * 4f32 + y * y).sqrt(); +} diff --git a/src/tools/clippy/tests/ui/floating_point_hypot.rs b/src/tools/clippy/tests/ui/floating_point_hypot.rs new file mode 100644 index 0000000000..586fd170ea --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_hypot.rs @@ -0,0 +1,14 @@ +// run-rustfix +#![warn(clippy::imprecise_flops)] + +fn main() { + let x = 3f32; + let y = 4f32; + let _ = (x * x + y * y).sqrt(); + let _ = ((x + 1f32) * (x + 1f32) + y * y).sqrt(); + let _ = (x.powi(2) + y.powi(2)).sqrt(); + // Cases where the lint shouldn't be applied + // TODO: linting this adds some complexity, but could be done + let _ = x.mul_add(x, y * y).sqrt(); + let _ = (x * 4f32 + y * y).sqrt(); +} diff --git a/src/tools/clippy/tests/ui/floating_point_hypot.stderr b/src/tools/clippy/tests/ui/floating_point_hypot.stderr new file mode 100644 index 0000000000..42069d9ee9 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_hypot.stderr @@ -0,0 +1,22 @@ +error: hypotenuse can be computed more accurately + --> $DIR/floating_point_hypot.rs:7:13 + | +LL | let _ = (x * x + y * y).sqrt(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.hypot(y)` + | + = note: `-D clippy::imprecise-flops` implied by `-D warnings` + +error: hypotenuse can be computed more accurately + --> $DIR/floating_point_hypot.rs:8:13 + | +LL | let _ = ((x + 1f32) * (x + 1f32) + y * y).sqrt(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 1f32).hypot(y)` + +error: hypotenuse can be computed more accurately + --> $DIR/floating_point_hypot.rs:9:13 + | +LL | let _ = (x.powi(2) + y.powi(2)).sqrt(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.hypot(y)` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_log.fixed b/src/tools/clippy/tests/ui/floating_point_log.fixed new file mode 100644 index 0000000000..7dc7ee94af --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_log.fixed @@ -0,0 +1,58 @@ +// run-rustfix +#![allow(dead_code, clippy::double_parens)] +#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)] + +const TWO: f32 = 2.0; +const E: f32 = std::f32::consts::E; + +fn check_log_base() { + let x = 1f32; + let _ = x.log2(); + let _ = x.log10(); + let _ = x.ln(); + let _ = x.log2(); + let _ = x.ln(); + + let x = 1f64; + let _ = x.log2(); + let _ = x.log10(); + let _ = x.ln(); +} + +fn check_ln1p() { + let x = 1f32; + let _ = 2.0f32.ln_1p(); + let _ = 2.0f32.ln_1p(); + let _ = x.ln_1p(); + let _ = (x / 2.0).ln_1p(); + let _ = x.powi(3).ln_1p(); + let _ = (x.powi(3) / 2.0).ln_1p(); + let _ = ((std::f32::consts::E - 1.0)).ln_1p(); + let _ = x.ln_1p(); + let _ = x.powi(3).ln_1p(); + let _ = (x + 2.0).ln_1p(); + let _ = (x / 2.0).ln_1p(); + // Cases where the lint shouldn't be applied + let _ = (1.0 + x + 2.0).ln(); + let _ = (x + 1.0 + 2.0).ln(); + let _ = (x + 1.0 / 2.0).ln(); + let _ = (1.0 + x - 2.0).ln(); + + let x = 1f64; + let _ = 2.0f64.ln_1p(); + let _ = 2.0f64.ln_1p(); + let _ = x.ln_1p(); + let _ = (x / 2.0).ln_1p(); + let _ = x.powi(3).ln_1p(); + let _ = x.ln_1p(); + let _ = x.powi(3).ln_1p(); + let _ = (x + 2.0).ln_1p(); + let _ = (x / 2.0).ln_1p(); + // Cases where the lint shouldn't be applied + let _ = (1.0 + x + 2.0).ln(); + let _ = (x + 1.0 + 2.0).ln(); + let _ = (x + 1.0 / 2.0).ln(); + let _ = (1.0 + x - 2.0).ln(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/floating_point_log.rs b/src/tools/clippy/tests/ui/floating_point_log.rs new file mode 100644 index 0000000000..01181484e7 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_log.rs @@ -0,0 +1,58 @@ +// run-rustfix +#![allow(dead_code, clippy::double_parens)] +#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)] + +const TWO: f32 = 2.0; +const E: f32 = std::f32::consts::E; + +fn check_log_base() { + let x = 1f32; + let _ = x.log(2f32); + let _ = x.log(10f32); + let _ = x.log(std::f32::consts::E); + let _ = x.log(TWO); + let _ = x.log(E); + + let x = 1f64; + let _ = x.log(2f64); + let _ = x.log(10f64); + let _ = x.log(std::f64::consts::E); +} + +fn check_ln1p() { + let x = 1f32; + let _ = (1f32 + 2.).ln(); + let _ = (1f32 + 2.0).ln(); + let _ = (1.0 + x).ln(); + let _ = (1.0 + x / 2.0).ln(); + let _ = (1.0 + x.powi(3)).ln(); + let _ = (1.0 + x.powi(3) / 2.0).ln(); + let _ = (1.0 + (std::f32::consts::E - 1.0)).ln(); + let _ = (x + 1.0).ln(); + let _ = (x.powi(3) + 1.0).ln(); + let _ = (x + 2.0 + 1.0).ln(); + let _ = (x / 2.0 + 1.0).ln(); + // Cases where the lint shouldn't be applied + let _ = (1.0 + x + 2.0).ln(); + let _ = (x + 1.0 + 2.0).ln(); + let _ = (x + 1.0 / 2.0).ln(); + let _ = (1.0 + x - 2.0).ln(); + + let x = 1f64; + let _ = (1f64 + 2.).ln(); + let _ = (1f64 + 2.0).ln(); + let _ = (1.0 + x).ln(); + let _ = (1.0 + x / 2.0).ln(); + let _ = (1.0 + x.powi(3)).ln(); + let _ = (x + 1.0).ln(); + let _ = (x.powi(3) + 1.0).ln(); + let _ = (x + 2.0 + 1.0).ln(); + let _ = (x / 2.0 + 1.0).ln(); + // Cases where the lint shouldn't be applied + let _ = (1.0 + x + 2.0).ln(); + let _ = (x + 1.0 + 2.0).ln(); + let _ = (x + 1.0 / 2.0).ln(); + let _ = (1.0 + x - 2.0).ln(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/floating_point_log.stderr b/src/tools/clippy/tests/ui/floating_point_log.stderr new file mode 100644 index 0000000000..900dc2b793 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_log.stderr @@ -0,0 +1,174 @@ +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:10:13 + | +LL | let _ = x.log(2f32); + | ^^^^^^^^^^^ help: consider using: `x.log2()` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:11:13 + | +LL | let _ = x.log(10f32); + | ^^^^^^^^^^^^ help: consider using: `x.log10()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:12:13 + | +LL | let _ = x.log(std::f32::consts::E); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:13:13 + | +LL | let _ = x.log(TWO); + | ^^^^^^^^^^ help: consider using: `x.log2()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:14:13 + | +LL | let _ = x.log(E); + | ^^^^^^^^ help: consider using: `x.ln()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:17:13 + | +LL | let _ = x.log(2f64); + | ^^^^^^^^^^^ help: consider using: `x.log2()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:18:13 + | +LL | let _ = x.log(10f64); + | ^^^^^^^^^^^^ help: consider using: `x.log10()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:19:13 + | +LL | let _ = x.log(std::f64::consts::E); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:24:13 + | +LL | let _ = (1f32 + 2.).ln(); + | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()` + | + = note: `-D clippy::imprecise-flops` implied by `-D warnings` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:25:13 + | +LL | let _ = (1f32 + 2.0).ln(); + | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:26:13 + | +LL | let _ = (1.0 + x).ln(); + | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:27:13 + | +LL | let _ = (1.0 + x / 2.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:28:13 + | +LL | let _ = (1.0 + x.powi(3)).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:29:13 + | +LL | let _ = (1.0 + x.powi(3) / 2.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x.powi(3) / 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:30:13 + | +LL | let _ = (1.0 + (std::f32::consts::E - 1.0)).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `((std::f32::consts::E - 1.0)).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:31:13 + | +LL | let _ = (x + 1.0).ln(); + | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:32:13 + | +LL | let _ = (x.powi(3) + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:33:13 + | +LL | let _ = (x + 2.0 + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:34:13 + | +LL | let _ = (x / 2.0 + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:42:13 + | +LL | let _ = (1f64 + 2.).ln(); + | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f64.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:43:13 + | +LL | let _ = (1f64 + 2.0).ln(); + | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f64.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:44:13 + | +LL | let _ = (1.0 + x).ln(); + | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:45:13 + | +LL | let _ = (1.0 + x / 2.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:46:13 + | +LL | let _ = (1.0 + x.powi(3)).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:47:13 + | +LL | let _ = (x + 1.0).ln(); + | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:48:13 + | +LL | let _ = (x.powi(3) + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:49:13 + | +LL | let _ = (x + 2.0 + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:50:13 + | +LL | let _ = (x / 2.0 + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` + +error: aborting due to 28 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_logbase.fixed b/src/tools/clippy/tests/ui/floating_point_logbase.fixed new file mode 100644 index 0000000000..13962a272d --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_logbase.fixed @@ -0,0 +1,16 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +fn main() { + let x = 3f32; + let y = 5f32; + let _ = x.log(y); + let _ = x.log(y); + let _ = x.log(y); + let _ = x.log(y); + // Cases where the lint shouldn't be applied + let _ = x.ln() / y.powf(3.2); + let _ = x.powf(3.2) / y.powf(3.2); + let _ = x.powf(3.2) / y.ln(); + let _ = x.log(5f32) / y.log(7f32); +} diff --git a/src/tools/clippy/tests/ui/floating_point_logbase.rs b/src/tools/clippy/tests/ui/floating_point_logbase.rs new file mode 100644 index 0000000000..26bc20d537 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_logbase.rs @@ -0,0 +1,16 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +fn main() { + let x = 3f32; + let y = 5f32; + let _ = x.ln() / y.ln(); + let _ = x.log2() / y.log2(); + let _ = x.log10() / y.log10(); + let _ = x.log(5f32) / y.log(5f32); + // Cases where the lint shouldn't be applied + let _ = x.ln() / y.powf(3.2); + let _ = x.powf(3.2) / y.powf(3.2); + let _ = x.powf(3.2) / y.ln(); + let _ = x.log(5f32) / y.log(7f32); +} diff --git a/src/tools/clippy/tests/ui/floating_point_logbase.stderr b/src/tools/clippy/tests/ui/floating_point_logbase.stderr new file mode 100644 index 0000000000..78354c2f62 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_logbase.stderr @@ -0,0 +1,28 @@ +error: log base can be expressed more clearly + --> $DIR/floating_point_logbase.rs:7:13 + | +LL | let _ = x.ln() / y.ln(); + | ^^^^^^^^^^^^^^^ help: consider using: `x.log(y)` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: log base can be expressed more clearly + --> $DIR/floating_point_logbase.rs:8:13 + | +LL | let _ = x.log2() / y.log2(); + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)` + +error: log base can be expressed more clearly + --> $DIR/floating_point_logbase.rs:9:13 + | +LL | let _ = x.log10() / y.log10(); + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)` + +error: log base can be expressed more clearly + --> $DIR/floating_point_logbase.rs:10:13 + | +LL | let _ = x.log(5f32) / y.log(5f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_mul_add.fixed b/src/tools/clippy/tests/ui/floating_point_mul_add.fixed new file mode 100644 index 0000000000..911700bab0 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_mul_add.fixed @@ -0,0 +1,26 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +fn main() { + let a: f64 = 1234.567; + let b: f64 = 45.67834; + let c: f64 = 0.0004; + let d: f64 = 0.0001; + + let _ = a.mul_add(b, c); + let _ = a.mul_add(b, c); + let _ = 2.0f64.mul_add(4.0, a); + let _ = 2.0f64.mul_add(4., a); + + let _ = a.mul_add(b, c); + let _ = a.mul_add(b, c); + let _ = (a * b).mul_add(c, d); + + let _ = a.mul_add(b, c).mul_add(a.mul_add(b, c), a.mul_add(b, c)) + c; + let _ = 1234.567_f64.mul_add(45.67834_f64, 0.0004_f64); + + let _ = a.mul_add(a, b).sqrt(); + + // Cases where the lint shouldn't be applied + let _ = (a * a + b * b).sqrt(); +} diff --git a/src/tools/clippy/tests/ui/floating_point_mul_add.rs b/src/tools/clippy/tests/ui/floating_point_mul_add.rs new file mode 100644 index 0000000000..d202385fc8 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_mul_add.rs @@ -0,0 +1,26 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +fn main() { + let a: f64 = 1234.567; + let b: f64 = 45.67834; + let c: f64 = 0.0004; + let d: f64 = 0.0001; + + let _ = a * b + c; + let _ = c + a * b; + let _ = a + 2.0 * 4.0; + let _ = a + 2. * 4.; + + let _ = (a * b) + c; + let _ = c + (a * b); + let _ = a * b * c + d; + + let _ = a.mul_add(b, c) * a.mul_add(b, c) + a.mul_add(b, c) + c; + let _ = 1234.567_f64 * 45.67834_f64 + 0.0004_f64; + + let _ = (a * a + b).sqrt(); + + // Cases where the lint shouldn't be applied + let _ = (a * a + b * b).sqrt(); +} diff --git a/src/tools/clippy/tests/ui/floating_point_mul_add.stderr b/src/tools/clippy/tests/ui/floating_point_mul_add.stderr new file mode 100644 index 0000000000..ac8d0c0cae --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_mul_add.stderr @@ -0,0 +1,64 @@ +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:10:13 + | +LL | let _ = a * b + c; + | ^^^^^^^^^ help: consider using: `a.mul_add(b, c)` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:11:13 + | +LL | let _ = c + a * b; + | ^^^^^^^^^ help: consider using: `a.mul_add(b, c)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:12:13 + | +LL | let _ = a + 2.0 * 4.0; + | ^^^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(4.0, a)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:13:13 + | +LL | let _ = a + 2. * 4.; + | ^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(4., a)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:15:13 + | +LL | let _ = (a * b) + c; + | ^^^^^^^^^^^ help: consider using: `a.mul_add(b, c)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:16:13 + | +LL | let _ = c + (a * b); + | ^^^^^^^^^^^ help: consider using: `a.mul_add(b, c)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:17:13 + | +LL | let _ = a * b * c + d; + | ^^^^^^^^^^^^^ help: consider using: `(a * b).mul_add(c, d)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:19:13 + | +LL | let _ = a.mul_add(b, c) * a.mul_add(b, c) + a.mul_add(b, c) + c; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `a.mul_add(b, c).mul_add(a.mul_add(b, c), a.mul_add(b, c))` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:20:13 + | +LL | let _ = 1234.567_f64 * 45.67834_f64 + 0.0004_f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1234.567_f64.mul_add(45.67834_f64, 0.0004_f64)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:22:13 + | +LL | let _ = (a * a + b).sqrt(); + | ^^^^^^^^^^^ help: consider using: `a.mul_add(a, b)` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_powf.fixed b/src/tools/clippy/tests/ui/floating_point_powf.fixed new file mode 100644 index 0000000000..b0641a100c --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_powf.fixed @@ -0,0 +1,42 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)] + +fn main() { + let x = 3f32; + let _ = x.exp2(); + let _ = 3.1f32.exp2(); + let _ = (-3.1f32).exp2(); + let _ = x.exp(); + let _ = 3.1f32.exp(); + let _ = (-3.1f32).exp(); + let _ = x.sqrt(); + let _ = x.cbrt(); + let _ = x.powi(3); + let _ = x.powi(-2); + let _ = x.powi(16_777_215); + let _ = x.powi(-16_777_215); + // Cases where the lint shouldn't be applied + let _ = x.powf(2.1); + let _ = x.powf(-2.1); + let _ = x.powf(16_777_216.0); + let _ = x.powf(-16_777_216.0); + + let x = 3f64; + let _ = x.exp2(); + let _ = 3.1f64.exp2(); + let _ = (-3.1f64).exp2(); + let _ = x.exp(); + let _ = 3.1f64.exp(); + let _ = (-3.1f64).exp(); + let _ = x.sqrt(); + let _ = x.cbrt(); + let _ = x.powi(3); + let _ = x.powi(-2); + let _ = x.powi(-2_147_483_648); + let _ = x.powi(2_147_483_647); + // Cases where the lint shouldn't be applied + let _ = x.powf(2.1); + let _ = x.powf(-2.1); + let _ = x.powf(-2_147_483_649.0); + let _ = x.powf(2_147_483_648.0); +} diff --git a/src/tools/clippy/tests/ui/floating_point_powf.rs b/src/tools/clippy/tests/ui/floating_point_powf.rs new file mode 100644 index 0000000000..a0a2c97390 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_powf.rs @@ -0,0 +1,42 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)] + +fn main() { + let x = 3f32; + let _ = 2f32.powf(x); + let _ = 2f32.powf(3.1); + let _ = 2f32.powf(-3.1); + let _ = std::f32::consts::E.powf(x); + let _ = std::f32::consts::E.powf(3.1); + let _ = std::f32::consts::E.powf(-3.1); + let _ = x.powf(1.0 / 2.0); + let _ = x.powf(1.0 / 3.0); + let _ = x.powf(3.0); + let _ = x.powf(-2.0); + let _ = x.powf(16_777_215.0); + let _ = x.powf(-16_777_215.0); + // Cases where the lint shouldn't be applied + let _ = x.powf(2.1); + let _ = x.powf(-2.1); + let _ = x.powf(16_777_216.0); + let _ = x.powf(-16_777_216.0); + + let x = 3f64; + let _ = 2f64.powf(x); + let _ = 2f64.powf(3.1); + let _ = 2f64.powf(-3.1); + let _ = std::f64::consts::E.powf(x); + let _ = std::f64::consts::E.powf(3.1); + let _ = std::f64::consts::E.powf(-3.1); + let _ = x.powf(1.0 / 2.0); + let _ = x.powf(1.0 / 3.0); + let _ = x.powf(3.0); + let _ = x.powf(-2.0); + let _ = x.powf(-2_147_483_648.0); + let _ = x.powf(2_147_483_647.0); + // Cases where the lint shouldn't be applied + let _ = x.powf(2.1); + let _ = x.powf(-2.1); + let _ = x.powf(-2_147_483_649.0); + let _ = x.powf(2_147_483_648.0); +} diff --git a/src/tools/clippy/tests/ui/floating_point_powf.stderr b/src/tools/clippy/tests/ui/floating_point_powf.stderr new file mode 100644 index 0000000000..2422eb911e --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_powf.stderr @@ -0,0 +1,150 @@ +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:6:13 + | +LL | let _ = 2f32.powf(x); + | ^^^^^^^^^^^^ help: consider using: `x.exp2()` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:7:13 + | +LL | let _ = 2f32.powf(3.1); + | ^^^^^^^^^^^^^^ help: consider using: `3.1f32.exp2()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:8:13 + | +LL | let _ = 2f32.powf(-3.1); + | ^^^^^^^^^^^^^^^ help: consider using: `(-3.1f32).exp2()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:9:13 + | +LL | let _ = std::f32::consts::E.powf(x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.exp()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:10:13 + | +LL | let _ = std::f32::consts::E.powf(3.1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `3.1f32.exp()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:11:13 + | +LL | let _ = std::f32::consts::E.powf(-3.1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-3.1f32).exp()` + +error: square-root of a number can be computed more efficiently and accurately + --> $DIR/floating_point_powf.rs:12:13 + | +LL | let _ = x.powf(1.0 / 2.0); + | ^^^^^^^^^^^^^^^^^ help: consider using: `x.sqrt()` + +error: cube-root of a number can be computed more accurately + --> $DIR/floating_point_powf.rs:13:13 + | +LL | let _ = x.powf(1.0 / 3.0); + | ^^^^^^^^^^^^^^^^^ help: consider using: `x.cbrt()` + | + = note: `-D clippy::imprecise-flops` implied by `-D warnings` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:14:13 + | +LL | let _ = x.powf(3.0); + | ^^^^^^^^^^^ help: consider using: `x.powi(3)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:15:13 + | +LL | let _ = x.powf(-2.0); + | ^^^^^^^^^^^^ help: consider using: `x.powi(-2)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:16:13 + | +LL | let _ = x.powf(16_777_215.0); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(16_777_215)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:17:13 + | +LL | let _ = x.powf(-16_777_215.0); + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(-16_777_215)` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:25:13 + | +LL | let _ = 2f64.powf(x); + | ^^^^^^^^^^^^ help: consider using: `x.exp2()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:26:13 + | +LL | let _ = 2f64.powf(3.1); + | ^^^^^^^^^^^^^^ help: consider using: `3.1f64.exp2()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:27:13 + | +LL | let _ = 2f64.powf(-3.1); + | ^^^^^^^^^^^^^^^ help: consider using: `(-3.1f64).exp2()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:28:13 + | +LL | let _ = std::f64::consts::E.powf(x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.exp()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:29:13 + | +LL | let _ = std::f64::consts::E.powf(3.1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `3.1f64.exp()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:30:13 + | +LL | let _ = std::f64::consts::E.powf(-3.1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-3.1f64).exp()` + +error: square-root of a number can be computed more efficiently and accurately + --> $DIR/floating_point_powf.rs:31:13 + | +LL | let _ = x.powf(1.0 / 2.0); + | ^^^^^^^^^^^^^^^^^ help: consider using: `x.sqrt()` + +error: cube-root of a number can be computed more accurately + --> $DIR/floating_point_powf.rs:32:13 + | +LL | let _ = x.powf(1.0 / 3.0); + | ^^^^^^^^^^^^^^^^^ help: consider using: `x.cbrt()` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:33:13 + | +LL | let _ = x.powf(3.0); + | ^^^^^^^^^^^ help: consider using: `x.powi(3)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:34:13 + | +LL | let _ = x.powf(-2.0); + | ^^^^^^^^^^^^ help: consider using: `x.powi(-2)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:35:13 + | +LL | let _ = x.powf(-2_147_483_648.0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(-2_147_483_648)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:36:13 + | +LL | let _ = x.powf(2_147_483_647.0); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2_147_483_647)` + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_powi.fixed b/src/tools/clippy/tests/ui/floating_point_powi.fixed new file mode 100644 index 0000000000..5676240059 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_powi.fixed @@ -0,0 +1,19 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +fn main() { + let one = 1; + let x = 3f32; + let _ = x * x; + let _ = x * x; + + let y = 4f32; + let _ = x.mul_add(x, y); + let _ = y.mul_add(y, x); + let _ = x.mul_add(x, y).sqrt(); + let _ = y.mul_add(y, x).sqrt(); + // Cases where the lint shouldn't be applied + let _ = x.powi(3); + let _ = x.powi(one + 1); + let _ = (x.powi(2) + y.powi(2)).sqrt(); +} diff --git a/src/tools/clippy/tests/ui/floating_point_powi.rs b/src/tools/clippy/tests/ui/floating_point_powi.rs new file mode 100644 index 0000000000..1f800e4628 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_powi.rs @@ -0,0 +1,19 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +fn main() { + let one = 1; + let x = 3f32; + let _ = x.powi(2); + let _ = x.powi(1 + 1); + + let y = 4f32; + let _ = x.powi(2) + y; + let _ = x + y.powi(2); + let _ = (x.powi(2) + y).sqrt(); + let _ = (x + y.powi(2)).sqrt(); + // Cases where the lint shouldn't be applied + let _ = x.powi(3); + let _ = x.powi(one + 1); + let _ = (x.powi(2) + y.powi(2)).sqrt(); +} diff --git a/src/tools/clippy/tests/ui/floating_point_powi.stderr b/src/tools/clippy/tests/ui/floating_point_powi.stderr new file mode 100644 index 0000000000..d5a5f1bcca --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_powi.stderr @@ -0,0 +1,40 @@ +error: square can be computed more efficiently + --> $DIR/floating_point_powi.rs:7:13 + | +LL | let _ = x.powi(2); + | ^^^^^^^^^ help: consider using: `x * x` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: square can be computed more efficiently + --> $DIR/floating_point_powi.rs:8:13 + | +LL | let _ = x.powi(1 + 1); + | ^^^^^^^^^^^^^ help: consider using: `x * x` + +error: square can be computed more efficiently + --> $DIR/floating_point_powi.rs:11:13 + | +LL | let _ = x.powi(2) + y; + | ^^^^^^^^^^^^^ help: consider using: `x.mul_add(x, y)` + +error: square can be computed more efficiently + --> $DIR/floating_point_powi.rs:12:13 + | +LL | let _ = x + y.powi(2); + | ^^^^^^^^^^^^^ help: consider using: `y.mul_add(y, x)` + +error: square can be computed more efficiently + --> $DIR/floating_point_powi.rs:13:13 + | +LL | let _ = (x.powi(2) + y).sqrt(); + | ^^^^^^^^^^^^^^^ help: consider using: `x.mul_add(x, y)` + +error: square can be computed more efficiently + --> $DIR/floating_point_powi.rs:14:13 + | +LL | let _ = (x + y.powi(2)).sqrt(); + | ^^^^^^^^^^^^^^^ help: consider using: `y.mul_add(y, x)` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_rad.fixed b/src/tools/clippy/tests/ui/floating_point_rad.fixed new file mode 100644 index 0000000000..92480c5db8 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_rad.fixed @@ -0,0 +1,13 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +fn main() { + let x = 3f32; + let _ = x.to_degrees(); + let _ = x.to_radians(); + // Cases where the lint shouldn't be applied + let _ = x * 90f32 / std::f32::consts::PI; + let _ = x * std::f32::consts::PI / 90f32; + let _ = x * 180f32 / std::f32::consts::E; + let _ = x * std::f32::consts::E / 180f32; +} diff --git a/src/tools/clippy/tests/ui/floating_point_rad.rs b/src/tools/clippy/tests/ui/floating_point_rad.rs new file mode 100644 index 0000000000..062e7c3fdc --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_rad.rs @@ -0,0 +1,13 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +fn main() { + let x = 3f32; + let _ = x * 180f32 / std::f32::consts::PI; + let _ = x * std::f32::consts::PI / 180f32; + // Cases where the lint shouldn't be applied + let _ = x * 90f32 / std::f32::consts::PI; + let _ = x * std::f32::consts::PI / 90f32; + let _ = x * 180f32 / std::f32::consts::E; + let _ = x * std::f32::consts::E / 180f32; +} diff --git a/src/tools/clippy/tests/ui/floating_point_rad.stderr b/src/tools/clippy/tests/ui/floating_point_rad.stderr new file mode 100644 index 0000000000..a6ffdca64e --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_rad.stderr @@ -0,0 +1,16 @@ +error: conversion to degrees can be done more accurately + --> $DIR/floating_point_rad.rs:6:13 + | +LL | let _ = x * 180f32 / std::f32::consts::PI; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.to_degrees()` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: conversion to radians can be done more accurately + --> $DIR/floating_point_rad.rs:7:13 + | +LL | let _ = x * std::f32::consts::PI / 180f32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.to_radians()` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/fn_address_comparisons.rs b/src/tools/clippy/tests/ui/fn_address_comparisons.rs new file mode 100644 index 0000000000..362dcb4fd8 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_address_comparisons.rs @@ -0,0 +1,20 @@ +use std::fmt::Debug; +use std::ptr; +use std::rc::Rc; +use std::sync::Arc; + +fn a() {} + +#[warn(clippy::fn_address_comparisons)] +fn main() { + type F = fn(); + let f: F = a; + let g: F = f; + + // These should fail: + let _ = f == a; + let _ = f != a; + + // These should be fine: + let _ = f == g; +} diff --git a/src/tools/clippy/tests/ui/fn_address_comparisons.stderr b/src/tools/clippy/tests/ui/fn_address_comparisons.stderr new file mode 100644 index 0000000000..9c1b5419a4 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_address_comparisons.stderr @@ -0,0 +1,16 @@ +error: comparing with a non-unique address of a function item + --> $DIR/fn_address_comparisons.rs:15:13 + | +LL | let _ = f == a; + | ^^^^^^ + | + = note: `-D clippy::fn-address-comparisons` implied by `-D warnings` + +error: comparing with a non-unique address of a function item + --> $DIR/fn_address_comparisons.rs:16:13 + | +LL | let _ = f != a; + | ^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/fn_params_excessive_bools.rs b/src/tools/clippy/tests/ui/fn_params_excessive_bools.rs new file mode 100644 index 0000000000..7d6fd607e6 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_params_excessive_bools.rs @@ -0,0 +1,44 @@ +#![warn(clippy::fn_params_excessive_bools)] + +extern "C" { + fn f(_: bool, _: bool, _: bool, _: bool); +} + +macro_rules! foo { + () => { + fn fff(_: bool, _: bool, _: bool, _: bool) {} + }; +} + +foo!(); + +#[no_mangle] +extern "C" fn k(_: bool, _: bool, _: bool, _: bool) {} +fn g(_: bool, _: bool, _: bool, _: bool) {} +fn h(_: bool, _: bool, _: bool) {} +fn e(_: S, _: S, _: Box, _: Vec) {} +fn t(_: S, _: S, _: Box, _: Vec, _: bool, _: bool, _: bool, _: bool) {} + +struct S {} +trait Trait { + fn f(_: bool, _: bool, _: bool, _: bool); + fn g(_: bool, _: bool, _: bool, _: Vec); +} + +impl S { + fn f(&self, _: bool, _: bool, _: bool, _: bool) {} + fn g(&self, _: bool, _: bool, _: bool) {} + #[no_mangle] + extern "C" fn h(_: bool, _: bool, _: bool, _: bool) {} +} + +impl Trait for S { + fn f(_: bool, _: bool, _: bool, _: bool) {} + fn g(_: bool, _: bool, _: bool, _: Vec) {} +} + +fn main() { + fn n(_: bool, _: u32, _: bool, _: Box, _: bool, _: bool) { + fn nn(_: bool, _: bool, _: bool, _: bool) {} + } +} diff --git a/src/tools/clippy/tests/ui/fn_params_excessive_bools.stderr b/src/tools/clippy/tests/ui/fn_params_excessive_bools.stderr new file mode 100644 index 0000000000..4e5dbc261d --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_params_excessive_bools.stderr @@ -0,0 +1,53 @@ +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:17:1 + | +LL | fn g(_: bool, _: bool, _: bool, _: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::fn-params-excessive-bools` implied by `-D warnings` + = help: consider refactoring bools into two-variant enums + +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:20:1 + | +LL | fn t(_: S, _: S, _: Box, _: Vec, _: bool, _: bool, _: bool, _: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider refactoring bools into two-variant enums + +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:24:5 + | +LL | fn f(_: bool, _: bool, _: bool, _: bool); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider refactoring bools into two-variant enums + +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:29:5 + | +LL | fn f(&self, _: bool, _: bool, _: bool, _: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider refactoring bools into two-variant enums + +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:41:5 + | +LL | / fn n(_: bool, _: u32, _: bool, _: Box, _: bool, _: bool) { +LL | | fn nn(_: bool, _: bool, _: bool, _: bool) {} +LL | | } + | |_____^ + | + = help: consider refactoring bools into two-variant enums + +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:42:9 + | +LL | fn nn(_: bool, _: bool, _: bool, _: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider refactoring bools into two-variant enums + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/fn_to_numeric_cast.rs b/src/tools/clippy/tests/ui/fn_to_numeric_cast.rs new file mode 100644 index 0000000000..a456c085c8 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_to_numeric_cast.rs @@ -0,0 +1,55 @@ +// ignore-32bit + +#![warn(clippy::fn_to_numeric_cast, clippy::fn_to_numeric_cast_with_truncation)] + +fn foo() -> String { + String::new() +} + +fn test_function_to_numeric_cast() { + let _ = foo as i8; + let _ = foo as i16; + let _ = foo as i32; + let _ = foo as i64; + let _ = foo as i128; + let _ = foo as isize; + + let _ = foo as u8; + let _ = foo as u16; + let _ = foo as u32; + let _ = foo as u64; + let _ = foo as u128; + + // Casting to usize is OK and should not warn + let _ = foo as usize; + + // Cast `f` (a `FnDef`) to `fn()` should not warn + fn f() {} + let _ = f as fn(); +} + +fn test_function_var_to_numeric_cast() { + let abc: fn() -> String = foo; + + let _ = abc as i8; + let _ = abc as i16; + let _ = abc as i32; + let _ = abc as i64; + let _ = abc as i128; + let _ = abc as isize; + + let _ = abc as u8; + let _ = abc as u16; + let _ = abc as u32; + let _ = abc as u64; + let _ = abc as u128; + + // Casting to usize is OK and should not warn + let _ = abc as usize; +} + +fn fn_with_fn_args(f: fn(i32) -> i32) -> i32 { + f as i32 +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/fn_to_numeric_cast.stderr b/src/tools/clippy/tests/ui/fn_to_numeric_cast.stderr new file mode 100644 index 0000000000..e9549e157c --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_to_numeric_cast.stderr @@ -0,0 +1,144 @@ +error: casting function pointer `foo` to `i8`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:10:13 + | +LL | let _ = foo as i8; + | ^^^^^^^^^ help: try: `foo as usize` + | + = note: `-D clippy::fn-to-numeric-cast-with-truncation` implied by `-D warnings` + +error: casting function pointer `foo` to `i16`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:11:13 + | +LL | let _ = foo as i16; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `i32`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:12:13 + | +LL | let _ = foo as i32; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `i64` + --> $DIR/fn_to_numeric_cast.rs:13:13 + | +LL | let _ = foo as i64; + | ^^^^^^^^^^ help: try: `foo as usize` + | + = note: `-D clippy::fn-to-numeric-cast` implied by `-D warnings` + +error: casting function pointer `foo` to `i128` + --> $DIR/fn_to_numeric_cast.rs:14:13 + | +LL | let _ = foo as i128; + | ^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `isize` + --> $DIR/fn_to_numeric_cast.rs:15:13 + | +LL | let _ = foo as isize; + | ^^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u8`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:17:13 + | +LL | let _ = foo as u8; + | ^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u16`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:18:13 + | +LL | let _ = foo as u16; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u32`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:19:13 + | +LL | let _ = foo as u32; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u64` + --> $DIR/fn_to_numeric_cast.rs:20:13 + | +LL | let _ = foo as u64; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u128` + --> $DIR/fn_to_numeric_cast.rs:21:13 + | +LL | let _ = foo as u128; + | ^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `abc` to `i8`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:34:13 + | +LL | let _ = abc as i8; + | ^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i16`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:35:13 + | +LL | let _ = abc as i16; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i32`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:36:13 + | +LL | let _ = abc as i32; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i64` + --> $DIR/fn_to_numeric_cast.rs:37:13 + | +LL | let _ = abc as i64; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i128` + --> $DIR/fn_to_numeric_cast.rs:38:13 + | +LL | let _ = abc as i128; + | ^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `isize` + --> $DIR/fn_to_numeric_cast.rs:39:13 + | +LL | let _ = abc as isize; + | ^^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u8`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:41:13 + | +LL | let _ = abc as u8; + | ^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u16`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:42:13 + | +LL | let _ = abc as u16; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u32`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:43:13 + | +LL | let _ = abc as u32; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u64` + --> $DIR/fn_to_numeric_cast.rs:44:13 + | +LL | let _ = abc as u64; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u128` + --> $DIR/fn_to_numeric_cast.rs:45:13 + | +LL | let _ = abc as u128; + | ^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `f` to `i32`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:52:5 + | +LL | f as i32 + | ^^^^^^^^ help: try: `f as usize` + +error: aborting due to 23 previous errors + diff --git a/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.rs b/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.rs new file mode 100644 index 0000000000..04ee985c08 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.rs @@ -0,0 +1,55 @@ +// ignore-64bit + +#![warn(clippy::fn_to_numeric_cast, clippy::fn_to_numeric_cast_with_truncation)] + +fn foo() -> String { + String::new() +} + +fn test_function_to_numeric_cast() { + let _ = foo as i8; + let _ = foo as i16; + let _ = foo as i32; + let _ = foo as i64; + let _ = foo as i128; + let _ = foo as isize; + + let _ = foo as u8; + let _ = foo as u16; + let _ = foo as u32; + let _ = foo as u64; + let _ = foo as u128; + + // Casting to usize is OK and should not warn + let _ = foo as usize; + + // Cast `f` (a `FnDef`) to `fn()` should not warn + fn f() {} + let _ = f as fn(); +} + +fn test_function_var_to_numeric_cast() { + let abc: fn() -> String = foo; + + let _ = abc as i8; + let _ = abc as i16; + let _ = abc as i32; + let _ = abc as i64; + let _ = abc as i128; + let _ = abc as isize; + + let _ = abc as u8; + let _ = abc as u16; + let _ = abc as u32; + let _ = abc as u64; + let _ = abc as u128; + + // Casting to usize is OK and should not warn + let _ = abc as usize; +} + +fn fn_with_fn_args(f: fn(i32) -> i32) -> i32 { + f as i32 +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.stderr b/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.stderr new file mode 100644 index 0000000000..08dd611d67 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.stderr @@ -0,0 +1,144 @@ +error: casting function pointer `foo` to `i8`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:10:13 + | +LL | let _ = foo as i8; + | ^^^^^^^^^ help: try: `foo as usize` + | + = note: `-D clippy::fn-to-numeric-cast-with-truncation` implied by `-D warnings` + +error: casting function pointer `foo` to `i16`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:11:13 + | +LL | let _ = foo as i16; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `i32` + --> $DIR/fn_to_numeric_cast_32bit.rs:12:13 + | +LL | let _ = foo as i32; + | ^^^^^^^^^^ help: try: `foo as usize` + | + = note: `-D clippy::fn-to-numeric-cast` implied by `-D warnings` + +error: casting function pointer `foo` to `i64` + --> $DIR/fn_to_numeric_cast_32bit.rs:13:13 + | +LL | let _ = foo as i64; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `i128` + --> $DIR/fn_to_numeric_cast_32bit.rs:14:13 + | +LL | let _ = foo as i128; + | ^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `isize` + --> $DIR/fn_to_numeric_cast_32bit.rs:15:13 + | +LL | let _ = foo as isize; + | ^^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u8`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:17:13 + | +LL | let _ = foo as u8; + | ^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u16`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:18:13 + | +LL | let _ = foo as u16; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u32` + --> $DIR/fn_to_numeric_cast_32bit.rs:19:13 + | +LL | let _ = foo as u32; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u64` + --> $DIR/fn_to_numeric_cast_32bit.rs:20:13 + | +LL | let _ = foo as u64; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u128` + --> $DIR/fn_to_numeric_cast_32bit.rs:21:13 + | +LL | let _ = foo as u128; + | ^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `abc` to `i8`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:34:13 + | +LL | let _ = abc as i8; + | ^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i16`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:35:13 + | +LL | let _ = abc as i16; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i32` + --> $DIR/fn_to_numeric_cast_32bit.rs:36:13 + | +LL | let _ = abc as i32; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i64` + --> $DIR/fn_to_numeric_cast_32bit.rs:37:13 + | +LL | let _ = abc as i64; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i128` + --> $DIR/fn_to_numeric_cast_32bit.rs:38:13 + | +LL | let _ = abc as i128; + | ^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `isize` + --> $DIR/fn_to_numeric_cast_32bit.rs:39:13 + | +LL | let _ = abc as isize; + | ^^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u8`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:41:13 + | +LL | let _ = abc as u8; + | ^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u16`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:42:13 + | +LL | let _ = abc as u16; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u32` + --> $DIR/fn_to_numeric_cast_32bit.rs:43:13 + | +LL | let _ = abc as u32; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u64` + --> $DIR/fn_to_numeric_cast_32bit.rs:44:13 + | +LL | let _ = abc as u64; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u128` + --> $DIR/fn_to_numeric_cast_32bit.rs:45:13 + | +LL | let _ = abc as u128; + | ^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `f` to `i32` + --> $DIR/fn_to_numeric_cast_32bit.rs:52:5 + | +LL | f as i32 + | ^^^^^^^^ help: try: `f as usize` + +error: aborting due to 23 previous errors + diff --git a/src/tools/clippy/tests/ui/for_kv_map.rs b/src/tools/clippy/tests/ui/for_kv_map.rs new file mode 100644 index 0000000000..39a8d960a7 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_kv_map.rs @@ -0,0 +1,50 @@ +#![warn(clippy::for_kv_map)] +#![allow(clippy::used_underscore_binding)] + +use std::collections::*; +use std::rc::Rc; + +fn main() { + let m: HashMap = HashMap::new(); + for (_, v) in &m { + let _v = v; + } + + let m: Rc> = Rc::new(HashMap::new()); + for (_, v) in &*m { + let _v = v; + // Here the `*` is not actually necessary, but the test tests that we don't + // suggest + // `in *m.values()` as we used to + } + + let mut m: HashMap = HashMap::new(); + for (_, v) in &mut m { + let _v = v; + } + + let m: &mut HashMap = &mut HashMap::new(); + for (_, v) in &mut *m { + let _v = v; + } + + let m: HashMap = HashMap::new(); + let rm = &m; + for (k, _value) in rm { + let _k = k; + } + + // The following should not produce warnings. + + let m: HashMap = HashMap::new(); + // No error, _value is actually used + for (k, _value) in &m { + let _ = _value; + let _k = k; + } + + let m: HashMap = Default::default(); + for (_, v) in m { + let _v = v; + } +} diff --git a/src/tools/clippy/tests/ui/for_kv_map.stderr b/src/tools/clippy/tests/ui/for_kv_map.stderr new file mode 100644 index 0000000000..241758c542 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_kv_map.stderr @@ -0,0 +1,58 @@ +error: you seem to want to iterate on a map's values + --> $DIR/for_kv_map.rs:9:19 + | +LL | for (_, v) in &m { + | ^^ + | + = note: `-D clippy::for-kv-map` implied by `-D warnings` +help: use the corresponding method + | +LL | for v in m.values() { + | ^ ^^^^^^^^^^ + +error: you seem to want to iterate on a map's values + --> $DIR/for_kv_map.rs:14:19 + | +LL | for (_, v) in &*m { + | ^^^ + | +help: use the corresponding method + | +LL | for v in (*m).values() { + | ^ ^^^^^^^^^^^^^ + +error: you seem to want to iterate on a map's values + --> $DIR/for_kv_map.rs:22:19 + | +LL | for (_, v) in &mut m { + | ^^^^^^ + | +help: use the corresponding method + | +LL | for v in m.values_mut() { + | ^ ^^^^^^^^^^^^^^ + +error: you seem to want to iterate on a map's values + --> $DIR/for_kv_map.rs:27:19 + | +LL | for (_, v) in &mut *m { + | ^^^^^^^ + | +help: use the corresponding method + | +LL | for v in (*m).values_mut() { + | ^ ^^^^^^^^^^^^^^^^^ + +error: you seem to want to iterate on a map's keys + --> $DIR/for_kv_map.rs:33:24 + | +LL | for (k, _value) in rm { + | ^^ + | +help: use the corresponding method + | +LL | for k in rm.keys() { + | ^ ^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/for_loop_fixable.fixed b/src/tools/clippy/tests/ui/for_loop_fixable.fixed new file mode 100644 index 0000000000..249a88a0b3 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_fixable.fixed @@ -0,0 +1,283 @@ +// run-rustfix + +#![allow(dead_code, unused)] + +use std::collections::*; + +#[warn(clippy::all)] +struct Unrelated(Vec); +impl Unrelated { + fn next(&self) -> std::slice::Iter { + self.0.iter() + } + + fn iter(&self) -> std::slice::Iter { + self.0.iter() + } +} + +#[warn( + clippy::needless_range_loop, + clippy::explicit_iter_loop, + clippy::explicit_into_iter_loop, + clippy::iter_next_loop, + clippy::for_kv_map +)] +#[allow( + clippy::linkedlist, + clippy::shadow_unrelated, + clippy::unnecessary_mut_passed, + clippy::similar_names +)] +#[allow(clippy::many_single_char_names, unused_variables)] +fn main() { + let mut vec = vec![1, 2, 3, 4]; + + // See #601 + for i in 0..10 { + // no error, id_col does not exist outside the loop + let mut id_col = vec![0f64; 10]; + id_col[i] = 1f64; + } + + for _v in &vec {} + + for _v in &mut vec {} + + let out_vec = vec![1, 2, 3]; + for _v in out_vec {} + + for _v in &vec {} // these are fine + for _v in &mut vec {} // these are fine + + for _v in &[1, 2, 3] {} + + for _v in (&mut [1, 2, 3]).iter() {} // no error + + for _v in &[0; 32] {} + + for _v in [0; 33].iter() {} // no error + + let ll: LinkedList<()> = LinkedList::new(); + for _v in &ll {} + + let vd: VecDeque<()> = VecDeque::new(); + for _v in &vd {} + + let bh: BinaryHeap<()> = BinaryHeap::new(); + for _v in &bh {} + + let hm: HashMap<(), ()> = HashMap::new(); + for _v in &hm {} + + let bt: BTreeMap<(), ()> = BTreeMap::new(); + for _v in &bt {} + + let hs: HashSet<()> = HashSet::new(); + for _v in &hs {} + + let bs: BTreeSet<()> = BTreeSet::new(); + for _v in &bs {} + + let u = Unrelated(vec![]); + for _v in u.next() {} // no error + for _v in u.iter() {} // no error + + let mut out = vec![]; + vec.iter().cloned().map(|x| out.push(x)).collect::>(); + let _y = vec.iter().cloned().map(|x| out.push(x)).collect::>(); // this is fine + + // Loop with explicit counter variable + + // Potential false positives + let mut _index = 0; + _index = 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + _index += 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + if true { + _index = 1 + } + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + let mut _index = 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index += 1; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index *= 2; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index = 1; + _index += 1 + } + + let mut _index = 0; + + for _v in &vec { + let mut _index = 0; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index += 1; + _index = 0; + } + + let mut _index = 0; + for _v in &vec { + for _x in 0..1 { + _index += 1; + } + _index += 1 + } + + let mut _index = 0; + for x in &vec { + if *x == 1 { + _index += 1 + } + } + + let mut _index = 0; + if true { + _index = 1 + }; + for _v in &vec { + _index += 1 + } + + let mut _index = 1; + if false { + _index = 0 + }; + for _v in &vec { + _index += 1 + } + + let mut index = 0; + { + let mut _x = &mut index; + } + for _v in &vec { + _index += 1 + } + + let mut index = 0; + for _v in &vec { + index += 1 + } + println!("index: {}", index); + + fn f(_: &T, _: &T) -> bool { + unimplemented!() + } + fn g(_: &mut [T], _: usize, _: usize) { + unimplemented!() + } + for i in 1..vec.len() { + if f(&vec[i - 1], &vec[i]) { + g(&mut vec, i - 1, i); + } + } + + for mid in 1..vec.len() { + let (_, _) = vec.split_at(mid); + } +} + +fn partition(v: &mut [T]) -> usize { + let pivot = v.len() - 1; + let mut i = 0; + for j in 0..pivot { + if v[j] <= v[pivot] { + v.swap(i, j); + i += 1; + } + } + v.swap(i, pivot); + i +} + +#[warn(clippy::needless_range_loop)] +pub fn manual_copy_same_destination(dst: &mut [i32], d: usize, s: usize) { + // Same source and destination - don't trigger lint + for i in 0..dst.len() { + dst[d + i] = dst[s + i]; + } +} + +mod issue_2496 { + pub trait Handle { + fn new_for_index(index: usize) -> Self; + fn index(&self) -> usize; + } + + pub fn test() -> H { + for x in 0..5 { + let next_handle = H::new_for_index(x); + println!("{}", next_handle.index()); + } + unimplemented!() + } +} + +// explicit_into_iter_loop bad suggestions +#[warn(clippy::explicit_into_iter_loop, clippy::explicit_iter_loop)] +mod issue_4958 { + fn takes_iterator(iterator: &T) + where + for<'a> &'a T: IntoIterator, + { + for i in iterator { + println!("{}", i); + } + } + + struct T; + impl IntoIterator for &T { + type Item = (); + type IntoIter = std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + vec![].into_iter() + } + } + + fn more_tests() { + let t = T; + let r = &t; + let rr = &&t; + + // This case is handled by `explicit_iter_loop`. No idea why. + for _ in &t {} + + for _ in r {} + + // No suggestion for this. + // We'd have to suggest `for _ in *rr {}` which is less clear. + for _ in rr.into_iter() {} + } +} diff --git a/src/tools/clippy/tests/ui/for_loop_fixable.rs b/src/tools/clippy/tests/ui/for_loop_fixable.rs new file mode 100644 index 0000000000..306d85a635 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_fixable.rs @@ -0,0 +1,283 @@ +// run-rustfix + +#![allow(dead_code, unused)] + +use std::collections::*; + +#[warn(clippy::all)] +struct Unrelated(Vec); +impl Unrelated { + fn next(&self) -> std::slice::Iter { + self.0.iter() + } + + fn iter(&self) -> std::slice::Iter { + self.0.iter() + } +} + +#[warn( + clippy::needless_range_loop, + clippy::explicit_iter_loop, + clippy::explicit_into_iter_loop, + clippy::iter_next_loop, + clippy::for_kv_map +)] +#[allow( + clippy::linkedlist, + clippy::shadow_unrelated, + clippy::unnecessary_mut_passed, + clippy::similar_names +)] +#[allow(clippy::many_single_char_names, unused_variables)] +fn main() { + let mut vec = vec![1, 2, 3, 4]; + + // See #601 + for i in 0..10 { + // no error, id_col does not exist outside the loop + let mut id_col = vec![0f64; 10]; + id_col[i] = 1f64; + } + + for _v in vec.iter() {} + + for _v in vec.iter_mut() {} + + let out_vec = vec![1, 2, 3]; + for _v in out_vec.into_iter() {} + + for _v in &vec {} // these are fine + for _v in &mut vec {} // these are fine + + for _v in [1, 2, 3].iter() {} + + for _v in (&mut [1, 2, 3]).iter() {} // no error + + for _v in [0; 32].iter() {} + + for _v in [0; 33].iter() {} // no error + + let ll: LinkedList<()> = LinkedList::new(); + for _v in ll.iter() {} + + let vd: VecDeque<()> = VecDeque::new(); + for _v in vd.iter() {} + + let bh: BinaryHeap<()> = BinaryHeap::new(); + for _v in bh.iter() {} + + let hm: HashMap<(), ()> = HashMap::new(); + for _v in hm.iter() {} + + let bt: BTreeMap<(), ()> = BTreeMap::new(); + for _v in bt.iter() {} + + let hs: HashSet<()> = HashSet::new(); + for _v in hs.iter() {} + + let bs: BTreeSet<()> = BTreeSet::new(); + for _v in bs.iter() {} + + let u = Unrelated(vec![]); + for _v in u.next() {} // no error + for _v in u.iter() {} // no error + + let mut out = vec![]; + vec.iter().cloned().map(|x| out.push(x)).collect::>(); + let _y = vec.iter().cloned().map(|x| out.push(x)).collect::>(); // this is fine + + // Loop with explicit counter variable + + // Potential false positives + let mut _index = 0; + _index = 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + _index += 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + if true { + _index = 1 + } + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + let mut _index = 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index += 1; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index *= 2; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index = 1; + _index += 1 + } + + let mut _index = 0; + + for _v in &vec { + let mut _index = 0; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index += 1; + _index = 0; + } + + let mut _index = 0; + for _v in &vec { + for _x in 0..1 { + _index += 1; + } + _index += 1 + } + + let mut _index = 0; + for x in &vec { + if *x == 1 { + _index += 1 + } + } + + let mut _index = 0; + if true { + _index = 1 + }; + for _v in &vec { + _index += 1 + } + + let mut _index = 1; + if false { + _index = 0 + }; + for _v in &vec { + _index += 1 + } + + let mut index = 0; + { + let mut _x = &mut index; + } + for _v in &vec { + _index += 1 + } + + let mut index = 0; + for _v in &vec { + index += 1 + } + println!("index: {}", index); + + fn f(_: &T, _: &T) -> bool { + unimplemented!() + } + fn g(_: &mut [T], _: usize, _: usize) { + unimplemented!() + } + for i in 1..vec.len() { + if f(&vec[i - 1], &vec[i]) { + g(&mut vec, i - 1, i); + } + } + + for mid in 1..vec.len() { + let (_, _) = vec.split_at(mid); + } +} + +fn partition(v: &mut [T]) -> usize { + let pivot = v.len() - 1; + let mut i = 0; + for j in 0..pivot { + if v[j] <= v[pivot] { + v.swap(i, j); + i += 1; + } + } + v.swap(i, pivot); + i +} + +#[warn(clippy::needless_range_loop)] +pub fn manual_copy_same_destination(dst: &mut [i32], d: usize, s: usize) { + // Same source and destination - don't trigger lint + for i in 0..dst.len() { + dst[d + i] = dst[s + i]; + } +} + +mod issue_2496 { + pub trait Handle { + fn new_for_index(index: usize) -> Self; + fn index(&self) -> usize; + } + + pub fn test() -> H { + for x in 0..5 { + let next_handle = H::new_for_index(x); + println!("{}", next_handle.index()); + } + unimplemented!() + } +} + +// explicit_into_iter_loop bad suggestions +#[warn(clippy::explicit_into_iter_loop, clippy::explicit_iter_loop)] +mod issue_4958 { + fn takes_iterator(iterator: &T) + where + for<'a> &'a T: IntoIterator, + { + for i in iterator.into_iter() { + println!("{}", i); + } + } + + struct T; + impl IntoIterator for &T { + type Item = (); + type IntoIter = std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + vec![].into_iter() + } + } + + fn more_tests() { + let t = T; + let r = &t; + let rr = &&t; + + // This case is handled by `explicit_iter_loop`. No idea why. + for _ in t.into_iter() {} + + for _ in r.into_iter() {} + + // No suggestion for this. + // We'd have to suggest `for _ in *rr {}` which is less clear. + for _ in rr.into_iter() {} + } +} diff --git a/src/tools/clippy/tests/ui/for_loop_fixable.stderr b/src/tools/clippy/tests/ui/for_loop_fixable.stderr new file mode 100644 index 0000000000..ddfe66d675 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_fixable.stderr @@ -0,0 +1,96 @@ +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:43:15 + | +LL | for _v in vec.iter() {} + | ^^^^^^^^^^ help: to write this more concisely, try: `&vec` + | + = note: `-D clippy::explicit-iter-loop` implied by `-D warnings` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:45:15 + | +LL | for _v in vec.iter_mut() {} + | ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&mut vec` + +error: it is more concise to loop over containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:48:15 + | +LL | for _v in out_vec.into_iter() {} + | ^^^^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `out_vec` + | + = note: `-D clippy::explicit-into-iter-loop` implied by `-D warnings` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:53:15 + | +LL | for _v in [1, 2, 3].iter() {} + | ^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[1, 2, 3]` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:57:15 + | +LL | for _v in [0; 32].iter() {} + | ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[0; 32]` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:62:15 + | +LL | for _v in ll.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&ll` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:65:15 + | +LL | for _v in vd.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&vd` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:68:15 + | +LL | for _v in bh.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&bh` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:71:15 + | +LL | for _v in hm.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&hm` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:74:15 + | +LL | for _v in bt.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&bt` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:77:15 + | +LL | for _v in hs.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&hs` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:80:15 + | +LL | for _v in bs.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&bs` + +error: it is more concise to loop over containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:255:18 + | +LL | for i in iterator.into_iter() { + | ^^^^^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `iterator` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:275:18 + | +LL | for _ in t.into_iter() {} + | ^^^^^^^^^^^^^ help: to write this more concisely, try: `&t` + +error: it is more concise to loop over containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:277:18 + | +LL | for _ in r.into_iter() {} + | ^^^^^^^^^^^^^ help: to write this more concisely, try: `r` + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/for_loop_unfixable.rs b/src/tools/clippy/tests/ui/for_loop_unfixable.rs new file mode 100644 index 0000000000..e73536052f --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_unfixable.rs @@ -0,0 +1,22 @@ +// Tests from for_loop.rs that don't have suggestions + +#[warn( + clippy::needless_range_loop, + clippy::explicit_iter_loop, + clippy::explicit_into_iter_loop, + clippy::iter_next_loop, + clippy::for_kv_map +)] +#[allow( + clippy::linkedlist, + clippy::shadow_unrelated, + clippy::unnecessary_mut_passed, + clippy::similar_names, + unused, + dead_code +)] +fn main() { + let vec = vec![1, 2, 3, 4]; + + for _v in vec.iter().next() {} +} diff --git a/src/tools/clippy/tests/ui/for_loop_unfixable.stderr b/src/tools/clippy/tests/ui/for_loop_unfixable.stderr new file mode 100644 index 0000000000..1c9287b6ac --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_unfixable.stderr @@ -0,0 +1,10 @@ +error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want + --> $DIR/for_loop_unfixable.rs:21:15 + | +LL | for _v in vec.iter().next() {} + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::iter-next-loop` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/for_loops_over_fallibles.rs b/src/tools/clippy/tests/ui/for_loops_over_fallibles.rs new file mode 100644 index 0000000000..1b9dde87cd --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loops_over_fallibles.rs @@ -0,0 +1,57 @@ +#![warn(clippy::for_loops_over_fallibles)] + +fn for_loops_over_fallibles() { + let option = Some(1); + let result = option.ok_or("x not found"); + let v = vec![0, 1, 2]; + + // check over an `Option` + for x in option { + println!("{}", x); + } + + // check over a `Result` + for x in result { + println!("{}", x); + } + + for x in option.ok_or("x not found") { + println!("{}", x); + } + + // make sure LOOP_OVER_NEXT lint takes clippy::precedence when next() is the last call + // in the chain + for x in v.iter().next() { + println!("{}", x); + } + + // make sure we lint when next() is not the last call in the chain + for x in v.iter().next().and(Some(0)) { + println!("{}", x); + } + + for x in v.iter().next().ok_or("x not found") { + println!("{}", x); + } + + // check for false positives + + // for loop false positive + for x in v { + println!("{}", x); + } + + // while let false positive for Option + while let Some(x) = option { + println!("{}", x); + break; + } + + // while let false positive for Result + while let Ok(x) = result { + println!("{}", x); + break; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/for_loops_over_fallibles.stderr b/src/tools/clippy/tests/ui/for_loops_over_fallibles.stderr new file mode 100644 index 0000000000..52b94875ae --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loops_over_fallibles.stderr @@ -0,0 +1,71 @@ +error: for loop over `option`, which is an `Option`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:9:14 + | +LL | for x in option { + | ^^^^^^ + | + = note: `-D clippy::for-loops-over-fallibles` implied by `-D warnings` + = help: consider replacing `for x in option` with `if let Some(x) = option` + +error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:14:14 + | +LL | for x in result { + | ^^^^^^ + | + = help: consider replacing `for x in result` with `if let Ok(x) = result` + +error: for loop over `option.ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:18:14 + | +LL | for x in option.ok_or("x not found") { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in option.ok_or("x not found")` with `if let Ok(x) = option.ok_or("x not found")` + +error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want + --> $DIR/for_loops_over_fallibles.rs:24:14 + | +LL | for x in v.iter().next() { + | ^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::iter_next_loop)]` on by default + +error: for loop over `v.iter().next().and(Some(0))`, which is an `Option`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:29:14 + | +LL | for x in v.iter().next().and(Some(0)) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in v.iter().next().and(Some(0))` with `if let Some(x) = v.iter().next().and(Some(0))` + +error: for loop over `v.iter().next().ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:33:14 + | +LL | for x in v.iter().next().ok_or("x not found") { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in v.iter().next().ok_or("x not found")` with `if let Ok(x) = v.iter().next().ok_or("x not found")` + +error: this loop never actually loops + --> $DIR/for_loops_over_fallibles.rs:45:5 + | +LL | / while let Some(x) = option { +LL | | println!("{}", x); +LL | | break; +LL | | } + | |_____^ + | + = note: `#[deny(clippy::never_loop)]` on by default + +error: this loop never actually loops + --> $DIR/for_loops_over_fallibles.rs:51:5 + | +LL | / while let Ok(x) = result { +LL | | println!("{}", x); +LL | | break; +LL | | } + | |_____^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/forget_ref.rs b/src/tools/clippy/tests/ui/forget_ref.rs new file mode 100644 index 0000000000..c49e6756a6 --- /dev/null +++ b/src/tools/clippy/tests/ui/forget_ref.rs @@ -0,0 +1,49 @@ +#![warn(clippy::forget_ref)] +#![allow(clippy::toplevel_ref_arg)] +#![allow(clippy::unnecessary_wraps)] + +use std::mem::forget; + +struct SomeStruct; + +fn main() { + forget(&SomeStruct); + + let mut owned = SomeStruct; + forget(&owned); + forget(&&owned); + forget(&mut owned); + forget(owned); //OK + + let reference1 = &SomeStruct; + forget(&*reference1); + + let reference2 = &mut SomeStruct; + forget(reference2); + + let ref reference3 = SomeStruct; + forget(reference3); +} + +#[allow(dead_code)] +fn test_generic_fn_forget(val: T) { + forget(&val); + forget(val); //OK +} + +#[allow(dead_code)] +fn test_similarly_named_function() { + fn forget(_val: T) {} + forget(&SomeStruct); //OK; call to unrelated function which happens to have the same name + std::mem::forget(&SomeStruct); +} + +#[derive(Copy, Clone)] +pub struct Error; +fn produce_half_owl_error() -> Result<(), Error> { + Ok(()) +} + +fn produce_half_owl_ok() -> Result { + Ok(true) +} diff --git a/src/tools/clippy/tests/ui/forget_ref.stderr b/src/tools/clippy/tests/ui/forget_ref.stderr new file mode 100644 index 0000000000..73409388ed --- /dev/null +++ b/src/tools/clippy/tests/ui/forget_ref.stderr @@ -0,0 +1,111 @@ +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing + --> $DIR/forget_ref.rs:10:5 + | +LL | forget(&SomeStruct); + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::forget-ref` implied by `-D warnings` +note: argument has type `&SomeStruct` + --> $DIR/forget_ref.rs:10:12 + | +LL | forget(&SomeStruct); + | ^^^^^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing + --> $DIR/forget_ref.rs:13:5 + | +LL | forget(&owned); + | ^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/forget_ref.rs:13:12 + | +LL | forget(&owned); + | ^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing + --> $DIR/forget_ref.rs:14:5 + | +LL | forget(&&owned); + | ^^^^^^^^^^^^^^^ + | +note: argument has type `&&SomeStruct` + --> $DIR/forget_ref.rs:14:12 + | +LL | forget(&&owned); + | ^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing + --> $DIR/forget_ref.rs:15:5 + | +LL | forget(&mut owned); + | ^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&mut SomeStruct` + --> $DIR/forget_ref.rs:15:12 + | +LL | forget(&mut owned); + | ^^^^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing + --> $DIR/forget_ref.rs:19:5 + | +LL | forget(&*reference1); + | ^^^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/forget_ref.rs:19:12 + | +LL | forget(&*reference1); + | ^^^^^^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing + --> $DIR/forget_ref.rs:22:5 + | +LL | forget(reference2); + | ^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&mut SomeStruct` + --> $DIR/forget_ref.rs:22:12 + | +LL | forget(reference2); + | ^^^^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing + --> $DIR/forget_ref.rs:25:5 + | +LL | forget(reference3); + | ^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/forget_ref.rs:25:12 + | +LL | forget(reference3); + | ^^^^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing + --> $DIR/forget_ref.rs:30:5 + | +LL | forget(&val); + | ^^^^^^^^^^^^ + | +note: argument has type `&T` + --> $DIR/forget_ref.rs:30:12 + | +LL | forget(&val); + | ^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing + --> $DIR/forget_ref.rs:38:5 + | +LL | std::mem::forget(&SomeStruct); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/forget_ref.rs:38:22 + | +LL | std::mem::forget(&SomeStruct); + | ^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/format.fixed b/src/tools/clippy/tests/ui/format.fixed new file mode 100644 index 0000000000..740a22a07d --- /dev/null +++ b/src/tools/clippy/tests/ui/format.fixed @@ -0,0 +1,68 @@ +// run-rustfix + +#![allow(clippy::print_literal, clippy::redundant_clone)] +#![warn(clippy::useless_format)] + +struct Foo(pub String); + +macro_rules! foo { + ($($t:tt)*) => (Foo(format!($($t)*))) +} + +fn main() { + "foo".to_string(); + "{}".to_string(); + "{} abc {}".to_string(); + r##"foo {} +" bar"##.to_string(); + + "foo".to_string(); + format!("{:?}", "foo"); // Don't warn about `Debug`. + format!("{:8}", "foo"); + format!("{:width$}", "foo", width = 8); + "foo".to_string(); // Warn when the format makes no difference. + "foo".to_string(); // Warn when the format makes no difference. + format!("foo {}", "bar"); + format!("{} bar", "foo"); + + let arg: String = "".to_owned(); + arg.to_string(); + format!("{:?}", arg); // Don't warn about debug. + format!("{:8}", arg); + format!("{:width$}", arg, width = 8); + arg.to_string(); // Warn when the format makes no difference. + arg.to_string(); // Warn when the format makes no difference. + format!("foo {}", arg); + format!("{} bar", arg); + + // We don’t want to warn for non-string args; see issue #697. + format!("{}", 42); + format!("{:?}", 42); + format!("{:+}", 42); + format!("foo {}", 42); + format!("{} bar", 42); + + // We only want to warn about `format!` itself. + println!("foo"); + println!("{}", "foo"); + println!("foo {}", "foo"); + println!("{}", 42); + println!("foo {}", 42); + + // A `format!` inside a macro should not trigger a warning. + foo!("should not warn"); + + // Precision on string means slicing without panicking on size. + format!("{:.1}", "foo"); // Could be `"foo"[..1]` + format!("{:.10}", "foo"); // Could not be `"foo"[..10]` + format!("{:.prec$}", "foo", prec = 1); + format!("{:.prec$}", "foo", prec = 10); + + 42.to_string(); + let x = std::path::PathBuf::from("/bar/foo/qux"); + x.display().to_string(); + + // False positive + let a = "foo".to_string(); + let _ = Some(a + "bar"); +} diff --git a/src/tools/clippy/tests/ui/format.rs b/src/tools/clippy/tests/ui/format.rs new file mode 100644 index 0000000000..b604d79cca --- /dev/null +++ b/src/tools/clippy/tests/ui/format.rs @@ -0,0 +1,70 @@ +// run-rustfix + +#![allow(clippy::print_literal, clippy::redundant_clone)] +#![warn(clippy::useless_format)] + +struct Foo(pub String); + +macro_rules! foo { + ($($t:tt)*) => (Foo(format!($($t)*))) +} + +fn main() { + format!("foo"); + format!("{{}}"); + format!("{{}} abc {{}}"); + format!( + r##"foo {{}} +" bar"## + ); + + format!("{}", "foo"); + format!("{:?}", "foo"); // Don't warn about `Debug`. + format!("{:8}", "foo"); + format!("{:width$}", "foo", width = 8); + format!("{:+}", "foo"); // Warn when the format makes no difference. + format!("{:<}", "foo"); // Warn when the format makes no difference. + format!("foo {}", "bar"); + format!("{} bar", "foo"); + + let arg: String = "".to_owned(); + format!("{}", arg); + format!("{:?}", arg); // Don't warn about debug. + format!("{:8}", arg); + format!("{:width$}", arg, width = 8); + format!("{:+}", arg); // Warn when the format makes no difference. + format!("{:<}", arg); // Warn when the format makes no difference. + format!("foo {}", arg); + format!("{} bar", arg); + + // We don’t want to warn for non-string args; see issue #697. + format!("{}", 42); + format!("{:?}", 42); + format!("{:+}", 42); + format!("foo {}", 42); + format!("{} bar", 42); + + // We only want to warn about `format!` itself. + println!("foo"); + println!("{}", "foo"); + println!("foo {}", "foo"); + println!("{}", 42); + println!("foo {}", 42); + + // A `format!` inside a macro should not trigger a warning. + foo!("should not warn"); + + // Precision on string means slicing without panicking on size. + format!("{:.1}", "foo"); // Could be `"foo"[..1]` + format!("{:.10}", "foo"); // Could not be `"foo"[..10]` + format!("{:.prec$}", "foo", prec = 1); + format!("{:.prec$}", "foo", prec = 10); + + format!("{}", 42.to_string()); + let x = std::path::PathBuf::from("/bar/foo/qux"); + format!("{}", x.display().to_string()); + + // False positive + let a = "foo".to_string(); + let _ = Some(format!("{}", a + "bar")); +} diff --git a/src/tools/clippy/tests/ui/format.stderr b/src/tools/clippy/tests/ui/format.stderr new file mode 100644 index 0000000000..96df7f37f7 --- /dev/null +++ b/src/tools/clippy/tests/ui/format.stderr @@ -0,0 +1,91 @@ +error: useless use of `format!` + --> $DIR/format.rs:13:5 + | +LL | format!("foo"); + | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string();` + | + = note: `-D clippy::useless-format` implied by `-D warnings` + +error: useless use of `format!` + --> $DIR/format.rs:14:5 + | +LL | format!("{{}}"); + | ^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{}".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:15:5 + | +LL | format!("{{}} abc {{}}"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{} abc {}".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:16:5 + | +LL | / format!( +LL | | r##"foo {{}} +LL | | " bar"## +LL | | ); + | |______^ + | +help: consider using `.to_string()` + | +LL | r##"foo {} +LL | " bar"##.to_string(); + | + +error: useless use of `format!` + --> $DIR/format.rs:21:5 + | +LL | format!("{}", "foo"); + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:25:5 + | +LL | format!("{:+}", "foo"); // Warn when the format makes no difference. + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:26:5 + | +LL | format!("{:<}", "foo"); // Warn when the format makes no difference. + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:31:5 + | +LL | format!("{}", arg); + | ^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:35:5 + | +LL | format!("{:+}", arg); // Warn when the format makes no difference. + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:36:5 + | +LL | format!("{:<}", arg); // Warn when the format makes no difference. + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:63:5 + | +LL | format!("{}", 42.to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `42.to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:65:5 + | +LL | format!("{}", x.display().to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.display().to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:69:18 + | +LL | let _ = Some(format!("{}", a + "bar")); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `a + "bar"` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/formatting.rs b/src/tools/clippy/tests/ui/formatting.rs new file mode 100644 index 0000000000..0d14807ff1 --- /dev/null +++ b/src/tools/clippy/tests/ui/formatting.rs @@ -0,0 +1,72 @@ +#![warn(clippy::all)] +#![allow(unused_variables)] +#![allow(unused_assignments)] +#![allow(clippy::if_same_then_else)] +#![allow(clippy::deref_addrof)] + +fn foo() -> bool { + true +} + +#[rustfmt::skip] +fn main() { + // weird op_eq formatting: + let mut a = 42; + a =- 35; + a =* &191; + + let mut b = true; + b =! false; + + // those are ok: + a = -35; + a = *&191; + b = !false; + + // possible missing comma in an array + let _ = &[ + -1, -2, -3 // <= no comma here + -4, -5, -6 + ]; + let _ = &[ + -1, -2, -3 // <= no comma here + *4, -5, -6 + ]; + + // those are ok: + let _ = &[ + -1, -2, -3, + -4, -5, -6 + ]; + let _ = &[ + -1, -2, -3, + -4, -5, -6, + ]; + let _ = &[ + 1 + 2, 3 + + 4, 5 + 6, + ]; + + // don't lint for bin op without unary equiv + // issue 3244 + vec![ + 1 + / 2, + ]; + // issue 3396 + vec![ + true + | false, + ]; + + // don't lint if the indentation suggests not to + let _ = &[ + 1 + 2, 3 + - 4, 5 + ]; + // lint if it doesn't + let _ = &[ + -1 + -4, + ]; +} diff --git a/src/tools/clippy/tests/ui/formatting.stderr b/src/tools/clippy/tests/ui/formatting.stderr new file mode 100644 index 0000000000..bde434c7e2 --- /dev/null +++ b/src/tools/clippy/tests/ui/formatting.stderr @@ -0,0 +1,52 @@ +error: this looks like you are trying to use `.. -= ..`, but you really are doing `.. = (- ..)` + --> $DIR/formatting.rs:15:6 + | +LL | a =- 35; + | ^^^^ + | + = note: `-D clippy::suspicious-assignment-formatting` implied by `-D warnings` + = note: to remove this lint, use either `-=` or `= -` + +error: this looks like you are trying to use `.. *= ..`, but you really are doing `.. = (* ..)` + --> $DIR/formatting.rs:16:6 + | +LL | a =* &191; + | ^^^^ + | + = note: to remove this lint, use either `*=` or `= *` + +error: this looks like you are trying to use `.. != ..`, but you really are doing `.. = (! ..)` + --> $DIR/formatting.rs:19:6 + | +LL | b =! false; + | ^^^^ + | + = note: to remove this lint, use either `!=` or `= !` + +error: possibly missing a comma here + --> $DIR/formatting.rs:28:19 + | +LL | -1, -2, -3 // <= no comma here + | ^ + | + = note: `-D clippy::possible-missing-comma` implied by `-D warnings` + = note: to remove this lint, add a comma or write the expr in a single line + +error: possibly missing a comma here + --> $DIR/formatting.rs:32:19 + | +LL | -1, -2, -3 // <= no comma here + | ^ + | + = note: to remove this lint, add a comma or write the expr in a single line + +error: possibly missing a comma here + --> $DIR/formatting.rs:69:11 + | +LL | -1 + | ^ + | + = note: to remove this lint, add a comma or write the expr in a single line + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/from_iter_instead_of_collect.fixed b/src/tools/clippy/tests/ui/from_iter_instead_of_collect.fixed new file mode 100644 index 0000000000..b5f548810e --- /dev/null +++ b/src/tools/clippy/tests/ui/from_iter_instead_of_collect.fixed @@ -0,0 +1,48 @@ +// run-rustfix + +#![warn(clippy::from_iter_instead_of_collect)] +#![allow(unused_imports)] + +use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; +use std::iter::FromIterator; + +fn main() { + let iter_expr = std::iter::repeat(5).take(5); + let _ = iter_expr.collect::>(); + + let _ = vec![5, 5, 5, 5].iter().enumerate().collect::>(); + + Vec::from_iter(vec![42u32]); + + let a = vec![0, 1, 2]; + assert_eq!(a, (0..3).collect::>()); + assert_eq!(a, (0..3).collect::>()); + + let mut b = (0..3).collect::>(); + b.push_back(4); + + let mut b = (0..3).collect::>(); + b.push_back(4); + + { + use std::collections; + let mut b = (0..3).collect::>(); + b.push_back(4); + } + + let values = [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]; + let bm = values.iter().cloned().collect::>(); + let mut bar = bm.range(0..2).collect::>(); + bar.insert(&4, &'e'); + + let mut bts = (0..3).collect::>(); + bts.insert(2); + { + use std::collections; + let _ = (0..3).collect::>(); + let _ = (0..3).collect::>(); + } + + for _i in [1, 2, 3].iter().collect::>() {} + for _i in [1, 2, 3].iter().collect::>() {} +} diff --git a/src/tools/clippy/tests/ui/from_iter_instead_of_collect.rs b/src/tools/clippy/tests/ui/from_iter_instead_of_collect.rs new file mode 100644 index 0000000000..b842b5451d --- /dev/null +++ b/src/tools/clippy/tests/ui/from_iter_instead_of_collect.rs @@ -0,0 +1,48 @@ +// run-rustfix + +#![warn(clippy::from_iter_instead_of_collect)] +#![allow(unused_imports)] + +use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; +use std::iter::FromIterator; + +fn main() { + let iter_expr = std::iter::repeat(5).take(5); + let _ = Vec::from_iter(iter_expr); + + let _ = HashMap::::from_iter(vec![5, 5, 5, 5].iter().enumerate()); + + Vec::from_iter(vec![42u32]); + + let a = vec![0, 1, 2]; + assert_eq!(a, Vec::from_iter(0..3)); + assert_eq!(a, Vec::::from_iter(0..3)); + + let mut b = VecDeque::from_iter(0..3); + b.push_back(4); + + let mut b = VecDeque::::from_iter(0..3); + b.push_back(4); + + { + use std::collections; + let mut b = collections::VecDeque::::from_iter(0..3); + b.push_back(4); + } + + let values = [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]; + let bm = BTreeMap::from_iter(values.iter().cloned()); + let mut bar = BTreeMap::from_iter(bm.range(0..2)); + bar.insert(&4, &'e'); + + let mut bts = BTreeSet::from_iter(0..3); + bts.insert(2); + { + use std::collections; + let _ = collections::BTreeSet::from_iter(0..3); + let _ = collections::BTreeSet::::from_iter(0..3); + } + + for _i in Vec::from_iter([1, 2, 3].iter()) {} + for _i in Vec::<&i32>::from_iter([1, 2, 3].iter()) {} +} diff --git a/src/tools/clippy/tests/ui/from_iter_instead_of_collect.stderr b/src/tools/clippy/tests/ui/from_iter_instead_of_collect.stderr new file mode 100644 index 0000000000..434734c9a2 --- /dev/null +++ b/src/tools/clippy/tests/ui/from_iter_instead_of_collect.stderr @@ -0,0 +1,88 @@ +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:11:13 + | +LL | let _ = Vec::from_iter(iter_expr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter_expr.collect::>()` + | + = note: `-D clippy::from-iter-instead-of-collect` implied by `-D warnings` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:13:13 + | +LL | let _ = HashMap::::from_iter(vec![5, 5, 5, 5].iter().enumerate()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `vec![5, 5, 5, 5].iter().enumerate().collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:18:19 + | +LL | assert_eq!(a, Vec::from_iter(0..3)); + | ^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:19:19 + | +LL | assert_eq!(a, Vec::::from_iter(0..3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:21:17 + | +LL | let mut b = VecDeque::from_iter(0..3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:24:17 + | +LL | let mut b = VecDeque::::from_iter(0..3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:29:21 + | +LL | let mut b = collections::VecDeque::::from_iter(0..3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:34:14 + | +LL | let bm = BTreeMap::from_iter(values.iter().cloned()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `values.iter().cloned().collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:35:19 + | +LL | let mut bar = BTreeMap::from_iter(bm.range(0..2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `bm.range(0..2).collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:38:19 + | +LL | let mut bts = BTreeSet::from_iter(0..3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:42:17 + | +LL | let _ = collections::BTreeSet::from_iter(0..3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:43:17 + | +LL | let _ = collections::BTreeSet::::from_iter(0..3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:46:15 + | +LL | for _i in Vec::from_iter([1, 2, 3].iter()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `[1, 2, 3].iter().collect::>()` + +error: usage of `FromIterator::from_iter` + --> $DIR/from_iter_instead_of_collect.rs:47:15 + | +LL | for _i in Vec::<&i32>::from_iter([1, 2, 3].iter()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `[1, 2, 3].iter().collect::>()` + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/from_over_into.rs b/src/tools/clippy/tests/ui/from_over_into.rs new file mode 100644 index 0000000000..292d0924fb --- /dev/null +++ b/src/tools/clippy/tests/ui/from_over_into.rs @@ -0,0 +1,21 @@ +#![warn(clippy::from_over_into)] + +// this should throw an error +struct StringWrapper(String); + +impl Into for String { + fn into(self) -> StringWrapper { + StringWrapper(self) + } +} + +// this is fine +struct A(String); + +impl From for A { + fn from(s: String) -> A { + A(s) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/from_over_into.stderr b/src/tools/clippy/tests/ui/from_over_into.stderr new file mode 100644 index 0000000000..b101d2704f --- /dev/null +++ b/src/tools/clippy/tests/ui/from_over_into.stderr @@ -0,0 +1,11 @@ +error: an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true + --> $DIR/from_over_into.rs:6:1 + | +LL | impl Into for String { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::from-over-into` implied by `-D warnings` + = help: consider to implement `From` instead + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/from_str_radix_10.rs b/src/tools/clippy/tests/ui/from_str_radix_10.rs new file mode 100644 index 0000000000..2f2ea04847 --- /dev/null +++ b/src/tools/clippy/tests/ui/from_str_radix_10.rs @@ -0,0 +1,52 @@ +#![warn(clippy::from_str_radix_10)] + +mod some_mod { + // fake function that shouldn't trigger the lint + pub fn from_str_radix(_: &str, _: u32) -> Result<(), std::num::ParseIntError> { + unimplemented!() + } +} + +// fake function that shouldn't trigger the lint +fn from_str_radix(_: &str, _: u32) -> Result<(), std::num::ParseIntError> { + unimplemented!() +} + +// to test parenthesis addition +struct Test; + +impl std::ops::Add for Test { + type Output = &'static str; + + fn add(self, _: Self) -> Self::Output { + "304" + } +} + +fn main() -> Result<(), Box> { + // all of these should trigger the lint + u32::from_str_radix("30", 10)?; + i64::from_str_radix("24", 10)?; + isize::from_str_radix("100", 10)?; + u8::from_str_radix("7", 10)?; + u16::from_str_radix(&("10".to_owned() + "5"), 10)?; + i128::from_str_radix(Test + Test, 10)?; + + let string = "300"; + i32::from_str_radix(string, 10)?; + + let stringier = "400".to_string(); + i32::from_str_radix(&stringier, 10)?; + + // none of these should trigger the lint + u16::from_str_radix("20", 3)?; + i32::from_str_radix("45", 12)?; + usize::from_str_radix("10", 16)?; + i128::from_str_radix("10", 13)?; + some_mod::from_str_radix("50", 10)?; + some_mod::from_str_radix("50", 6)?; + from_str_radix("50", 10)?; + from_str_radix("50", 6)?; + + Ok(()) +} diff --git a/src/tools/clippy/tests/ui/from_str_radix_10.stderr b/src/tools/clippy/tests/ui/from_str_radix_10.stderr new file mode 100644 index 0000000000..471bf52a9a --- /dev/null +++ b/src/tools/clippy/tests/ui/from_str_radix_10.stderr @@ -0,0 +1,52 @@ +error: this call to `from_str_radix` can be replaced with a call to `str::parse` + --> $DIR/from_str_radix_10.rs:28:5 + | +LL | u32::from_str_radix("30", 10)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"30".parse::()` + | + = note: `-D clippy::from-str-radix-10` implied by `-D warnings` + +error: this call to `from_str_radix` can be replaced with a call to `str::parse` + --> $DIR/from_str_radix_10.rs:29:5 + | +LL | i64::from_str_radix("24", 10)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"24".parse::()` + +error: this call to `from_str_radix` can be replaced with a call to `str::parse` + --> $DIR/from_str_radix_10.rs:30:5 + | +LL | isize::from_str_radix("100", 10)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"100".parse::()` + +error: this call to `from_str_radix` can be replaced with a call to `str::parse` + --> $DIR/from_str_radix_10.rs:31:5 + | +LL | u8::from_str_radix("7", 10)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"7".parse::()` + +error: this call to `from_str_radix` can be replaced with a call to `str::parse` + --> $DIR/from_str_radix_10.rs:32:5 + | +LL | u16::from_str_radix(&("10".to_owned() + "5"), 10)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(("10".to_owned() + "5")).parse::()` + +error: this call to `from_str_radix` can be replaced with a call to `str::parse` + --> $DIR/from_str_radix_10.rs:33:5 + | +LL | i128::from_str_radix(Test + Test, 10)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(Test + Test).parse::()` + +error: this call to `from_str_radix` can be replaced with a call to `str::parse` + --> $DIR/from_str_radix_10.rs:36:5 + | +LL | i32::from_str_radix(string, 10)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.parse::()` + +error: this call to `from_str_radix` can be replaced with a call to `str::parse` + --> $DIR/from_str_radix_10.rs:39:5 + | +LL | i32::from_str_radix(&stringier, 10)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `stringier.parse::()` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/functions.rs b/src/tools/clippy/tests/ui/functions.rs new file mode 100644 index 0000000000..271754cb06 --- /dev/null +++ b/src/tools/clippy/tests/ui/functions.rs @@ -0,0 +1,104 @@ +#![warn(clippy::all)] +#![allow(dead_code)] +#![allow(unused_unsafe, clippy::missing_safety_doc)] + +// TOO_MANY_ARGUMENTS +fn good(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool) {} + +fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {} + +#[rustfmt::skip] +fn bad_multiline( + one: u32, + two: u32, + three: &str, + four: bool, + five: f32, + six: f32, + seven: bool, + eight: () +) { + let _one = one; + let _two = two; + let _three = three; + let _four = four; + let _five = five; + let _six = six; + let _seven = seven; +} + +// don't lint extern fns +extern "C" fn extern_fn( + _one: u32, + _two: u32, + _three: *const u8, + _four: bool, + _five: f32, + _six: f32, + _seven: bool, + _eight: *const std::ffi::c_void, +) { +} + +pub trait Foo { + fn good(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool); + fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()); + + fn ptr(p: *const u8); +} + +pub struct Bar; + +impl Bar { + fn good_method(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool) {} + fn bad_method(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {} +} + +// ok, we don’t want to warn implementations +impl Foo for Bar { + fn good(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool) {} + fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {} + + fn ptr(p: *const u8) { + println!("{}", unsafe { *p }); + println!("{:?}", unsafe { p.as_ref() }); + unsafe { std::ptr::read(p) }; + } +} + +// NOT_UNSAFE_PTR_ARG_DEREF + +fn private(p: *const u8) { + println!("{}", unsafe { *p }); +} + +pub fn public(p: *const u8) { + println!("{}", unsafe { *p }); + println!("{:?}", unsafe { p.as_ref() }); + unsafe { std::ptr::read(p) }; +} + +impl Bar { + fn private(self, p: *const u8) { + println!("{}", unsafe { *p }); + } + + pub fn public(self, p: *const u8) { + println!("{}", unsafe { *p }); + println!("{:?}", unsafe { p.as_ref() }); + unsafe { std::ptr::read(p) }; + } + + pub fn public_ok(self, p: *const u8) { + if !p.is_null() { + println!("{:p}", p); + } + } + + pub unsafe fn public_unsafe(self, p: *const u8) { + println!("{}", unsafe { *p }); + println!("{:?}", unsafe { p.as_ref() }); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/functions.stderr b/src/tools/clippy/tests/ui/functions.stderr new file mode 100644 index 0000000000..0a86568b18 --- /dev/null +++ b/src/tools/clippy/tests/ui/functions.stderr @@ -0,0 +1,90 @@ +error: this function has too many arguments (8/7) + --> $DIR/functions.rs:8:1 + | +LL | fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::too-many-arguments` implied by `-D warnings` + +error: this function has too many arguments (8/7) + --> $DIR/functions.rs:11:1 + | +LL | / fn bad_multiline( +LL | | one: u32, +LL | | two: u32, +LL | | three: &str, +... | +LL | | eight: () +LL | | ) { + | |__^ + +error: this function has too many arguments (8/7) + --> $DIR/functions.rs:45:5 + | +LL | fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this function has too many arguments (8/7) + --> $DIR/functions.rs:54:5 + | +LL | fn bad_method(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:63:34 + | +LL | println!("{}", unsafe { *p }); + | ^ + | + = note: `-D clippy::not-unsafe-ptr-arg-deref` implied by `-D warnings` + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:64:35 + | +LL | println!("{:?}", unsafe { p.as_ref() }); + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:65:33 + | +LL | unsafe { std::ptr::read(p) }; + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:76:30 + | +LL | println!("{}", unsafe { *p }); + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:77:31 + | +LL | println!("{:?}", unsafe { p.as_ref() }); + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:78:29 + | +LL | unsafe { std::ptr::read(p) }; + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:87:34 + | +LL | println!("{}", unsafe { *p }); + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:88:35 + | +LL | println!("{:?}", unsafe { p.as_ref() }); + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:89:33 + | +LL | unsafe { std::ptr::read(p) }; + | ^ + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/functions_maxlines.rs b/src/tools/clippy/tests/ui/functions_maxlines.rs new file mode 100644 index 0000000000..5e1ee55e01 --- /dev/null +++ b/src/tools/clippy/tests/ui/functions_maxlines.rs @@ -0,0 +1,163 @@ +#![warn(clippy::too_many_lines)] + +fn good_lines() { + /* println!("This is good."); */ + // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); */ + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); +} + +fn bad_lines() { + println!("Dont get confused by braces: {{}}"); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/functions_maxlines.stderr b/src/tools/clippy/tests/ui/functions_maxlines.stderr new file mode 100644 index 0000000000..dc6c8ba2f1 --- /dev/null +++ b/src/tools/clippy/tests/ui/functions_maxlines.stderr @@ -0,0 +1,16 @@ +error: this function has too many lines (102/100) + --> $DIR/functions_maxlines.rs:58:1 + | +LL | / fn bad_lines() { +LL | | println!("Dont get confused by braces: {{}}"); +LL | | println!("This is bad."); +LL | | println!("This is bad."); +... | +LL | | println!("This is bad."); +LL | | } + | |_^ + | + = note: `-D clippy::too-many-lines` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/future_not_send.rs b/src/tools/clippy/tests/ui/future_not_send.rs new file mode 100644 index 0000000000..d3a920de4b --- /dev/null +++ b/src/tools/clippy/tests/ui/future_not_send.rs @@ -0,0 +1,80 @@ +// edition:2018 +#![warn(clippy::future_not_send)] + +use std::cell::Cell; +use std::rc::Rc; +use std::sync::Arc; + +async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { + async { true }.await +} + +pub async fn public_future(rc: Rc<[u8]>) { + async { true }.await; +} + +pub async fn public_send(arc: Arc<[u8]>) -> bool { + async { false }.await +} + +async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { + true +} + +pub async fn public_future2(rc: Rc<[u8]>) {} + +pub async fn public_send2(arc: Arc<[u8]>) -> bool { + false +} + +struct Dummy { + rc: Rc<[u8]>, +} + +impl Dummy { + async fn private_future(&self) -> usize { + async { true }.await; + self.rc.len() + } + + pub async fn public_future(&self) { + self.private_future().await; + } + + #[allow(clippy::manual_async_fn)] + pub fn public_send(&self) -> impl std::future::Future { + async { false } + } +} + +async fn generic_future(t: T) -> T +where + T: Send, +{ + let rt = &t; + async { true }.await; + t +} + +async fn generic_future_send(t: T) +where + T: Send, +{ + async { true }.await; +} + +async fn unclear_future(t: T) {} + +fn main() { + let rc = Rc::new([1, 2, 3]); + private_future(rc.clone(), &Cell::new(42)); + public_future(rc.clone()); + let arc = Arc::new([4, 5, 6]); + public_send(arc); + generic_future(42); + generic_future_send(42); + + let dummy = Dummy { rc }; + dummy.public_future(); + dummy.public_send(); +} diff --git a/src/tools/clippy/tests/ui/future_not_send.stderr b/src/tools/clippy/tests/ui/future_not_send.stderr new file mode 100644 index 0000000000..b59dbb3e76 --- /dev/null +++ b/src/tools/clippy/tests/ui/future_not_send.stderr @@ -0,0 +1,145 @@ +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:8:62 + | +LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ^^^^ future returned by `private_future` is not `Send` + | + = note: `-D clippy::future-not-send` implied by `-D warnings` +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:9:5 + | +LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { + | -- has type `std::rc::Rc<[u8]>` which is not `Send` +LL | async { true }.await + | ^^^^^^^^^^^^^^^^^^^^ await occurs here, with `rc` maybe used later +LL | } + | - `rc` is later dropped here + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:9:5 + | +LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ---- has type `&std::cell::Cell` which is not `Send` +LL | async { true }.await + | ^^^^^^^^^^^^^^^^^^^^ await occurs here, with `cell` maybe used later +LL | } + | - `cell` is later dropped here + = note: `std::cell::Cell` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:12:42 + | +LL | pub async fn public_future(rc: Rc<[u8]>) { + | ^ future returned by `public_future` is not `Send` + | +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:13:5 + | +LL | pub async fn public_future(rc: Rc<[u8]>) { + | -- has type `std::rc::Rc<[u8]>` which is not `Send` +LL | async { true }.await; + | ^^^^^^^^^^^^^^^^^^^^ await occurs here, with `rc` maybe used later +LL | } + | - `rc` is later dropped here + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:20:63 + | +LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ^^^^ future returned by `private_future2` is not `Send` + | +note: captured value is not `Send` + --> $DIR/future_not_send.rs:20:26 + | +LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ^^ has type `std::rc::Rc<[u8]>` which is not `Send` + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` +note: captured value is not `Send` + --> $DIR/future_not_send.rs:20:40 + | +LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ^^^^ has type `&std::cell::Cell` which is not `Send` + = note: `std::cell::Cell` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:24:43 + | +LL | pub async fn public_future2(rc: Rc<[u8]>) {} + | ^ future returned by `public_future2` is not `Send` + | +note: captured value is not `Send` + --> $DIR/future_not_send.rs:24:29 + | +LL | pub async fn public_future2(rc: Rc<[u8]>) {} + | ^^ has type `std::rc::Rc<[u8]>` which is not `Send` + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:35:39 + | +LL | async fn private_future(&self) -> usize { + | ^^^^^ future returned by `private_future` is not `Send` + | +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:36:9 + | +LL | async fn private_future(&self) -> usize { + | ----- has type `&Dummy` which is not `Send` +LL | async { true }.await; + | ^^^^^^^^^^^^^^^^^^^^ await occurs here, with `&self` maybe used later +LL | self.rc.len() +LL | } + | - `&self` is later dropped here + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:40:39 + | +LL | pub async fn public_future(&self) { + | ^ future returned by `public_future` is not `Send` + | +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:41:9 + | +LL | pub async fn public_future(&self) { + | ----- has type `&Dummy` which is not `Send` +LL | self.private_future().await; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `&self` maybe used later +LL | } + | - `&self` is later dropped here + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:50:37 + | +LL | async fn generic_future(t: T) -> T + | ^ future returned by `generic_future` is not `Send` + | +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:55:5 + | +LL | let rt = &t; + | -- has type `&T` which is not `Send` +LL | async { true }.await; + | ^^^^^^^^^^^^^^^^^^^^ await occurs here, with `rt` maybe used later +LL | t +LL | } + | - `rt` is later dropped here + = note: `T` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:66:34 + | +LL | async fn unclear_future(t: T) {} + | ^ future returned by `unclear_future` is not `Send` + | +note: captured value is not `Send` + --> $DIR/future_not_send.rs:66:28 + | +LL | async fn unclear_future(t: T) {} + | ^ has type `T` which is not `Send` + = note: `T` doesn't implement `std::marker::Send` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/get_last_with_len.fixed b/src/tools/clippy/tests/ui/get_last_with_len.fixed new file mode 100644 index 0000000000..c8b363f9c3 --- /dev/null +++ b/src/tools/clippy/tests/ui/get_last_with_len.fixed @@ -0,0 +1,31 @@ +// run-rustfix + +#![warn(clippy::get_last_with_len)] + +fn dont_use_last() { + let x = vec![2, 3, 5]; + let _ = x.last(); // ~ERROR Use x.last() +} + +fn indexing_two_from_end() { + let x = vec![2, 3, 5]; + let _ = x.get(x.len() - 2); +} + +fn index_into_last() { + let x = vec![2, 3, 5]; + let _ = x[x.len() - 1]; +} + +fn use_last_with_different_vec_length() { + let x = vec![2, 3, 5]; + let y = vec!['a', 'b', 'c']; + let _ = x.get(y.len() - 1); +} + +fn main() { + dont_use_last(); + indexing_two_from_end(); + index_into_last(); + use_last_with_different_vec_length(); +} diff --git a/src/tools/clippy/tests/ui/get_last_with_len.rs b/src/tools/clippy/tests/ui/get_last_with_len.rs new file mode 100644 index 0000000000..bf9cb2d7e0 --- /dev/null +++ b/src/tools/clippy/tests/ui/get_last_with_len.rs @@ -0,0 +1,31 @@ +// run-rustfix + +#![warn(clippy::get_last_with_len)] + +fn dont_use_last() { + let x = vec![2, 3, 5]; + let _ = x.get(x.len() - 1); // ~ERROR Use x.last() +} + +fn indexing_two_from_end() { + let x = vec![2, 3, 5]; + let _ = x.get(x.len() - 2); +} + +fn index_into_last() { + let x = vec![2, 3, 5]; + let _ = x[x.len() - 1]; +} + +fn use_last_with_different_vec_length() { + let x = vec![2, 3, 5]; + let y = vec!['a', 'b', 'c']; + let _ = x.get(y.len() - 1); +} + +fn main() { + dont_use_last(); + indexing_two_from_end(); + index_into_last(); + use_last_with_different_vec_length(); +} diff --git a/src/tools/clippy/tests/ui/get_last_with_len.stderr b/src/tools/clippy/tests/ui/get_last_with_len.stderr new file mode 100644 index 0000000000..55baf87384 --- /dev/null +++ b/src/tools/clippy/tests/ui/get_last_with_len.stderr @@ -0,0 +1,10 @@ +error: accessing last element with `x.get(x.len() - 1)` + --> $DIR/get_last_with_len.rs:7:13 + | +LL | let _ = x.get(x.len() - 1); // ~ERROR Use x.last() + | ^^^^^^^^^^^^^^^^^^ help: try: `x.last()` + | + = note: `-D clippy::get-last-with-len` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/get_unwrap.fixed b/src/tools/clippy/tests/ui/get_unwrap.fixed new file mode 100644 index 0000000000..924c02a405 --- /dev/null +++ b/src/tools/clippy/tests/ui/get_unwrap.fixed @@ -0,0 +1,62 @@ +// run-rustfix +#![allow(unused_mut, clippy::from_iter_instead_of_collect)] +#![deny(clippy::get_unwrap)] + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::collections::VecDeque; +use std::iter::FromIterator; + +struct GetFalsePositive { + arr: [u32; 3], +} + +impl GetFalsePositive { + fn get(&self, pos: usize) -> Option<&u32> { + self.arr.get(pos) + } + fn get_mut(&mut self, pos: usize) -> Option<&mut u32> { + self.arr.get_mut(pos) + } +} + +fn main() { + let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let mut some_slice = &mut [0, 1, 2, 3]; + let mut some_vec = vec![0, 1, 2, 3]; + let mut some_vecdeque: VecDeque<_> = some_vec.iter().cloned().collect(); + let mut some_hashmap: HashMap = HashMap::from_iter(vec![(1, 'a'), (2, 'b')]); + let mut some_btreemap: BTreeMap = BTreeMap::from_iter(vec![(1, 'a'), (2, 'b')]); + let mut false_positive = GetFalsePositive { arr: [0, 1, 2] }; + + { + // Test `get().unwrap()` + let _ = &boxed_slice[1]; + let _ = &some_slice[0]; + let _ = &some_vec[0]; + let _ = &some_vecdeque[0]; + let _ = &some_hashmap[&1]; + let _ = &some_btreemap[&1]; + let _ = false_positive.get(0).unwrap(); + // Test with deref + let _: u8 = boxed_slice[1]; + } + + { + // Test `get_mut().unwrap()` + boxed_slice[0] = 1; + some_slice[0] = 1; + some_vec[0] = 1; + some_vecdeque[0] = 1; + // Check false positives + *some_hashmap.get_mut(&1).unwrap() = 'b'; + *some_btreemap.get_mut(&1).unwrap() = 'b'; + *false_positive.get_mut(0).unwrap() = 1; + } + + { + // Test `get().unwrap().foo()` and `get_mut().unwrap().bar()` + let _ = some_vec[0..1].to_vec(); + let _ = some_vec[0..1].to_vec(); + } +} diff --git a/src/tools/clippy/tests/ui/get_unwrap.rs b/src/tools/clippy/tests/ui/get_unwrap.rs new file mode 100644 index 0000000000..c0c37bb720 --- /dev/null +++ b/src/tools/clippy/tests/ui/get_unwrap.rs @@ -0,0 +1,62 @@ +// run-rustfix +#![allow(unused_mut, clippy::from_iter_instead_of_collect)] +#![deny(clippy::get_unwrap)] + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::collections::VecDeque; +use std::iter::FromIterator; + +struct GetFalsePositive { + arr: [u32; 3], +} + +impl GetFalsePositive { + fn get(&self, pos: usize) -> Option<&u32> { + self.arr.get(pos) + } + fn get_mut(&mut self, pos: usize) -> Option<&mut u32> { + self.arr.get_mut(pos) + } +} + +fn main() { + let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let mut some_slice = &mut [0, 1, 2, 3]; + let mut some_vec = vec![0, 1, 2, 3]; + let mut some_vecdeque: VecDeque<_> = some_vec.iter().cloned().collect(); + let mut some_hashmap: HashMap = HashMap::from_iter(vec![(1, 'a'), (2, 'b')]); + let mut some_btreemap: BTreeMap = BTreeMap::from_iter(vec![(1, 'a'), (2, 'b')]); + let mut false_positive = GetFalsePositive { arr: [0, 1, 2] }; + + { + // Test `get().unwrap()` + let _ = boxed_slice.get(1).unwrap(); + let _ = some_slice.get(0).unwrap(); + let _ = some_vec.get(0).unwrap(); + let _ = some_vecdeque.get(0).unwrap(); + let _ = some_hashmap.get(&1).unwrap(); + let _ = some_btreemap.get(&1).unwrap(); + let _ = false_positive.get(0).unwrap(); + // Test with deref + let _: u8 = *boxed_slice.get(1).unwrap(); + } + + { + // Test `get_mut().unwrap()` + *boxed_slice.get_mut(0).unwrap() = 1; + *some_slice.get_mut(0).unwrap() = 1; + *some_vec.get_mut(0).unwrap() = 1; + *some_vecdeque.get_mut(0).unwrap() = 1; + // Check false positives + *some_hashmap.get_mut(&1).unwrap() = 'b'; + *some_btreemap.get_mut(&1).unwrap() = 'b'; + *false_positive.get_mut(0).unwrap() = 1; + } + + { + // Test `get().unwrap().foo()` and `get_mut().unwrap().bar()` + let _ = some_vec.get(0..1).unwrap().to_vec(); + let _ = some_vec.get_mut(0..1).unwrap().to_vec(); + } +} diff --git a/src/tools/clippy/tests/ui/get_unwrap.stderr b/src/tools/clippy/tests/ui/get_unwrap.stderr new file mode 100644 index 0000000000..76a098df82 --- /dev/null +++ b/src/tools/clippy/tests/ui/get_unwrap.stderr @@ -0,0 +1,86 @@ +error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:34:17 + | +LL | let _ = boxed_slice.get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]` + | +note: the lint level is defined here + --> $DIR/get_unwrap.rs:3:9 + | +LL | #![deny(clippy::get_unwrap)] + | ^^^^^^^^^^^^^^^^^^ + +error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:35:17 + | +LL | let _ = some_slice.get(0).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_slice[0]` + +error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:36:17 + | +LL | let _ = some_vec.get(0).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vec[0]` + +error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:37:17 + | +LL | let _ = some_vecdeque.get(0).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vecdeque[0]` + +error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:38:17 + | +LL | let _ = some_hashmap.get(&1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_hashmap[&1]` + +error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:39:17 + | +LL | let _ = some_btreemap.get(&1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_btreemap[&1]` + +error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:42:21 + | +LL | let _: u8 = *boxed_slice.get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[1]` + +error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:47:9 + | +LL | *boxed_slice.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[0]` + +error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:48:9 + | +LL | *some_slice.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_slice[0]` + +error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:49:9 + | +LL | *some_vec.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0]` + +error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:50:9 + | +LL | *some_vecdeque.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vecdeque[0]` + +error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:59:17 + | +LL | let _ = some_vec.get(0..1).unwrap().to_vec(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]` + +error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:60:17 + | +LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/identity_op.rs b/src/tools/clippy/tests/ui/identity_op.rs new file mode 100644 index 0000000000..ceaacaaf6b --- /dev/null +++ b/src/tools/clippy/tests/ui/identity_op.rs @@ -0,0 +1,41 @@ +const ONE: i64 = 1; +const NEG_ONE: i64 = -1; +const ZERO: i64 = 0; + +#[allow( + clippy::eq_op, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::double_parens +)] +#[warn(clippy::identity_op)] +#[rustfmt::skip] +fn main() { + let x = 0; + + x + 0; + x + (1 - 1); + x + 1; + 0 + x; + 1 + x; + x - ZERO; //no error, as we skip lookups (for now) + x | (0); + ((ZERO)) | x; //no error, as we skip lookups (for now) + + x * 1; + 1 * x; + x / ONE; //no error, as we skip lookups (for now) + + x / 2; //no false positive + + x & NEG_ONE; //no error, as we skip lookups (for now) + -1 & x; + + let u: u8 = 0; + u & 255; + + 1 << 0; // no error, this case is allowed, see issue 3430 + 42 << 0; + 1 >> 0; + 42 >> 0; +} diff --git a/src/tools/clippy/tests/ui/identity_op.stderr b/src/tools/clippy/tests/ui/identity_op.stderr new file mode 100644 index 0000000000..d8d44a74f9 --- /dev/null +++ b/src/tools/clippy/tests/ui/identity_op.stderr @@ -0,0 +1,70 @@ +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:16:5 + | +LL | x + 0; + | ^^^^^ + | + = note: `-D clippy::identity-op` implied by `-D warnings` + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:17:5 + | +LL | x + (1 - 1); + | ^^^^^^^^^^^ + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:19:5 + | +LL | 0 + x; + | ^^^^^ + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:22:5 + | +LL | x | (0); + | ^^^^^^^ + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:25:5 + | +LL | x * 1; + | ^^^^^ + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:26:5 + | +LL | 1 * x; + | ^^^^^ + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:32:5 + | +LL | -1 & x; + | ^^^^^^ + +error: the operation is ineffective. Consider reducing it to `u` + --> $DIR/identity_op.rs:35:5 + | +LL | u & 255; + | ^^^^^^^ + +error: the operation is ineffective. Consider reducing it to `42` + --> $DIR/identity_op.rs:38:5 + | +LL | 42 << 0; + | ^^^^^^^ + +error: the operation is ineffective. Consider reducing it to `1` + --> $DIR/identity_op.rs:39:5 + | +LL | 1 >> 0; + | ^^^^^^ + +error: the operation is ineffective. Consider reducing it to `42` + --> $DIR/identity_op.rs:40:5 + | +LL | 42 >> 0; + | ^^^^^^^ + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/if_let_mutex.rs b/src/tools/clippy/tests/ui/if_let_mutex.rs new file mode 100644 index 0000000000..58feae422a --- /dev/null +++ b/src/tools/clippy/tests/ui/if_let_mutex.rs @@ -0,0 +1,42 @@ +#![warn(clippy::if_let_mutex)] + +use std::ops::Deref; +use std::sync::Mutex; + +fn do_stuff(_: T) {} + +fn if_let() { + let m = Mutex::new(1_u8); + if let Err(locked) = m.lock() { + do_stuff(locked); + } else { + let lock = m.lock().unwrap(); + do_stuff(lock); + }; +} + +// This is the most common case as the above case is pretty +// contrived. +fn if_let_option() { + let m = Mutex::new(Some(0_u8)); + if let Some(locked) = m.lock().unwrap().deref() { + do_stuff(locked); + } else { + let lock = m.lock().unwrap(); + do_stuff(lock); + }; +} + +// When mutexs are different don't warn +fn if_let_different_mutex() { + let m = Mutex::new(Some(0_u8)); + let other = Mutex::new(None::); + if let Some(locked) = m.lock().unwrap().deref() { + do_stuff(locked); + } else { + let lock = other.lock().unwrap(); + do_stuff(lock); + }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/if_let_mutex.stderr b/src/tools/clippy/tests/ui/if_let_mutex.stderr new file mode 100644 index 0000000000..e9c4d91633 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_let_mutex.stderr @@ -0,0 +1,29 @@ +error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock + --> $DIR/if_let_mutex.rs:10:5 + | +LL | / if let Err(locked) = m.lock() { +LL | | do_stuff(locked); +LL | | } else { +LL | | let lock = m.lock().unwrap(); +LL | | do_stuff(lock); +LL | | }; + | |_____^ + | + = note: `-D clippy::if-let-mutex` implied by `-D warnings` + = help: move the lock call outside of the `if let ...` expression + +error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock + --> $DIR/if_let_mutex.rs:22:5 + | +LL | / if let Some(locked) = m.lock().unwrap().deref() { +LL | | do_stuff(locked); +LL | | } else { +LL | | let lock = m.lock().unwrap(); +LL | | do_stuff(lock); +LL | | }; + | |_____^ + | + = help: move the lock call outside of the `if let ...` expression + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/if_let_some_result.fixed b/src/tools/clippy/tests/ui/if_let_some_result.fixed new file mode 100644 index 0000000000..62a25ce2d1 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_let_some_result.fixed @@ -0,0 +1,27 @@ +// run-rustfix + +#![warn(clippy::if_let_some_result)] + +fn str_to_int(x: &str) -> i32 { + if let Ok(y) = x.parse() { y } else { 0 } +} + +fn str_to_int_ok(x: &str) -> i32 { + if let Ok(y) = x.parse() { y } else { 0 } +} + +#[rustfmt::skip] +fn strange_some_no_else(x: &str) -> i32 { + { + if let Ok(y) = x . parse() { + return y; + }; + 0 + } +} + +fn main() { + let _ = str_to_int("1"); + let _ = str_to_int_ok("2"); + let _ = strange_some_no_else("3"); +} diff --git a/src/tools/clippy/tests/ui/if_let_some_result.rs b/src/tools/clippy/tests/ui/if_let_some_result.rs new file mode 100644 index 0000000000..234ff5e9e8 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_let_some_result.rs @@ -0,0 +1,27 @@ +// run-rustfix + +#![warn(clippy::if_let_some_result)] + +fn str_to_int(x: &str) -> i32 { + if let Some(y) = x.parse().ok() { y } else { 0 } +} + +fn str_to_int_ok(x: &str) -> i32 { + if let Ok(y) = x.parse() { y } else { 0 } +} + +#[rustfmt::skip] +fn strange_some_no_else(x: &str) -> i32 { + { + if let Some(y) = x . parse() . ok () { + return y; + }; + 0 + } +} + +fn main() { + let _ = str_to_int("1"); + let _ = str_to_int_ok("2"); + let _ = strange_some_no_else("3"); +} diff --git a/src/tools/clippy/tests/ui/if_let_some_result.stderr b/src/tools/clippy/tests/ui/if_let_some_result.stderr new file mode 100644 index 0000000000..0646dd27f3 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_let_some_result.stderr @@ -0,0 +1,25 @@ +error: matching on `Some` with `ok()` is redundant + --> $DIR/if_let_some_result.rs:6:5 + | +LL | if let Some(y) = x.parse().ok() { y } else { 0 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::if-let-some-result` implied by `-D warnings` +help: consider matching on `Ok(y)` and removing the call to `ok` instead + | +LL | if let Ok(y) = x.parse() { y } else { 0 } + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: matching on `Some` with `ok()` is redundant + --> $DIR/if_let_some_result.rs:16:9 + | +LL | if let Some(y) = x . parse() . ok () { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider matching on `Ok(y)` and removing the call to `ok` instead + | +LL | if let Ok(y) = x . parse() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/if_not_else.rs b/src/tools/clippy/tests/ui/if_not_else.rs new file mode 100644 index 0000000000..dc3fb1ceac --- /dev/null +++ b/src/tools/clippy/tests/ui/if_not_else.rs @@ -0,0 +1,19 @@ +#![warn(clippy::all)] +#![warn(clippy::if_not_else)] + +fn bla() -> bool { + unimplemented!() +} + +fn main() { + if !bla() { + println!("Bugs"); + } else { + println!("Bunny"); + } + if 4 != 5 { + println!("Bugs"); + } else { + println!("Bunny"); + } +} diff --git a/src/tools/clippy/tests/ui/if_not_else.stderr b/src/tools/clippy/tests/ui/if_not_else.stderr new file mode 100644 index 0000000000..53d1b86d02 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_not_else.stderr @@ -0,0 +1,27 @@ +error: unnecessary boolean `not` operation + --> $DIR/if_not_else.rs:9:5 + | +LL | / if !bla() { +LL | | println!("Bugs"); +LL | | } else { +LL | | println!("Bunny"); +LL | | } + | |_____^ + | + = note: `-D clippy::if-not-else` implied by `-D warnings` + = help: remove the `!` and swap the blocks of the `if`/`else` + +error: unnecessary `!=` operation + --> $DIR/if_not_else.rs:14:5 + | +LL | / if 4 != 5 { +LL | | println!("Bugs"); +LL | | } else { +LL | | println!("Bunny"); +LL | | } + | |_____^ + | + = help: change to `==` and swap the blocks of the `if`/`else` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/if_same_then_else.rs b/src/tools/clippy/tests/ui/if_same_then_else.rs new file mode 100644 index 0000000000..9c5fe02f75 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_same_then_else.rs @@ -0,0 +1,157 @@ +#![warn(clippy::if_same_then_else)] +#![allow( + clippy::blacklisted_name, + clippy::eq_op, + clippy::never_loop, + clippy::no_effect, + clippy::unused_unit, + clippy::zero_divided_by_zero +)] + +struct Foo { + bar: u8, +} + +fn foo() -> bool { + unimplemented!() +} + +fn if_same_then_else() { + if true { + Foo { bar: 42 }; + 0..10; + ..; + 0..; + ..10; + 0..=10; + foo(); + } else { + //~ ERROR same body as `if` block + Foo { bar: 42 }; + 0..10; + ..; + 0..; + ..10; + 0..=10; + foo(); + } + + if true { + Foo { bar: 42 }; + } else { + Foo { bar: 43 }; + } + + if true { + (); + } else { + () + } + + if true { + 0..10; + } else { + 0..=10; + } + + if true { + foo(); + foo(); + } else { + foo(); + } + + let _ = if true { + 0.0 + } else { + //~ ERROR same body as `if` block + 0.0 + }; + + let _ = if true { + -0.0 + } else { + //~ ERROR same body as `if` block + -0.0 + }; + + let _ = if true { 0.0 } else { -0.0 }; + + // Different NaNs + let _ = if true { 0.0 / 0.0 } else { f32::NAN }; + + if true { + foo(); + } + + let _ = if true { + 42 + } else { + //~ ERROR same body as `if` block + 42 + }; + + if true { + let bar = if true { 42 } else { 43 }; + + while foo() { + break; + } + bar + 1; + } else { + //~ ERROR same body as `if` block + let bar = if true { 42 } else { 43 }; + + while foo() { + break; + } + bar + 1; + } + + if true { + let _ = match 42 { + 42 => 1, + a if a > 0 => 2, + 10..=15 => 3, + _ => 4, + }; + } else if false { + foo(); + } else if foo() { + let _ = match 42 { + 42 => 1, + a if a > 0 => 2, + 10..=15 => 3, + _ => 4, + }; + } +} + +// Issue #2423. This was causing an ICE. +fn func() { + if true { + f(&[0; 62]); + f(&[0; 4]); + f(&[0; 3]); + } else { + f(&[0; 62]); + f(&[0; 6]); + f(&[0; 6]); + } +} + +fn f(val: &[u8]) {} + +mod issue_5698 { + fn mul_not_always_commutative(x: i32, y: i32) -> i32 { + if x == 42 { + x * y + } else if x == 21 { + y * x + } else { + 0 + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/if_same_then_else.stderr b/src/tools/clippy/tests/ui/if_same_then_else.stderr new file mode 100644 index 0000000000..d9fdf06fa8 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_same_then_else.stderr @@ -0,0 +1,112 @@ +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:28:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | Foo { bar: 42 }; +LL | | 0..10; +... | +LL | | foo(); +LL | | } + | |_____^ + | + = note: `-D clippy::if-same-then-else` implied by `-D warnings` +note: same as this + --> $DIR/if_same_then_else.rs:20:13 + | +LL | if true { + | _____________^ +LL | | Foo { bar: 42 }; +LL | | 0..10; +LL | | ..; +... | +LL | | foo(); +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:66:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | 0.0 +LL | | }; + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else.rs:64:21 + | +LL | let _ = if true { + | _____________________^ +LL | | 0.0 +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:73:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | -0.0 +LL | | }; + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else.rs:71:21 + | +LL | let _ = if true { + | _____________________^ +LL | | -0.0 +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:89:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | 42 +LL | | }; + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else.rs:87:21 + | +LL | let _ = if true { + | _____________________^ +LL | | 42 +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:101:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | let bar = if true { 42 } else { 43 }; +LL | | +... | +LL | | bar + 1; +LL | | } + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else.rs:94:13 + | +LL | if true { + | _____________^ +LL | | let bar = if true { 42 } else { 43 }; +LL | | +LL | | while foo() { +... | +LL | | bar + 1; +LL | | } else { + | |_____^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/if_same_then_else2.rs b/src/tools/clippy/tests/ui/if_same_then_else2.rs new file mode 100644 index 0000000000..a2ff1b741c --- /dev/null +++ b/src/tools/clippy/tests/ui/if_same_then_else2.rs @@ -0,0 +1,141 @@ +#![warn(clippy::if_same_then_else)] +#![allow( + clippy::blacklisted_name, + clippy::collapsible_else_if, + clippy::collapsible_if, + clippy::ifs_same_cond, + clippy::needless_return, + clippy::single_element_loop +)] + +fn if_same_then_else2() -> Result<&'static str, ()> { + if true { + for _ in &[42] { + let foo: &Option<_> = &Some::(42); + if foo.is_some() { + break; + } else { + continue; + } + } + } else { + //~ ERROR same body as `if` block + for _ in &[42] { + let bar: &Option<_> = &Some::(42); + if bar.is_some() { + break; + } else { + continue; + } + } + } + + if true { + if let Some(a) = Some(42) {} + } else { + //~ ERROR same body as `if` block + if let Some(a) = Some(42) {} + } + + if true { + if let (1, .., 3) = (1, 2, 3) {} + } else { + //~ ERROR same body as `if` block + if let (1, .., 3) = (1, 2, 3) {} + } + + if true { + if let (1, .., 3) = (1, 2, 3) {} + } else { + if let (.., 3) = (1, 2, 3) {} + } + + if true { + if let (1, .., 3) = (1, 2, 3) {} + } else { + if let (.., 4) = (1, 2, 3) {} + } + + if true { + if let (1, .., 3) = (1, 2, 3) {} + } else { + if let (.., 1, 3) = (1, 2, 3) {} + } + + if true { + if let Some(42) = None {} + } else { + if let Option::Some(42) = None {} + } + + if true { + if let Some(42) = None:: {} + } else { + if let Some(42) = None {} + } + + if true { + if let Some(42) = None:: {} + } else { + if let Some(42) = None:: {} + } + + if true { + if let Some(a) = Some(42) {} + } else { + if let Some(a) = Some(43) {} + } + + // Same NaNs + let _ = if true { + f32::NAN + } else { + //~ ERROR same body as `if` block + f32::NAN + }; + + if true { + Ok("foo")?; + } else { + //~ ERROR same body as `if` block + Ok("foo")?; + } + + if true { + let foo = ""; + return Ok(&foo[0..]); + } else if false { + let foo = "bar"; + return Ok(&foo[0..]); + } else { + let foo = ""; + return Ok(&foo[0..]); + } + + if true { + let foo = ""; + return Ok(&foo[0..]); + } else if false { + let foo = "bar"; + return Ok(&foo[0..]); + } else if true { + let foo = ""; + return Ok(&foo[0..]); + } else { + let foo = ""; + return Ok(&foo[0..]); + } + + // False positive `if_same_then_else`: `let (x, y)` vs. `let (y, x)`; see issue #3559. + if true { + let foo = ""; + let (x, y) = (1, 2); + return Ok(&foo[x..y]); + } else { + let foo = ""; + let (y, x) = (1, 2); + return Ok(&foo[x..y]); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/if_same_then_else2.stderr b/src/tools/clippy/tests/ui/if_same_then_else2.stderr new file mode 100644 index 0000000000..454322d8aa --- /dev/null +++ b/src/tools/clippy/tests/ui/if_same_then_else2.stderr @@ -0,0 +1,125 @@ +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:21:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | for _ in &[42] { +LL | | let bar: &Option<_> = &Some::(42); +... | +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::if-same-then-else` implied by `-D warnings` +note: same as this + --> $DIR/if_same_then_else2.rs:12:13 + | +LL | if true { + | _____________^ +LL | | for _ in &[42] { +LL | | let foo: &Option<_> = &Some::(42); +LL | | if foo.is_some() { +... | +LL | | } +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:35:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | if let Some(a) = Some(42) {} +LL | | } + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else2.rs:33:13 + | +LL | if true { + | _____________^ +LL | | if let Some(a) = Some(42) {} +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:42:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | if let (1, .., 3) = (1, 2, 3) {} +LL | | } + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else2.rs:40:13 + | +LL | if true { + | _____________^ +LL | | if let (1, .., 3) = (1, 2, 3) {} +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:92:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | f32::NAN +LL | | }; + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else2.rs:90:21 + | +LL | let _ = if true { + | _____________________^ +LL | | f32::NAN +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:99:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | Ok("foo")?; +LL | | } + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else2.rs:97:13 + | +LL | if true { + | _____________^ +LL | | Ok("foo")?; +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:124:12 + | +LL | } else { + | ____________^ +LL | | let foo = ""; +LL | | return Ok(&foo[0..]); +LL | | } + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else2.rs:121:20 + | +LL | } else if true { + | ____________________^ +LL | | let foo = ""; +LL | | return Ok(&foo[0..]); +LL | | } else { + | |_____^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/ifs_same_cond.rs b/src/tools/clippy/tests/ui/ifs_same_cond.rs new file mode 100644 index 0000000000..80e9839ff4 --- /dev/null +++ b/src/tools/clippy/tests/ui/ifs_same_cond.rs @@ -0,0 +1,46 @@ +#![warn(clippy::ifs_same_cond)] +#![allow(clippy::if_same_then_else, clippy::comparison_chain)] // all empty blocks + +fn ifs_same_cond() { + let a = 0; + let b = false; + + if b { + } else if b { + //~ ERROR ifs same condition + } + + if a == 1 { + } else if a == 1 { + //~ ERROR ifs same condition + } + + if 2 * a == 1 { + } else if 2 * a == 2 { + } else if 2 * a == 1 { + //~ ERROR ifs same condition + } else if a == 1 { + } + + // See #659 + if cfg!(feature = "feature1-659") { + 1 + } else if cfg!(feature = "feature2-659") { + 2 + } else { + 3 + }; + + let mut v = vec![1]; + if v.pop() == None { + // ok, functions + } else if v.pop() == None { + } + + if v.len() == 42 { + // ok, functions + } else if v.len() == 42 { + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/ifs_same_cond.stderr b/src/tools/clippy/tests/ui/ifs_same_cond.stderr new file mode 100644 index 0000000000..0c8f49b868 --- /dev/null +++ b/src/tools/clippy/tests/ui/ifs_same_cond.stderr @@ -0,0 +1,39 @@ +error: this `if` has the same condition as a previous `if` + --> $DIR/ifs_same_cond.rs:9:15 + | +LL | } else if b { + | ^ + | + = note: `-D clippy::ifs-same-cond` implied by `-D warnings` +note: same as this + --> $DIR/ifs_same_cond.rs:8:8 + | +LL | if b { + | ^ + +error: this `if` has the same condition as a previous `if` + --> $DIR/ifs_same_cond.rs:14:15 + | +LL | } else if a == 1 { + | ^^^^^^ + | +note: same as this + --> $DIR/ifs_same_cond.rs:13:8 + | +LL | if a == 1 { + | ^^^^^^ + +error: this `if` has the same condition as a previous `if` + --> $DIR/ifs_same_cond.rs:20:15 + | +LL | } else if 2 * a == 1 { + | ^^^^^^^^^^ + | +note: same as this + --> $DIR/ifs_same_cond.rs:18:8 + | +LL | if 2 * a == 1 { + | ^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/impl.rs b/src/tools/clippy/tests/ui/impl.rs new file mode 100644 index 0000000000..1c46e3a533 --- /dev/null +++ b/src/tools/clippy/tests/ui/impl.rs @@ -0,0 +1,36 @@ +#![allow(dead_code)] +#![warn(clippy::multiple_inherent_impl)] + +struct MyStruct; + +impl MyStruct { + fn first() {} +} + +impl MyStruct { + fn second() {} +} + +impl<'a> MyStruct { + fn lifetimed() {} +} + +mod submod { + struct MyStruct; + impl MyStruct { + fn other() {} + } + + impl super::MyStruct { + fn third() {} + } +} + +use std::fmt; +impl fmt::Debug for MyStruct { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MyStruct {{ }}") + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/impl.stderr b/src/tools/clippy/tests/ui/impl.stderr new file mode 100644 index 0000000000..aab688cc2d --- /dev/null +++ b/src/tools/clippy/tests/ui/impl.stderr @@ -0,0 +1,35 @@ +error: multiple implementations of this structure + --> $DIR/impl.rs:10:1 + | +LL | / impl MyStruct { +LL | | fn second() {} +LL | | } + | |_^ + | + = note: `-D clippy::multiple-inherent-impl` implied by `-D warnings` +note: first implementation here + --> $DIR/impl.rs:6:1 + | +LL | / impl MyStruct { +LL | | fn first() {} +LL | | } + | |_^ + +error: multiple implementations of this structure + --> $DIR/impl.rs:24:5 + | +LL | / impl super::MyStruct { +LL | | fn third() {} +LL | | } + | |_____^ + | +note: first implementation here + --> $DIR/impl.rs:6:1 + | +LL | / impl MyStruct { +LL | | fn first() {} +LL | | } + | |_^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/implicit_clone.rs b/src/tools/clippy/tests/ui/implicit_clone.rs new file mode 100644 index 0000000000..1910152216 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_clone.rs @@ -0,0 +1,108 @@ +#![warn(clippy::implicit_clone)] +#![allow(clippy::redundant_clone)] +use std::borrow::Borrow; +use std::ffi::{OsStr, OsString}; +use std::path::PathBuf; + +fn return_owned_from_slice(slice: &[u32]) -> Vec { + slice.to_owned() +} + +pub fn own_same(v: T) -> T +where + T: ToOwned, +{ + v.to_owned() +} + +pub fn own_same_from_ref(v: &T) -> T +where + T: ToOwned, +{ + v.to_owned() +} + +pub fn own_different(v: T) -> U +where + T: ToOwned, +{ + v.to_owned() +} + +#[derive(Copy, Clone)] +struct Kitten {} +impl Kitten { + // badly named method + fn to_vec(self) -> Kitten { + Kitten {} + } +} +impl Borrow for Kitten { + fn borrow(&self) -> &BorrowedKitten { + static VALUE: BorrowedKitten = BorrowedKitten {}; + &VALUE + } +} + +struct BorrowedKitten {} +impl ToOwned for BorrowedKitten { + type Owned = Kitten; + fn to_owned(&self) -> Kitten { + Kitten {} + } +} + +mod weird { + #[allow(clippy::ptr_arg)] + pub fn to_vec(v: &Vec) -> Vec { + v.clone() + } +} + +fn main() { + let vec = vec![5]; + let _ = return_owned_from_slice(&vec); + let _ = vec.to_owned(); + let _ = vec.to_vec(); + + let vec_ref = &vec; + let _ = return_owned_from_slice(&vec_ref); + let _ = vec_ref.to_owned(); + let _ = vec_ref.to_vec(); + + // we expect no lint for this + let _ = weird::to_vec(&vec); + + // we expect no lints for this + let slice: &[u32] = &[1, 2, 3, 4, 5]; + let _ = return_owned_from_slice(slice); + let _ = slice.to_owned(); + let _ = slice.to_vec(); + + let str = "hello world".to_string(); + let _ = str.to_owned(); + + // testing w/ an arbitrary type + let kitten = Kitten {}; + let _ = kitten.to_owned(); + let _ = own_same_from_ref(&kitten); + // this shouln't lint + let _ = kitten.to_vec(); + + // we expect no lints for this + let borrowed = BorrowedKitten {}; + let _ = borrowed.to_owned(); + + let pathbuf = PathBuf::new(); + let _ = pathbuf.to_owned(); + let _ = pathbuf.to_path_buf(); + + let os_string = OsString::from("foo"); + let _ = os_string.to_owned(); + let _ = os_string.to_os_string(); + + // we expect no lints for this + let os_str = OsStr::new("foo"); + let _ = os_str.to_owned(); + let _ = os_str.to_os_string(); +} diff --git a/src/tools/clippy/tests/ui/implicit_clone.stderr b/src/tools/clippy/tests/ui/implicit_clone.stderr new file mode 100644 index 0000000000..e6f7527b67 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_clone.stderr @@ -0,0 +1,64 @@ +error: implicitly cloning a `Vec` by calling `to_owned` on its dereferenced type + --> $DIR/implicit_clone.rs:65:17 + | +LL | let _ = vec.to_owned(); + | ^^^^^^^^ help: consider using: `clone` + | + = note: `-D clippy::implicit-clone` implied by `-D warnings` + +error: implicitly cloning a `Vec` by calling `to_vec` on its dereferenced type + --> $DIR/implicit_clone.rs:66:17 + | +LL | let _ = vec.to_vec(); + | ^^^^^^ help: consider using: `clone` + +error: implicitly cloning a `Vec` by calling `to_owned` on its dereferenced type + --> $DIR/implicit_clone.rs:70:21 + | +LL | let _ = vec_ref.to_owned(); + | ^^^^^^^^ help: consider using: `clone` + +error: implicitly cloning a `Vec` by calling `to_vec` on its dereferenced type + --> $DIR/implicit_clone.rs:71:21 + | +LL | let _ = vec_ref.to_vec(); + | ^^^^^^ help: consider using: `clone` + +error: implicitly cloning a `String` by calling `to_owned` on its dereferenced type + --> $DIR/implicit_clone.rs:83:17 + | +LL | let _ = str.to_owned(); + | ^^^^^^^^ help: consider using: `clone` + +error: implicitly cloning a `Kitten` by calling `to_owned` on its dereferenced type + --> $DIR/implicit_clone.rs:87:20 + | +LL | let _ = kitten.to_owned(); + | ^^^^^^^^ help: consider using: `clone` + +error: implicitly cloning a `PathBuf` by calling `to_owned` on its dereferenced type + --> $DIR/implicit_clone.rs:97:21 + | +LL | let _ = pathbuf.to_owned(); + | ^^^^^^^^ help: consider using: `clone` + +error: implicitly cloning a `PathBuf` by calling `to_path_buf` on its dereferenced type + --> $DIR/implicit_clone.rs:98:21 + | +LL | let _ = pathbuf.to_path_buf(); + | ^^^^^^^^^^^ help: consider using: `clone` + +error: implicitly cloning a `OsString` by calling `to_owned` on its dereferenced type + --> $DIR/implicit_clone.rs:101:23 + | +LL | let _ = os_string.to_owned(); + | ^^^^^^^^ help: consider using: `clone` + +error: implicitly cloning a `OsString` by calling `to_os_string` on its dereferenced type + --> $DIR/implicit_clone.rs:102:23 + | +LL | let _ = os_string.to_os_string(); + | ^^^^^^^^^^^^ help: consider using: `clone` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/implicit_hasher.rs b/src/tools/clippy/tests/ui/implicit_hasher.rs new file mode 100644 index 0000000000..fdcc9a33f5 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_hasher.rs @@ -0,0 +1,99 @@ +// aux-build:implicit_hasher_macros.rs +#![deny(clippy::implicit_hasher)] +#![allow(unused)] + +#[macro_use] +extern crate implicit_hasher_macros; + +use std::cmp::Eq; +use std::collections::{HashMap, HashSet}; +use std::hash::{BuildHasher, Hash}; + +pub trait Foo: Sized { + fn make() -> (Self, Self); +} + +impl Foo for HashMap { + fn make() -> (Self, Self) { + // OK, don't suggest to modify these + let _: HashMap = HashMap::new(); + let _: HashSet = HashSet::new(); + + (HashMap::new(), HashMap::with_capacity(10)) + } +} +impl Foo for (HashMap,) { + fn make() -> (Self, Self) { + ((HashMap::new(),), (HashMap::with_capacity(10),)) + } +} +impl Foo for HashMap { + fn make() -> (Self, Self) { + (HashMap::new(), HashMap::with_capacity(10)) + } +} + +impl Foo for HashMap { + fn make() -> (Self, Self) { + (HashMap::default(), HashMap::with_capacity_and_hasher(10, S::default())) + } +} +impl Foo for HashMap { + fn make() -> (Self, Self) { + (HashMap::default(), HashMap::with_capacity_and_hasher(10, S::default())) + } +} + +impl Foo for HashSet { + fn make() -> (Self, Self) { + (HashSet::new(), HashSet::with_capacity(10)) + } +} +impl Foo for HashSet { + fn make() -> (Self, Self) { + (HashSet::new(), HashSet::with_capacity(10)) + } +} + +impl Foo for HashSet { + fn make() -> (Self, Self) { + (HashSet::default(), HashSet::with_capacity_and_hasher(10, S::default())) + } +} +impl Foo for HashSet { + fn make() -> (Self, Self) { + (HashSet::default(), HashSet::with_capacity_and_hasher(10, S::default())) + } +} + +pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} + +macro_rules! gen { + (impl) => { + impl Foo for HashMap { + fn make() -> (Self, Self) { + (HashMap::new(), HashMap::with_capacity(10)) + } + } + }; + + (fn $name:ident) => { + pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} + }; +} +#[rustfmt::skip] +gen!(impl); +gen!(fn bar); + +// When the macro is in a different file, the suggestion spans can't be combined properly +// and should not cause an ICE +// See #2707 +#[macro_use] +#[path = "../auxiliary/test_macro.rs"] +pub mod test_macro; +__implicit_hasher_test_macro!(impl for HashMap where V: test_macro::A); + +// #4260 +implicit_hasher_fn!(); + +fn main() {} diff --git a/src/tools/clippy/tests/ui/implicit_hasher.stderr b/src/tools/clippy/tests/ui/implicit_hasher.stderr new file mode 100644 index 0000000000..2b06d66177 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_hasher.stderr @@ -0,0 +1,153 @@ +error: impl for `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:16:35 + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/implicit_hasher.rs:2:9 + | +LL | #![deny(clippy::implicit_hasher)] + | ^^^^^^^^^^^^^^^^^^^^^^^ +help: consider adding a type parameter + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | (HashMap::default(), HashMap::with_capacity_and_hasher(10, Default::default())) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: impl for `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:25:36 + | +LL | impl Foo for (HashMap,) { + | ^^^^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | impl Foo for (HashMap,) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | ((HashMap::default(),), (HashMap::with_capacity_and_hasher(10, Default::default()),)) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: impl for `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:30:19 + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | (HashMap::default(), HashMap::with_capacity_and_hasher(10, Default::default())) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: impl for `HashSet` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:47:32 + | +LL | impl Foo for HashSet { + | ^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | impl Foo for HashSet { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | (HashSet::default(), HashSet::with_capacity_and_hasher(10, Default::default())) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: impl for `HashSet` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:52:19 + | +LL | impl Foo for HashSet { + | ^^^^^^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | impl Foo for HashSet { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | (HashSet::default(), HashSet::with_capacity_and_hasher(10, Default::default())) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter of type `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:69:23 + | +LL | pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ + +error: parameter of type `HashSet` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:69:53 + | +LL | pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ + +error: impl for `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:73:43 + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^ +... +LL | gen!(impl); + | ----------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider adding a type parameter + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | (HashMap::default(), HashMap::with_capacity_and_hasher(10, Default::default())) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter of type `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:81:33 + | +LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^ +... +LL | gen!(fn bar); + | ------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider adding a type parameter + | +LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ + +error: parameter of type `HashSet` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:81:63 + | +LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^ +... +LL | gen!(fn bar); + | ------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider adding a type parameter + | +LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/implicit_return.fixed b/src/tools/clippy/tests/ui/implicit_return.fixed new file mode 100644 index 0000000000..59f7ad9c10 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_return.fixed @@ -0,0 +1,97 @@ +// run-rustfix + +#![warn(clippy::implicit_return)] +#![allow(clippy::needless_return, unused)] + +fn test_end_of_fn() -> bool { + if true { + // no error! + return true; + } + + return true +} + +#[allow(clippy::needless_bool)] +fn test_if_block() -> bool { + if true { return true } else { return false } +} + +#[rustfmt::skip] +fn test_match(x: bool) -> bool { + match x { + true => return false, + false => { return true }, + } +} + +#[allow(clippy::needless_return)] +fn test_match_with_unreachable(x: bool) -> bool { + match x { + true => return false, + false => unreachable!(), + } +} + +#[allow(clippy::never_loop)] +fn test_loop() -> bool { + loop { + return true; + } +} + +#[allow(clippy::never_loop)] +fn test_loop_with_block() -> bool { + loop { + { + return true; + } + } +} + +#[allow(clippy::never_loop)] +fn test_loop_with_nests() -> bool { + loop { + if true { + return true; + } else { + let _ = true; + } + } +} + +#[allow(clippy::redundant_pattern_matching)] +fn test_loop_with_if_let() -> bool { + loop { + if let Some(x) = Some(true) { + return x; + } + } +} + +fn test_closure() { + #[rustfmt::skip] + let _ = || { return true }; + let _ = || return true; +} + +fn test_panic() -> bool { + panic!() +} + +fn test_return_macro() -> String { + return format!("test {}", "test") +} + +fn main() { + let _ = test_end_of_fn(); + let _ = test_if_block(); + let _ = test_match(true); + let _ = test_match_with_unreachable(true); + let _ = test_loop(); + let _ = test_loop_with_block(); + let _ = test_loop_with_nests(); + let _ = test_loop_with_if_let(); + test_closure(); + let _ = test_return_macro(); +} diff --git a/src/tools/clippy/tests/ui/implicit_return.rs b/src/tools/clippy/tests/ui/implicit_return.rs new file mode 100644 index 0000000000..2c1bc04651 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_return.rs @@ -0,0 +1,97 @@ +// run-rustfix + +#![warn(clippy::implicit_return)] +#![allow(clippy::needless_return, unused)] + +fn test_end_of_fn() -> bool { + if true { + // no error! + return true; + } + + true +} + +#[allow(clippy::needless_bool)] +fn test_if_block() -> bool { + if true { true } else { false } +} + +#[rustfmt::skip] +fn test_match(x: bool) -> bool { + match x { + true => false, + false => { true }, + } +} + +#[allow(clippy::needless_return)] +fn test_match_with_unreachable(x: bool) -> bool { + match x { + true => return false, + false => unreachable!(), + } +} + +#[allow(clippy::never_loop)] +fn test_loop() -> bool { + loop { + break true; + } +} + +#[allow(clippy::never_loop)] +fn test_loop_with_block() -> bool { + loop { + { + break true; + } + } +} + +#[allow(clippy::never_loop)] +fn test_loop_with_nests() -> bool { + loop { + if true { + break true; + } else { + let _ = true; + } + } +} + +#[allow(clippy::redundant_pattern_matching)] +fn test_loop_with_if_let() -> bool { + loop { + if let Some(x) = Some(true) { + return x; + } + } +} + +fn test_closure() { + #[rustfmt::skip] + let _ = || { true }; + let _ = || true; +} + +fn test_panic() -> bool { + panic!() +} + +fn test_return_macro() -> String { + format!("test {}", "test") +} + +fn main() { + let _ = test_end_of_fn(); + let _ = test_if_block(); + let _ = test_match(true); + let _ = test_match_with_unreachable(true); + let _ = test_loop(); + let _ = test_loop_with_block(); + let _ = test_loop_with_nests(); + let _ = test_loop_with_if_let(); + test_closure(); + let _ = test_return_macro(); +} diff --git a/src/tools/clippy/tests/ui/implicit_return.stderr b/src/tools/clippy/tests/ui/implicit_return.stderr new file mode 100644 index 0000000000..3608319e5b --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_return.stderr @@ -0,0 +1,70 @@ +error: missing `return` statement + --> $DIR/implicit_return.rs:12:5 + | +LL | true + | ^^^^ help: add `return` as shown: `return true` + | + = note: `-D clippy::implicit-return` implied by `-D warnings` + +error: missing `return` statement + --> $DIR/implicit_return.rs:17:15 + | +LL | if true { true } else { false } + | ^^^^ help: add `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:17:29 + | +LL | if true { true } else { false } + | ^^^^^ help: add `return` as shown: `return false` + +error: missing `return` statement + --> $DIR/implicit_return.rs:23:17 + | +LL | true => false, + | ^^^^^ help: add `return` as shown: `return false` + +error: missing `return` statement + --> $DIR/implicit_return.rs:24:20 + | +LL | false => { true }, + | ^^^^ help: add `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:39:9 + | +LL | break true; + | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:47:13 + | +LL | break true; + | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:56:13 + | +LL | break true; + | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:74:18 + | +LL | let _ = || { true }; + | ^^^^ help: add `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:75:16 + | +LL | let _ = || true; + | ^^^^ help: add `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:83:5 + | +LL | format!("test {}", "test") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `return` as shown: `return format!("test {}", "test")` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/implicit_saturating_sub.fixed b/src/tools/clippy/tests/ui/implicit_saturating_sub.fixed new file mode 100644 index 0000000000..859765d08a --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_saturating_sub.fixed @@ -0,0 +1,160 @@ +// run-rustfix +#![allow(unused_assignments, unused_mut, clippy::assign_op_pattern)] +#![warn(clippy::implicit_saturating_sub)] + +fn main() { + // Tests for unsigned integers + + let end_8: u8 = 10; + let start_8: u8 = 5; + let mut u_8: u8 = end_8 - start_8; + + // Lint + u_8 = u_8.saturating_sub(1); + + match end_8 { + 10 => { + // Lint + u_8 = u_8.saturating_sub(1); + }, + 11 => u_8 += 1, + _ => u_8 = 0, + } + + let end_16: u16 = 40; + let start_16: u16 = 35; + + let mut u_16: u16 = end_16 - start_16; + + // Lint + u_16 = u_16.saturating_sub(1); + + let mut end_32: u32 = 7010; + let mut start_32: u32 = 7000; + + let mut u_32: u32 = end_32 - start_32; + + // Lint + u_32 = u_32.saturating_sub(1); + + // No Lint + if u_32 > 0 { + u_16 += 1; + } + + // No Lint + if u_32 != 0 { + end_32 -= 1; + start_32 += 1; + } + + let mut end_64: u64 = 75001; + let mut start_64: u64 = 75000; + + let mut u_64: u64 = end_64 - start_64; + + // Lint + u_64 = u_64.saturating_sub(1); + + // Lint + u_64 = u_64.saturating_sub(1); + + // Lint + u_64 = u_64.saturating_sub(1); + + // No Lint + if u_64 >= 1 { + u_64 -= 1; + } + + // No Lint + if u_64 > 0 { + end_64 -= 1; + } + + // Tests for usize + let end_usize: usize = 8054; + let start_usize: usize = 8050; + + let mut u_usize: usize = end_usize - start_usize; + + // Lint + u_usize = u_usize.saturating_sub(1); + + // Tests for signed integers + + let endi_8: i8 = 10; + let starti_8: i8 = 50; + + let mut i_8: i8 = endi_8 - starti_8; + + // Lint + i_8 = i_8.saturating_sub(1); + + // Lint + i_8 = i_8.saturating_sub(1); + + // Lint + i_8 = i_8.saturating_sub(1); + + // Lint + i_8 = i_8.saturating_sub(1); + + let endi_16: i16 = 45; + let starti_16: i16 = 44; + + let mut i_16: i16 = endi_16 - starti_16; + + // Lint + i_16 = i_16.saturating_sub(1); + + // Lint + i_16 = i_16.saturating_sub(1); + + // Lint + i_16 = i_16.saturating_sub(1); + + // Lint + i_16 = i_16.saturating_sub(1); + + let endi_32: i32 = 45; + let starti_32: i32 = 44; + + let mut i_32: i32 = endi_32 - starti_32; + + // Lint + i_32 = i_32.saturating_sub(1); + + // Lint + i_32 = i_32.saturating_sub(1); + + // Lint + i_32 = i_32.saturating_sub(1); + + // Lint + i_32 = i_32.saturating_sub(1); + + let endi_64: i64 = 45; + let starti_64: i64 = 44; + + let mut i_64: i64 = endi_64 - starti_64; + + // Lint + i_64 = i_64.saturating_sub(1); + + // Lint + i_64 = i_64.saturating_sub(1); + + // Lint + i_64 = i_64.saturating_sub(1); + + // No Lint + if i_64 > 0 { + i_64 -= 1; + } + + // No Lint + if i_64 != 0 { + i_64 -= 1; + } +} diff --git a/src/tools/clippy/tests/ui/implicit_saturating_sub.rs b/src/tools/clippy/tests/ui/implicit_saturating_sub.rs new file mode 100644 index 0000000000..2f32a7b157 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_saturating_sub.rs @@ -0,0 +1,206 @@ +// run-rustfix +#![allow(unused_assignments, unused_mut, clippy::assign_op_pattern)] +#![warn(clippy::implicit_saturating_sub)] + +fn main() { + // Tests for unsigned integers + + let end_8: u8 = 10; + let start_8: u8 = 5; + let mut u_8: u8 = end_8 - start_8; + + // Lint + if u_8 > 0 { + u_8 = u_8 - 1; + } + + match end_8 { + 10 => { + // Lint + if u_8 > 0 { + u_8 -= 1; + } + }, + 11 => u_8 += 1, + _ => u_8 = 0, + } + + let end_16: u16 = 40; + let start_16: u16 = 35; + + let mut u_16: u16 = end_16 - start_16; + + // Lint + if u_16 > 0 { + u_16 -= 1; + } + + let mut end_32: u32 = 7010; + let mut start_32: u32 = 7000; + + let mut u_32: u32 = end_32 - start_32; + + // Lint + if u_32 != 0 { + u_32 -= 1; + } + + // No Lint + if u_32 > 0 { + u_16 += 1; + } + + // No Lint + if u_32 != 0 { + end_32 -= 1; + start_32 += 1; + } + + let mut end_64: u64 = 75001; + let mut start_64: u64 = 75000; + + let mut u_64: u64 = end_64 - start_64; + + // Lint + if u_64 > 0 { + u_64 -= 1; + } + + // Lint + if 0 < u_64 { + u_64 -= 1; + } + + // Lint + if 0 != u_64 { + u_64 -= 1; + } + + // No Lint + if u_64 >= 1 { + u_64 -= 1; + } + + // No Lint + if u_64 > 0 { + end_64 -= 1; + } + + // Tests for usize + let end_usize: usize = 8054; + let start_usize: usize = 8050; + + let mut u_usize: usize = end_usize - start_usize; + + // Lint + if u_usize > 0 { + u_usize -= 1; + } + + // Tests for signed integers + + let endi_8: i8 = 10; + let starti_8: i8 = 50; + + let mut i_8: i8 = endi_8 - starti_8; + + // Lint + if i_8 > i8::MIN { + i_8 -= 1; + } + + // Lint + if i_8 > i8::MIN { + i_8 -= 1; + } + + // Lint + if i_8 != i8::MIN { + i_8 -= 1; + } + + // Lint + if i_8 != i8::MIN { + i_8 -= 1; + } + + let endi_16: i16 = 45; + let starti_16: i16 = 44; + + let mut i_16: i16 = endi_16 - starti_16; + + // Lint + if i_16 > i16::MIN { + i_16 -= 1; + } + + // Lint + if i_16 > i16::MIN { + i_16 -= 1; + } + + // Lint + if i_16 != i16::MIN { + i_16 -= 1; + } + + // Lint + if i_16 != i16::MIN { + i_16 -= 1; + } + + let endi_32: i32 = 45; + let starti_32: i32 = 44; + + let mut i_32: i32 = endi_32 - starti_32; + + // Lint + if i_32 > i32::MIN { + i_32 -= 1; + } + + // Lint + if i_32 > i32::MIN { + i_32 -= 1; + } + + // Lint + if i_32 != i32::MIN { + i_32 -= 1; + } + + // Lint + if i_32 != i32::MIN { + i_32 -= 1; + } + + let endi_64: i64 = 45; + let starti_64: i64 = 44; + + let mut i_64: i64 = endi_64 - starti_64; + + // Lint + if i64::MIN < i_64 { + i_64 -= 1; + } + + // Lint + if i64::MIN != i_64 { + i_64 -= 1; + } + + // Lint + if i64::MIN < i_64 { + i_64 -= 1; + } + + // No Lint + if i_64 > 0 { + i_64 -= 1; + } + + // No Lint + if i_64 != 0 { + i_64 -= 1; + } +} diff --git a/src/tools/clippy/tests/ui/implicit_saturating_sub.stderr b/src/tools/clippy/tests/ui/implicit_saturating_sub.stderr new file mode 100644 index 0000000000..5bb9a60642 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_saturating_sub.stderr @@ -0,0 +1,188 @@ +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:13:5 + | +LL | / if u_8 > 0 { +LL | | u_8 = u_8 - 1; +LL | | } + | |_____^ help: try: `u_8 = u_8.saturating_sub(1);` + | + = note: `-D clippy::implicit-saturating-sub` implied by `-D warnings` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:20:13 + | +LL | / if u_8 > 0 { +LL | | u_8 -= 1; +LL | | } + | |_____________^ help: try: `u_8 = u_8.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:34:5 + | +LL | / if u_16 > 0 { +LL | | u_16 -= 1; +LL | | } + | |_____^ help: try: `u_16 = u_16.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:44:5 + | +LL | / if u_32 != 0 { +LL | | u_32 -= 1; +LL | | } + | |_____^ help: try: `u_32 = u_32.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:65:5 + | +LL | / if u_64 > 0 { +LL | | u_64 -= 1; +LL | | } + | |_____^ help: try: `u_64 = u_64.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:70:5 + | +LL | / if 0 < u_64 { +LL | | u_64 -= 1; +LL | | } + | |_____^ help: try: `u_64 = u_64.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:75:5 + | +LL | / if 0 != u_64 { +LL | | u_64 -= 1; +LL | | } + | |_____^ help: try: `u_64 = u_64.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:96:5 + | +LL | / if u_usize > 0 { +LL | | u_usize -= 1; +LL | | } + | |_____^ help: try: `u_usize = u_usize.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:108:5 + | +LL | / if i_8 > i8::MIN { +LL | | i_8 -= 1; +LL | | } + | |_____^ help: try: `i_8 = i_8.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:113:5 + | +LL | / if i_8 > i8::MIN { +LL | | i_8 -= 1; +LL | | } + | |_____^ help: try: `i_8 = i_8.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:118:5 + | +LL | / if i_8 != i8::MIN { +LL | | i_8 -= 1; +LL | | } + | |_____^ help: try: `i_8 = i_8.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:123:5 + | +LL | / if i_8 != i8::MIN { +LL | | i_8 -= 1; +LL | | } + | |_____^ help: try: `i_8 = i_8.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:133:5 + | +LL | / if i_16 > i16::MIN { +LL | | i_16 -= 1; +LL | | } + | |_____^ help: try: `i_16 = i_16.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:138:5 + | +LL | / if i_16 > i16::MIN { +LL | | i_16 -= 1; +LL | | } + | |_____^ help: try: `i_16 = i_16.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:143:5 + | +LL | / if i_16 != i16::MIN { +LL | | i_16 -= 1; +LL | | } + | |_____^ help: try: `i_16 = i_16.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:148:5 + | +LL | / if i_16 != i16::MIN { +LL | | i_16 -= 1; +LL | | } + | |_____^ help: try: `i_16 = i_16.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:158:5 + | +LL | / if i_32 > i32::MIN { +LL | | i_32 -= 1; +LL | | } + | |_____^ help: try: `i_32 = i_32.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:163:5 + | +LL | / if i_32 > i32::MIN { +LL | | i_32 -= 1; +LL | | } + | |_____^ help: try: `i_32 = i_32.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:168:5 + | +LL | / if i_32 != i32::MIN { +LL | | i_32 -= 1; +LL | | } + | |_____^ help: try: `i_32 = i_32.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:173:5 + | +LL | / if i_32 != i32::MIN { +LL | | i_32 -= 1; +LL | | } + | |_____^ help: try: `i_32 = i_32.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:183:5 + | +LL | / if i64::MIN < i_64 { +LL | | i_64 -= 1; +LL | | } + | |_____^ help: try: `i_64 = i_64.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:188:5 + | +LL | / if i64::MIN != i_64 { +LL | | i_64 -= 1; +LL | | } + | |_____^ help: try: `i_64 = i_64.saturating_sub(1);` + +error: implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:193:5 + | +LL | / if i64::MIN < i_64 { +LL | | i_64 -= 1; +LL | | } + | |_____^ help: try: `i_64 = i_64.saturating_sub(1);` + +error: aborting due to 23 previous errors + diff --git a/src/tools/clippy/tests/ui/inconsistent_digit_grouping.fixed b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.fixed new file mode 100644 index 0000000000..dd683e7f74 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.fixed @@ -0,0 +1,47 @@ +// run-rustfix +#[warn(clippy::inconsistent_digit_grouping)] +#[deny(clippy::unreadable_literal)] +#[allow(unused_variables, clippy::excessive_precision)] +fn main() { + macro_rules! mac1 { + () => { + 1_23_456 + }; + } + macro_rules! mac2 { + () => { + 1_234.5678_f32 + }; + } + + let good = ( + 123, + 1_234, + 1_2345_6789, + 123_f32, + 1_234.12_f32, + 1_234.123_4_f32, + 1.123_456_7_f32, + ); + let bad = (123_456, 12_345_678, 1_234_567, 1_234.567_8_f32, 1.234_567_8_f32); + + // Test padding + let _ = 0x0010_0000; + let _ = 0x0100_0000; + let _ = 0x1000_0000; + let _ = 0x0001_0000_0000_u64; + + // Test suggestion when fraction has no digits + let _: f32 = 123_456.; + + // Test UUID formatted literal + let _: u128 = 0x12345678_1234_1234_1234_123456789012; + + // Ignore literals in macros + let _ = mac1!(); + let _ = mac2!(); + + // Issue #6096 + // Allow separating exponent with '_' + let _ = 1.025_011_10_E0; +} diff --git a/src/tools/clippy/tests/ui/inconsistent_digit_grouping.rs b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.rs new file mode 100644 index 0000000000..d5d27c853c --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.rs @@ -0,0 +1,47 @@ +// run-rustfix +#[warn(clippy::inconsistent_digit_grouping)] +#[deny(clippy::unreadable_literal)] +#[allow(unused_variables, clippy::excessive_precision)] +fn main() { + macro_rules! mac1 { + () => { + 1_23_456 + }; + } + macro_rules! mac2 { + () => { + 1_234.5678_f32 + }; + } + + let good = ( + 123, + 1_234, + 1_2345_6789, + 123_f32, + 1_234.12_f32, + 1_234.123_4_f32, + 1.123_456_7_f32, + ); + let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + + // Test padding + let _ = 0x100000; + let _ = 0x1000000; + let _ = 0x10000000; + let _ = 0x100000000_u64; + + // Test suggestion when fraction has no digits + let _: f32 = 1_23_456.; + + // Test UUID formatted literal + let _: u128 = 0x12345678_1234_1234_1234_123456789012; + + // Ignore literals in macros + let _ = mac1!(); + let _ = mac2!(); + + // Issue #6096 + // Allow separating exponent with '_' + let _ = 1.025_011_10_E0; +} diff --git a/src/tools/clippy/tests/ui/inconsistent_digit_grouping.stderr b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.stderr new file mode 100644 index 0000000000..b8ac915546 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.stderr @@ -0,0 +1,70 @@ +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:26:16 + | +LL | let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + | ^^^^^^^^ help: consider: `123_456` + | + = note: `-D clippy::inconsistent-digit-grouping` implied by `-D warnings` + +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:26:26 + | +LL | let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + | ^^^^^^^^^^ help: consider: `12_345_678` + +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:26:38 + | +LL | let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + | ^^^^^^^^ help: consider: `1_234_567` + +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:26:48 + | +LL | let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + | ^^^^^^^^^^^^^^ help: consider: `1_234.567_8_f32` + +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:26:64 + | +LL | let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + | ^^^^^^^^^^^^^^ help: consider: `1.234_567_8_f32` + +error: long literal lacking separators + --> $DIR/inconsistent_digit_grouping.rs:29:13 + | +LL | let _ = 0x100000; + | ^^^^^^^^ help: consider: `0x0010_0000` + | +note: the lint level is defined here + --> $DIR/inconsistent_digit_grouping.rs:3:8 + | +LL | #[deny(clippy::unreadable_literal)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: long literal lacking separators + --> $DIR/inconsistent_digit_grouping.rs:30:13 + | +LL | let _ = 0x1000000; + | ^^^^^^^^^ help: consider: `0x0100_0000` + +error: long literal lacking separators + --> $DIR/inconsistent_digit_grouping.rs:31:13 + | +LL | let _ = 0x10000000; + | ^^^^^^^^^^ help: consider: `0x1000_0000` + +error: long literal lacking separators + --> $DIR/inconsistent_digit_grouping.rs:32:13 + | +LL | let _ = 0x100000000_u64; + | ^^^^^^^^^^^^^^^ help: consider: `0x0001_0000_0000_u64` + +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:35:18 + | +LL | let _: f32 = 1_23_456.; + | ^^^^^^^^^ help: consider: `123_456.` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/inconsistent_struct_constructor.fixed b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.fixed new file mode 100644 index 0000000000..8d9c311003 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.fixed @@ -0,0 +1,61 @@ +// run-rustfix +// edition:2018 +#![warn(clippy::inconsistent_struct_constructor)] +#![allow(clippy::redundant_field_names)] +#![allow(clippy::unnecessary_operation)] +#![allow(clippy::no_effect)] +#![allow(dead_code)] + +#[derive(Default)] +struct Foo { + x: i32, + y: i32, + z: i32, +} + +mod without_base { + use super::Foo; + + fn test() { + let x = 1; + let y = 1; + let z = 1; + + // Should lint. + Foo { x, y, z }; + + // Shoule NOT lint because the order is the same as in the definition. + Foo { x, y, z }; + + // Should NOT lint because z is not a shorthand init. + Foo { y, x, z: z }; + } +} + +mod with_base { + use super::Foo; + + fn test() { + let x = 1; + let z = 1; + + // Should lint. + Foo { x, z, ..Default::default() }; + + // Should NOT lint because the order is consistent with the definition. + Foo { + x, + z, + ..Default::default() + }; + + // Should NOT lint because z is not a shorthand init. + Foo { + z: z, + x, + ..Default::default() + }; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/inconsistent_struct_constructor.rs b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.rs new file mode 100644 index 0000000000..63fac91050 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.rs @@ -0,0 +1,65 @@ +// run-rustfix +// edition:2018 +#![warn(clippy::inconsistent_struct_constructor)] +#![allow(clippy::redundant_field_names)] +#![allow(clippy::unnecessary_operation)] +#![allow(clippy::no_effect)] +#![allow(dead_code)] + +#[derive(Default)] +struct Foo { + x: i32, + y: i32, + z: i32, +} + +mod without_base { + use super::Foo; + + fn test() { + let x = 1; + let y = 1; + let z = 1; + + // Should lint. + Foo { y, x, z }; + + // Shoule NOT lint because the order is the same as in the definition. + Foo { x, y, z }; + + // Should NOT lint because z is not a shorthand init. + Foo { y, x, z: z }; + } +} + +mod with_base { + use super::Foo; + + fn test() { + let x = 1; + let z = 1; + + // Should lint. + Foo { + z, + x, + ..Default::default() + }; + + // Should NOT lint because the order is consistent with the definition. + Foo { + x, + z, + ..Default::default() + }; + + // Should NOT lint because z is not a shorthand init. + Foo { + z: z, + x, + ..Default::default() + }; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/inconsistent_struct_constructor.stderr b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.stderr new file mode 100644 index 0000000000..d7abe44f25 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.stderr @@ -0,0 +1,20 @@ +error: inconsistent struct constructor + --> $DIR/inconsistent_struct_constructor.rs:25:9 + | +LL | Foo { y, x, z }; + | ^^^^^^^^^^^^^^^ help: try: `Foo { x, y, z }` + | + = note: `-D clippy::inconsistent-struct-constructor` implied by `-D warnings` + +error: inconsistent struct constructor + --> $DIR/inconsistent_struct_constructor.rs:43:9 + | +LL | / Foo { +LL | | z, +LL | | x, +LL | | ..Default::default() +LL | | }; + | |_________^ help: try: `Foo { x, z, ..Default::default() }` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/indexing_slicing_index.rs b/src/tools/clippy/tests/ui/indexing_slicing_index.rs new file mode 100644 index 0000000000..ca8ca53c80 --- /dev/null +++ b/src/tools/clippy/tests/ui/indexing_slicing_index.rs @@ -0,0 +1,32 @@ +#![warn(clippy::indexing_slicing)] +// We also check the out_of_bounds_indexing lint here, because it lints similar things and +// we want to avoid false positives. +#![warn(clippy::out_of_bounds_indexing)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +fn main() { + let x = [1, 2, 3, 4]; + let index: usize = 1; + x[index]; + x[4]; // Ok, let rustc's `const_err` lint handle `usize` indexing on arrays. + x[1 << 3]; // Ok, let rustc's `const_err` lint handle `usize` indexing on arrays. + + x[0]; // Ok, should not produce stderr. + x[3]; // Ok, should not produce stderr. + + let y = &x; + y[0]; // Ok, referencing shouldn't affect this lint. See the issue 6021 + y[4]; // Ok, rustc will handle references too. + + let v = vec![0; 5]; + v[0]; + v[10]; + v[1 << 3]; + + const N: usize = 15; // Out of bounds + const M: usize = 3; // In bounds + x[N]; // Ok, let rustc's `const_err` lint handle `usize` indexing on arrays. + x[M]; // Ok, should not produce stderr. + v[N]; + v[M]; +} diff --git a/src/tools/clippy/tests/ui/indexing_slicing_index.stderr b/src/tools/clippy/tests/ui/indexing_slicing_index.stderr new file mode 100644 index 0000000000..76ecec3348 --- /dev/null +++ b/src/tools/clippy/tests/ui/indexing_slicing_index.stderr @@ -0,0 +1,51 @@ +error: indexing may panic + --> $DIR/indexing_slicing_index.rs:10:5 + | +LL | x[index]; + | ^^^^^^^^ + | + = note: `-D clippy::indexing-slicing` implied by `-D warnings` + = help: consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic + --> $DIR/indexing_slicing_index.rs:22:5 + | +LL | v[0]; + | ^^^^ + | + = help: consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic + --> $DIR/indexing_slicing_index.rs:23:5 + | +LL | v[10]; + | ^^^^^ + | + = help: consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic + --> $DIR/indexing_slicing_index.rs:24:5 + | +LL | v[1 << 3]; + | ^^^^^^^^^ + | + = help: consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic + --> $DIR/indexing_slicing_index.rs:30:5 + | +LL | v[N]; + | ^^^^ + | + = help: consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic + --> $DIR/indexing_slicing_index.rs:31:5 + | +LL | v[M]; + | ^^^^ + | + = help: consider using `.get(n)` or `.get_mut(n)` instead + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/indexing_slicing_slice.rs b/src/tools/clippy/tests/ui/indexing_slicing_slice.rs new file mode 100644 index 0000000000..7b107db39f --- /dev/null +++ b/src/tools/clippy/tests/ui/indexing_slicing_slice.rs @@ -0,0 +1,37 @@ +#![warn(clippy::indexing_slicing)] +// We also check the out_of_bounds_indexing lint here, because it lints similar things and +// we want to avoid false positives. +#![warn(clippy::out_of_bounds_indexing)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +fn main() { + let x = [1, 2, 3, 4]; + let index: usize = 1; + let index_from: usize = 2; + let index_to: usize = 3; + &x[index..]; + &x[..index]; + &x[index_from..index_to]; + &x[index_from..][..index_to]; // Two lint reports, one for [index_from..] and another for [..index_to]. + &x[5..][..10]; // Two lint reports, one for out of bounds [5..] and another for slicing [..10]. + &x[0..][..3]; + &x[1..][..5]; + + &x[0..].get(..3); // Ok, should not produce stderr. + &x[0..3]; // Ok, should not produce stderr. + + let y = &x; + &y[1..2]; + &y[0..=4]; + &y[..=4]; + + &y[..]; // Ok, should not produce stderr. + + let v = vec![0; 5]; + &v[10..100]; + &x[10..][..100]; // Two lint reports, one for [10..] and another for [..100]. + &v[10..]; + &v[..100]; + + &v[..]; // Ok, should not produce stderr. +} diff --git a/src/tools/clippy/tests/ui/indexing_slicing_slice.stderr b/src/tools/clippy/tests/ui/indexing_slicing_slice.stderr new file mode 100644 index 0000000000..f70722b92a --- /dev/null +++ b/src/tools/clippy/tests/ui/indexing_slicing_slice.stderr @@ -0,0 +1,125 @@ +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:12:6 + | +LL | &x[index..]; + | ^^^^^^^^^^ + | + = note: `-D clippy::indexing-slicing` implied by `-D warnings` + = help: consider using `.get(n..)` or .get_mut(n..)` instead + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:13:6 + | +LL | &x[..index]; + | ^^^^^^^^^^ + | + = help: consider using `.get(..n)`or `.get_mut(..n)` instead + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:14:6 + | +LL | &x[index_from..index_to]; + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `.get(n..m)` or `.get_mut(n..m)` instead + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:15:6 + | +LL | &x[index_from..][..index_to]; // Two lint reports, one for [index_from..] and another for [..index_to]. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `.get(..n)`or `.get_mut(..n)` instead + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:15:6 + | +LL | &x[index_from..][..index_to]; // Two lint reports, one for [index_from..] and another for [..index_to]. + | ^^^^^^^^^^^^^^^ + | + = help: consider using `.get(n..)` or .get_mut(n..)` instead + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:16:6 + | +LL | &x[5..][..10]; // Two lint reports, one for out of bounds [5..] and another for slicing [..10]. + | ^^^^^^^^^^^^ + | + = help: consider using `.get(..n)`or `.get_mut(..n)` instead + +error: range is out of bounds + --> $DIR/indexing_slicing_slice.rs:16:8 + | +LL | &x[5..][..10]; // Two lint reports, one for out of bounds [5..] and another for slicing [..10]. + | ^ + | + = note: `-D clippy::out-of-bounds-indexing` implied by `-D warnings` + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:17:6 + | +LL | &x[0..][..3]; + | ^^^^^^^^^^^ + | + = help: consider using `.get(..n)`or `.get_mut(..n)` instead + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:18:6 + | +LL | &x[1..][..5]; + | ^^^^^^^^^^^ + | + = help: consider using `.get(..n)`or `.get_mut(..n)` instead + +error: range is out of bounds + --> $DIR/indexing_slicing_slice.rs:25:12 + | +LL | &y[0..=4]; + | ^ + +error: range is out of bounds + --> $DIR/indexing_slicing_slice.rs:26:11 + | +LL | &y[..=4]; + | ^ + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:31:6 + | +LL | &v[10..100]; + | ^^^^^^^^^^ + | + = help: consider using `.get(n..m)` or `.get_mut(n..m)` instead + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:32:6 + | +LL | &x[10..][..100]; // Two lint reports, one for [10..] and another for [..100]. + | ^^^^^^^^^^^^^^ + | + = help: consider using `.get(..n)`or `.get_mut(..n)` instead + +error: range is out of bounds + --> $DIR/indexing_slicing_slice.rs:32:8 + | +LL | &x[10..][..100]; // Two lint reports, one for [10..] and another for [..100]. + | ^^ + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:33:6 + | +LL | &v[10..]; + | ^^^^^^^ + | + = help: consider using `.get(n..)` or .get_mut(n..)` instead + +error: slicing may panic + --> $DIR/indexing_slicing_slice.rs:34:6 + | +LL | &v[..100]; + | ^^^^^^^^ + | + = help: consider using `.get(..n)`or `.get_mut(..n)` instead + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/inefficient_to_string.fixed b/src/tools/clippy/tests/ui/inefficient_to_string.fixed new file mode 100644 index 0000000000..c972b9419e --- /dev/null +++ b/src/tools/clippy/tests/ui/inefficient_to_string.fixed @@ -0,0 +1,31 @@ +// run-rustfix +#![deny(clippy::inefficient_to_string)] + +use std::borrow::Cow; + +fn main() { + let rstr: &str = "hello"; + let rrstr: &&str = &rstr; + let rrrstr: &&&str = &rrstr; + let _: String = rstr.to_string(); + let _: String = (*rrstr).to_string(); + let _: String = (**rrrstr).to_string(); + + let string: String = String::from("hello"); + let rstring: &String = &string; + let rrstring: &&String = &rstring; + let rrrstring: &&&String = &rrstring; + let _: String = string.to_string(); + let _: String = rstring.to_string(); + let _: String = (*rrstring).to_string(); + let _: String = (**rrrstring).to_string(); + + let cow: Cow<'_, str> = Cow::Borrowed("hello"); + let rcow: &Cow<'_, str> = &cow; + let rrcow: &&Cow<'_, str> = &rcow; + let rrrcow: &&&Cow<'_, str> = &rrcow; + let _: String = cow.to_string(); + let _: String = rcow.to_string(); + let _: String = (*rrcow).to_string(); + let _: String = (**rrrcow).to_string(); +} diff --git a/src/tools/clippy/tests/ui/inefficient_to_string.rs b/src/tools/clippy/tests/ui/inefficient_to_string.rs new file mode 100644 index 0000000000..acdc55aa0d --- /dev/null +++ b/src/tools/clippy/tests/ui/inefficient_to_string.rs @@ -0,0 +1,31 @@ +// run-rustfix +#![deny(clippy::inefficient_to_string)] + +use std::borrow::Cow; + +fn main() { + let rstr: &str = "hello"; + let rrstr: &&str = &rstr; + let rrrstr: &&&str = &rrstr; + let _: String = rstr.to_string(); + let _: String = rrstr.to_string(); + let _: String = rrrstr.to_string(); + + let string: String = String::from("hello"); + let rstring: &String = &string; + let rrstring: &&String = &rstring; + let rrrstring: &&&String = &rrstring; + let _: String = string.to_string(); + let _: String = rstring.to_string(); + let _: String = rrstring.to_string(); + let _: String = rrrstring.to_string(); + + let cow: Cow<'_, str> = Cow::Borrowed("hello"); + let rcow: &Cow<'_, str> = &cow; + let rrcow: &&Cow<'_, str> = &rcow; + let rrrcow: &&&Cow<'_, str> = &rrcow; + let _: String = cow.to_string(); + let _: String = rcow.to_string(); + let _: String = rrcow.to_string(); + let _: String = rrrcow.to_string(); +} diff --git a/src/tools/clippy/tests/ui/inefficient_to_string.stderr b/src/tools/clippy/tests/ui/inefficient_to_string.stderr new file mode 100644 index 0000000000..4be46161e8 --- /dev/null +++ b/src/tools/clippy/tests/ui/inefficient_to_string.stderr @@ -0,0 +1,55 @@ +error: calling `to_string` on `&&str` + --> $DIR/inefficient_to_string.rs:11:21 + | +LL | let _: String = rrstr.to_string(); + | ^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrstr).to_string()` + | +note: the lint level is defined here + --> $DIR/inefficient_to_string.rs:2:9 + | +LL | #![deny(clippy::inefficient_to_string)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: `&str` implements `ToString` through a slower blanket impl, but `str` has a fast specialization of `ToString` + +error: calling `to_string` on `&&&str` + --> $DIR/inefficient_to_string.rs:12:21 + | +LL | let _: String = rrrstr.to_string(); + | ^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrstr).to_string()` + | + = help: `&&str` implements `ToString` through a slower blanket impl, but `str` has a fast specialization of `ToString` + +error: calling `to_string` on `&&std::string::String` + --> $DIR/inefficient_to_string.rs:20:21 + | +LL | let _: String = rrstring.to_string(); + | ^^^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrstring).to_string()` + | + = help: `&std::string::String` implements `ToString` through a slower blanket impl, but `std::string::String` has a fast specialization of `ToString` + +error: calling `to_string` on `&&&std::string::String` + --> $DIR/inefficient_to_string.rs:21:21 + | +LL | let _: String = rrrstring.to_string(); + | ^^^^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrstring).to_string()` + | + = help: `&&std::string::String` implements `ToString` through a slower blanket impl, but `std::string::String` has a fast specialization of `ToString` + +error: calling `to_string` on `&&std::borrow::Cow` + --> $DIR/inefficient_to_string.rs:29:21 + | +LL | let _: String = rrcow.to_string(); + | ^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrcow).to_string()` + | + = help: `&std::borrow::Cow` implements `ToString` through a slower blanket impl, but `std::borrow::Cow` has a fast specialization of `ToString` + +error: calling `to_string` on `&&&std::borrow::Cow` + --> $DIR/inefficient_to_string.rs:30:21 + | +LL | let _: String = rrrcow.to_string(); + | ^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrcow).to_string()` + | + = help: `&&std::borrow::Cow` implements `ToString` through a slower blanket impl, but `std::borrow::Cow` has a fast specialization of `ToString` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/infallible_destructuring_match.fixed b/src/tools/clippy/tests/ui/infallible_destructuring_match.fixed new file mode 100644 index 0000000000..b8e40d9955 --- /dev/null +++ b/src/tools/clippy/tests/ui/infallible_destructuring_match.fixed @@ -0,0 +1,112 @@ +// run-rustfix +#![feature(exhaustive_patterns, never_type)] +#![allow(dead_code, unreachable_code, unused_variables)] +#![allow(clippy::let_and_return)] + +enum SingleVariantEnum { + Variant(i32), +} + +struct TupleStruct(i32); + +enum EmptyEnum {} + +macro_rules! match_enum { + ($param:expr) => { + let data = match $param { + SingleVariantEnum::Variant(i) => i, + }; + }; +} + +fn infallible_destructuring_match_enum() { + let wrapper = SingleVariantEnum::Variant(0); + + // This should lint! + let SingleVariantEnum::Variant(data) = wrapper; + + // This shouldn't (inside macro) + match_enum!(wrapper); + + // This shouldn't! + let data = match wrapper { + SingleVariantEnum::Variant(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + SingleVariantEnum::Variant(i) => -1, + }; + + let SingleVariantEnum::Variant(data) = wrapper; +} + +macro_rules! match_struct { + ($param:expr) => { + let data = match $param { + TupleStruct(i) => i, + }; + }; +} + +fn infallible_destructuring_match_struct() { + let wrapper = TupleStruct(0); + + // This should lint! + let TupleStruct(data) = wrapper; + + // This shouldn't (inside macro) + match_struct!(wrapper); + + // This shouldn't! + let data = match wrapper { + TupleStruct(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + TupleStruct(i) => -1, + }; + + let TupleStruct(data) = wrapper; +} + +macro_rules! match_never_enum { + ($param:expr) => { + let data = match $param { + Ok(i) => i, + }; + }; +} + +fn never_enum() { + let wrapper: Result = Ok(23); + + // This should lint! + let Ok(data) = wrapper; + + // This shouldn't (inside macro) + match_never_enum!(wrapper); + + // This shouldn't! + let data = match wrapper { + Ok(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + Ok(i) => -1, + }; + + let Ok(data) = wrapper; +} + +impl EmptyEnum { + fn match_on(&self) -> ! { + // The lint shouldn't pick this up, as `let` won't work here! + let data = match *self {}; + data + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/infallible_destructuring_match.rs b/src/tools/clippy/tests/ui/infallible_destructuring_match.rs new file mode 100644 index 0000000000..106cd438b9 --- /dev/null +++ b/src/tools/clippy/tests/ui/infallible_destructuring_match.rs @@ -0,0 +1,118 @@ +// run-rustfix +#![feature(exhaustive_patterns, never_type)] +#![allow(dead_code, unreachable_code, unused_variables)] +#![allow(clippy::let_and_return)] + +enum SingleVariantEnum { + Variant(i32), +} + +struct TupleStruct(i32); + +enum EmptyEnum {} + +macro_rules! match_enum { + ($param:expr) => { + let data = match $param { + SingleVariantEnum::Variant(i) => i, + }; + }; +} + +fn infallible_destructuring_match_enum() { + let wrapper = SingleVariantEnum::Variant(0); + + // This should lint! + let data = match wrapper { + SingleVariantEnum::Variant(i) => i, + }; + + // This shouldn't (inside macro) + match_enum!(wrapper); + + // This shouldn't! + let data = match wrapper { + SingleVariantEnum::Variant(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + SingleVariantEnum::Variant(i) => -1, + }; + + let SingleVariantEnum::Variant(data) = wrapper; +} + +macro_rules! match_struct { + ($param:expr) => { + let data = match $param { + TupleStruct(i) => i, + }; + }; +} + +fn infallible_destructuring_match_struct() { + let wrapper = TupleStruct(0); + + // This should lint! + let data = match wrapper { + TupleStruct(i) => i, + }; + + // This shouldn't (inside macro) + match_struct!(wrapper); + + // This shouldn't! + let data = match wrapper { + TupleStruct(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + TupleStruct(i) => -1, + }; + + let TupleStruct(data) = wrapper; +} + +macro_rules! match_never_enum { + ($param:expr) => { + let data = match $param { + Ok(i) => i, + }; + }; +} + +fn never_enum() { + let wrapper: Result = Ok(23); + + // This should lint! + let data = match wrapper { + Ok(i) => i, + }; + + // This shouldn't (inside macro) + match_never_enum!(wrapper); + + // This shouldn't! + let data = match wrapper { + Ok(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + Ok(i) => -1, + }; + + let Ok(data) = wrapper; +} + +impl EmptyEnum { + fn match_on(&self) -> ! { + // The lint shouldn't pick this up, as `let` won't work here! + let data = match *self {}; + data + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/infallible_destructuring_match.stderr b/src/tools/clippy/tests/ui/infallible_destructuring_match.stderr new file mode 100644 index 0000000000..1b78db4201 --- /dev/null +++ b/src/tools/clippy/tests/ui/infallible_destructuring_match.stderr @@ -0,0 +1,28 @@ +error: you seem to be trying to use `match` to destructure a single infallible pattern. Consider using `let` + --> $DIR/infallible_destructuring_match.rs:26:5 + | +LL | / let data = match wrapper { +LL | | SingleVariantEnum::Variant(i) => i, +LL | | }; + | |______^ help: try this: `let SingleVariantEnum::Variant(data) = wrapper;` + | + = note: `-D clippy::infallible-destructuring-match` implied by `-D warnings` + +error: you seem to be trying to use `match` to destructure a single infallible pattern. Consider using `let` + --> $DIR/infallible_destructuring_match.rs:58:5 + | +LL | / let data = match wrapper { +LL | | TupleStruct(i) => i, +LL | | }; + | |______^ help: try this: `let TupleStruct(data) = wrapper;` + +error: you seem to be trying to use `match` to destructure a single infallible pattern. Consider using `let` + --> $DIR/infallible_destructuring_match.rs:90:5 + | +LL | / let data = match wrapper { +LL | | Ok(i) => i, +LL | | }; + | |______^ help: try this: `let Ok(data) = wrapper;` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/infinite_iter.rs b/src/tools/clippy/tests/ui/infinite_iter.rs new file mode 100644 index 0000000000..1fe6889776 --- /dev/null +++ b/src/tools/clippy/tests/ui/infinite_iter.rs @@ -0,0 +1,69 @@ +use std::iter::repeat; +fn square_is_lower_64(x: &u32) -> bool { + x * x < 64 +} + +#[allow(clippy::maybe_infinite_iter)] +#[deny(clippy::infinite_iter)] +fn infinite_iters() { + repeat(0_u8).collect::>(); // infinite iter + (0..8_u32).take_while(square_is_lower_64).cycle().count(); // infinite iter + (0..8_u64).chain(0..).max(); // infinite iter + (0_usize..) + .chain([0usize, 1, 2].iter().cloned()) + .skip_while(|x| *x != 42) + .min(); // infinite iter + (0..8_u32) + .rev() + .cycle() + .map(|x| x + 1_u32) + .for_each(|x| println!("{}", x)); // infinite iter + (0..3_u32).flat_map(|x| x..).sum::(); // infinite iter + (0_usize..).flat_map(|x| 0..x).product::(); // infinite iter + (0_u64..).filter(|x| x % 2 == 0).last(); // infinite iter + (0..42_u64).by_ref().last(); // not an infinite, because ranges are double-ended + (0..).next(); // iterator is not exhausted +} + +#[deny(clippy::maybe_infinite_iter)] +fn potential_infinite_iters() { + (0..).zip((0..).take_while(square_is_lower_64)).count(); // maybe infinite iter + repeat(42).take_while(|x| *x == 42).chain(0..42).max(); // maybe infinite iter + (1..) + .scan(0, |state, x| { + *state += x; + Some(*state) + }) + .min(); // maybe infinite iter + (0..).find(|x| *x == 24); // maybe infinite iter + (0..).position(|x| x == 24); // maybe infinite iter + (0..).any(|x| x == 24); // maybe infinite iter + (0..).all(|x| x == 24); // maybe infinite iter + + (0..).zip(0..42).take_while(|&(x, _)| x != 42).count(); // not infinite + repeat(42).take_while(|x| *x == 42).next(); // iterator is not exhausted +} + +fn main() { + infinite_iters(); + potential_infinite_iters(); +} + +mod finite_collect { + use std::collections::HashSet; + use std::iter::FromIterator; + + struct C; + impl FromIterator for C { + fn from_iter>(iter: I) -> Self { + C + } + } + + fn check_collect() { + let _: HashSet = (0..).collect(); // Infinite iter + + // Some data structures don't collect infinitely, such as `ArrayVec` + let _: C = (0..).collect(); + } +} diff --git a/src/tools/clippy/tests/ui/infinite_iter.stderr b/src/tools/clippy/tests/ui/infinite_iter.stderr new file mode 100644 index 0000000000..5f5e7ac9f2 --- /dev/null +++ b/src/tools/clippy/tests/ui/infinite_iter.stderr @@ -0,0 +1,109 @@ +error: infinite iteration detected + --> $DIR/infinite_iter.rs:9:5 + | +LL | repeat(0_u8).collect::>(); // infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/infinite_iter.rs:7:8 + | +LL | #[deny(clippy::infinite_iter)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:10:5 + | +LL | (0..8_u32).take_while(square_is_lower_64).cycle().count(); // infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:11:5 + | +LL | (0..8_u64).chain(0..).max(); // infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:16:5 + | +LL | / (0..8_u32) +LL | | .rev() +LL | | .cycle() +LL | | .map(|x| x + 1_u32) +LL | | .for_each(|x| println!("{}", x)); // infinite iter + | |________________________________________^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:22:5 + | +LL | (0_usize..).flat_map(|x| 0..x).product::(); // infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:23:5 + | +LL | (0_u64..).filter(|x| x % 2 == 0).last(); // infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:30:5 + | +LL | (0..).zip((0..).take_while(square_is_lower_64)).count(); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/infinite_iter.rs:28:8 + | +LL | #[deny(clippy::maybe_infinite_iter)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:31:5 + | +LL | repeat(42).take_while(|x| *x == 42).chain(0..42).max(); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:32:5 + | +LL | / (1..) +LL | | .scan(0, |state, x| { +LL | | *state += x; +LL | | Some(*state) +LL | | }) +LL | | .min(); // maybe infinite iter + | |______________^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:38:5 + | +LL | (0..).find(|x| *x == 24); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:39:5 + | +LL | (0..).position(|x| x == 24); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:40:5 + | +LL | (0..).any(|x| x == 24); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:41:5 + | +LL | (0..).all(|x| x == 24); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:64:31 + | +LL | let _: HashSet = (0..).collect(); // Infinite iter + | ^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::infinite_iter)]` on by default + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/infinite_loop.rs b/src/tools/clippy/tests/ui/infinite_loop.rs new file mode 100644 index 0000000000..72591f12ba --- /dev/null +++ b/src/tools/clippy/tests/ui/infinite_loop.rs @@ -0,0 +1,206 @@ +fn fn_val(i: i32) -> i32 { + unimplemented!() +} +fn fn_constref(i: &i32) -> i32 { + unimplemented!() +} +fn fn_mutref(i: &mut i32) { + unimplemented!() +} +fn fooi() -> i32 { + unimplemented!() +} +fn foob() -> bool { + unimplemented!() +} + +#[allow(clippy::many_single_char_names)] +fn immutable_condition() { + // Should warn when all vars mentioned are immutable + let y = 0; + while y < 10 { + println!("KO - y is immutable"); + } + + let x = 0; + while y < 10 && x < 3 { + let mut k = 1; + k += 2; + println!("KO - x and y immutable"); + } + + let cond = false; + while !cond { + println!("KO - cond immutable"); + } + + let mut i = 0; + while y < 10 && i < 3 { + i += 1; + println!("OK - i is mutable"); + } + + let mut mut_cond = false; + while !mut_cond || cond { + mut_cond = true; + println!("OK - mut_cond is mutable"); + } + + while fooi() < x { + println!("OK - Fn call results may vary"); + } + + while foob() { + println!("OK - Fn call results may vary"); + } + + let mut a = 0; + let mut c = move || { + while a < 5 { + a += 1; + println!("OK - a is mutable"); + } + }; + c(); + + let mut tup = (0, 0); + while tup.0 < 5 { + tup.0 += 1; + println!("OK - tup.0 gets mutated") + } +} + +fn unused_var() { + // Should warn when a (mutable) var is not used in while body + let (mut i, mut j) = (0, 0); + + while i < 3 { + j = 3; + println!("KO - i not mentioned"); + } + + while i < 3 && j > 0 { + println!("KO - i and j not mentioned"); + } + + while i < 3 { + let mut i = 5; + fn_mutref(&mut i); + println!("KO - shadowed"); + } + + while i < 3 && j > 0 { + i = 5; + println!("OK - i in cond and mentioned"); + } +} + +fn used_immutable() { + let mut i = 0; + + while i < 3 { + fn_constref(&i); + println!("KO - const reference"); + } + + while i < 3 { + fn_val(i); + println!("KO - passed by value"); + } + + while i < 3 { + println!("OK - passed by mutable reference"); + fn_mutref(&mut i) + } + + while i < 3 { + fn_mutref(&mut i); + println!("OK - passed by mutable reference"); + } +} + +const N: i32 = 5; +const B: bool = false; + +fn consts() { + while false { + println!("Constants are not linted"); + } + + while B { + println!("Constants are not linted"); + } + + while N > 0 { + println!("Constants are not linted"); + } +} + +use std::cell::Cell; + +fn maybe_i_mutate(i: &Cell) { + unimplemented!() +} + +fn internally_mutable() { + let b = Cell::new(true); + + while b.get() { + // b cannot be silently coerced to `bool` + maybe_i_mutate(&b); + println!("OK - Method call within condition"); + } +} + +struct Counter { + count: usize, +} + +impl Counter { + fn inc(&mut self) { + self.count += 1; + } + + fn inc_n(&mut self, n: usize) { + while self.count < n { + self.inc(); + } + println!("OK - self borrowed mutably"); + } + + fn print_n(&self, n: usize) { + while self.count < n { + println!("KO - {} is not mutated", self.count); + } + } +} + +fn while_loop_with_break_and_return() { + let y = 0; + while y < 10 { + if y == 0 { + break; + } + println!("KO - loop contains break"); + } + + while y < 10 { + if y == 0 { + return; + } + println!("KO - loop contains return"); + } +} + +fn main() { + immutable_condition(); + unused_var(); + used_immutable(); + internally_mutable(); + + let mut c = Counter { count: 0 }; + c.inc_n(5); + c.print_n(2); + + while_loop_with_break_and_return(); +} diff --git a/src/tools/clippy/tests/ui/infinite_loop.stderr b/src/tools/clippy/tests/ui/infinite_loop.stderr new file mode 100644 index 0000000000..1fcb29eff1 --- /dev/null +++ b/src/tools/clippy/tests/ui/infinite_loop.stderr @@ -0,0 +1,95 @@ +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:21:11 + | +LL | while y < 10 { + | ^^^^^^ + | + = note: `#[deny(clippy::while_immutable_condition)]` on by default + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:26:11 + | +LL | while y < 10 && x < 3 { + | ^^^^^^^^^^^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:33:11 + | +LL | while !cond { + | ^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:77:11 + | +LL | while i < 3 { + | ^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:82:11 + | +LL | while i < 3 && j > 0 { + | ^^^^^^^^^^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:86:11 + | +LL | while i < 3 { + | ^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:101:11 + | +LL | while i < 3 { + | ^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:106:11 + | +LL | while i < 3 { + | ^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:172:15 + | +LL | while self.count < n { + | ^^^^^^^^^^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:180:11 + | +LL | while y < 10 { + | ^^^^^^ + | + = note: this may lead to an infinite or to a never running loop + = note: this loop contains `return`s or `break`s + = help: rewrite it as `if cond { loop { } }` + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:187:11 + | +LL | while y < 10 { + | ^^^^^^ + | + = note: this may lead to an infinite or to a never running loop + = note: this loop contains `return`s or `break`s + = help: rewrite it as `if cond { loop { } }` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/inherent_to_string.rs b/src/tools/clippy/tests/ui/inherent_to_string.rs new file mode 100644 index 0000000000..6e65fdbd04 --- /dev/null +++ b/src/tools/clippy/tests/ui/inherent_to_string.rs @@ -0,0 +1,107 @@ +#![warn(clippy::inherent_to_string)] +#![deny(clippy::inherent_to_string_shadow_display)] +#![allow(clippy::many_single_char_names)] + +use std::fmt; + +trait FalsePositive { + fn to_string(&self) -> String; +} + +struct A; +struct B; +struct C; +struct D; +struct E; +struct F; +struct G; + +impl A { + // Should be detected; emit warning + fn to_string(&self) -> String { + "A.to_string()".to_string() + } + + // Should not be detected as it does not match the function signature + fn to_str(&self) -> String { + "A.to_str()".to_string() + } +} + +// Should not be detected as it is a free function +fn to_string() -> String { + "free to_string()".to_string() +} + +impl B { + // Should not be detected, wrong return type + fn to_string(&self) -> i32 { + 42 + } +} + +impl C { + // Should be detected and emit error as C also implements Display + fn to_string(&self) -> String { + "C.to_string()".to_string() + } +} + +impl fmt::Display for C { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "impl Display for C") + } +} + +impl FalsePositive for D { + // Should not be detected, as it is a trait function + fn to_string(&self) -> String { + "impl FalsePositive for D".to_string() + } +} + +impl E { + // Should not be detected, as it is not bound to an instance + fn to_string() -> String { + "E::to_string()".to_string() + } +} + +impl F { + // Should not be detected, as it does not match the function signature + fn to_string(&self, _i: i32) -> String { + "F.to_string()".to_string() + } +} + +impl G { + // Should not be detected, as it does not match the function signature + fn to_string(&self) -> String { + "G.to_string()".to_string() + } +} + +fn main() { + let a = A; + a.to_string(); + a.to_str(); + + to_string(); + + let b = B; + b.to_string(); + + let c = C; + C.to_string(); + + let d = D; + d.to_string(); + + E::to_string(); + + let f = F; + f.to_string(1); + + let g = G; + g.to_string::<1>(); +} diff --git a/src/tools/clippy/tests/ui/inherent_to_string.stderr b/src/tools/clippy/tests/ui/inherent_to_string.stderr new file mode 100644 index 0000000000..f5fcc193b4 --- /dev/null +++ b/src/tools/clippy/tests/ui/inherent_to_string.stderr @@ -0,0 +1,28 @@ +error: implementation of inherent method `to_string(&self) -> String` for type `A` + --> $DIR/inherent_to_string.rs:21:5 + | +LL | / fn to_string(&self) -> String { +LL | | "A.to_string()".to_string() +LL | | } + | |_____^ + | + = note: `-D clippy::inherent-to-string` implied by `-D warnings` + = help: implement trait `Display` for type `A` instead + +error: type `C` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display` + --> $DIR/inherent_to_string.rs:45:5 + | +LL | / fn to_string(&self) -> String { +LL | | "C.to_string()".to_string() +LL | | } + | |_____^ + | +note: the lint level is defined here + --> $DIR/inherent_to_string.rs:2:9 + | +LL | #![deny(clippy::inherent_to_string_shadow_display)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: remove the inherent method from type `C` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/inline_fn_without_body.fixed b/src/tools/clippy/tests/ui/inline_fn_without_body.fixed new file mode 100644 index 0000000000..fe21a71a42 --- /dev/null +++ b/src/tools/clippy/tests/ui/inline_fn_without_body.fixed @@ -0,0 +1,17 @@ +// run-rustfix + +#![warn(clippy::inline_fn_without_body)] +#![allow(clippy::inline_always)] + +trait Foo { + fn default_inline(); + + fn always_inline(); + + fn never_inline(); + + #[inline] + fn has_body() {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/inline_fn_without_body.rs b/src/tools/clippy/tests/ui/inline_fn_without_body.rs new file mode 100644 index 0000000000..5074698946 --- /dev/null +++ b/src/tools/clippy/tests/ui/inline_fn_without_body.rs @@ -0,0 +1,20 @@ +// run-rustfix + +#![warn(clippy::inline_fn_without_body)] +#![allow(clippy::inline_always)] + +trait Foo { + #[inline] + fn default_inline(); + + #[inline(always)] + fn always_inline(); + + #[inline(never)] + fn never_inline(); + + #[inline] + fn has_body() {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/inline_fn_without_body.stderr b/src/tools/clippy/tests/ui/inline_fn_without_body.stderr new file mode 100644 index 0000000000..32d35e209b --- /dev/null +++ b/src/tools/clippy/tests/ui/inline_fn_without_body.stderr @@ -0,0 +1,28 @@ +error: use of `#[inline]` on trait method `default_inline` which has no body + --> $DIR/inline_fn_without_body.rs:7:5 + | +LL | #[inline] + | _____-^^^^^^^^ +LL | | fn default_inline(); + | |____- help: remove + | + = note: `-D clippy::inline-fn-without-body` implied by `-D warnings` + +error: use of `#[inline]` on trait method `always_inline` which has no body + --> $DIR/inline_fn_without_body.rs:10:5 + | +LL | #[inline(always)] + | _____-^^^^^^^^^^^^^^^^ +LL | | fn always_inline(); + | |____- help: remove + +error: use of `#[inline]` on trait method `never_inline` which has no body + --> $DIR/inline_fn_without_body.rs:13:5 + | +LL | #[inline(never)] + | _____-^^^^^^^^^^^^^^^ +LL | | fn never_inline(); + | |____- help: remove + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/inspect_for_each.rs b/src/tools/clippy/tests/ui/inspect_for_each.rs new file mode 100644 index 0000000000..7fe45c83bc --- /dev/null +++ b/src/tools/clippy/tests/ui/inspect_for_each.rs @@ -0,0 +1,22 @@ +#![warn(clippy::inspect_for_each)] + +fn main() { + let a: Vec = vec![1, 2, 3, 4, 5]; + + let mut b: Vec = Vec::new(); + a.into_iter().inspect(|x| assert!(*x > 0)).for_each(|x| { + let y = do_some(x); + let z = do_more(y); + b.push(z); + }); + + assert_eq!(b, vec![4, 5, 6, 7, 8]); +} + +fn do_some(a: usize) -> usize { + a + 1 +} + +fn do_more(a: usize) -> usize { + a + 2 +} diff --git a/src/tools/clippy/tests/ui/inspect_for_each.stderr b/src/tools/clippy/tests/ui/inspect_for_each.stderr new file mode 100644 index 0000000000..9f976bb745 --- /dev/null +++ b/src/tools/clippy/tests/ui/inspect_for_each.stderr @@ -0,0 +1,16 @@ +error: called `inspect(..).for_each(..)` on an `Iterator` + --> $DIR/inspect_for_each.rs:7:19 + | +LL | a.into_iter().inspect(|x| assert!(*x > 0)).for_each(|x| { + | ___________________^ +LL | | let y = do_some(x); +LL | | let z = do_more(y); +LL | | b.push(z); +LL | | }); + | |______^ + | + = note: `-D clippy::inspect-for-each` implied by `-D warnings` + = help: move the code from `inspect(..)` to `for_each(..)` and remove the `inspect(..)` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/int_plus_one.fixed b/src/tools/clippy/tests/ui/int_plus_one.fixed new file mode 100644 index 0000000000..642830f24f --- /dev/null +++ b/src/tools/clippy/tests/ui/int_plus_one.fixed @@ -0,0 +1,17 @@ +// run-rustfix + +#[allow(clippy::no_effect, clippy::unnecessary_operation)] +#[warn(clippy::int_plus_one)] +fn main() { + let x = 1i32; + let y = 0i32; + + let _ = x > y; + let _ = y < x; + + let _ = x > y; + let _ = y < x; + + let _ = x > y; // should be ok + let _ = y < x; // should be ok +} diff --git a/src/tools/clippy/tests/ui/int_plus_one.rs b/src/tools/clippy/tests/ui/int_plus_one.rs new file mode 100644 index 0000000000..0755a0c79d --- /dev/null +++ b/src/tools/clippy/tests/ui/int_plus_one.rs @@ -0,0 +1,17 @@ +// run-rustfix + +#[allow(clippy::no_effect, clippy::unnecessary_operation)] +#[warn(clippy::int_plus_one)] +fn main() { + let x = 1i32; + let y = 0i32; + + let _ = x >= y + 1; + let _ = y + 1 <= x; + + let _ = x - 1 >= y; + let _ = y <= x - 1; + + let _ = x > y; // should be ok + let _ = y < x; // should be ok +} diff --git a/src/tools/clippy/tests/ui/int_plus_one.stderr b/src/tools/clippy/tests/ui/int_plus_one.stderr new file mode 100644 index 0000000000..c5b020ba8c --- /dev/null +++ b/src/tools/clippy/tests/ui/int_plus_one.stderr @@ -0,0 +1,28 @@ +error: unnecessary `>= y + 1` or `x - 1 >=` + --> $DIR/int_plus_one.rs:9:13 + | +LL | let _ = x >= y + 1; + | ^^^^^^^^^^ help: change it to: `x > y` + | + = note: `-D clippy::int-plus-one` implied by `-D warnings` + +error: unnecessary `>= y + 1` or `x - 1 >=` + --> $DIR/int_plus_one.rs:10:13 + | +LL | let _ = y + 1 <= x; + | ^^^^^^^^^^ help: change it to: `y < x` + +error: unnecessary `>= y + 1` or `x - 1 >=` + --> $DIR/int_plus_one.rs:12:13 + | +LL | let _ = x - 1 >= y; + | ^^^^^^^^^^ help: change it to: `x > y` + +error: unnecessary `>= y + 1` or `x - 1 >=` + --> $DIR/int_plus_one.rs:13:13 + | +LL | let _ = y <= x - 1; + | ^^^^^^^^^^ help: change it to: `y < x` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/integer_arithmetic.rs b/src/tools/clippy/tests/ui/integer_arithmetic.rs new file mode 100644 index 0000000000..b74c93dc4a --- /dev/null +++ b/src/tools/clippy/tests/ui/integer_arithmetic.rs @@ -0,0 +1,109 @@ +#![warn(clippy::integer_arithmetic, clippy::float_arithmetic)] +#![allow( + unused, + clippy::shadow_reuse, + clippy::shadow_unrelated, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::op_ref +)] + +#[rustfmt::skip] +fn main() { + let mut i = 1i32; + let mut var1 = 0i32; + let mut var2 = -1i32; + 1 + i; + i * 2; + 1 % + i / 2; // no error, this is part of the expression in the preceding line + i - 2 + 2 - i; + -i; + i >> 1; + i << 1; + + // no error, overflows are checked by `overflowing_literals` + -1; + -(-1); + + i & 1; // no wrapping + i | 1; + i ^ 1; + + i += 1; + i -= 1; + i *= 2; + i /= 2; + i /= 0; + i /= -1; + i /= var1; + i /= var2; + i %= 2; + i %= 0; + i %= -1; + i %= var1; + i %= var2; + i <<= 3; + i >>= 2; + + // no errors + i |= 1; + i &= 1; + i ^= i; + + // No errors for the following items because they are constant expressions + enum Foo { + Bar = -2, + } + struct Baz([i32; 1 + 1]); + union Qux { + field: [i32; 1 + 1], + } + type Alias = [i32; 1 + 1]; + + const FOO: i32 = -2; + static BAR: i32 = -2; + + let _: [i32; 1 + 1] = [0, 0]; + + let _: [i32; 1 + 1] = { + let a: [i32; 1 + 1] = [0, 0]; + a + }; + + trait Trait { + const ASSOC: i32 = 1 + 1; + } + + impl Trait for Foo { + const ASSOC: i32 = { + let _: [i32; 1 + 1]; + fn foo() {} + 1 + 1 + }; + } +} + +// warn on references as well! (#5328) +pub fn int_arith_ref() { + 3 + &1; + &3 + 1; + &3 + &1; +} + +pub fn foo(x: &i32) -> i32 { + let a = 5; + a + x +} + +pub fn bar(x: &i32, y: &i32) -> i32 { + x + y +} + +pub fn baz(x: i32, y: &i32) -> i32 { + x + y +} + +pub fn qux(x: i32, y: i32) -> i32 { + (&x + &y) +} diff --git a/src/tools/clippy/tests/ui/integer_arithmetic.stderr b/src/tools/clippy/tests/ui/integer_arithmetic.stderr new file mode 100644 index 0000000000..add3b6b90f --- /dev/null +++ b/src/tools/clippy/tests/ui/integer_arithmetic.stderr @@ -0,0 +1,169 @@ +error: this operation will panic at runtime + --> $DIR/integer_arithmetic.rs:37:5 + | +LL | i /= 0; + | ^^^^^^ attempt to divide `_` by zero + | + = note: `#[deny(unconditional_panic)]` on by default + +error: this operation will panic at runtime + --> $DIR/integer_arithmetic.rs:42:5 + | +LL | i %= 0; + | ^^^^^^ attempt to calculate the remainder of `_` with a divisor of zero + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:16:5 + | +LL | 1 + i; + | ^^^^^ + | + = note: `-D clippy::integer-arithmetic` implied by `-D warnings` + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:17:5 + | +LL | i * 2; + | ^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:18:5 + | +LL | / 1 % +LL | | i / 2; // no error, this is part of the expression in the preceding line + | |_____^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:20:5 + | +LL | i - 2 + 2 - i; + | ^^^^^^^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:21:5 + | +LL | -i; + | ^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:22:5 + | +LL | i >> 1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:23:5 + | +LL | i << 1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:33:5 + | +LL | i += 1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:34:5 + | +LL | i -= 1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:35:5 + | +LL | i *= 2; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:38:11 + | +LL | i /= -1; + | ^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:39:5 + | +LL | i /= var1; + | ^^^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:40:5 + | +LL | i /= var2; + | ^^^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:43:11 + | +LL | i %= -1; + | ^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:44:5 + | +LL | i %= var1; + | ^^^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:45:5 + | +LL | i %= var2; + | ^^^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:46:5 + | +LL | i <<= 3; + | ^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:47:5 + | +LL | i >>= 2; + | ^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:89:5 + | +LL | 3 + &1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:90:5 + | +LL | &3 + 1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:91:5 + | +LL | &3 + &1; + | ^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:96:5 + | +LL | a + x + | ^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:100:5 + | +LL | x + y + | ^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:104:5 + | +LL | x + y + | ^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:108:5 + | +LL | (&x + &y) + | ^^^^^^^^^ + +error: aborting due to 27 previous errors + diff --git a/src/tools/clippy/tests/ui/integer_division.rs b/src/tools/clippy/tests/ui/integer_division.rs new file mode 100644 index 0000000000..800c752575 --- /dev/null +++ b/src/tools/clippy/tests/ui/integer_division.rs @@ -0,0 +1,9 @@ +#![warn(clippy::integer_division)] + +fn main() { + let two = 2; + let n = 1 / 2; + let o = 1 / two; + let p = two / 4; + let x = 1. / 2.0; +} diff --git a/src/tools/clippy/tests/ui/integer_division.stderr b/src/tools/clippy/tests/ui/integer_division.stderr new file mode 100644 index 0000000000..cbb7f88142 --- /dev/null +++ b/src/tools/clippy/tests/ui/integer_division.stderr @@ -0,0 +1,27 @@ +error: integer division + --> $DIR/integer_division.rs:5:13 + | +LL | let n = 1 / 2; + | ^^^^^ + | + = note: `-D clippy::integer-division` implied by `-D warnings` + = help: division of integers may cause loss of precision. consider using floats + +error: integer division + --> $DIR/integer_division.rs:6:13 + | +LL | let o = 1 / two; + | ^^^^^^^ + | + = help: division of integers may cause loss of precision. consider using floats + +error: integer division + --> $DIR/integer_division.rs:7:13 + | +LL | let p = two / 4; + | ^^^^^^^ + | + = help: division of integers may cause loss of precision. consider using floats + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/into_iter_on_ref.fixed b/src/tools/clippy/tests/ui/into_iter_on_ref.fixed new file mode 100644 index 0000000000..7f92d0dbdc --- /dev/null +++ b/src/tools/clippy/tests/ui/into_iter_on_ref.fixed @@ -0,0 +1,45 @@ +// run-rustfix +#![allow(clippy::useless_vec)] +#![warn(clippy::into_iter_on_ref)] + +struct X; +use std::collections::*; + +fn main() { + for _ in &[1, 2, 3] {} + for _ in vec![X, X] {} + for _ in &vec![X, X] {} + + let _ = vec![1, 2, 3].into_iter(); + let _ = (&vec![1, 2, 3]).iter(); //~ WARN equivalent to .iter() + let _ = vec![1, 2, 3].into_boxed_slice().iter(); //~ WARN equivalent to .iter() + let _ = std::rc::Rc::from(&[X][..]).iter(); //~ WARN equivalent to .iter() + let _ = std::sync::Arc::from(&[X][..]).iter(); //~ WARN equivalent to .iter() + + let _ = (&&&&&&&[1, 2, 3]).iter(); //~ ERROR equivalent to .iter() + let _ = (&&&&mut &&&[1, 2, 3]).iter(); //~ ERROR equivalent to .iter() + let _ = (&mut &mut &mut [1, 2, 3]).iter_mut(); //~ ERROR equivalent to .iter_mut() + + let _ = (&Some(4)).iter(); //~ WARN equivalent to .iter() + let _ = (&mut Some(5)).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&Ok::<_, i32>(6)).iter(); //~ WARN equivalent to .iter() + let _ = (&mut Err::(7)).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&Vec::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&mut Vec::::new()).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&BTreeMap::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&mut BTreeMap::::new()).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&VecDeque::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&mut VecDeque::::new()).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&LinkedList::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&mut LinkedList::::new()).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&HashMap::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&mut HashMap::::new()).iter_mut(); //~ WARN equivalent to .iter_mut() + + let _ = (&BTreeSet::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&BinaryHeap::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&HashSet::::new()).iter(); //~ WARN equivalent to .iter() + let _ = std::path::Path::new("12/34").iter(); //~ WARN equivalent to .iter() + let _ = std::path::PathBuf::from("12/34").iter(); //~ ERROR equivalent to .iter() + + let _ = (&[1, 2, 3]).iter().next(); //~ WARN equivalent to .iter() +} diff --git a/src/tools/clippy/tests/ui/into_iter_on_ref.rs b/src/tools/clippy/tests/ui/into_iter_on_ref.rs new file mode 100644 index 0000000000..416056d3fd --- /dev/null +++ b/src/tools/clippy/tests/ui/into_iter_on_ref.rs @@ -0,0 +1,45 @@ +// run-rustfix +#![allow(clippy::useless_vec)] +#![warn(clippy::into_iter_on_ref)] + +struct X; +use std::collections::*; + +fn main() { + for _ in &[1, 2, 3] {} + for _ in vec![X, X] {} + for _ in &vec![X, X] {} + + let _ = vec![1, 2, 3].into_iter(); + let _ = (&vec![1, 2, 3]).into_iter(); //~ WARN equivalent to .iter() + let _ = vec![1, 2, 3].into_boxed_slice().into_iter(); //~ WARN equivalent to .iter() + let _ = std::rc::Rc::from(&[X][..]).into_iter(); //~ WARN equivalent to .iter() + let _ = std::sync::Arc::from(&[X][..]).into_iter(); //~ WARN equivalent to .iter() + + let _ = (&&&&&&&[1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter() + let _ = (&&&&mut &&&[1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter() + let _ = (&mut &mut &mut [1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter_mut() + + let _ = (&Some(4)).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut Some(5)).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&Ok::<_, i32>(6)).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut Err::(7)).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&Vec::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut Vec::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&BTreeMap::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut BTreeMap::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&VecDeque::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut VecDeque::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&LinkedList::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut LinkedList::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&HashMap::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut HashMap::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + + let _ = (&BTreeSet::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&BinaryHeap::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&HashSet::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = std::path::Path::new("12/34").into_iter(); //~ WARN equivalent to .iter() + let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter() + + let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter() +} diff --git a/src/tools/clippy/tests/ui/into_iter_on_ref.stderr b/src/tools/clippy/tests/ui/into_iter_on_ref.stderr new file mode 100644 index 0000000000..28003b365b --- /dev/null +++ b/src/tools/clippy/tests/ui/into_iter_on_ref.stderr @@ -0,0 +1,166 @@ +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `Vec` + --> $DIR/into_iter_on_ref.rs:14:30 + | +LL | let _ = (&vec![1, 2, 3]).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + | + = note: `-D clippy::into-iter-on-ref` implied by `-D warnings` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `slice` + --> $DIR/into_iter_on_ref.rs:15:46 + | +LL | let _ = vec![1, 2, 3].into_boxed_slice().into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `slice` + --> $DIR/into_iter_on_ref.rs:16:41 + | +LL | let _ = std::rc::Rc::from(&[X][..]).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `slice` + --> $DIR/into_iter_on_ref.rs:17:44 + | +LL | let _ = std::sync::Arc::from(&[X][..]).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `array` + --> $DIR/into_iter_on_ref.rs:19:32 + | +LL | let _ = (&&&&&&&[1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `array` + --> $DIR/into_iter_on_ref.rs:20:36 + | +LL | let _ = (&&&&mut &&&[1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `array` + --> $DIR/into_iter_on_ref.rs:21:40 + | +LL | let _ = (&mut &mut &mut [1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `Option` + --> $DIR/into_iter_on_ref.rs:23:24 + | +LL | let _ = (&Some(4)).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `Option` + --> $DIR/into_iter_on_ref.rs:24:28 + | +LL | let _ = (&mut Some(5)).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `Result` + --> $DIR/into_iter_on_ref.rs:25:32 + | +LL | let _ = (&Ok::<_, i32>(6)).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `Result` + --> $DIR/into_iter_on_ref.rs:26:37 + | +LL | let _ = (&mut Err::(7)).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `Vec` + --> $DIR/into_iter_on_ref.rs:27:34 + | +LL | let _ = (&Vec::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `Vec` + --> $DIR/into_iter_on_ref.rs:28:38 + | +LL | let _ = (&mut Vec::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `BTreeMap` + --> $DIR/into_iter_on_ref.rs:29:44 + | +LL | let _ = (&BTreeMap::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `BTreeMap` + --> $DIR/into_iter_on_ref.rs:30:48 + | +LL | let _ = (&mut BTreeMap::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `VecDeque` + --> $DIR/into_iter_on_ref.rs:31:39 + | +LL | let _ = (&VecDeque::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `VecDeque` + --> $DIR/into_iter_on_ref.rs:32:43 + | +LL | let _ = (&mut VecDeque::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `LinkedList` + --> $DIR/into_iter_on_ref.rs:33:41 + | +LL | let _ = (&LinkedList::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `LinkedList` + --> $DIR/into_iter_on_ref.rs:34:45 + | +LL | let _ = (&mut LinkedList::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `HashMap` + --> $DIR/into_iter_on_ref.rs:35:43 + | +LL | let _ = (&HashMap::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `HashMap` + --> $DIR/into_iter_on_ref.rs:36:47 + | +LL | let _ = (&mut HashMap::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `BTreeSet` + --> $DIR/into_iter_on_ref.rs:38:39 + | +LL | let _ = (&BTreeSet::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `BinaryHeap` + --> $DIR/into_iter_on_ref.rs:39:41 + | +LL | let _ = (&BinaryHeap::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `HashSet` + --> $DIR/into_iter_on_ref.rs:40:38 + | +LL | let _ = (&HashSet::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `Path` + --> $DIR/into_iter_on_ref.rs:41:43 + | +LL | let _ = std::path::Path::new("12/34").into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `PathBuf` + --> $DIR/into_iter_on_ref.rs:42:47 + | +LL | let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `array` + --> $DIR/into_iter_on_ref.rs:44:26 + | +LL | let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: aborting due to 27 previous errors + diff --git a/src/tools/clippy/tests/ui/invalid_upcast_comparisons.rs b/src/tools/clippy/tests/ui/invalid_upcast_comparisons.rs new file mode 100644 index 0000000000..697416dcee --- /dev/null +++ b/src/tools/clippy/tests/ui/invalid_upcast_comparisons.rs @@ -0,0 +1,85 @@ +#![warn(clippy::invalid_upcast_comparisons)] +#![allow( + unused, + clippy::eq_op, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::cast_lossless +)] + +fn mk_value() -> T { + unimplemented!() +} + +fn main() { + let u32: u32 = mk_value(); + let u8: u8 = mk_value(); + let i32: i32 = mk_value(); + let i8: i8 = mk_value(); + + // always false, since no u8 can be > 300 + (u8 as u32) > 300; + (u8 as i32) > 300; + (u8 as u32) == 300; + (u8 as i32) == 300; + 300 < (u8 as u32); + 300 < (u8 as i32); + 300 == (u8 as u32); + 300 == (u8 as i32); + // inverted of the above + (u8 as u32) <= 300; + (u8 as i32) <= 300; + (u8 as u32) != 300; + (u8 as i32) != 300; + 300 >= (u8 as u32); + 300 >= (u8 as i32); + 300 != (u8 as u32); + 300 != (u8 as i32); + + // always false, since u8 -> i32 doesn't wrap + (u8 as i32) < 0; + -5 != (u8 as i32); + // inverted of the above + (u8 as i32) >= 0; + -5 == (u8 as i32); + + // always false, since no u8 can be 1337 + 1337 == (u8 as i32); + 1337 == (u8 as u32); + // inverted of the above + 1337 != (u8 as i32); + 1337 != (u8 as u32); + + // Those are Ok: + (u8 as u32) > 20; + 42 == (u8 as i32); + 42 != (u8 as i32); + 42 > (u8 as i32); + (u8 as i32) == 42; + (u8 as i32) != 42; + (u8 as i32) > 42; + (u8 as i32) < 42; + + (u8 as i8) == -1; + (u8 as i8) != -1; + (u8 as i32) > -1; + (u8 as i32) < -1; + (u32 as i32) < -5; + (u32 as i32) < 10; + + (i8 as u8) == 1; + (i8 as u8) != 1; + (i8 as u8) < 1; + (i8 as u8) > 1; + (i32 as u32) < 5; + (i32 as u32) < 10; + + -5 < (u32 as i32); + 0 <= (u32 as i32); + 0 < (u32 as i32); + + -5 > (u32 as i32); + -5 >= (u8 as i32); + + -5 == (u32 as i32); +} diff --git a/src/tools/clippy/tests/ui/invalid_upcast_comparisons.stderr b/src/tools/clippy/tests/ui/invalid_upcast_comparisons.stderr new file mode 100644 index 0000000000..03c3fb80aa --- /dev/null +++ b/src/tools/clippy/tests/ui/invalid_upcast_comparisons.stderr @@ -0,0 +1,166 @@ +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:21:5 + | +LL | (u8 as u32) > 300; + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-upcast-comparisons` implied by `-D warnings` + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:22:5 + | +LL | (u8 as i32) > 300; + | ^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:23:5 + | +LL | (u8 as u32) == 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:24:5 + | +LL | (u8 as i32) == 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:25:5 + | +LL | 300 < (u8 as u32); + | ^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:26:5 + | +LL | 300 < (u8 as i32); + | ^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:27:5 + | +LL | 300 == (u8 as u32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:28:5 + | +LL | 300 == (u8 as i32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:30:5 + | +LL | (u8 as u32) <= 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:31:5 + | +LL | (u8 as i32) <= 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:32:5 + | +LL | (u8 as u32) != 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:33:5 + | +LL | (u8 as i32) != 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:34:5 + | +LL | 300 >= (u8 as u32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:35:5 + | +LL | 300 >= (u8 as i32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:36:5 + | +LL | 300 != (u8 as u32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:37:5 + | +LL | 300 != (u8 as i32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:40:5 + | +LL | (u8 as i32) < 0; + | ^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:41:5 + | +LL | -5 != (u8 as i32); + | ^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:43:5 + | +LL | (u8 as i32) >= 0; + | ^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:44:5 + | +LL | -5 == (u8 as i32); + | ^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:47:5 + | +LL | 1337 == (u8 as i32); + | ^^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:48:5 + | +LL | 1337 == (u8 as u32); + | ^^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:50:5 + | +LL | 1337 != (u8 as i32); + | ^^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:51:5 + | +LL | 1337 != (u8 as u32); + | ^^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:65:5 + | +LL | (u8 as i32) > -1; + | ^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:66:5 + | +LL | (u8 as i32) < -1; + | ^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:82:5 + | +LL | -5 >= (u8 as i32); + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 27 previous errors + diff --git a/src/tools/clippy/tests/ui/issue-3145.rs b/src/tools/clippy/tests/ui/issue-3145.rs new file mode 100644 index 0000000000..586d13647d --- /dev/null +++ b/src/tools/clippy/tests/ui/issue-3145.rs @@ -0,0 +1,3 @@ +fn main() { + println!("{}" a); //~ERROR expected `,`, found `a` +} diff --git a/src/tools/clippy/tests/ui/issue-3145.stderr b/src/tools/clippy/tests/ui/issue-3145.stderr new file mode 100644 index 0000000000..a35032aa15 --- /dev/null +++ b/src/tools/clippy/tests/ui/issue-3145.stderr @@ -0,0 +1,8 @@ +error: expected `,`, found `a` + --> $DIR/issue-3145.rs:2:19 + | +LL | println!("{}" a); //~ERROR expected `,`, found `a` + | ^ expected `,` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/issue_2356.rs b/src/tools/clippy/tests/ui/issue_2356.rs new file mode 100644 index 0000000000..da580a1839 --- /dev/null +++ b/src/tools/clippy/tests/ui/issue_2356.rs @@ -0,0 +1,24 @@ +#![deny(clippy::while_let_on_iterator)] + +use std::iter::Iterator; + +struct Foo; + +impl Foo { + fn foo1>(mut it: I) { + while let Some(_) = it.next() { + println!("{:?}", it.size_hint()); + } + } + + fn foo2>(mut it: I) { + while let Some(e) = it.next() { + println!("{:?}", e); + } + } +} + +fn main() { + Foo::foo1(vec![].into_iter()); + Foo::foo2(vec![].into_iter()); +} diff --git a/src/tools/clippy/tests/ui/issue_2356.stderr b/src/tools/clippy/tests/ui/issue_2356.stderr new file mode 100644 index 0000000000..51b872e21c --- /dev/null +++ b/src/tools/clippy/tests/ui/issue_2356.stderr @@ -0,0 +1,14 @@ +error: this loop could be written as a `for` loop + --> $DIR/issue_2356.rs:15:9 + | +LL | while let Some(e) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for e in it` + | +note: the lint level is defined here + --> $DIR/issue_2356.rs:1:9 + | +LL | #![deny(clippy::while_let_on_iterator)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/issue_4266.rs b/src/tools/clippy/tests/ui/issue_4266.rs new file mode 100644 index 0000000000..8a9d5a3d1d --- /dev/null +++ b/src/tools/clippy/tests/ui/issue_4266.rs @@ -0,0 +1,37 @@ +// edition:2018 +#![allow(dead_code)] + +async fn sink1<'a>(_: &'a str) {} // lint +async fn sink1_elided(_: &str) {} // ok + +// lint +async fn one_to_one<'a>(s: &'a str) -> &'a str { + s +} + +// ok +async fn one_to_one_elided(s: &str) -> &str { + s +} + +// ok +async fn all_to_one<'a>(a: &'a str, _b: &'a str) -> &'a str { + a +} + +// async fn unrelated(_: &str, _: &str) {} // Not allowed in async fn + +// #3988 +struct Foo; +impl Foo { + // ok + pub async fn foo(&mut self) {} +} + +// rust-lang/rust#61115 +// ok +async fn print(s: &str) { + println!("{}", s); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/issue_4266.stderr b/src/tools/clippy/tests/ui/issue_4266.stderr new file mode 100644 index 0000000000..0426508e62 --- /dev/null +++ b/src/tools/clippy/tests/ui/issue_4266.stderr @@ -0,0 +1,16 @@ +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/issue_4266.rs:4:1 + | +LL | async fn sink1<'a>(_: &'a str) {} // lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::needless-lifetimes` implied by `-D warnings` + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/issue_4266.rs:8:1 + | +LL | async fn one_to_one<'a>(s: &'a str) -> &'a str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/item_after_statement.rs b/src/tools/clippy/tests/ui/item_after_statement.rs new file mode 100644 index 0000000000..d439ca1e4e --- /dev/null +++ b/src/tools/clippy/tests/ui/item_after_statement.rs @@ -0,0 +1,52 @@ +#![warn(clippy::items_after_statements)] + +fn ok() { + fn foo() { + println!("foo"); + } + foo(); +} + +fn last() { + foo(); + fn foo() { + println!("foo"); + } +} + +fn main() { + foo(); + fn foo() { + println!("foo"); + } + foo(); +} + +fn mac() { + let mut a = 5; + println!("{}", a); + // do not lint this, because it needs to be after `a` + macro_rules! b { + () => {{ + a = 6; + fn say_something() { + println!("something"); + } + }}; + } + b!(); + println!("{}", a); +} + +fn semicolon() { + struct S { + a: u32, + }; + impl S { + fn new(a: u32) -> Self { + Self { a } + } + } + + let _ = S::new(3); +} diff --git a/src/tools/clippy/tests/ui/item_after_statement.stderr b/src/tools/clippy/tests/ui/item_after_statement.stderr new file mode 100644 index 0000000000..68a3c81b6a --- /dev/null +++ b/src/tools/clippy/tests/ui/item_after_statement.stderr @@ -0,0 +1,33 @@ +error: adding items after statements is confusing, since items exist from the start of the scope + --> $DIR/item_after_statement.rs:12:5 + | +LL | / fn foo() { +LL | | println!("foo"); +LL | | } + | |_____^ + | + = note: `-D clippy::items-after-statements` implied by `-D warnings` + +error: adding items after statements is confusing, since items exist from the start of the scope + --> $DIR/item_after_statement.rs:19:5 + | +LL | / fn foo() { +LL | | println!("foo"); +LL | | } + | |_____^ + +error: adding items after statements is confusing, since items exist from the start of the scope + --> $DIR/item_after_statement.rs:32:13 + | +LL | / fn say_something() { +LL | | println!("something"); +LL | | } + | |_____________^ +... +LL | b!(); + | ----- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/iter_cloned_collect.fixed b/src/tools/clippy/tests/ui/iter_cloned_collect.fixed new file mode 100644 index 0000000000..2773227e26 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_cloned_collect.fixed @@ -0,0 +1,22 @@ +// run-rustfix + +#![allow(unused)] + +use std::collections::HashSet; +use std::collections::VecDeque; + +fn main() { + let v = [1, 2, 3, 4, 5]; + let v2: Vec = v.to_vec(); + let v3: HashSet = v.iter().cloned().collect(); + let v4: VecDeque = v.iter().cloned().collect(); + + // Handle macro expansion in suggestion + let _: Vec = vec![1, 2, 3].to_vec(); + + // Issue #3704 + unsafe { + let _: Vec = std::ffi::CStr::from_ptr(std::ptr::null()) + .to_bytes().to_vec(); + } +} diff --git a/src/tools/clippy/tests/ui/iter_cloned_collect.rs b/src/tools/clippy/tests/ui/iter_cloned_collect.rs new file mode 100644 index 0000000000..60a4eac23c --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_cloned_collect.rs @@ -0,0 +1,25 @@ +// run-rustfix + +#![allow(unused)] + +use std::collections::HashSet; +use std::collections::VecDeque; + +fn main() { + let v = [1, 2, 3, 4, 5]; + let v2: Vec = v.iter().cloned().collect(); + let v3: HashSet = v.iter().cloned().collect(); + let v4: VecDeque = v.iter().cloned().collect(); + + // Handle macro expansion in suggestion + let _: Vec = vec![1, 2, 3].iter().cloned().collect(); + + // Issue #3704 + unsafe { + let _: Vec = std::ffi::CStr::from_ptr(std::ptr::null()) + .to_bytes() + .iter() + .cloned() + .collect(); + } +} diff --git a/src/tools/clippy/tests/ui/iter_cloned_collect.stderr b/src/tools/clippy/tests/ui/iter_cloned_collect.stderr new file mode 100644 index 0000000000..b90a1e6c91 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_cloned_collect.stderr @@ -0,0 +1,26 @@ +error: called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable + --> $DIR/iter_cloned_collect.rs:10:27 + | +LL | let v2: Vec = v.iter().cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.to_vec()` + | + = note: `-D clippy::iter-cloned-collect` implied by `-D warnings` + +error: called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable + --> $DIR/iter_cloned_collect.rs:15:38 + | +LL | let _: Vec = vec![1, 2, 3].iter().cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.to_vec()` + +error: called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable + --> $DIR/iter_cloned_collect.rs:20:24 + | +LL | .to_bytes() + | ________________________^ +LL | | .iter() +LL | | .cloned() +LL | | .collect(); + | |______________________^ help: try: `.to_vec()` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/iter_count.fixed b/src/tools/clippy/tests/ui/iter_count.fixed new file mode 100644 index 0000000000..b11dadda6c --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_count.fixed @@ -0,0 +1,86 @@ +// run-rustfix +// aux-build:option_helpers.rs + +#![warn(clippy::iter_count)] +#![allow( + unused_variables, + array_into_iter, + unused_mut, + clippy::into_iter_on_ref, + clippy::unnecessary_operation +)] + +extern crate option_helpers; + +use option_helpers::IteratorFalsePositives; +use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; + +/// Struct to generate false positives for things with `.iter()`. +#[derive(Copy, Clone)] +struct HasIter; + +impl HasIter { + fn iter(self) -> IteratorFalsePositives { + IteratorFalsePositives { foo: 0 } + } + + fn iter_mut(self) -> IteratorFalsePositives { + IteratorFalsePositives { foo: 0 } + } + + fn into_iter(self) -> IteratorFalsePositives { + IteratorFalsePositives { foo: 0 } + } +} + +fn main() { + let mut vec = vec![0, 1, 2, 3]; + let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let mut vec_deque: VecDeque<_> = vec.iter().cloned().collect(); + let mut hash_set = HashSet::new(); + let mut hash_map = HashMap::new(); + let mut b_tree_map = BTreeMap::new(); + let mut b_tree_set = BTreeSet::new(); + let mut linked_list = LinkedList::new(); + let mut binary_heap = BinaryHeap::new(); + hash_set.insert(1); + hash_map.insert(1, 2); + b_tree_map.insert(1, 2); + b_tree_set.insert(1); + linked_list.push_back(1); + binary_heap.push(1); + + &vec[..].len(); + vec.len(); + boxed_slice.len(); + vec_deque.len(); + hash_set.len(); + hash_map.len(); + b_tree_map.len(); + b_tree_set.len(); + linked_list.len(); + binary_heap.len(); + + vec.len(); + &vec[..].len(); + vec_deque.len(); + hash_map.len(); + b_tree_map.len(); + linked_list.len(); + + &vec[..].len(); + vec.len(); + vec_deque.len(); + hash_set.len(); + hash_map.len(); + b_tree_map.len(); + b_tree_set.len(); + linked_list.len(); + binary_heap.len(); + + // Make sure we don't lint for non-relevant types. + let false_positive = HasIter; + false_positive.iter().count(); + false_positive.iter_mut().count(); + false_positive.into_iter().count(); +} diff --git a/src/tools/clippy/tests/ui/iter_count.rs b/src/tools/clippy/tests/ui/iter_count.rs new file mode 100644 index 0000000000..7d49c6a3db --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_count.rs @@ -0,0 +1,86 @@ +// run-rustfix +// aux-build:option_helpers.rs + +#![warn(clippy::iter_count)] +#![allow( + unused_variables, + array_into_iter, + unused_mut, + clippy::into_iter_on_ref, + clippy::unnecessary_operation +)] + +extern crate option_helpers; + +use option_helpers::IteratorFalsePositives; +use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; + +/// Struct to generate false positives for things with `.iter()`. +#[derive(Copy, Clone)] +struct HasIter; + +impl HasIter { + fn iter(self) -> IteratorFalsePositives { + IteratorFalsePositives { foo: 0 } + } + + fn iter_mut(self) -> IteratorFalsePositives { + IteratorFalsePositives { foo: 0 } + } + + fn into_iter(self) -> IteratorFalsePositives { + IteratorFalsePositives { foo: 0 } + } +} + +fn main() { + let mut vec = vec![0, 1, 2, 3]; + let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let mut vec_deque: VecDeque<_> = vec.iter().cloned().collect(); + let mut hash_set = HashSet::new(); + let mut hash_map = HashMap::new(); + let mut b_tree_map = BTreeMap::new(); + let mut b_tree_set = BTreeSet::new(); + let mut linked_list = LinkedList::new(); + let mut binary_heap = BinaryHeap::new(); + hash_set.insert(1); + hash_map.insert(1, 2); + b_tree_map.insert(1, 2); + b_tree_set.insert(1); + linked_list.push_back(1); + binary_heap.push(1); + + &vec[..].iter().count(); + vec.iter().count(); + boxed_slice.iter().count(); + vec_deque.iter().count(); + hash_set.iter().count(); + hash_map.iter().count(); + b_tree_map.iter().count(); + b_tree_set.iter().count(); + linked_list.iter().count(); + binary_heap.iter().count(); + + vec.iter_mut().count(); + &vec[..].iter_mut().count(); + vec_deque.iter_mut().count(); + hash_map.iter_mut().count(); + b_tree_map.iter_mut().count(); + linked_list.iter_mut().count(); + + &vec[..].into_iter().count(); + vec.into_iter().count(); + vec_deque.into_iter().count(); + hash_set.into_iter().count(); + hash_map.into_iter().count(); + b_tree_map.into_iter().count(); + b_tree_set.into_iter().count(); + linked_list.into_iter().count(); + binary_heap.into_iter().count(); + + // Make sure we don't lint for non-relevant types. + let false_positive = HasIter; + false_positive.iter().count(); + false_positive.iter_mut().count(); + false_positive.into_iter().count(); +} diff --git a/src/tools/clippy/tests/ui/iter_count.stderr b/src/tools/clippy/tests/ui/iter_count.stderr new file mode 100644 index 0000000000..f3fb98e65b --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_count.stderr @@ -0,0 +1,154 @@ +error: called `.iter().count()` on a `slice` + --> $DIR/iter_count.rs:53:6 + | +LL | &vec[..].iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec[..].len()` + | + = note: `-D clippy::iter-count` implied by `-D warnings` + +error: called `.iter().count()` on a `Vec` + --> $DIR/iter_count.rs:54:5 + | +LL | vec.iter().count(); + | ^^^^^^^^^^^^^^^^^^ help: try: `vec.len()` + +error: called `.iter().count()` on a `slice` + --> $DIR/iter_count.rs:55:5 + | +LL | boxed_slice.iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `boxed_slice.len()` + +error: called `.iter().count()` on a `VecDeque` + --> $DIR/iter_count.rs:56:5 + | +LL | vec_deque.iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec_deque.len()` + +error: called `.iter().count()` on a `HashSet` + --> $DIR/iter_count.rs:57:5 + | +LL | hash_set.iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `hash_set.len()` + +error: called `.iter().count()` on a `HashMap` + --> $DIR/iter_count.rs:58:5 + | +LL | hash_map.iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `hash_map.len()` + +error: called `.iter().count()` on a `BTreeMap` + --> $DIR/iter_count.rs:59:5 + | +LL | b_tree_map.iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b_tree_map.len()` + +error: called `.iter().count()` on a `BTreeSet` + --> $DIR/iter_count.rs:60:5 + | +LL | b_tree_set.iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b_tree_set.len()` + +error: called `.iter().count()` on a `LinkedList` + --> $DIR/iter_count.rs:61:5 + | +LL | linked_list.iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `linked_list.len()` + +error: called `.iter().count()` on a `BinaryHeap` + --> $DIR/iter_count.rs:62:5 + | +LL | binary_heap.iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `binary_heap.len()` + +error: called `.iter_mut().count()` on a `Vec` + --> $DIR/iter_count.rs:64:5 + | +LL | vec.iter_mut().count(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.len()` + +error: called `.iter_mut().count()` on a `slice` + --> $DIR/iter_count.rs:65:6 + | +LL | &vec[..].iter_mut().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec[..].len()` + +error: called `.iter_mut().count()` on a `VecDeque` + --> $DIR/iter_count.rs:66:5 + | +LL | vec_deque.iter_mut().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec_deque.len()` + +error: called `.iter_mut().count()` on a `HashMap` + --> $DIR/iter_count.rs:67:5 + | +LL | hash_map.iter_mut().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `hash_map.len()` + +error: called `.iter_mut().count()` on a `BTreeMap` + --> $DIR/iter_count.rs:68:5 + | +LL | b_tree_map.iter_mut().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b_tree_map.len()` + +error: called `.iter_mut().count()` on a `LinkedList` + --> $DIR/iter_count.rs:69:5 + | +LL | linked_list.iter_mut().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `linked_list.len()` + +error: called `.into_iter().count()` on a `slice` + --> $DIR/iter_count.rs:71:6 + | +LL | &vec[..].into_iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec[..].len()` + +error: called `.into_iter().count()` on a `Vec` + --> $DIR/iter_count.rs:72:5 + | +LL | vec.into_iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.len()` + +error: called `.into_iter().count()` on a `VecDeque` + --> $DIR/iter_count.rs:73:5 + | +LL | vec_deque.into_iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec_deque.len()` + +error: called `.into_iter().count()` on a `HashSet` + --> $DIR/iter_count.rs:74:5 + | +LL | hash_set.into_iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `hash_set.len()` + +error: called `.into_iter().count()` on a `HashMap` + --> $DIR/iter_count.rs:75:5 + | +LL | hash_map.into_iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `hash_map.len()` + +error: called `.into_iter().count()` on a `BTreeMap` + --> $DIR/iter_count.rs:76:5 + | +LL | b_tree_map.into_iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b_tree_map.len()` + +error: called `.into_iter().count()` on a `BTreeSet` + --> $DIR/iter_count.rs:77:5 + | +LL | b_tree_set.into_iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b_tree_set.len()` + +error: called `.into_iter().count()` on a `LinkedList` + --> $DIR/iter_count.rs:78:5 + | +LL | linked_list.into_iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `linked_list.len()` + +error: called `.into_iter().count()` on a `BinaryHeap` + --> $DIR/iter_count.rs:79:5 + | +LL | binary_heap.into_iter().count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `binary_heap.len()` + +error: aborting due to 25 previous errors + diff --git a/src/tools/clippy/tests/ui/iter_next_slice.fixed b/src/tools/clippy/tests/ui/iter_next_slice.fixed new file mode 100644 index 0000000000..79c1db87ac --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_next_slice.fixed @@ -0,0 +1,24 @@ +// run-rustfix +#![warn(clippy::iter_next_slice)] + +fn main() { + // test code goes here + let s = [1, 2, 3]; + let v = vec![1, 2, 3]; + + s.get(0); + // Should be replaced by s.get(0) + + s.get(2); + // Should be replaced by s.get(2) + + v.get(5); + // Should be replaced by v.get(5) + + v.get(0); + // Should be replaced by v.get(0) + + let o = Some(5); + o.iter().next(); + // Shouldn't be linted since this is not a Slice or an Array +} diff --git a/src/tools/clippy/tests/ui/iter_next_slice.rs b/src/tools/clippy/tests/ui/iter_next_slice.rs new file mode 100644 index 0000000000..ef9a55f3d9 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_next_slice.rs @@ -0,0 +1,24 @@ +// run-rustfix +#![warn(clippy::iter_next_slice)] + +fn main() { + // test code goes here + let s = [1, 2, 3]; + let v = vec![1, 2, 3]; + + s.iter().next(); + // Should be replaced by s.get(0) + + s[2..].iter().next(); + // Should be replaced by s.get(2) + + v[5..].iter().next(); + // Should be replaced by v.get(5) + + v.iter().next(); + // Should be replaced by v.get(0) + + let o = Some(5); + o.iter().next(); + // Shouldn't be linted since this is not a Slice or an Array +} diff --git a/src/tools/clippy/tests/ui/iter_next_slice.stderr b/src/tools/clippy/tests/ui/iter_next_slice.stderr new file mode 100644 index 0000000000..8c10a252ee --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_next_slice.stderr @@ -0,0 +1,28 @@ +error: using `.iter().next()` on an array + --> $DIR/iter_next_slice.rs:9:5 + | +LL | s.iter().next(); + | ^^^^^^^^^^^^^^^ help: try calling: `s.get(0)` + | + = note: `-D clippy::iter-next-slice` implied by `-D warnings` + +error: using `.iter().next()` on a Slice without end index + --> $DIR/iter_next_slice.rs:12:5 + | +LL | s[2..].iter().next(); + | ^^^^^^^^^^^^^^^^^^^^ help: try calling: `s.get(2)` + +error: using `.iter().next()` on a Slice without end index + --> $DIR/iter_next_slice.rs:15:5 + | +LL | v[5..].iter().next(); + | ^^^^^^^^^^^^^^^^^^^^ help: try calling: `v.get(5)` + +error: using `.iter().next()` on an array + --> $DIR/iter_next_slice.rs:18:5 + | +LL | v.iter().next(); + | ^^^^^^^^^^^^^^^ help: try calling: `v.get(0)` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/iter_nth.rs b/src/tools/clippy/tests/ui/iter_nth.rs new file mode 100644 index 0000000000..9c21dd82ee --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_nth.rs @@ -0,0 +1,56 @@ +// aux-build:option_helpers.rs + +#![warn(clippy::iter_nth)] + +#[macro_use] +extern crate option_helpers; + +use option_helpers::IteratorFalsePositives; +use std::collections::VecDeque; + +/// Struct to generate false positives for things with `.iter()`. +#[derive(Copy, Clone)] +struct HasIter; + +impl HasIter { + fn iter(self) -> IteratorFalsePositives { + IteratorFalsePositives { foo: 0 } + } + + fn iter_mut(self) -> IteratorFalsePositives { + IteratorFalsePositives { foo: 0 } + } +} + +/// Checks implementation of `ITER_NTH` lint. +fn iter_nth() { + let mut some_vec = vec![0, 1, 2, 3]; + let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let mut some_vec_deque: VecDeque<_> = some_vec.iter().cloned().collect(); + + { + // Make sure we lint `.iter()` for relevant types. + let bad_vec = some_vec.iter().nth(3); + let bad_slice = &some_vec[..].iter().nth(3); + let bad_boxed_slice = boxed_slice.iter().nth(3); + let bad_vec_deque = some_vec_deque.iter().nth(3); + } + + { + // Make sure we lint `.iter_mut()` for relevant types. + let bad_vec = some_vec.iter_mut().nth(3); + } + { + let bad_slice = &some_vec[..].iter_mut().nth(3); + } + { + let bad_vec_deque = some_vec_deque.iter_mut().nth(3); + } + + // Make sure we don't lint for non-relevant types. + let false_positive = HasIter; + let ok = false_positive.iter().nth(3); + let ok_mut = false_positive.iter_mut().nth(3); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/iter_nth.stderr b/src/tools/clippy/tests/ui/iter_nth.stderr new file mode 100644 index 0000000000..d00b2fb672 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_nth.stderr @@ -0,0 +1,59 @@ +error: called `.iter().nth()` on a Vec + --> $DIR/iter_nth.rs:33:23 + | +LL | let bad_vec = some_vec.iter().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::iter-nth` implied by `-D warnings` + = help: calling `.get()` is both faster and more readable + +error: called `.iter().nth()` on a slice + --> $DIR/iter_nth.rs:34:26 + | +LL | let bad_slice = &some_vec[..].iter().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get()` is both faster and more readable + +error: called `.iter().nth()` on a slice + --> $DIR/iter_nth.rs:35:31 + | +LL | let bad_boxed_slice = boxed_slice.iter().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get()` is both faster and more readable + +error: called `.iter().nth()` on a VecDeque + --> $DIR/iter_nth.rs:36:29 + | +LL | let bad_vec_deque = some_vec_deque.iter().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get()` is both faster and more readable + +error: called `.iter_mut().nth()` on a Vec + --> $DIR/iter_nth.rs:41:23 + | +LL | let bad_vec = some_vec.iter_mut().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get_mut()` is both faster and more readable + +error: called `.iter_mut().nth()` on a slice + --> $DIR/iter_nth.rs:44:26 + | +LL | let bad_slice = &some_vec[..].iter_mut().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get_mut()` is both faster and more readable + +error: called `.iter_mut().nth()` on a VecDeque + --> $DIR/iter_nth.rs:47:29 + | +LL | let bad_vec_deque = some_vec_deque.iter_mut().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get_mut()` is both faster and more readable + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/iter_nth_zero.fixed b/src/tools/clippy/tests/ui/iter_nth_zero.fixed new file mode 100644 index 0000000000..b54147c94d --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_nth_zero.fixed @@ -0,0 +1,31 @@ +// run-rustfix + +#![warn(clippy::iter_nth_zero)] +use std::collections::HashSet; + +struct Foo {} + +impl Foo { + fn nth(&self, index: usize) -> usize { + index + 1 + } +} + +fn main() { + let f = Foo {}; + f.nth(0); // lint does not apply here + + let mut s = HashSet::new(); + s.insert(1); + let _x = s.iter().next(); + + let mut s2 = HashSet::new(); + s2.insert(2); + let mut iter = s2.iter(); + let _y = iter.next(); + + let mut s3 = HashSet::new(); + s3.insert(3); + let mut iter2 = s3.iter(); + let _unwrapped = iter2.next().unwrap(); +} diff --git a/src/tools/clippy/tests/ui/iter_nth_zero.rs b/src/tools/clippy/tests/ui/iter_nth_zero.rs new file mode 100644 index 0000000000..b92c7d18ad --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_nth_zero.rs @@ -0,0 +1,31 @@ +// run-rustfix + +#![warn(clippy::iter_nth_zero)] +use std::collections::HashSet; + +struct Foo {} + +impl Foo { + fn nth(&self, index: usize) -> usize { + index + 1 + } +} + +fn main() { + let f = Foo {}; + f.nth(0); // lint does not apply here + + let mut s = HashSet::new(); + s.insert(1); + let _x = s.iter().nth(0); + + let mut s2 = HashSet::new(); + s2.insert(2); + let mut iter = s2.iter(); + let _y = iter.nth(0); + + let mut s3 = HashSet::new(); + s3.insert(3); + let mut iter2 = s3.iter(); + let _unwrapped = iter2.nth(0).unwrap(); +} diff --git a/src/tools/clippy/tests/ui/iter_nth_zero.stderr b/src/tools/clippy/tests/ui/iter_nth_zero.stderr new file mode 100644 index 0000000000..29c56f3a94 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_nth_zero.stderr @@ -0,0 +1,22 @@ +error: called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent + --> $DIR/iter_nth_zero.rs:20:14 + | +LL | let _x = s.iter().nth(0); + | ^^^^^^^^^^^^^^^ help: try calling `.next()` instead of `.nth(0)`: `s.iter().next()` + | + = note: `-D clippy::iter-nth-zero` implied by `-D warnings` + +error: called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent + --> $DIR/iter_nth_zero.rs:25:14 + | +LL | let _y = iter.nth(0); + | ^^^^^^^^^^^ help: try calling `.next()` instead of `.nth(0)`: `iter.next()` + +error: called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent + --> $DIR/iter_nth_zero.rs:30:22 + | +LL | let _unwrapped = iter2.nth(0).unwrap(); + | ^^^^^^^^^^^^ help: try calling `.next()` instead of `.nth(0)`: `iter2.next()` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/iter_skip_next.fixed b/src/tools/clippy/tests/ui/iter_skip_next.fixed new file mode 100644 index 0000000000..928b6acb95 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_skip_next.fixed @@ -0,0 +1,22 @@ +// run-rustfix +// aux-build:option_helpers.rs + +#![warn(clippy::iter_skip_next)] +#![allow(clippy::blacklisted_name)] +#![allow(clippy::iter_nth)] + +extern crate option_helpers; + +use option_helpers::IteratorFalsePositives; + +/// Checks implementation of `ITER_SKIP_NEXT` lint +fn main() { + let some_vec = vec![0, 1, 2, 3]; + let _ = some_vec.iter().nth(42); + let _ = some_vec.iter().cycle().nth(42); + let _ = (1..10).nth(10); + let _ = &some_vec[..].iter().nth(3); + let foo = IteratorFalsePositives { foo: 0 }; + let _ = foo.skip(42).next(); + let _ = foo.filter().skip(42).next(); +} diff --git a/src/tools/clippy/tests/ui/iter_skip_next.rs b/src/tools/clippy/tests/ui/iter_skip_next.rs new file mode 100644 index 0000000000..7075e2598e --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_skip_next.rs @@ -0,0 +1,22 @@ +// run-rustfix +// aux-build:option_helpers.rs + +#![warn(clippy::iter_skip_next)] +#![allow(clippy::blacklisted_name)] +#![allow(clippy::iter_nth)] + +extern crate option_helpers; + +use option_helpers::IteratorFalsePositives; + +/// Checks implementation of `ITER_SKIP_NEXT` lint +fn main() { + let some_vec = vec![0, 1, 2, 3]; + let _ = some_vec.iter().skip(42).next(); + let _ = some_vec.iter().cycle().skip(42).next(); + let _ = (1..10).skip(10).next(); + let _ = &some_vec[..].iter().skip(3).next(); + let foo = IteratorFalsePositives { foo: 0 }; + let _ = foo.skip(42).next(); + let _ = foo.filter().skip(42).next(); +} diff --git a/src/tools/clippy/tests/ui/iter_skip_next.stderr b/src/tools/clippy/tests/ui/iter_skip_next.stderr new file mode 100644 index 0000000000..486de718bb --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_skip_next.stderr @@ -0,0 +1,28 @@ +error: called `skip(..).next()` on an iterator + --> $DIR/iter_skip_next.rs:15:28 + | +LL | let _ = some_vec.iter().skip(42).next(); + | ^^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(42)` + | + = note: `-D clippy::iter-skip-next` implied by `-D warnings` + +error: called `skip(..).next()` on an iterator + --> $DIR/iter_skip_next.rs:16:36 + | +LL | let _ = some_vec.iter().cycle().skip(42).next(); + | ^^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(42)` + +error: called `skip(..).next()` on an iterator + --> $DIR/iter_skip_next.rs:17:20 + | +LL | let _ = (1..10).skip(10).next(); + | ^^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(10)` + +error: called `skip(..).next()` on an iterator + --> $DIR/iter_skip_next.rs:18:33 + | +LL | let _ = &some_vec[..].iter().skip(3).next(); + | ^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(3)` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/iterator_step_by_zero.rs b/src/tools/clippy/tests/ui/iterator_step_by_zero.rs new file mode 100644 index 0000000000..13d1cfd428 --- /dev/null +++ b/src/tools/clippy/tests/ui/iterator_step_by_zero.rs @@ -0,0 +1,28 @@ +#[warn(clippy::iterator_step_by_zero)] +fn main() { + let _ = vec!["A", "B", "B"].iter().step_by(0); + let _ = "XXX".chars().step_by(0); + let _ = (0..1).step_by(0); + + // No error, not an iterator. + let y = NotIterator; + y.step_by(0); + + // No warning for non-zero step + let _ = (0..1).step_by(1); + + let _ = (1..).step_by(0); + let _ = (1..=2).step_by(0); + + let x = 0..1; + let _ = x.step_by(0); + + // check const eval + let v1 = vec![1, 2, 3]; + let _ = v1.iter().step_by(2 / 3); +} + +struct NotIterator; +impl NotIterator { + fn step_by(&self, _: u32) {} +} diff --git a/src/tools/clippy/tests/ui/iterator_step_by_zero.stderr b/src/tools/clippy/tests/ui/iterator_step_by_zero.stderr new file mode 100644 index 0000000000..d792aea11d --- /dev/null +++ b/src/tools/clippy/tests/ui/iterator_step_by_zero.stderr @@ -0,0 +1,46 @@ +error: `Iterator::step_by(0)` will panic at runtime + --> $DIR/iterator_step_by_zero.rs:3:13 + | +LL | let _ = vec!["A", "B", "B"].iter().step_by(0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::iterator-step-by-zero` implied by `-D warnings` + +error: `Iterator::step_by(0)` will panic at runtime + --> $DIR/iterator_step_by_zero.rs:4:13 + | +LL | let _ = "XXX".chars().step_by(0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `Iterator::step_by(0)` will panic at runtime + --> $DIR/iterator_step_by_zero.rs:5:13 + | +LL | let _ = (0..1).step_by(0); + | ^^^^^^^^^^^^^^^^^ + +error: `Iterator::step_by(0)` will panic at runtime + --> $DIR/iterator_step_by_zero.rs:14:13 + | +LL | let _ = (1..).step_by(0); + | ^^^^^^^^^^^^^^^^ + +error: `Iterator::step_by(0)` will panic at runtime + --> $DIR/iterator_step_by_zero.rs:15:13 + | +LL | let _ = (1..=2).step_by(0); + | ^^^^^^^^^^^^^^^^^^ + +error: `Iterator::step_by(0)` will panic at runtime + --> $DIR/iterator_step_by_zero.rs:18:13 + | +LL | let _ = x.step_by(0); + | ^^^^^^^^^^^^ + +error: `Iterator::step_by(0)` will panic at runtime + --> $DIR/iterator_step_by_zero.rs:22:13 + | +LL | let _ = v1.iter().step_by(2 / 3); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/large_const_arrays.fixed b/src/tools/clippy/tests/ui/large_const_arrays.fixed new file mode 100644 index 0000000000..c5af07c8a1 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_const_arrays.fixed @@ -0,0 +1,37 @@ +// run-rustfix + +#![warn(clippy::large_const_arrays)] +#![allow(dead_code)] + +#[derive(Clone, Copy)] +pub struct S { + pub data: [u64; 32], +} + +// Should lint +pub(crate) static FOO_PUB_CRATE: [u32; 1_000_000] = [0u32; 1_000_000]; +pub static FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; +static FOO: [u32; 1_000_000] = [0u32; 1_000_000]; + +// Good +pub(crate) const G_FOO_PUB_CRATE: [u32; 1_000] = [0u32; 1_000]; +pub const G_FOO_PUB: [u32; 1_000] = [0u32; 1_000]; +const G_FOO: [u32; 1_000] = [0u32; 1_000]; + +fn main() { + // Should lint + pub static BAR_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; + static BAR: [u32; 1_000_000] = [0u32; 1_000_000]; + pub static BAR_STRUCT_PUB: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + static BAR_STRUCT: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + pub static BAR_S_PUB: [Option<&str>; 200_000] = [Some("str"); 200_000]; + static BAR_S: [Option<&str>; 200_000] = [Some("str"); 200_000]; + + // Good + pub const G_BAR_PUB: [u32; 1_000] = [0u32; 1_000]; + const G_BAR: [u32; 1_000] = [0u32; 1_000]; + pub const G_BAR_STRUCT_PUB: [S; 500] = [S { data: [0; 32] }; 500]; + const G_BAR_STRUCT: [S; 500] = [S { data: [0; 32] }; 500]; + pub const G_BAR_S_PUB: [Option<&str>; 200] = [Some("str"); 200]; + const G_BAR_S: [Option<&str>; 200] = [Some("str"); 200]; +} diff --git a/src/tools/clippy/tests/ui/large_const_arrays.rs b/src/tools/clippy/tests/ui/large_const_arrays.rs new file mode 100644 index 0000000000..a160b9f8ad --- /dev/null +++ b/src/tools/clippy/tests/ui/large_const_arrays.rs @@ -0,0 +1,37 @@ +// run-rustfix + +#![warn(clippy::large_const_arrays)] +#![allow(dead_code)] + +#[derive(Clone, Copy)] +pub struct S { + pub data: [u64; 32], +} + +// Should lint +pub(crate) const FOO_PUB_CRATE: [u32; 1_000_000] = [0u32; 1_000_000]; +pub const FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; +const FOO: [u32; 1_000_000] = [0u32; 1_000_000]; + +// Good +pub(crate) const G_FOO_PUB_CRATE: [u32; 1_000] = [0u32; 1_000]; +pub const G_FOO_PUB: [u32; 1_000] = [0u32; 1_000]; +const G_FOO: [u32; 1_000] = [0u32; 1_000]; + +fn main() { + // Should lint + pub const BAR_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; + const BAR: [u32; 1_000_000] = [0u32; 1_000_000]; + pub const BAR_STRUCT_PUB: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + const BAR_STRUCT: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + pub const BAR_S_PUB: [Option<&str>; 200_000] = [Some("str"); 200_000]; + const BAR_S: [Option<&str>; 200_000] = [Some("str"); 200_000]; + + // Good + pub const G_BAR_PUB: [u32; 1_000] = [0u32; 1_000]; + const G_BAR: [u32; 1_000] = [0u32; 1_000]; + pub const G_BAR_STRUCT_PUB: [S; 500] = [S { data: [0; 32] }; 500]; + const G_BAR_STRUCT: [S; 500] = [S { data: [0; 32] }; 500]; + pub const G_BAR_S_PUB: [Option<&str>; 200] = [Some("str"); 200]; + const G_BAR_S: [Option<&str>; 200] = [Some("str"); 200]; +} diff --git a/src/tools/clippy/tests/ui/large_const_arrays.stderr b/src/tools/clippy/tests/ui/large_const_arrays.stderr new file mode 100644 index 0000000000..3fb0acbca6 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_const_arrays.stderr @@ -0,0 +1,76 @@ +error: large array defined as const + --> $DIR/large_const_arrays.rs:12:1 + | +LL | pub(crate) const FOO_PUB_CRATE: [u32; 1_000_000] = [0u32; 1_000_000]; + | ^^^^^^^^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + | + = note: `-D clippy::large-const-arrays` implied by `-D warnings` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:13:1 + | +LL | pub const FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; + | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:14:1 + | +LL | const FOO: [u32; 1_000_000] = [0u32; 1_000_000]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:23:5 + | +LL | pub const BAR_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; + | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:24:5 + | +LL | const BAR: [u32; 1_000_000] = [0u32; 1_000_000]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:25:5 + | +LL | pub const BAR_STRUCT_PUB: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:26:5 + | +LL | const BAR_STRUCT: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:27:5 + | +LL | pub const BAR_S_PUB: [Option<&str>; 200_000] = [Some("str"); 200_000]; + | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:28:5 + | +LL | const BAR_S: [Option<&str>; 200_000] = [Some("str"); 200_000]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/large_digit_groups.fixed b/src/tools/clippy/tests/ui/large_digit_groups.fixed new file mode 100644 index 0000000000..3430c137ec --- /dev/null +++ b/src/tools/clippy/tests/ui/large_digit_groups.fixed @@ -0,0 +1,31 @@ +// run-rustfix +#![warn(clippy::large_digit_groups)] + +fn main() { + macro_rules! mac { + () => { + 0b1_10110_i64 + }; + } + + let _good = ( + 0b1011_i64, + 0o1_234_u32, + 0x0123_4567, + 1_2345_6789, + 1234_f32, + 1_234.12_f32, + 1_234.123_f32, + 1.123_4_f32, + ); + let _bad = ( + 0b11_0110_i64, + 0xdead_beef_usize, + 123_456_f32, + 123_456.12_f32, + 123_456.123_45_f64, + 123_456.123_456_f64, + ); + // Ignore literals in macros + let _ = mac!(); +} diff --git a/src/tools/clippy/tests/ui/large_digit_groups.rs b/src/tools/clippy/tests/ui/large_digit_groups.rs new file mode 100644 index 0000000000..ac116d5dbd --- /dev/null +++ b/src/tools/clippy/tests/ui/large_digit_groups.rs @@ -0,0 +1,31 @@ +// run-rustfix +#![warn(clippy::large_digit_groups)] + +fn main() { + macro_rules! mac { + () => { + 0b1_10110_i64 + }; + } + + let _good = ( + 0b1011_i64, + 0o1_234_u32, + 0x1_234_567, + 1_2345_6789, + 1234_f32, + 1_234.12_f32, + 1_234.123_f32, + 1.123_4_f32, + ); + let _bad = ( + 0b1_10110_i64, + 0xd_e_adbee_f_usize, + 1_23456_f32, + 1_23456.12_f32, + 1_23456.12345_f64, + 1_23456.12345_6_f64, + ); + // Ignore literals in macros + let _ = mac!(); +} diff --git a/src/tools/clippy/tests/ui/large_digit_groups.stderr b/src/tools/clippy/tests/ui/large_digit_groups.stderr new file mode 100644 index 0000000000..13d108b56e --- /dev/null +++ b/src/tools/clippy/tests/ui/large_digit_groups.stderr @@ -0,0 +1,48 @@ +error: digits of hex or binary literal not grouped by four + --> $DIR/large_digit_groups.rs:14:9 + | +LL | 0x1_234_567, + | ^^^^^^^^^^^ help: consider: `0x0123_4567` + | + = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` + +error: digits of hex or binary literal not grouped by four + --> $DIR/large_digit_groups.rs:22:9 + | +LL | 0b1_10110_i64, + | ^^^^^^^^^^^^^ help: consider: `0b11_0110_i64` + +error: digits of hex or binary literal not grouped by four + --> $DIR/large_digit_groups.rs:23:9 + | +LL | 0xd_e_adbee_f_usize, + | ^^^^^^^^^^^^^^^^^^^ help: consider: `0xdead_beef_usize` + +error: digit groups should be smaller + --> $DIR/large_digit_groups.rs:24:9 + | +LL | 1_23456_f32, + | ^^^^^^^^^^^ help: consider: `123_456_f32` + | + = note: `-D clippy::large-digit-groups` implied by `-D warnings` + +error: digit groups should be smaller + --> $DIR/large_digit_groups.rs:25:9 + | +LL | 1_23456.12_f32, + | ^^^^^^^^^^^^^^ help: consider: `123_456.12_f32` + +error: digit groups should be smaller + --> $DIR/large_digit_groups.rs:26:9 + | +LL | 1_23456.12345_f64, + | ^^^^^^^^^^^^^^^^^ help: consider: `123_456.123_45_f64` + +error: digit groups should be smaller + --> $DIR/large_digit_groups.rs:27:9 + | +LL | 1_23456.12345_6_f64, + | ^^^^^^^^^^^^^^^^^^^ help: consider: `123_456.123_456_f64` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/large_enum_variant.rs b/src/tools/clippy/tests/ui/large_enum_variant.rs new file mode 100644 index 0000000000..d22fee3f27 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_enum_variant.rs @@ -0,0 +1,61 @@ +// aux-build:macro_rules.rs + +#![allow(dead_code)] +#![allow(unused_variables)] +#![warn(clippy::large_enum_variant)] + +#[macro_use] +extern crate macro_rules; + +enum LargeEnum { + A(i32), + B([i32; 8000]), +} + +enum GenericEnumOk { + A(i32), + B([T; 8000]), +} + +enum GenericEnum2 { + A(i32), + B([i32; 8000]), + C(T, [i32; 8000]), +} + +trait SomeTrait { + type Item; +} + +enum LargeEnumGeneric { + Var(A::Item), +} + +enum LargeEnum2 { + VariantOk(i32, u32), + ContainingLargeEnum(LargeEnum), +} +enum LargeEnum3 { + ContainingMoreThanOneField(i32, [i32; 8000], [i32; 9500]), + VoidVariant, + StructLikeLittle { x: i32, y: i32 }, +} + +enum LargeEnum4 { + VariantOk(i32, u32), + StructLikeLarge { x: [i32; 8000], y: i32 }, +} + +enum LargeEnum5 { + VariantOk(i32, u32), + StructLikeLarge2 { x: [i32; 8000] }, +} + +enum LargeEnumOk { + LargeA([i32; 8000]), + LargeB([i32; 8001]), +} + +fn main() { + large_enum_variant!(); +} diff --git a/src/tools/clippy/tests/ui/large_enum_variant.stderr b/src/tools/clippy/tests/ui/large_enum_variant.stderr new file mode 100644 index 0000000000..d39a4d462a --- /dev/null +++ b/src/tools/clippy/tests/ui/large_enum_variant.stderr @@ -0,0 +1,68 @@ +error: large size difference between variants + --> $DIR/large_enum_variant.rs:12:5 + | +LL | B([i32; 8000]), + | ^^^^^^^^^^^^^^ this variant is 32000 bytes + | + = note: `-D clippy::large-enum-variant` implied by `-D warnings` +note: and the second-largest variant is 4 bytes: + --> $DIR/large_enum_variant.rs:11:5 + | +LL | A(i32), + | ^^^^^^ +help: consider boxing the large fields to reduce the total size of the enum + | +LL | B(Box<[i32; 8000]>), + | ^^^^^^^^^^^^^^^^ + +error: large size difference between variants + --> $DIR/large_enum_variant.rs:36:5 + | +LL | ContainingLargeEnum(LargeEnum), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this variant is 32004 bytes + | +note: and the second-largest variant is 8 bytes: + --> $DIR/large_enum_variant.rs:35:5 + | +LL | VariantOk(i32, u32), + | ^^^^^^^^^^^^^^^^^^^ +help: consider boxing the large fields to reduce the total size of the enum + | +LL | ContainingLargeEnum(Box), + | ^^^^^^^^^^^^^^ + +error: large size difference between variants + --> $DIR/large_enum_variant.rs:46:5 + | +LL | StructLikeLarge { x: [i32; 8000], y: i32 }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this variant is 32004 bytes + | +note: and the second-largest variant is 8 bytes: + --> $DIR/large_enum_variant.rs:45:5 + | +LL | VariantOk(i32, u32), + | ^^^^^^^^^^^^^^^^^^^ +help: consider boxing the large fields to reduce the total size of the enum + --> $DIR/large_enum_variant.rs:46:5 + | +LL | StructLikeLarge { x: [i32; 8000], y: i32 }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: large size difference between variants + --> $DIR/large_enum_variant.rs:51:5 + | +LL | StructLikeLarge2 { x: [i32; 8000] }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this variant is 32000 bytes + | +note: and the second-largest variant is 8 bytes: + --> $DIR/large_enum_variant.rs:50:5 + | +LL | VariantOk(i32, u32), + | ^^^^^^^^^^^^^^^^^^^ +help: consider boxing the large fields to reduce the total size of the enum + | +LL | StructLikeLarge2 { x: Box<[i32; 8000]> }, + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/large_stack_arrays.rs b/src/tools/clippy/tests/ui/large_stack_arrays.rs new file mode 100644 index 0000000000..d9161bfcf1 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_stack_arrays.rs @@ -0,0 +1,30 @@ +#![warn(clippy::large_stack_arrays)] +#![allow(clippy::large_enum_variant)] + +#[derive(Clone, Copy)] +struct S { + pub data: [u64; 32], +} + +#[derive(Clone, Copy)] +enum E { + S(S), + T(u32), +} + +fn main() { + let bad = ( + [0u32; 20_000_000], + [S { data: [0; 32] }; 5000], + [Some(""); 20_000_000], + [E::T(0); 5000], + ); + + let good = ( + [0u32; 1000], + [S { data: [0; 32] }; 1000], + [Some(""); 1000], + [E::T(0); 1000], + [(); 20_000_000], + ); +} diff --git a/src/tools/clippy/tests/ui/large_stack_arrays.stderr b/src/tools/clippy/tests/ui/large_stack_arrays.stderr new file mode 100644 index 0000000000..58c0a77c1c --- /dev/null +++ b/src/tools/clippy/tests/ui/large_stack_arrays.stderr @@ -0,0 +1,35 @@ +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:17:9 + | +LL | [0u32; 20_000_000], + | ^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::large-stack-arrays` implied by `-D warnings` + = help: consider allocating on the heap with `vec![0u32; 20_000_000].into_boxed_slice()` + +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:18:9 + | +LL | [S { data: [0; 32] }; 5000], + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with `vec![S { data: [0; 32] }; 5000].into_boxed_slice()` + +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:19:9 + | +LL | [Some(""); 20_000_000], + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with `vec![Some(""); 20_000_000].into_boxed_slice()` + +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:20:9 + | +LL | [E::T(0); 5000], + | ^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with `vec![E::T(0); 5000].into_boxed_slice()` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/large_types_passed_by_value.rs b/src/tools/clippy/tests/ui/large_types_passed_by_value.rs new file mode 100644 index 0000000000..e4a2e9df4d --- /dev/null +++ b/src/tools/clippy/tests/ui/large_types_passed_by_value.rs @@ -0,0 +1,66 @@ +// normalize-stderr-test "\(\d+ byte\)" -> "(N byte)" +// normalize-stderr-test "\(limit: \d+ byte\)" -> "(limit: N byte)" + +#![warn(clippy::large_types_passed_by_value)] + +pub struct Large([u8; 2048]); + +#[derive(Clone, Copy)] +pub struct LargeAndCopy([u8; 2048]); + +pub struct Small([u8; 4]); + +#[derive(Clone, Copy)] +pub struct SmallAndCopy([u8; 4]); + +fn small(a: Small, b: SmallAndCopy) {} +fn not_copy(a: Large) {} +fn by_ref(a: &Large, b: &LargeAndCopy) {} +fn mutable(mut a: LargeAndCopy) {} +fn bad(a: LargeAndCopy) {} +pub fn bad_but_pub(a: LargeAndCopy) {} + +impl LargeAndCopy { + fn self_is_ok(self) {} + fn other_is_not_ok(self, other: LargeAndCopy) {} + fn unless_other_can_change(self, mut other: LargeAndCopy) {} + pub fn or_were_in_public(self, other: LargeAndCopy) {} +} + +trait LargeTypeDevourer { + fn devoure_array(&self, array: [u8; 6666]); + fn devoure_tuple(&self, tup: (LargeAndCopy, LargeAndCopy)); + fn devoure_array_and_tuple_wow(&self, array: [u8; 6666], tup: (LargeAndCopy, LargeAndCopy)); +} + +pub trait PubLargeTypeDevourer { + fn devoure_array_in_public(&self, array: [u8; 6666]); +} + +struct S {} +impl LargeTypeDevourer for S { + fn devoure_array(&self, array: [u8; 6666]) { + todo!(); + } + fn devoure_tuple(&self, tup: (LargeAndCopy, LargeAndCopy)) { + todo!(); + } + fn devoure_array_and_tuple_wow(&self, array: [u8; 6666], tup: (LargeAndCopy, LargeAndCopy)) { + todo!(); + } +} + +#[inline(always)] +fn foo_always(x: LargeAndCopy) { + todo!(); +} +#[inline(never)] +fn foo_never(x: LargeAndCopy) { + todo!(); +} +#[inline] +fn foo(x: LargeAndCopy) { + todo!(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/large_types_passed_by_value.stderr b/src/tools/clippy/tests/ui/large_types_passed_by_value.stderr new file mode 100644 index 0000000000..5f42dcfb9b --- /dev/null +++ b/src/tools/clippy/tests/ui/large_types_passed_by_value.stderr @@ -0,0 +1,52 @@ +error: this argument (N byte) is passed by value, but might be more efficient if passed by reference (limit: N byte) + --> $DIR/large_types_passed_by_value.rs:20:11 + | +LL | fn bad(a: LargeAndCopy) {} + | ^^^^^^^^^^^^ help: consider passing by reference instead: `&LargeAndCopy` + | + = note: `-D clippy::large-types-passed-by-value` implied by `-D warnings` + +error: this argument (N byte) is passed by value, but might be more efficient if passed by reference (limit: N byte) + --> $DIR/large_types_passed_by_value.rs:25:37 + | +LL | fn other_is_not_ok(self, other: LargeAndCopy) {} + | ^^^^^^^^^^^^ help: consider passing by reference instead: `&LargeAndCopy` + +error: this argument (N byte) is passed by value, but might be more efficient if passed by reference (limit: N byte) + --> $DIR/large_types_passed_by_value.rs:31:36 + | +LL | fn devoure_array(&self, array: [u8; 6666]); + | ^^^^^^^^^^ help: consider passing by reference instead: `&[u8; 6666]` + +error: this argument (N byte) is passed by value, but might be more efficient if passed by reference (limit: N byte) + --> $DIR/large_types_passed_by_value.rs:32:34 + | +LL | fn devoure_tuple(&self, tup: (LargeAndCopy, LargeAndCopy)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider passing by reference instead: `&(LargeAndCopy, LargeAndCopy)` + +error: this argument (N byte) is passed by value, but might be more efficient if passed by reference (limit: N byte) + --> $DIR/large_types_passed_by_value.rs:33:50 + | +LL | fn devoure_array_and_tuple_wow(&self, array: [u8; 6666], tup: (LargeAndCopy, LargeAndCopy)); + | ^^^^^^^^^^ help: consider passing by reference instead: `&[u8; 6666]` + +error: this argument (N byte) is passed by value, but might be more efficient if passed by reference (limit: N byte) + --> $DIR/large_types_passed_by_value.rs:33:67 + | +LL | fn devoure_array_and_tuple_wow(&self, array: [u8; 6666], tup: (LargeAndCopy, LargeAndCopy)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider passing by reference instead: `&(LargeAndCopy, LargeAndCopy)` + +error: this argument (N byte) is passed by value, but might be more efficient if passed by reference (limit: N byte) + --> $DIR/large_types_passed_by_value.rs:58:17 + | +LL | fn foo_never(x: LargeAndCopy) { + | ^^^^^^^^^^^^ help: consider passing by reference instead: `&LargeAndCopy` + +error: this argument (N byte) is passed by value, but might be more efficient if passed by reference (limit: N byte) + --> $DIR/large_types_passed_by_value.rs:62:11 + | +LL | fn foo(x: LargeAndCopy) { + | ^^^^^^^^^^^^ help: consider passing by reference instead: `&LargeAndCopy` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/len_without_is_empty.rs b/src/tools/clippy/tests/ui/len_without_is_empty.rs new file mode 100644 index 0000000000..6b3636a482 --- /dev/null +++ b/src/tools/clippy/tests/ui/len_without_is_empty.rs @@ -0,0 +1,190 @@ +#![warn(clippy::len_without_is_empty)] +#![allow(dead_code, unused)] + +pub struct PubOne; + +impl PubOne { + pub fn len(&self) -> isize { + 1 + } +} + +impl PubOne { + // A second impl for this struct -- the error span shouldn't mention this. + pub fn irrelevant(&self) -> bool { + false + } +} + +// Identical to `PubOne`, but with an `allow` attribute on the impl complaining `len`. +pub struct PubAllowed; + +#[allow(clippy::len_without_is_empty)] +impl PubAllowed { + pub fn len(&self) -> isize { + 1 + } +} + +// No `allow` attribute on this impl block, but that doesn't matter -- we only require one on the +// impl containing `len`. +impl PubAllowed { + pub fn irrelevant(&self) -> bool { + false + } +} + +pub struct PubAllowedFn; + +impl PubAllowedFn { + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> isize { + 1 + } +} + +#[allow(clippy::len_without_is_empty)] +pub struct PubAllowedStruct; + +impl PubAllowedStruct { + pub fn len(&self) -> isize { + 1 + } +} + +pub trait PubTraitsToo { + fn len(&self) -> isize; +} + +impl PubTraitsToo for One { + fn len(&self) -> isize { + 0 + } +} + +pub struct HasIsEmpty; + +impl HasIsEmpty { + pub fn len(&self) -> isize { + 1 + } + + fn is_empty(&self) -> bool { + false + } +} + +pub struct HasWrongIsEmpty; + +impl HasWrongIsEmpty { + pub fn len(&self) -> isize { + 1 + } + + pub fn is_empty(&self, x: u32) -> bool { + false + } +} + +pub struct MismatchedSelf; + +impl MismatchedSelf { + pub fn len(self) -> isize { + 1 + } + + pub fn is_empty(&self) -> bool { + false + } +} + +struct NotPubOne; + +impl NotPubOne { + pub fn len(&self) -> isize { + // No error; `len` is pub but `NotPubOne` is not exported anyway. + 1 + } +} + +struct One; + +impl One { + fn len(&self) -> isize { + // No error; `len` is private; see issue #1085. + 1 + } +} + +trait TraitsToo { + fn len(&self) -> isize; + // No error; `len` is private; see issue #1085. +} + +impl TraitsToo for One { + fn len(&self) -> isize { + 0 + } +} + +struct HasPrivateIsEmpty; + +impl HasPrivateIsEmpty { + pub fn len(&self) -> isize { + 1 + } + + fn is_empty(&self) -> bool { + false + } +} + +struct Wither; + +pub trait WithIsEmpty { + fn len(&self) -> isize; + fn is_empty(&self) -> bool; +} + +impl WithIsEmpty for Wither { + fn len(&self) -> isize { + 1 + } + + fn is_empty(&self) -> bool { + false + } +} + +pub trait Empty { + fn is_empty(&self) -> bool; +} + +pub trait InheritingEmpty: Empty { + // Must not trigger `LEN_WITHOUT_IS_EMPTY`. + fn len(&self) -> isize; +} + +// This used to ICE. +pub trait Foo: Sized {} + +pub trait DependsOnFoo: Foo { + fn len(&mut self) -> usize; +} + +pub struct MultipleImpls; + +// issue #1562 +impl MultipleImpls { + pub fn len(&self) -> usize { + 1 + } +} + +impl MultipleImpls { + pub fn is_empty(&self) -> bool { + false + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/len_without_is_empty.stderr b/src/tools/clippy/tests/ui/len_without_is_empty.stderr new file mode 100644 index 0000000000..f106506faf --- /dev/null +++ b/src/tools/clippy/tests/ui/len_without_is_empty.stderr @@ -0,0 +1,64 @@ +error: struct `PubOne` has a public `len` method, but no `is_empty` method + --> $DIR/len_without_is_empty.rs:7:5 + | +LL | pub fn len(&self) -> isize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::len-without-is-empty` implied by `-D warnings` + +error: trait `PubTraitsToo` has a `len` method but no (possibly inherited) `is_empty` method + --> $DIR/len_without_is_empty.rs:55:1 + | +LL | / pub trait PubTraitsToo { +LL | | fn len(&self) -> isize; +LL | | } + | |_^ + +error: struct `HasIsEmpty` has a public `len` method, but a private `is_empty` method + --> $DIR/len_without_is_empty.rs:68:5 + | +LL | pub fn len(&self) -> isize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `is_empty` defined here + --> $DIR/len_without_is_empty.rs:72:5 + | +LL | fn is_empty(&self) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: struct `HasWrongIsEmpty` has a public `len` method, but the `is_empty` method has an unexpected signature + --> $DIR/len_without_is_empty.rs:80:5 + | +LL | pub fn len(&self) -> isize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `is_empty` defined here + --> $DIR/len_without_is_empty.rs:84:5 + | +LL | pub fn is_empty(&self, x: u32) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature: `(&self) -> bool` + +error: struct `MismatchedSelf` has a public `len` method, but the `is_empty` method has an unexpected signature + --> $DIR/len_without_is_empty.rs:92:5 + | +LL | pub fn len(self) -> isize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `is_empty` defined here + --> $DIR/len_without_is_empty.rs:96:5 + | +LL | pub fn is_empty(&self) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature: `(self) -> bool` + +error: trait `DependsOnFoo` has a `len` method but no (possibly inherited) `is_empty` method + --> $DIR/len_without_is_empty.rs:171:1 + | +LL | / pub trait DependsOnFoo: Foo { +LL | | fn len(&mut self) -> usize; +LL | | } + | |_^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/len_zero.fixed b/src/tools/clippy/tests/ui/len_zero.fixed new file mode 100644 index 0000000000..1f3b8ac99b --- /dev/null +++ b/src/tools/clippy/tests/ui/len_zero.fixed @@ -0,0 +1,143 @@ +// run-rustfix + +#![warn(clippy::len_zero)] +#![allow(dead_code, unused, clippy::len_without_is_empty)] + +pub struct One; +struct Wither; + +trait TraitsToo { + fn len(&self) -> isize; + // No error; `len` is private; see issue #1085. +} + +impl TraitsToo for One { + fn len(&self) -> isize { + 0 + } +} + +pub struct HasIsEmpty; + +impl HasIsEmpty { + pub fn len(&self) -> isize { + 1 + } + + fn is_empty(&self) -> bool { + false + } +} + +pub struct HasWrongIsEmpty; + +impl HasWrongIsEmpty { + pub fn len(&self) -> isize { + 1 + } + + pub fn is_empty(&self, x: u32) -> bool { + false + } +} + +pub trait WithIsEmpty { + fn len(&self) -> isize; + fn is_empty(&self) -> bool; +} + +impl WithIsEmpty for Wither { + fn len(&self) -> isize { + 1 + } + + fn is_empty(&self) -> bool { + false + } +} + +fn main() { + let x = [1, 2]; + if x.is_empty() { + println!("This should not happen!"); + } + + if "".is_empty() {} + + let y = One; + if y.len() == 0 { + // No error; `One` does not have `.is_empty()`. + println!("This should not happen either!"); + } + + let z: &dyn TraitsToo = &y; + if z.len() > 0 { + // No error; `TraitsToo` has no `.is_empty()` method. + println!("Nor should this!"); + } + + let has_is_empty = HasIsEmpty; + if has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if has_is_empty.len() > 1 { + // No error. + println!("This can happen."); + } + if has_is_empty.len() <= 1 { + // No error. + println!("This can happen."); + } + if has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if has_is_empty.is_empty() { + println!("Or this!"); + } + if 1 < has_is_empty.len() { + // No error. + println!("This can happen."); + } + if 1 >= has_is_empty.len() { + // No error. + println!("This can happen."); + } + assert!(!has_is_empty.is_empty()); + + let with_is_empty: &dyn WithIsEmpty = &Wither; + if with_is_empty.is_empty() { + println!("Or this!"); + } + assert!(!with_is_empty.is_empty()); + + let has_wrong_is_empty = HasWrongIsEmpty; + if has_wrong_is_empty.len() == 0 { + // No error; `HasWrongIsEmpty` does not have `.is_empty()`. + println!("Or this!"); + } +} + +fn test_slice(b: &[u8]) { + if !b.is_empty() {} +} diff --git a/src/tools/clippy/tests/ui/len_zero.rs b/src/tools/clippy/tests/ui/len_zero.rs new file mode 100644 index 0000000000..dc21de0001 --- /dev/null +++ b/src/tools/clippy/tests/ui/len_zero.rs @@ -0,0 +1,143 @@ +// run-rustfix + +#![warn(clippy::len_zero)] +#![allow(dead_code, unused, clippy::len_without_is_empty)] + +pub struct One; +struct Wither; + +trait TraitsToo { + fn len(&self) -> isize; + // No error; `len` is private; see issue #1085. +} + +impl TraitsToo for One { + fn len(&self) -> isize { + 0 + } +} + +pub struct HasIsEmpty; + +impl HasIsEmpty { + pub fn len(&self) -> isize { + 1 + } + + fn is_empty(&self) -> bool { + false + } +} + +pub struct HasWrongIsEmpty; + +impl HasWrongIsEmpty { + pub fn len(&self) -> isize { + 1 + } + + pub fn is_empty(&self, x: u32) -> bool { + false + } +} + +pub trait WithIsEmpty { + fn len(&self) -> isize; + fn is_empty(&self) -> bool; +} + +impl WithIsEmpty for Wither { + fn len(&self) -> isize { + 1 + } + + fn is_empty(&self) -> bool { + false + } +} + +fn main() { + let x = [1, 2]; + if x.len() == 0 { + println!("This should not happen!"); + } + + if "".len() == 0 {} + + let y = One; + if y.len() == 0 { + // No error; `One` does not have `.is_empty()`. + println!("This should not happen either!"); + } + + let z: &dyn TraitsToo = &y; + if z.len() > 0 { + // No error; `TraitsToo` has no `.is_empty()` method. + println!("Nor should this!"); + } + + let has_is_empty = HasIsEmpty; + if has_is_empty.len() == 0 { + println!("Or this!"); + } + if has_is_empty.len() != 0 { + println!("Or this!"); + } + if has_is_empty.len() > 0 { + println!("Or this!"); + } + if has_is_empty.len() < 1 { + println!("Or this!"); + } + if has_is_empty.len() >= 1 { + println!("Or this!"); + } + if has_is_empty.len() > 1 { + // No error. + println!("This can happen."); + } + if has_is_empty.len() <= 1 { + // No error. + println!("This can happen."); + } + if 0 == has_is_empty.len() { + println!("Or this!"); + } + if 0 != has_is_empty.len() { + println!("Or this!"); + } + if 0 < has_is_empty.len() { + println!("Or this!"); + } + if 1 <= has_is_empty.len() { + println!("Or this!"); + } + if 1 > has_is_empty.len() { + println!("Or this!"); + } + if 1 < has_is_empty.len() { + // No error. + println!("This can happen."); + } + if 1 >= has_is_empty.len() { + // No error. + println!("This can happen."); + } + assert!(!has_is_empty.is_empty()); + + let with_is_empty: &dyn WithIsEmpty = &Wither; + if with_is_empty.len() == 0 { + println!("Or this!"); + } + assert!(!with_is_empty.is_empty()); + + let has_wrong_is_empty = HasWrongIsEmpty; + if has_wrong_is_empty.len() == 0 { + // No error; `HasWrongIsEmpty` does not have `.is_empty()`. + println!("Or this!"); + } +} + +fn test_slice(b: &[u8]) { + if b.len() != 0 {} +} diff --git a/src/tools/clippy/tests/ui/len_zero.stderr b/src/tools/clippy/tests/ui/len_zero.stderr new file mode 100644 index 0000000000..6c71f1beea --- /dev/null +++ b/src/tools/clippy/tests/ui/len_zero.stderr @@ -0,0 +1,88 @@ +error: length comparison to zero + --> $DIR/len_zero.rs:61:8 + | +LL | if x.len() == 0 { + | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `x.is_empty()` + | + = note: `-D clippy::len-zero` implied by `-D warnings` + +error: length comparison to zero + --> $DIR/len_zero.rs:65:8 + | +LL | if "".len() == 0 {} + | ^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `"".is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:80:8 + | +LL | if has_is_empty.len() == 0 { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:83:8 + | +LL | if has_is_empty.len() != 0 { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:86:8 + | +LL | if has_is_empty.len() > 0 { + | ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to one + --> $DIR/len_zero.rs:89:8 + | +LL | if has_is_empty.len() < 1 { + | ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` + +error: length comparison to one + --> $DIR/len_zero.rs:92:8 + | +LL | if has_is_empty.len() >= 1 { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:103:8 + | +LL | if 0 == has_is_empty.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:106:8 + | +LL | if 0 != has_is_empty.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:109:8 + | +LL | if 0 < has_is_empty.len() { + | ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to one + --> $DIR/len_zero.rs:112:8 + | +LL | if 1 <= has_is_empty.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to one + --> $DIR/len_zero.rs:115:8 + | +LL | if 1 > has_is_empty.len() { + | ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:129:8 + | +LL | if with_is_empty.len() == 0 { + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `with_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:142:8 + | +LL | if b.len() != 0 {} + | ^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!b.is_empty()` + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/len_zero_ranges.fixed b/src/tools/clippy/tests/ui/len_zero_ranges.fixed new file mode 100644 index 0000000000..7978176624 --- /dev/null +++ b/src/tools/clippy/tests/ui/len_zero_ranges.fixed @@ -0,0 +1,17 @@ +// run-rustfix + +#![warn(clippy::len_zero)] +#![allow(unused)] + +// Now that `Range(Inclusive)::is_empty` is stable (1.47), we can always suggest this +mod issue_3807 { + fn suggestion_is_fine_range() { + let _ = (0..42).is_empty(); + } + + fn suggestion_is_fine_range_inclusive() { + let _ = (0_u8..=42).is_empty(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/len_zero_ranges.rs b/src/tools/clippy/tests/ui/len_zero_ranges.rs new file mode 100644 index 0000000000..a0eb51cc97 --- /dev/null +++ b/src/tools/clippy/tests/ui/len_zero_ranges.rs @@ -0,0 +1,17 @@ +// run-rustfix + +#![warn(clippy::len_zero)] +#![allow(unused)] + +// Now that `Range(Inclusive)::is_empty` is stable (1.47), we can always suggest this +mod issue_3807 { + fn suggestion_is_fine_range() { + let _ = (0..42).len() == 0; + } + + fn suggestion_is_fine_range_inclusive() { + let _ = (0_u8..=42).len() == 0; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/len_zero_ranges.stderr b/src/tools/clippy/tests/ui/len_zero_ranges.stderr new file mode 100644 index 0000000000..d0defb5a79 --- /dev/null +++ b/src/tools/clippy/tests/ui/len_zero_ranges.stderr @@ -0,0 +1,16 @@ +error: length comparison to zero + --> $DIR/len_zero_ranges.rs:9:17 + | +LL | let _ = (0..42).len() == 0; + | ^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(0..42).is_empty()` + | + = note: `-D clippy::len-zero` implied by `-D warnings` + +error: length comparison to zero + --> $DIR/len_zero_ranges.rs:13:17 + | +LL | let _ = (0_u8..=42).len() == 0; + | ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(0_u8..=42).is_empty()` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/let_and_return.rs b/src/tools/clippy/tests/ui/let_and_return.rs new file mode 100644 index 0000000000..e3561863c1 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_and_return.rs @@ -0,0 +1,169 @@ +#![allow(unused)] +#![warn(clippy::let_and_return)] + +fn test() -> i32 { + let _y = 0; // no warning + let x = 5; + x +} + +fn test_inner() -> i32 { + if true { + let x = 5; + x + } else { + 0 + } +} + +fn test_nowarn_1() -> i32 { + let mut x = 5; + x += 1; + x +} + +fn test_nowarn_2() -> i32 { + let x = 5; + x + 1 +} + +fn test_nowarn_3() -> (i32, i32) { + // this should technically warn, but we do not compare complex patterns + let (x, y) = (5, 9); + (x, y) +} + +fn test_nowarn_4() -> i32 { + // this should technically warn, but not b/c of clippy::let_and_return, but b/c of useless type + let x: i32 = 5; + x +} + +fn test_nowarn_5(x: i16) -> u16 { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let x = x as u16; + x +} + +// False positive example +trait Decode { + fn decode(d: D) -> Result + where + Self: Sized; +} + +macro_rules! tuple_encode { + ($($x:ident),*) => ( + impl<$($x: Decode),*> Decode for ($($x),*) { + #[inline] + #[allow(non_snake_case)] + fn decode(mut d: D) -> Result { + // Shouldn't trigger lint + Ok(($({let $x = Decode::decode(&mut d)?; $x }),*)) + } + } + ); +} + +tuple_encode!(T0, T1, T2, T3, T4, T5, T6, T7); + +mod no_lint_if_stmt_borrows { + mod issue_3792 { + use std::io::{self, BufRead, Stdin}; + + fn read_line() -> String { + let stdin = io::stdin(); + let line = stdin.lock().lines().next().unwrap().unwrap(); + line + } + } + + mod issue_3324 { + use std::cell::RefCell; + use std::rc::{Rc, Weak}; + + fn test(value: Weak>) -> u32 { + let value = value.upgrade().unwrap(); + let ret = value.borrow().baz(); + ret + } + + struct Bar {} + + impl Bar { + fn new() -> Self { + Bar {} + } + fn baz(&self) -> u32 { + 0 + } + } + + fn main() { + let a = Rc::new(RefCell::new(Bar::new())); + let b = Rc::downgrade(&a); + test(b); + } + } + + mod free_function { + struct Inner; + + struct Foo<'a> { + inner: &'a Inner, + } + + impl Drop for Foo<'_> { + fn drop(&mut self) {} + } + + impl<'a> Foo<'a> { + fn new(inner: &'a Inner) -> Self { + Self { inner } + } + + fn value(&self) -> i32 { + 42 + } + } + + fn some_foo(inner: &Inner) -> Foo<'_> { + Foo { inner } + } + + fn test() -> i32 { + let x = Inner {}; + let value = some_foo(&x).value(); + value + } + + fn test2() -> i32 { + let x = Inner {}; + let value = Foo::new(&x).value(); + value + } + } +} + +mod issue_5729 { + use std::sync::Arc; + + trait Foo {} + + trait FooStorage { + fn foo_cloned(&self) -> Arc; + } + + struct FooStorageImpl { + foo: Arc, + } + + impl FooStorage for FooStorageImpl { + fn foo_cloned(&self) -> Arc { + let clone = Arc::clone(&self.foo); + clone + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/let_and_return.stderr b/src/tools/clippy/tests/ui/let_and_return.stderr new file mode 100644 index 0000000000..a6941dabeb --- /dev/null +++ b/src/tools/clippy/tests/ui/let_and_return.stderr @@ -0,0 +1,45 @@ +error: returning the result of a `let` binding from a block + --> $DIR/let_and_return.rs:7:5 + | +LL | let x = 5; + | ---------- unnecessary `let` binding +LL | x + | ^ + | + = note: `-D clippy::let-and-return` implied by `-D warnings` +help: return the expression directly + | +LL | +LL | 5 + | + +error: returning the result of a `let` binding from a block + --> $DIR/let_and_return.rs:13:9 + | +LL | let x = 5; + | ---------- unnecessary `let` binding +LL | x + | ^ + | +help: return the expression directly + | +LL | +LL | 5 + | + +error: returning the result of a `let` binding from a block + --> $DIR/let_and_return.rs:164:13 + | +LL | let clone = Arc::clone(&self.foo); + | ---------------------------------- unnecessary `let` binding +LL | clone + | ^^^^^ + | +help: return the expression directly + | +LL | +LL | Arc::clone(&self.foo) as _ + | + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/let_if_seq.rs b/src/tools/clippy/tests/ui/let_if_seq.rs new file mode 100644 index 0000000000..32a67f181d --- /dev/null +++ b/src/tools/clippy/tests/ui/let_if_seq.rs @@ -0,0 +1,120 @@ +#![allow( + unused_variables, + unused_assignments, + clippy::similar_names, + clippy::blacklisted_name +)] +#![warn(clippy::useless_let_if_seq)] + +fn f() -> bool { + true +} +fn g(x: i32) -> i32 { + x + 1 +} + +fn issue985() -> i32 { + let mut x = 42; + if f() { + x = g(x); + } + + x +} + +fn issue985_alt() -> i32 { + let mut x = 42; + if f() { + f(); + } else { + x = g(x); + } + + x +} + +#[allow(clippy::manual_strip)] +fn issue975() -> String { + let mut udn = "dummy".to_string(); + if udn.starts_with("uuid:") { + udn = String::from(&udn[5..]); + } + udn +} + +fn early_return() -> u8 { + // FIXME: we could extend the lint to include such cases: + let foo; + + if f() { + return 42; + } else { + foo = 0; + } + + foo +} + +fn main() { + early_return(); + issue975(); + issue985(); + issue985_alt(); + + let mut foo = 0; + if f() { + foo = 42; + } + + let mut bar = 0; + if f() { + f(); + bar = 42; + } else { + f(); + } + + let quz; + if f() { + quz = 42; + } else { + quz = 0; + } + + // `toto` is used several times + let mut toto; + if f() { + toto = 42; + } else { + for i in &[1, 2] { + toto = *i; + } + + toto = 2; + } + + // found in libcore, the inner if is not a statement but the block's expr + let mut ch = b'x'; + if f() { + ch = b'*'; + if f() { + ch = b'?'; + } + } + + // baz needs to be mut + let mut baz = 0; + if f() { + baz = 42; + } + + baz = 1337; + + // issue 3043 - types with interior mutability should not trigger this lint + use std::cell::Cell; + let mut val = Cell::new(1); + if true { + val = Cell::new(2); + } + println!("{}", val.get()); +} diff --git a/src/tools/clippy/tests/ui/let_if_seq.stderr b/src/tools/clippy/tests/ui/let_if_seq.stderr new file mode 100644 index 0000000000..7de560c734 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_if_seq.stderr @@ -0,0 +1,50 @@ +error: `if _ { .. } else { .. }` is an expression + --> $DIR/let_if_seq.rs:64:5 + | +LL | / let mut foo = 0; +LL | | if f() { +LL | | foo = 42; +LL | | } + | |_____^ help: it is more idiomatic to write: `let foo = if f() { 42 } else { 0 };` + | + = note: `-D clippy::useless-let-if-seq` implied by `-D warnings` + = note: you might not need `mut` at all + +error: `if _ { .. } else { .. }` is an expression + --> $DIR/let_if_seq.rs:69:5 + | +LL | / let mut bar = 0; +LL | | if f() { +LL | | f(); +LL | | bar = 42; +LL | | } else { +LL | | f(); +LL | | } + | |_____^ help: it is more idiomatic to write: `let bar = if f() { ..; 42 } else { ..; 0 };` + | + = note: you might not need `mut` at all + +error: `if _ { .. } else { .. }` is an expression + --> $DIR/let_if_seq.rs:77:5 + | +LL | / let quz; +LL | | if f() { +LL | | quz = 42; +LL | | } else { +LL | | quz = 0; +LL | | } + | |_____^ help: it is more idiomatic to write: `let quz = if f() { 42 } else { 0 };` + +error: `if _ { .. } else { .. }` is an expression + --> $DIR/let_if_seq.rs:106:5 + | +LL | / let mut baz = 0; +LL | | if f() { +LL | | baz = 42; +LL | | } + | |_____^ help: it is more idiomatic to write: `let baz = if f() { 42 } else { 0 };` + | + = note: you might not need `mut` at all + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/let_underscore_drop.rs b/src/tools/clippy/tests/ui/let_underscore_drop.rs new file mode 100644 index 0000000000..50744f81c3 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_drop.rs @@ -0,0 +1,27 @@ +#![warn(clippy::let_underscore_drop)] + +struct Droppable; + +impl Drop for Droppable { + fn drop(&mut self) {} +} + +fn main() { + let unit = (); + let boxed = Box::new(()); + let droppable = Droppable; + let optional = Some(Droppable); + + let _ = (); + let _ = Box::new(()); + let _ = Droppable; + let _ = Some(Droppable); + + // no lint for reference + let _ = droppable_ref(); +} + +#[must_use] +fn droppable_ref() -> &'static mut Droppable { + unimplemented!() +} diff --git a/src/tools/clippy/tests/ui/let_underscore_drop.stderr b/src/tools/clippy/tests/ui/let_underscore_drop.stderr new file mode 100644 index 0000000000..66069e0c5e --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_drop.stderr @@ -0,0 +1,27 @@ +error: non-binding `let` on a type that implements `Drop` + --> $DIR/let_underscore_drop.rs:16:5 + | +LL | let _ = Box::new(()); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::let-underscore-drop` implied by `-D warnings` + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding `let` on a type that implements `Drop` + --> $DIR/let_underscore_drop.rs:17:5 + | +LL | let _ = Droppable; + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding `let` on a type that implements `Drop` + --> $DIR/let_underscore_drop.rs:18:5 + | +LL | let _ = Some(Droppable); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/let_underscore_lock.rs b/src/tools/clippy/tests/ui/let_underscore_lock.rs new file mode 100644 index 0000000000..88fb216a74 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_lock.rs @@ -0,0 +1,13 @@ +#![warn(clippy::let_underscore_lock)] + +fn main() { + let m = std::sync::Mutex::new(()); + let rw = std::sync::RwLock::new(()); + + let _ = m.lock(); + let _ = rw.read(); + let _ = rw.write(); + let _ = m.try_lock(); + let _ = rw.try_read(); + let _ = rw.try_write(); +} diff --git a/src/tools/clippy/tests/ui/let_underscore_lock.stderr b/src/tools/clippy/tests/ui/let_underscore_lock.stderr new file mode 100644 index 0000000000..5d5f6059ef --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_lock.stderr @@ -0,0 +1,51 @@ +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:7:5 + | +LL | let _ = m.lock(); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::let-underscore-lock` implied by `-D warnings` + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:8:5 + | +LL | let _ = rw.read(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:9:5 + | +LL | let _ = rw.write(); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:10:5 + | +LL | let _ = m.try_lock(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:11:5 + | +LL | let _ = rw.try_read(); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:12:5 + | +LL | let _ = rw.try_write(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/let_underscore_must_use.rs b/src/tools/clippy/tests/ui/let_underscore_must_use.rs new file mode 100644 index 0000000000..a842e872a3 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_must_use.rs @@ -0,0 +1,95 @@ +#![warn(clippy::let_underscore_must_use)] +#![allow(clippy::unnecessary_wraps)] + +// Debug implementations can fire this lint, +// so we shouldn't lint external macros +#[derive(Debug)] +struct Foo { + field: i32, +} + +#[must_use] +fn f() -> u32 { + 0 +} + +fn g() -> Result { + Ok(0) +} + +#[must_use] +fn l(x: T) -> T { + x +} + +fn h() -> u32 { + 0 +} + +struct S {} + +impl S { + #[must_use] + pub fn f(&self) -> u32 { + 0 + } + + pub fn g(&self) -> Result { + Ok(0) + } + + fn k(&self) -> u32 { + 0 + } + + #[must_use] + fn h() -> u32 { + 0 + } + + fn p() -> Result { + Ok(0) + } +} + +trait Trait { + #[must_use] + fn a() -> u32; +} + +impl Trait for S { + fn a() -> u32 { + 0 + } +} + +fn main() { + let _ = f(); + let _ = g(); + let _ = h(); + let _ = l(0_u32); + + let s = S {}; + + let _ = s.f(); + let _ = s.g(); + let _ = s.k(); + + let _ = S::h(); + let _ = S::p(); + + let _ = S::a(); + + let _ = if true { Ok(()) } else { Err(()) }; + + let a = Result::<(), ()>::Ok(()); + + let _ = a.is_ok(); + + let _ = a.map(|_| ()); + + let _ = a; + + #[allow(clippy::let_underscore_must_use)] + let _ = a; +} diff --git a/src/tools/clippy/tests/ui/let_underscore_must_use.stderr b/src/tools/clippy/tests/ui/let_underscore_must_use.stderr new file mode 100644 index 0000000000..5b751ea56d --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_must_use.stderr @@ -0,0 +1,99 @@ +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:67:5 + | +LL | let _ = f(); + | ^^^^^^^^^^^^ + | + = note: `-D clippy::let-underscore-must-use` implied by `-D warnings` + = help: consider explicitly using function result + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:68:5 + | +LL | let _ = g(); + | ^^^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:70:5 + | +LL | let _ = l(0_u32); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider explicitly using function result + +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:74:5 + | +LL | let _ = s.f(); + | ^^^^^^^^^^^^^^ + | + = help: consider explicitly using function result + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:75:5 + | +LL | let _ = s.g(); + | ^^^^^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:78:5 + | +LL | let _ = S::h(); + | ^^^^^^^^^^^^^^^ + | + = help: consider explicitly using function result + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:79:5 + | +LL | let _ = S::p(); + | ^^^^^^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:81:5 + | +LL | let _ = S::a(); + | ^^^^^^^^^^^^^^^ + | + = help: consider explicitly using function result + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:83:5 + | +LL | let _ = if true { Ok(()) } else { Err(()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:87:5 + | +LL | let _ = a.is_ok(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider explicitly using function result + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:89:5 + | +LL | let _ = a.map(|_| ()); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:91:5 + | +LL | let _ = a; + | ^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/let_unit.fixed b/src/tools/clippy/tests/ui/let_unit.fixed new file mode 100644 index 0000000000..f398edc23c --- /dev/null +++ b/src/tools/clippy/tests/ui/let_unit.fixed @@ -0,0 +1,63 @@ +// run-rustfix + +#![warn(clippy::let_unit_value)] +#![allow(clippy::no_effect)] +#![allow(unused_variables)] + +macro_rules! let_and_return { + ($n:expr) => {{ + let ret = $n; + }}; +} + +fn main() { + println!("x"); + let _y = 1; // this is fine + let _z = ((), 1); // this as well + if true { + (); + } + + consume_units_with_for_loop(); // should be fine as well + + multiline_sugg(); + + let_and_return!(()) // should be fine +} + +// Related to issue #1964 +fn consume_units_with_for_loop() { + // `for_let_unit` lint should not be triggered by consuming them using for loop. + let v = vec![(), (), ()]; + let mut count = 0; + for _ in v { + count += 1; + } + assert_eq!(count, 3); + + // Same for consuming from some other Iterator. + let (tx, rx) = ::std::sync::mpsc::channel(); + tx.send(()).unwrap(); + drop(tx); + + count = 0; + for _ in rx.iter() { + count += 1; + } + assert_eq!(count, 1); +} + +fn multiline_sugg() { + let v: Vec = vec![2]; + + v + .into_iter() + .map(|i| i * 2) + .filter(|i| i % 2 == 0) + .map(|_| ()) + .next() + .unwrap(); +} + +#[derive(Copy, Clone)] +pub struct ContainsUnit(()); // should be fine diff --git a/src/tools/clippy/tests/ui/let_unit.rs b/src/tools/clippy/tests/ui/let_unit.rs new file mode 100644 index 0000000000..af5b1fb2ac --- /dev/null +++ b/src/tools/clippy/tests/ui/let_unit.rs @@ -0,0 +1,63 @@ +// run-rustfix + +#![warn(clippy::let_unit_value)] +#![allow(clippy::no_effect)] +#![allow(unused_variables)] + +macro_rules! let_and_return { + ($n:expr) => {{ + let ret = $n; + }}; +} + +fn main() { + let _x = println!("x"); + let _y = 1; // this is fine + let _z = ((), 1); // this as well + if true { + let _a = (); + } + + consume_units_with_for_loop(); // should be fine as well + + multiline_sugg(); + + let_and_return!(()) // should be fine +} + +// Related to issue #1964 +fn consume_units_with_for_loop() { + // `for_let_unit` lint should not be triggered by consuming them using for loop. + let v = vec![(), (), ()]; + let mut count = 0; + for _ in v { + count += 1; + } + assert_eq!(count, 3); + + // Same for consuming from some other Iterator. + let (tx, rx) = ::std::sync::mpsc::channel(); + tx.send(()).unwrap(); + drop(tx); + + count = 0; + for _ in rx.iter() { + count += 1; + } + assert_eq!(count, 1); +} + +fn multiline_sugg() { + let v: Vec = vec![2]; + + let _ = v + .into_iter() + .map(|i| i * 2) + .filter(|i| i % 2 == 0) + .map(|_| ()) + .next() + .unwrap(); +} + +#[derive(Copy, Clone)] +pub struct ContainsUnit(()); // should be fine diff --git a/src/tools/clippy/tests/ui/let_unit.stderr b/src/tools/clippy/tests/ui/let_unit.stderr new file mode 100644 index 0000000000..eb8482087b --- /dev/null +++ b/src/tools/clippy/tests/ui/let_unit.stderr @@ -0,0 +1,38 @@ +error: this let-binding has unit value + --> $DIR/let_unit.rs:14:5 + | +LL | let _x = println!("x"); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `println!("x");` + | + = note: `-D clippy::let-unit-value` implied by `-D warnings` + +error: this let-binding has unit value + --> $DIR/let_unit.rs:18:9 + | +LL | let _a = (); + | ^^^^^^^^^^^^ help: omit the `let` binding: `();` + +error: this let-binding has unit value + --> $DIR/let_unit.rs:53:5 + | +LL | / let _ = v +LL | | .into_iter() +LL | | .map(|i| i * 2) +LL | | .filter(|i| i % 2 == 0) +LL | | .map(|_| ()) +LL | | .next() +LL | | .unwrap(); + | |__________________^ + | +help: omit the `let` binding + | +LL | v +LL | .into_iter() +LL | .map(|i| i * 2) +LL | .filter(|i| i % 2 == 0) +LL | .map(|_| ()) +LL | .next() + ... + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/literals.rs b/src/tools/clippy/tests/ui/literals.rs new file mode 100644 index 0000000000..a72a74b913 --- /dev/null +++ b/src/tools/clippy/tests/ui/literals.rs @@ -0,0 +1,41 @@ +// does not test any rustfixable lints + +#![warn(clippy::mixed_case_hex_literals)] +#![warn(clippy::zero_prefixed_literal)] +#![allow(clippy::unseparated_literal_suffix)] +#![allow(dead_code)] + +fn main() { + let ok1 = 0xABCD; + let ok3 = 0xab_cd; + let ok4 = 0xab_cd_i32; + let ok5 = 0xAB_CD_u32; + let ok5 = 0xAB_CD_isize; + let fail1 = 0xabCD; + let fail2 = 0xabCD_u32; + let fail2 = 0xabCD_isize; + let fail_multi_zero = 000_123usize; + + let ok9 = 0; + let ok10 = 0_i64; + let fail8 = 0123; + + let ok11 = 0o123; + let ok12 = 0b10_1010; + + let ok13 = 0xab_abcd; + let ok14 = 0xBAFE_BAFE; + let ok15 = 0xab_cabc_abca_bcab_cabc; + let ok16 = 0xFE_BAFE_ABAB_ABCD; + let ok17 = 0x123_4567_8901_usize; + let ok18 = 0xF; + + let fail19 = 12_3456_21; + let fail22 = 3__4___23; + let fail23 = 3__16___23; + + let fail24 = 0xAB_ABC_AB; + let fail25 = 0b01_100_101; + let ok26 = 0x6_A0_BF; + let ok27 = 0b1_0010_0101; +} diff --git a/src/tools/clippy/tests/ui/literals.stderr b/src/tools/clippy/tests/ui/literals.stderr new file mode 100644 index 0000000000..64ceeb316d --- /dev/null +++ b/src/tools/clippy/tests/ui/literals.stderr @@ -0,0 +1,87 @@ +error: inconsistent casing in hexadecimal literal + --> $DIR/literals.rs:14:17 + | +LL | let fail1 = 0xabCD; + | ^^^^^^ + | + = note: `-D clippy::mixed-case-hex-literals` implied by `-D warnings` + +error: inconsistent casing in hexadecimal literal + --> $DIR/literals.rs:15:17 + | +LL | let fail2 = 0xabCD_u32; + | ^^^^^^^^^^ + +error: inconsistent casing in hexadecimal literal + --> $DIR/literals.rs:16:17 + | +LL | let fail2 = 0xabCD_isize; + | ^^^^^^^^^^^^ + +error: this is a decimal constant + --> $DIR/literals.rs:17:27 + | +LL | let fail_multi_zero = 000_123usize; + | ^^^^^^^^^^^^ + | + = note: `-D clippy::zero-prefixed-literal` implied by `-D warnings` +help: if you mean to use a decimal constant, remove the `0` to avoid confusion + | +LL | let fail_multi_zero = 123usize; + | ^^^^^^^^ +help: if you mean to use an octal constant, use `0o` + | +LL | let fail_multi_zero = 0o123usize; + | ^^^^^^^^^^ + +error: this is a decimal constant + --> $DIR/literals.rs:21:17 + | +LL | let fail8 = 0123; + | ^^^^ + | +help: if you mean to use a decimal constant, remove the `0` to avoid confusion + | +LL | let fail8 = 123; + | ^^^ +help: if you mean to use an octal constant, use `0o` + | +LL | let fail8 = 0o123; + | ^^^^^ + +error: digits grouped inconsistently by underscores + --> $DIR/literals.rs:33:18 + | +LL | let fail19 = 12_3456_21; + | ^^^^^^^^^^ help: consider: `12_345_621` + | + = note: `-D clippy::inconsistent-digit-grouping` implied by `-D warnings` + +error: digits grouped inconsistently by underscores + --> $DIR/literals.rs:34:18 + | +LL | let fail22 = 3__4___23; + | ^^^^^^^^^ help: consider: `3_423` + +error: digits grouped inconsistently by underscores + --> $DIR/literals.rs:35:18 + | +LL | let fail23 = 3__16___23; + | ^^^^^^^^^^ help: consider: `31_623` + +error: digits of hex or binary literal not grouped by four + --> $DIR/literals.rs:37:18 + | +LL | let fail24 = 0xAB_ABC_AB; + | ^^^^^^^^^^^ help: consider: `0x0ABA_BCAB` + | + = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` + +error: digits of hex or binary literal not grouped by four + --> $DIR/literals.rs:38:18 + | +LL | let fail25 = 0b01_100_101; + | ^^^^^^^^^^^^ help: consider: `0b0110_0101` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/logic_bug.rs b/src/tools/clippy/tests/ui/logic_bug.rs new file mode 100644 index 0000000000..a01c6ef99d --- /dev/null +++ b/src/tools/clippy/tests/ui/logic_bug.rs @@ -0,0 +1,26 @@ +#![allow(unused, clippy::many_single_char_names, clippy::diverging_sub_expression)] +#![warn(clippy::logic_bug)] + +fn main() { + let a: bool = unimplemented!(); + let b: bool = unimplemented!(); + let c: bool = unimplemented!(); + let d: bool = unimplemented!(); + let e: bool = unimplemented!(); + let _ = a && b || a; + let _ = !(a && b); + let _ = false && a; + // don't lint on cfgs + let _ = cfg!(you_shall_not_not_pass) && a; + let _ = a || !b || !c || !d || !e; + let _ = !(a && b || c); +} + +fn equality_stuff() { + let a: i32 = unimplemented!(); + let b: i32 = unimplemented!(); + let _ = a == b && a != b; + let _ = a < b && a >= b; + let _ = a > b && a <= b; + let _ = a > b && a == b; +} diff --git a/src/tools/clippy/tests/ui/logic_bug.stderr b/src/tools/clippy/tests/ui/logic_bug.stderr new file mode 100644 index 0000000000..8f55e1c8ad --- /dev/null +++ b/src/tools/clippy/tests/ui/logic_bug.stderr @@ -0,0 +1,63 @@ +error: this boolean expression contains a logic bug + --> $DIR/logic_bug.rs:10:13 + | +LL | let _ = a && b || a; + | ^^^^^^^^^^^ help: it would look like the following: `a` + | + = note: `-D clippy::logic-bug` implied by `-D warnings` +help: this expression can be optimized out by applying boolean operations to the outer expression + --> $DIR/logic_bug.rs:10:18 + | +LL | let _ = a && b || a; + | ^ + +error: this boolean expression contains a logic bug + --> $DIR/logic_bug.rs:12:13 + | +LL | let _ = false && a; + | ^^^^^^^^^^ help: it would look like the following: `false` + | +help: this expression can be optimized out by applying boolean operations to the outer expression + --> $DIR/logic_bug.rs:12:22 + | +LL | let _ = false && a; + | ^ + +error: this boolean expression contains a logic bug + --> $DIR/logic_bug.rs:22:13 + | +LL | let _ = a == b && a != b; + | ^^^^^^^^^^^^^^^^ help: it would look like the following: `false` + | +help: this expression can be optimized out by applying boolean operations to the outer expression + --> $DIR/logic_bug.rs:22:13 + | +LL | let _ = a == b && a != b; + | ^^^^^^ + +error: this boolean expression contains a logic bug + --> $DIR/logic_bug.rs:23:13 + | +LL | let _ = a < b && a >= b; + | ^^^^^^^^^^^^^^^ help: it would look like the following: `false` + | +help: this expression can be optimized out by applying boolean operations to the outer expression + --> $DIR/logic_bug.rs:23:13 + | +LL | let _ = a < b && a >= b; + | ^^^^^ + +error: this boolean expression contains a logic bug + --> $DIR/logic_bug.rs:24:13 + | +LL | let _ = a > b && a <= b; + | ^^^^^^^^^^^^^^^ help: it would look like the following: `false` + | +help: this expression can be optimized out by applying boolean operations to the outer expression + --> $DIR/logic_bug.rs:24:13 + | +LL | let _ = a > b && a <= b; + | ^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/lossy_float_literal.fixed b/src/tools/clippy/tests/ui/lossy_float_literal.fixed new file mode 100644 index 0000000000..24e372354f --- /dev/null +++ b/src/tools/clippy/tests/ui/lossy_float_literal.fixed @@ -0,0 +1,35 @@ +// run-rustfix +#![warn(clippy::lossy_float_literal)] + +fn main() { + // Lossy whole-number float literals + let _: f32 = 16_777_216.0; + let _: f32 = 16_777_220.0; + let _: f32 = 16_777_220.0; + let _: f32 = 16_777_220.0; + let _ = 16_777_220_f32; + let _: f32 = -16_777_220.0; + let _: f64 = 9_007_199_254_740_992.0; + let _: f64 = 9_007_199_254_740_992.0; + let _: f64 = 9_007_199_254_740_992.0; + let _ = 9_007_199_254_740_992_f64; + let _: f64 = -9_007_199_254_740_992.0; + + // Lossless whole number float literals + let _: f32 = 16_777_216.0; + let _: f32 = 16_777_218.0; + let _: f32 = 16_777_220.0; + let _: f32 = -16_777_216.0; + let _: f32 = -16_777_220.0; + let _: f64 = 16_777_217.0; + let _: f64 = -16_777_217.0; + let _: f64 = 9_007_199_254_740_992.0; + let _: f64 = -9_007_199_254_740_992.0; + + // Ignored whole number float literals + let _: f32 = 1e25; + let _: f32 = 1E25; + let _: f64 = 1e99; + let _: f64 = 1E99; + let _: f32 = 0.1; +} diff --git a/src/tools/clippy/tests/ui/lossy_float_literal.rs b/src/tools/clippy/tests/ui/lossy_float_literal.rs new file mode 100644 index 0000000000..3dcf98fa0b --- /dev/null +++ b/src/tools/clippy/tests/ui/lossy_float_literal.rs @@ -0,0 +1,35 @@ +// run-rustfix +#![warn(clippy::lossy_float_literal)] + +fn main() { + // Lossy whole-number float literals + let _: f32 = 16_777_217.0; + let _: f32 = 16_777_219.0; + let _: f32 = 16_777_219.; + let _: f32 = 16_777_219.000; + let _ = 16_777_219f32; + let _: f32 = -16_777_219.0; + let _: f64 = 9_007_199_254_740_993.0; + let _: f64 = 9_007_199_254_740_993.; + let _: f64 = 9_007_199_254_740_993.00; + let _ = 9_007_199_254_740_993f64; + let _: f64 = -9_007_199_254_740_993.0; + + // Lossless whole number float literals + let _: f32 = 16_777_216.0; + let _: f32 = 16_777_218.0; + let _: f32 = 16_777_220.0; + let _: f32 = -16_777_216.0; + let _: f32 = -16_777_220.0; + let _: f64 = 16_777_217.0; + let _: f64 = -16_777_217.0; + let _: f64 = 9_007_199_254_740_992.0; + let _: f64 = -9_007_199_254_740_992.0; + + // Ignored whole number float literals + let _: f32 = 1e25; + let _: f32 = 1E25; + let _: f64 = 1e99; + let _: f64 = 1E99; + let _: f32 = 0.1; +} diff --git a/src/tools/clippy/tests/ui/lossy_float_literal.stderr b/src/tools/clippy/tests/ui/lossy_float_literal.stderr new file mode 100644 index 0000000000..d2193c0c81 --- /dev/null +++ b/src/tools/clippy/tests/ui/lossy_float_literal.stderr @@ -0,0 +1,70 @@ +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:6:18 + | +LL | let _: f32 = 16_777_217.0; + | ^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_216.0` + | + = note: `-D clippy::lossy-float-literal` implied by `-D warnings` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:7:18 + | +LL | let _: f32 = 16_777_219.0; + | ^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:8:18 + | +LL | let _: f32 = 16_777_219.; + | ^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:9:18 + | +LL | let _: f32 = 16_777_219.000; + | ^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:10:13 + | +LL | let _ = 16_777_219f32; + | ^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220_f32` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:11:19 + | +LL | let _: f32 = -16_777_219.0; + | ^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:12:18 + | +LL | let _: f64 = 9_007_199_254_740_993.0; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:13:18 + | +LL | let _: f64 = 9_007_199_254_740_993.; + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:14:18 + | +LL | let _: f64 = 9_007_199_254_740_993.00; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:15:13 + | +LL | let _ = 9_007_199_254_740_993f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992_f64` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:16:19 + | +LL | let _: f64 = -9_007_199_254_740_993.0; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/macro_use_imports.fixed b/src/tools/clippy/tests/ui/macro_use_imports.fixed new file mode 100644 index 0000000000..91e34c6216 --- /dev/null +++ b/src/tools/clippy/tests/ui/macro_use_imports.fixed @@ -0,0 +1,43 @@ +// compile-flags: --edition 2018 +// aux-build:macro_rules.rs +// aux-build:macro_use_helper.rs +// run-rustfix +// ignore-32bit + +#![allow(unused_imports, unreachable_code, unused_variables, dead_code)] +#![allow(clippy::single_component_path_imports)] +#![warn(clippy::macro_use_imports)] + +#[macro_use] +extern crate macro_use_helper as mac; + +#[macro_use] +extern crate clippy_mini_macro_test as mini_mac; + +mod a { + use mac::{pub_macro, inner_mod_macro, function_macro, ty_macro, pub_in_private_macro}; + use mac; + use mini_mac::ClippyMiniMacroTest; + use mini_mac; + use mac::{inner::foofoo, inner::try_err}; + use mac::inner; + use mac::inner::nested::string_add; + use mac::inner::nested; + + #[derive(ClippyMiniMacroTest)] + struct Test; + + fn test() { + pub_macro!(); + inner_mod_macro!(); + pub_in_private_macro!(_var); + function_macro!(); + let v: ty_macro!() = Vec::default(); + + inner::try_err!(); + inner::foofoo!(); + nested::string_add!(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/macro_use_imports.rs b/src/tools/clippy/tests/ui/macro_use_imports.rs new file mode 100644 index 0000000000..9c3c50c5d4 --- /dev/null +++ b/src/tools/clippy/tests/ui/macro_use_imports.rs @@ -0,0 +1,43 @@ +// compile-flags: --edition 2018 +// aux-build:macro_rules.rs +// aux-build:macro_use_helper.rs +// run-rustfix +// ignore-32bit + +#![allow(unused_imports, unreachable_code, unused_variables, dead_code)] +#![allow(clippy::single_component_path_imports)] +#![warn(clippy::macro_use_imports)] + +#[macro_use] +extern crate macro_use_helper as mac; + +#[macro_use] +extern crate clippy_mini_macro_test as mini_mac; + +mod a { + #[macro_use] + use mac; + #[macro_use] + use mini_mac; + #[macro_use] + use mac::inner; + #[macro_use] + use mac::inner::nested; + + #[derive(ClippyMiniMacroTest)] + struct Test; + + fn test() { + pub_macro!(); + inner_mod_macro!(); + pub_in_private_macro!(_var); + function_macro!(); + let v: ty_macro!() = Vec::default(); + + inner::try_err!(); + inner::foofoo!(); + nested::string_add!(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/macro_use_imports.stderr b/src/tools/clippy/tests/ui/macro_use_imports.stderr new file mode 100644 index 0000000000..f8c86c8d91 --- /dev/null +++ b/src/tools/clippy/tests/ui/macro_use_imports.stderr @@ -0,0 +1,28 @@ +error: `macro_use` attributes are no longer needed in the Rust 2018 edition + --> $DIR/macro_use_imports.rs:18:5 + | +LL | #[macro_use] + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{pub_macro, inner_mod_macro, function_macro, ty_macro, pub_in_private_macro};` + | + = note: `-D clippy::macro-use-imports` implied by `-D warnings` + +error: `macro_use` attributes are no longer needed in the Rust 2018 edition + --> $DIR/macro_use_imports.rs:20:5 + | +LL | #[macro_use] + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mini_mac::ClippyMiniMacroTest;` + +error: `macro_use` attributes are no longer needed in the Rust 2018 edition + --> $DIR/macro_use_imports.rs:22:5 + | +LL | #[macro_use] + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{inner::foofoo, inner::try_err};` + +error: `macro_use` attributes are no longer needed in the Rust 2018 edition + --> $DIR/macro_use_imports.rs:24:5 + | +LL | #[macro_use] + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::inner::nested::string_add;` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_async_fn.fixed b/src/tools/clippy/tests/ui/manual_async_fn.fixed new file mode 100644 index 0000000000..5184f6fdb8 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_async_fn.fixed @@ -0,0 +1,111 @@ +// run-rustfix +// edition:2018 +#![warn(clippy::manual_async_fn)] +#![allow(unused)] + +use std::future::Future; + +async fn fut() -> i32 { 42 } + +#[rustfmt::skip] +async fn fut2() -> i32 { 42 } + +#[rustfmt::skip] +async fn fut3() -> i32 { 42 } + +async fn empty_fut() {} + +#[rustfmt::skip] +async fn empty_fut2() {} + +#[rustfmt::skip] +async fn empty_fut3() {} + +async fn core_fut() -> i32 { 42 } + +// should be ignored +fn has_other_stmts() -> impl core::future::Future { + let _ = 42; + async move { 42 } +} + +// should be ignored +fn not_fut() -> i32 { + 42 +} + +// should be ignored +async fn already_async() -> impl Future { + async { 42 } +} + +struct S {} +impl S { + async fn inh_fut() -> i32 { + // NOTE: this code is here just to check that the indentation is correct in the suggested fix + let a = 42; + let b = 21; + if a < b { + let c = 21; + let d = 42; + if c < d { + let _ = 42; + } + } + 42 + } + + // should be ignored + fn not_fut(&self) -> i32 { + 42 + } + + // should be ignored + fn has_other_stmts() -> impl core::future::Future { + let _ = 42; + async move { 42 } + } + + // should be ignored + async fn already_async(&self) -> impl Future { + async { 42 } + } +} + +// Tests related to lifetime capture + +async fn elided(_: &i32) -> i32 { 42 } + +// should be ignored +fn elided_not_bound(_: &i32) -> impl Future { + async { 42 } +} + +async fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> i32 { 42 } + +// should be ignored +#[allow(clippy::needless_lifetimes)] +fn explicit_not_bound<'a, 'b>(_: &'a i32, _: &'b i32) -> impl Future { + async { 42 } +} + +// should be ignored +mod issue_5765 { + use std::future::Future; + + struct A; + impl A { + fn f(&self) -> impl Future { + async {} + } + } + + fn test() { + let _future = { + let a = A; + a.f() + }; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_async_fn.rs b/src/tools/clippy/tests/ui/manual_async_fn.rs new file mode 100644 index 0000000000..68c0e591f0 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_async_fn.rs @@ -0,0 +1,131 @@ +// run-rustfix +// edition:2018 +#![warn(clippy::manual_async_fn)] +#![allow(unused)] + +use std::future::Future; + +fn fut() -> impl Future { + async { 42 } +} + +#[rustfmt::skip] +fn fut2() ->impl Future { + async { 42 } +} + +#[rustfmt::skip] +fn fut3()-> impl Future { + async { 42 } +} + +fn empty_fut() -> impl Future { + async {} +} + +#[rustfmt::skip] +fn empty_fut2() ->impl Future { + async {} +} + +#[rustfmt::skip] +fn empty_fut3()-> impl Future { + async {} +} + +fn core_fut() -> impl core::future::Future { + async move { 42 } +} + +// should be ignored +fn has_other_stmts() -> impl core::future::Future { + let _ = 42; + async move { 42 } +} + +// should be ignored +fn not_fut() -> i32 { + 42 +} + +// should be ignored +async fn already_async() -> impl Future { + async { 42 } +} + +struct S {} +impl S { + fn inh_fut() -> impl Future { + async { + // NOTE: this code is here just to check that the indentation is correct in the suggested fix + let a = 42; + let b = 21; + if a < b { + let c = 21; + let d = 42; + if c < d { + let _ = 42; + } + } + 42 + } + } + + // should be ignored + fn not_fut(&self) -> i32 { + 42 + } + + // should be ignored + fn has_other_stmts() -> impl core::future::Future { + let _ = 42; + async move { 42 } + } + + // should be ignored + async fn already_async(&self) -> impl Future { + async { 42 } + } +} + +// Tests related to lifetime capture + +fn elided(_: &i32) -> impl Future + '_ { + async { 42 } +} + +// should be ignored +fn elided_not_bound(_: &i32) -> impl Future { + async { 42 } +} + +fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> impl Future + 'a + 'b { + async { 42 } +} + +// should be ignored +#[allow(clippy::needless_lifetimes)] +fn explicit_not_bound<'a, 'b>(_: &'a i32, _: &'b i32) -> impl Future { + async { 42 } +} + +// should be ignored +mod issue_5765 { + use std::future::Future; + + struct A; + impl A { + fn f(&self) -> impl Future { + async {} + } + } + + fn test() { + let _future = { + let a = A; + a.f() + }; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_async_fn.stderr b/src/tools/clippy/tests/ui/manual_async_fn.stderr new file mode 100644 index 0000000000..fdd43db325 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_async_fn.stderr @@ -0,0 +1,158 @@ +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:8:1 + | +LL | fn fut() -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::manual-async-fn` implied by `-D warnings` +help: make the function `async` and return the output of the future directly + | +LL | async fn fut() -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn fut() -> impl Future { 42 } + | ^^^^^^ + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:13:1 + | +LL | fn fut2() ->impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | async fn fut2() -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn fut2() ->impl Future { 42 } + | ^^^^^^ + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:18:1 + | +LL | fn fut3()-> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | async fn fut3() -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn fut3()-> impl Future { 42 } + | ^^^^^^ + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:22:1 + | +LL | fn empty_fut() -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and remove the return type + | +LL | async fn empty_fut() { + | ^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn empty_fut() -> impl Future {} + | ^^ + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:27:1 + | +LL | fn empty_fut2() ->impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and remove the return type + | +LL | async fn empty_fut2() { + | ^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn empty_fut2() ->impl Future {} + | ^^ + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:32:1 + | +LL | fn empty_fut3()-> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and remove the return type + | +LL | async fn empty_fut3() { + | ^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn empty_fut3()-> impl Future {} + | ^^ + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:36:1 + | +LL | fn core_fut() -> impl core::future::Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | async fn core_fut() -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn core_fut() -> impl core::future::Future { 42 } + | ^^^^^^ + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:58:5 + | +LL | fn inh_fut() -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | async fn inh_fut() -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn inh_fut() -> impl Future { +LL | // NOTE: this code is here just to check that the indentation is correct in the suggested fix +LL | let a = 42; +LL | let b = 21; +LL | if a < b { +LL | let c = 21; + ... + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:93:1 + | +LL | fn elided(_: &i32) -> impl Future + '_ { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | async fn elided(_: &i32) -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn elided(_: &i32) -> impl Future + '_ { 42 } + | ^^^^^^ + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:102:1 + | +LL | fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> impl Future + 'a + 'b { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | async fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> impl Future + 'a + 'b { 42 } + | ^^^^^^ + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_filter_map.fixed b/src/tools/clippy/tests/ui/manual_filter_map.fixed new file mode 100644 index 0000000000..fc8f58f8ea --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_filter_map.fixed @@ -0,0 +1,37 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::manual_filter_map)] +#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure + +fn main() { + // is_some(), unwrap() + let _ = (0..).filter_map(|a| to_opt(a)); + + // ref pattern, expect() + let _ = (0..).filter_map(|a| to_opt(a)); + + // is_ok(), unwrap_or() + let _ = (0..).filter_map(|a| to_res(a).ok()); +} + +fn no_lint() { + // no shared code + let _ = (0..).filter(|n| *n > 1).map(|n| n + 1); + + // very close but different since filter() provides a reference + let _ = (0..).filter(|n| to_opt(n).is_some()).map(|a| to_opt(a).unwrap()); + + // similar but different + let _ = (0..).filter(|n| to_opt(n).is_some()).map(|n| to_res(n).unwrap()); + let _ = (0..) + .filter(|n| to_opt(n).map(|n| n + 1).is_some()) + .map(|a| to_opt(a).unwrap()); +} + +fn to_opt(_: T) -> Option { + unimplemented!() +} + +fn to_res(_: T) -> Result { + unimplemented!() +} diff --git a/src/tools/clippy/tests/ui/manual_filter_map.rs b/src/tools/clippy/tests/ui/manual_filter_map.rs new file mode 100644 index 0000000000..3af4bbee3b --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_filter_map.rs @@ -0,0 +1,37 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::manual_filter_map)] +#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure + +fn main() { + // is_some(), unwrap() + let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); + + // ref pattern, expect() + let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); + + // is_ok(), unwrap_or() + let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); +} + +fn no_lint() { + // no shared code + let _ = (0..).filter(|n| *n > 1).map(|n| n + 1); + + // very close but different since filter() provides a reference + let _ = (0..).filter(|n| to_opt(n).is_some()).map(|a| to_opt(a).unwrap()); + + // similar but different + let _ = (0..).filter(|n| to_opt(n).is_some()).map(|n| to_res(n).unwrap()); + let _ = (0..) + .filter(|n| to_opt(n).map(|n| n + 1).is_some()) + .map(|a| to_opt(a).unwrap()); +} + +fn to_opt(_: T) -> Option { + unimplemented!() +} + +fn to_res(_: T) -> Result { + unimplemented!() +} diff --git a/src/tools/clippy/tests/ui/manual_filter_map.stderr b/src/tools/clippy/tests/ui/manual_filter_map.stderr new file mode 100644 index 0000000000..4d4e2d5c12 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_filter_map.stderr @@ -0,0 +1,22 @@ +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:8:19 + | +LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))` + | + = note: `-D clippy::manual-filter-map` implied by `-D warnings` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:11:19 + | +LL | let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:14:19 + | +LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_res(a).ok())` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_find_map.fixed b/src/tools/clippy/tests/ui/manual_find_map.fixed new file mode 100644 index 0000000000..95e97c4fd1 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_find_map.fixed @@ -0,0 +1,37 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::manual_find_map)] +#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure + +fn main() { + // is_some(), unwrap() + let _ = (0..).find_map(|a| to_opt(a)); + + // ref pattern, expect() + let _ = (0..).find_map(|a| to_opt(a)); + + // is_ok(), unwrap_or() + let _ = (0..).find_map(|a| to_res(a).ok()); +} + +fn no_lint() { + // no shared code + let _ = (0..).filter(|n| *n > 1).map(|n| n + 1); + + // very close but different since filter() provides a reference + let _ = (0..).find(|n| to_opt(n).is_some()).map(|a| to_opt(a).unwrap()); + + // similar but different + let _ = (0..).find(|n| to_opt(n).is_some()).map(|n| to_res(n).unwrap()); + let _ = (0..) + .find(|n| to_opt(n).map(|n| n + 1).is_some()) + .map(|a| to_opt(a).unwrap()); +} + +fn to_opt(_: T) -> Option { + unimplemented!() +} + +fn to_res(_: T) -> Result { + unimplemented!() +} diff --git a/src/tools/clippy/tests/ui/manual_find_map.rs b/src/tools/clippy/tests/ui/manual_find_map.rs new file mode 100644 index 0000000000..cd3c82e3b2 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_find_map.rs @@ -0,0 +1,37 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::manual_find_map)] +#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure + +fn main() { + // is_some(), unwrap() + let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); + + // ref pattern, expect() + let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); + + // is_ok(), unwrap_or() + let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); +} + +fn no_lint() { + // no shared code + let _ = (0..).filter(|n| *n > 1).map(|n| n + 1); + + // very close but different since filter() provides a reference + let _ = (0..).find(|n| to_opt(n).is_some()).map(|a| to_opt(a).unwrap()); + + // similar but different + let _ = (0..).find(|n| to_opt(n).is_some()).map(|n| to_res(n).unwrap()); + let _ = (0..) + .find(|n| to_opt(n).map(|n| n + 1).is_some()) + .map(|a| to_opt(a).unwrap()); +} + +fn to_opt(_: T) -> Option { + unimplemented!() +} + +fn to_res(_: T) -> Result { + unimplemented!() +} diff --git a/src/tools/clippy/tests/ui/manual_find_map.stderr b/src/tools/clippy/tests/ui/manual_find_map.stderr new file mode 100644 index 0000000000..9e7f798df4 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_find_map.stderr @@ -0,0 +1,22 @@ +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:8:19 + | +LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))` + | + = note: `-D clippy::manual-find-map` implied by `-D warnings` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:11:19 + | +LL | let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:14:19 + | +LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_res(a).ok())` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_flatten.rs b/src/tools/clippy/tests/ui/manual_flatten.rs new file mode 100644 index 0000000000..cff68eca93 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_flatten.rs @@ -0,0 +1,76 @@ +#![warn(clippy::manual_flatten)] + +fn main() { + // Test for loop over implicitly adjusted `Iterator` with `if let` expression + let x = vec![Some(1), Some(2), Some(3)]; + for n in x { + if let Some(y) = n { + println!("{}", y); + } + } + + // Test for loop over implicitly implicitly adjusted `Iterator` with `if let` statement + let y: Vec> = vec![]; + for n in y.clone() { + if let Ok(n) = n { + println!("{}", n); + }; + } + + // Test for loop over by reference + for n in &y { + if let Ok(n) = n { + println!("{}", n); + } + } + + // Test for loop over an implicit reference + // Note: if `clippy::manual_flatten` is made autofixable, this case will + // lead to a follow-up lint `clippy::into_iter_on_ref` + let z = &y; + for n in z { + if let Ok(n) = n { + println!("{}", n); + } + } + + // Test for loop over `Iterator` with `if let` expression + let z = vec![Some(1), Some(2), Some(3)]; + let z = z.iter(); + for n in z { + if let Some(m) = n { + println!("{}", m); + } + } + + // Using the `None` variant should not trigger the lint + // Note: for an autofixable suggestion, the binding in the for loop has to take the + // name of the binding in the `if let` + let z = vec![Some(1), Some(2), Some(3)]; + for n in z { + if n.is_none() { + println!("Nada."); + } + } + + // Using the `Err` variant should not trigger the lint + for n in y.clone() { + if let Err(e) = n { + println!("Oops: {}!", e); + } + } + + // Having an else clause should not trigger the lint + for n in y.clone() { + if let Ok(n) = n { + println!("{}", n); + } else { + println!("Oops!"); + } + } + + // Using manual flatten should not trigger the lint + for n in vec![Some(1), Some(2), Some(3)].iter().flatten() { + println!("{}", n); + } +} diff --git a/src/tools/clippy/tests/ui/manual_flatten.stderr b/src/tools/clippy/tests/ui/manual_flatten.stderr new file mode 100644 index 0000000000..855dd9130e --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_flatten.stderr @@ -0,0 +1,108 @@ +error: unnecessary `if let` since only the `Some` variant of the iterator element is used + --> $DIR/manual_flatten.rs:6:5 + | +LL | for n in x { + | ^ - help: try: `x.into_iter().flatten()` + | _____| + | | +LL | | if let Some(y) = n { +LL | | println!("{}", y); +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::manual-flatten` implied by `-D warnings` +help: ...and remove the `if let` statement in the for loop + --> $DIR/manual_flatten.rs:7:9 + | +LL | / if let Some(y) = n { +LL | | println!("{}", y); +LL | | } + | |_________^ + +error: unnecessary `if let` since only the `Ok` variant of the iterator element is used + --> $DIR/manual_flatten.rs:14:5 + | +LL | for n in y.clone() { + | ^ --------- help: try: `y.clone().into_iter().flatten()` + | _____| + | | +LL | | if let Ok(n) = n { +LL | | println!("{}", n); +LL | | }; +LL | | } + | |_____^ + | +help: ...and remove the `if let` statement in the for loop + --> $DIR/manual_flatten.rs:15:9 + | +LL | / if let Ok(n) = n { +LL | | println!("{}", n); +LL | | }; + | |_________^ + +error: unnecessary `if let` since only the `Ok` variant of the iterator element is used + --> $DIR/manual_flatten.rs:21:5 + | +LL | for n in &y { + | ^ -- help: try: `y.iter().flatten()` + | _____| + | | +LL | | if let Ok(n) = n { +LL | | println!("{}", n); +LL | | } +LL | | } + | |_____^ + | +help: ...and remove the `if let` statement in the for loop + --> $DIR/manual_flatten.rs:22:9 + | +LL | / if let Ok(n) = n { +LL | | println!("{}", n); +LL | | } + | |_________^ + +error: unnecessary `if let` since only the `Ok` variant of the iterator element is used + --> $DIR/manual_flatten.rs:31:5 + | +LL | for n in z { + | ^ - help: try: `z.into_iter().flatten()` + | _____| + | | +LL | | if let Ok(n) = n { +LL | | println!("{}", n); +LL | | } +LL | | } + | |_____^ + | +help: ...and remove the `if let` statement in the for loop + --> $DIR/manual_flatten.rs:32:9 + | +LL | / if let Ok(n) = n { +LL | | println!("{}", n); +LL | | } + | |_________^ + +error: unnecessary `if let` since only the `Some` variant of the iterator element is used + --> $DIR/manual_flatten.rs:40:5 + | +LL | for n in z { + | ^ - help: try: `z.flatten()` + | _____| + | | +LL | | if let Some(m) = n { +LL | | println!("{}", m); +LL | | } +LL | | } + | |_____^ + | +help: ...and remove the `if let` statement in the for loop + --> $DIR/manual_flatten.rs:41:9 + | +LL | / if let Some(m) = n { +LL | | println!("{}", m); +LL | | } + | |_________^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_map_option.fixed b/src/tools/clippy/tests/ui/manual_map_option.fixed new file mode 100644 index 0000000000..9222aaf6c7 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_map_option.fixed @@ -0,0 +1,131 @@ +// edition:2018 +// run-rustfix + +#![warn(clippy::manual_map)] +#![allow( + clippy::no_effect, + clippy::map_identity, + clippy::unit_arg, + clippy::match_ref_pats, + dead_code +)] + +fn main() { + Some(0).map(|_| 2); + + Some(0).map(|x| x + 1); + + Some("").map(|x| x.is_empty()); + + Some(0).map(|x| !x); + + #[rustfmt::skip] + Some(0).map(std::convert::identity); + + Some(&String::new()).map(|x| str::len(x)); + + match Some(0) { + Some(x) if false => Some(x + 1), + _ => None, + }; + + Some([0, 1]).as_ref().map(|x| x[0]); + + Some(0).map(|x| x * 2); + + Some(String::new()).as_ref().map(|x| x.is_empty()); + + Some(String::new()).as_ref().map(|x| x.len()); + + Some(0).map(|x| x + x); + + #[warn(clippy::option_map_unit_fn)] + match &mut Some(String::new()) { + Some(x) => Some(x.push_str("")), + None => None, + }; + + #[allow(clippy::option_map_unit_fn)] + { + Some(String::new()).as_mut().map(|x| x.push_str("")); + } + + Some(String::new()).as_ref().map(|x| x.len()); + + Some(String::new()).as_ref().map(|x| x.is_empty()); + + Some((0, 1, 2)).map(|(x, y, z)| x + y + z); + + Some([1, 2, 3]).map(|[first, ..]| first); + + Some((String::new(), "test")).as_ref().map(|(x, y)| (y, x)); + + match Some((String::new(), 0)) { + Some((ref x, y)) => Some((y, x)), + None => None, + }; + + match Some(Some(0)) { + Some(Some(_)) | Some(None) => Some(0), + None => None, + }; + + match Some(Some((0, 1))) { + Some(Some((x, 1))) => Some(x), + _ => None, + }; + + // #6795 + fn f1() -> Result<(), ()> { + let _ = match Some(Ok(())) { + Some(x) => Some(x?), + None => None, + }; + Ok(()) + } + + for &x in Some(Some(true)).iter() { + let _ = match x { + Some(x) => Some(if x { continue } else { x }), + None => None, + }; + } + + // #6797 + let x1 = (Some(String::new()), 0); + let x2 = x1.0; + match x2 { + Some(x) => Some((x, x1.1)), + None => None, + }; + + struct S1 { + x: Option, + y: u32, + } + impl S1 { + fn f(self) -> Option<(String, u32)> { + match self.x { + Some(x) => Some((x, self.y)), + None => None, + } + } + } + + // #6811 + Some(0).map(|x| vec![x]); + + option_env!("").map(String::from); + + // #6819 + async fn f2(x: u32) -> u32 { + x + } + + async fn f3() { + match Some(0) { + Some(x) => Some(f2(x).await), + None => None, + }; + } +} diff --git a/src/tools/clippy/tests/ui/manual_map_option.rs b/src/tools/clippy/tests/ui/manual_map_option.rs new file mode 100644 index 0000000000..1ccb450619 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_map_option.rs @@ -0,0 +1,189 @@ +// edition:2018 +// run-rustfix + +#![warn(clippy::manual_map)] +#![allow( + clippy::no_effect, + clippy::map_identity, + clippy::unit_arg, + clippy::match_ref_pats, + dead_code +)] + +fn main() { + match Some(0) { + Some(_) => Some(2), + None:: => None, + }; + + match Some(0) { + Some(x) => Some(x + 1), + _ => None, + }; + + match Some("") { + Some(x) => Some(x.is_empty()), + None => None, + }; + + if let Some(x) = Some(0) { + Some(!x) + } else { + None + }; + + #[rustfmt::skip] + match Some(0) { + Some(x) => { Some(std::convert::identity(x)) } + None => { None } + }; + + match Some(&String::new()) { + Some(x) => Some(str::len(x)), + None => None, + }; + + match Some(0) { + Some(x) if false => Some(x + 1), + _ => None, + }; + + match &Some([0, 1]) { + Some(x) => Some(x[0]), + &None => None, + }; + + match &Some(0) { + &Some(x) => Some(x * 2), + None => None, + }; + + match Some(String::new()) { + Some(ref x) => Some(x.is_empty()), + _ => None, + }; + + match &&Some(String::new()) { + Some(x) => Some(x.len()), + _ => None, + }; + + match &&Some(0) { + &&Some(x) => Some(x + x), + &&_ => None, + }; + + #[warn(clippy::option_map_unit_fn)] + match &mut Some(String::new()) { + Some(x) => Some(x.push_str("")), + None => None, + }; + + #[allow(clippy::option_map_unit_fn)] + { + match &mut Some(String::new()) { + Some(x) => Some(x.push_str("")), + None => None, + }; + } + + match &mut Some(String::new()) { + Some(ref x) => Some(x.len()), + None => None, + }; + + match &mut &Some(String::new()) { + Some(x) => Some(x.is_empty()), + &mut _ => None, + }; + + match Some((0, 1, 2)) { + Some((x, y, z)) => Some(x + y + z), + None => None, + }; + + match Some([1, 2, 3]) { + Some([first, ..]) => Some(first), + None => None, + }; + + match &Some((String::new(), "test")) { + Some((x, y)) => Some((y, x)), + None => None, + }; + + match Some((String::new(), 0)) { + Some((ref x, y)) => Some((y, x)), + None => None, + }; + + match Some(Some(0)) { + Some(Some(_)) | Some(None) => Some(0), + None => None, + }; + + match Some(Some((0, 1))) { + Some(Some((x, 1))) => Some(x), + _ => None, + }; + + // #6795 + fn f1() -> Result<(), ()> { + let _ = match Some(Ok(())) { + Some(x) => Some(x?), + None => None, + }; + Ok(()) + } + + for &x in Some(Some(true)).iter() { + let _ = match x { + Some(x) => Some(if x { continue } else { x }), + None => None, + }; + } + + // #6797 + let x1 = (Some(String::new()), 0); + let x2 = x1.0; + match x2 { + Some(x) => Some((x, x1.1)), + None => None, + }; + + struct S1 { + x: Option, + y: u32, + } + impl S1 { + fn f(self) -> Option<(String, u32)> { + match self.x { + Some(x) => Some((x, self.y)), + None => None, + } + } + } + + // #6811 + match Some(0) { + Some(x) => Some(vec![x]), + None => None, + }; + + match option_env!("") { + Some(x) => Some(String::from(x)), + None => None, + }; + + // #6819 + async fn f2(x: u32) -> u32 { + x + } + + async fn f3() { + match Some(0) { + Some(x) => Some(f2(x).await), + None => None, + }; + } +} diff --git a/src/tools/clippy/tests/ui/manual_map_option.stderr b/src/tools/clippy/tests/ui/manual_map_option.stderr new file mode 100644 index 0000000000..d9f86eecd9 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_map_option.stderr @@ -0,0 +1,176 @@ +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:14:5 + | +LL | / match Some(0) { +LL | | Some(_) => Some(2), +LL | | None:: => None, +LL | | }; + | |_____^ help: try this: `Some(0).map(|_| 2)` + | + = note: `-D clippy::manual-map` implied by `-D warnings` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:19:5 + | +LL | / match Some(0) { +LL | | Some(x) => Some(x + 1), +LL | | _ => None, +LL | | }; + | |_____^ help: try this: `Some(0).map(|x| x + 1)` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:24:5 + | +LL | / match Some("") { +LL | | Some(x) => Some(x.is_empty()), +LL | | None => None, +LL | | }; + | |_____^ help: try this: `Some("").map(|x| x.is_empty())` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:29:5 + | +LL | / if let Some(x) = Some(0) { +LL | | Some(!x) +LL | | } else { +LL | | None +LL | | }; + | |_____^ help: try this: `Some(0).map(|x| !x)` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:36:5 + | +LL | / match Some(0) { +LL | | Some(x) => { Some(std::convert::identity(x)) } +LL | | None => { None } +LL | | }; + | |_____^ help: try this: `Some(0).map(std::convert::identity)` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:41:5 + | +LL | / match Some(&String::new()) { +LL | | Some(x) => Some(str::len(x)), +LL | | None => None, +LL | | }; + | |_____^ help: try this: `Some(&String::new()).map(|x| str::len(x))` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:51:5 + | +LL | / match &Some([0, 1]) { +LL | | Some(x) => Some(x[0]), +LL | | &None => None, +LL | | }; + | |_____^ help: try this: `Some([0, 1]).as_ref().map(|x| x[0])` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:56:5 + | +LL | / match &Some(0) { +LL | | &Some(x) => Some(x * 2), +LL | | None => None, +LL | | }; + | |_____^ help: try this: `Some(0).map(|x| x * 2)` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:61:5 + | +LL | / match Some(String::new()) { +LL | | Some(ref x) => Some(x.is_empty()), +LL | | _ => None, +LL | | }; + | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.is_empty())` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:66:5 + | +LL | / match &&Some(String::new()) { +LL | | Some(x) => Some(x.len()), +LL | | _ => None, +LL | | }; + | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.len())` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:71:5 + | +LL | / match &&Some(0) { +LL | | &&Some(x) => Some(x + x), +LL | | &&_ => None, +LL | | }; + | |_____^ help: try this: `Some(0).map(|x| x + x)` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:84:9 + | +LL | / match &mut Some(String::new()) { +LL | | Some(x) => Some(x.push_str("")), +LL | | None => None, +LL | | }; + | |_________^ help: try this: `Some(String::new()).as_mut().map(|x| x.push_str(""))` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:90:5 + | +LL | / match &mut Some(String::new()) { +LL | | Some(ref x) => Some(x.len()), +LL | | None => None, +LL | | }; + | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.len())` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:95:5 + | +LL | / match &mut &Some(String::new()) { +LL | | Some(x) => Some(x.is_empty()), +LL | | &mut _ => None, +LL | | }; + | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.is_empty())` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:100:5 + | +LL | / match Some((0, 1, 2)) { +LL | | Some((x, y, z)) => Some(x + y + z), +LL | | None => None, +LL | | }; + | |_____^ help: try this: `Some((0, 1, 2)).map(|(x, y, z)| x + y + z)` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:105:5 + | +LL | / match Some([1, 2, 3]) { +LL | | Some([first, ..]) => Some(first), +LL | | None => None, +LL | | }; + | |_____^ help: try this: `Some([1, 2, 3]).map(|[first, ..]| first)` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:110:5 + | +LL | / match &Some((String::new(), "test")) { +LL | | Some((x, y)) => Some((y, x)), +LL | | None => None, +LL | | }; + | |_____^ help: try this: `Some((String::new(), "test")).as_ref().map(|(x, y)| (y, x))` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:168:5 + | +LL | / match Some(0) { +LL | | Some(x) => Some(vec![x]), +LL | | None => None, +LL | | }; + | |_____^ help: try this: `Some(0).map(|x| vec![x])` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option.rs:173:5 + | +LL | / match option_env!("") { +LL | | Some(x) => Some(String::from(x)), +LL | | None => None, +LL | | }; + | |_____^ help: try this: `option_env!("").map(String::from)` + +error: aborting due to 19 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_memcpy/with_loop_counters.rs b/src/tools/clippy/tests/ui/manual_memcpy/with_loop_counters.rs new file mode 100644 index 0000000000..ba388a05a2 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_memcpy/with_loop_counters.rs @@ -0,0 +1,88 @@ +#![warn(clippy::needless_range_loop, clippy::manual_memcpy)] + +pub fn manual_copy_with_counters(src: &[i32], dst: &mut [i32], dst2: &mut [i32]) { + let mut count = 0; + for i in 3..src.len() { + dst[i] = src[count]; + count += 1; + } + + let mut count = 0; + for i in 3..src.len() { + dst[count] = src[i]; + count += 1; + } + + let mut count = 3; + for i in 0..src.len() { + dst[count] = src[i]; + count += 1; + } + + let mut count = 3; + for i in 0..src.len() { + dst[i] = src[count]; + count += 1; + } + + let mut count = 0; + for i in 3..(3 + src.len()) { + dst[i] = src[count]; + count += 1; + } + + let mut count = 3; + for i in 5..src.len() { + dst[i] = src[count - 2]; + count += 1; + } + + let mut count = 2; + for i in 0..dst.len() { + dst[i] = src[count]; + count += 1; + } + + let mut count = 5; + for i in 3..10 { + dst[i] = src[count]; + count += 1; + } + + let mut count = 3; + let mut count2 = 30; + for i in 0..src.len() { + dst[count] = src[i]; + dst2[count2] = src[i]; + count += 1; + count2 += 1; + } + + // make sure parentheses are added properly to bitwise operators, which have lower precedence than + // arithmetric ones + let mut count = 0 << 1; + for i in 0..1 << 1 { + dst[count] = src[i + 2]; + count += 1; + } + + // make sure incrementing expressions without semicolons at the end of loops are handled correctly. + let mut count = 0; + for i in 3..src.len() { + dst[i] = src[count]; + count += 1 + } + + // make sure ones where the increment is not at the end of the loop. + // As a possible enhancement, one could adjust the offset in the suggestion according to + // the position. For example, if the increment is at the top of the loop; + // treating the loop counter as if it were initialized 1 greater than the original value. + let mut count = 0; + #[allow(clippy::needless_range_loop)] + for i in 0..src.len() { + count += 1; + dst[i] = src[count]; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_memcpy/with_loop_counters.stderr b/src/tools/clippy/tests/ui/manual_memcpy/with_loop_counters.stderr new file mode 100644 index 0000000000..2547b19f5d --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_memcpy/with_loop_counters.stderr @@ -0,0 +1,111 @@ +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:5:5 + | +LL | / for i in 3..src.len() { +LL | | dst[i] = src[count]; +LL | | count += 1; +LL | | } + | |_____^ help: try replacing the loop by: `dst[3..src.len()].clone_from_slice(&src[..(src.len() - 3)]);` + | + = note: `-D clippy::manual-memcpy` implied by `-D warnings` + +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:11:5 + | +LL | / for i in 3..src.len() { +LL | | dst[count] = src[i]; +LL | | count += 1; +LL | | } + | |_____^ help: try replacing the loop by: `dst[..(src.len() - 3)].clone_from_slice(&src[3..]);` + +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:17:5 + | +LL | / for i in 0..src.len() { +LL | | dst[count] = src[i]; +LL | | count += 1; +LL | | } + | |_____^ help: try replacing the loop by: `dst[3..(src.len() + 3)].clone_from_slice(&src[..]);` + +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:23:5 + | +LL | / for i in 0..src.len() { +LL | | dst[i] = src[count]; +LL | | count += 1; +LL | | } + | |_____^ help: try replacing the loop by: `dst[..src.len()].clone_from_slice(&src[3..(src.len() + 3)]);` + +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:29:5 + | +LL | / for i in 3..(3 + src.len()) { +LL | | dst[i] = src[count]; +LL | | count += 1; +LL | | } + | |_____^ help: try replacing the loop by: `dst[3..((3 + src.len()))].clone_from_slice(&src[..((3 + src.len()) - 3)]);` + +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:35:5 + | +LL | / for i in 5..src.len() { +LL | | dst[i] = src[count - 2]; +LL | | count += 1; +LL | | } + | |_____^ help: try replacing the loop by: `dst[5..src.len()].clone_from_slice(&src[(3 - 2)..((src.len() - 2) + 3 - 5)]);` + +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:41:5 + | +LL | / for i in 0..dst.len() { +LL | | dst[i] = src[count]; +LL | | count += 1; +LL | | } + | |_____^ help: try replacing the loop by: `dst.clone_from_slice(&src[2..(dst.len() + 2)]);` + +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:47:5 + | +LL | / for i in 3..10 { +LL | | dst[i] = src[count]; +LL | | count += 1; +LL | | } + | |_____^ help: try replacing the loop by: `dst[3..10].clone_from_slice(&src[5..(10 + 5 - 3)]);` + +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:54:5 + | +LL | / for i in 0..src.len() { +LL | | dst[count] = src[i]; +LL | | dst2[count2] = src[i]; +LL | | count += 1; +LL | | count2 += 1; +LL | | } + | |_____^ + | +help: try replacing the loop by + | +LL | dst[3..(src.len() + 3)].clone_from_slice(&src[..]); +LL | dst2[30..(src.len() + 30)].clone_from_slice(&src[..]); + | + +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:64:5 + | +LL | / for i in 0..1 << 1 { +LL | | dst[count] = src[i + 2]; +LL | | count += 1; +LL | | } + | |_____^ help: try replacing the loop by: `dst[(0 << 1)..((1 << 1) + (0 << 1))].clone_from_slice(&src[2..((1 << 1) + 2)]);` + +error: it looks like you're manually copying between slices + --> $DIR/with_loop_counters.rs:71:5 + | +LL | / for i in 3..src.len() { +LL | | dst[i] = src[count]; +LL | | count += 1 +LL | | } + | |_____^ help: try replacing the loop by: `dst[3..src.len()].clone_from_slice(&src[..(src.len() - 3)]);` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.rs b/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.rs new file mode 100644 index 0000000000..0083f94798 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.rs @@ -0,0 +1,125 @@ +#![warn(clippy::needless_range_loop, clippy::manual_memcpy)] + +const LOOP_OFFSET: usize = 5000; + +pub fn manual_copy(src: &[i32], dst: &mut [i32], dst2: &mut [i32]) { + // plain manual memcpy + for i in 0..src.len() { + dst[i] = src[i]; + } + + // dst offset memcpy + for i in 0..src.len() { + dst[i + 10] = src[i]; + } + + // src offset memcpy + for i in 0..src.len() { + dst[i] = src[i + 10]; + } + + // src offset memcpy + for i in 11..src.len() { + dst[i] = src[i - 10]; + } + + // overwrite entire dst + for i in 0..dst.len() { + dst[i] = src[i]; + } + + // manual copy with branch - can't easily convert to memcpy! + for i in 0..src.len() { + dst[i] = src[i]; + if dst[i] > 5 { + break; + } + } + + // multiple copies - suggest two memcpy statements + for i in 10..256 { + dst[i] = src[i - 5]; + dst2[i + 500] = src[i] + } + + // this is a reversal - the copy lint shouldn't be triggered + for i in 10..LOOP_OFFSET { + dst[i + LOOP_OFFSET] = src[LOOP_OFFSET - i]; + } + + let some_var = 5; + // Offset in variable + for i in 10..LOOP_OFFSET { + dst[i + LOOP_OFFSET] = src[i - some_var]; + } + + // Non continuous copy - don't trigger lint + for i in 0..10 { + dst[i + i] = src[i]; + } + + let src_vec = vec![1, 2, 3, 4, 5]; + let mut dst_vec = vec![0, 0, 0, 0, 0]; + + // make sure vectors are supported + for i in 0..src_vec.len() { + dst_vec[i] = src_vec[i]; + } + + // lint should not trigger when either + // source or destination type is not + // slice-like, like DummyStruct + struct DummyStruct(i32); + + impl ::std::ops::Index for DummyStruct { + type Output = i32; + + fn index(&self, _: usize) -> &i32 { + &self.0 + } + } + + let src = DummyStruct(5); + let mut dst_vec = vec![0; 10]; + + for i in 0..10 { + dst_vec[i] = src[i]; + } + + // Simplify suggestion (issue #3004) + let src = [0, 1, 2, 3, 4]; + let mut dst = [0, 0, 0, 0, 0, 0]; + let from = 1; + + for i in from..from + src.len() { + dst[i] = src[i - from]; + } + + for i in from..from + 3 { + dst[i] = src[i - from]; + } + + #[allow(clippy::identity_op)] + for i in 0..5 { + dst[i - 0] = src[i]; + } + + #[allow(clippy::reversed_empty_ranges)] + for i in 0..0 { + dst[i] = src[i]; + } + + // `RangeTo` `for` loop - don't trigger lint + for i in 0.. { + dst[i] = src[i]; + } +} + +#[warn(clippy::needless_range_loop, clippy::manual_memcpy)] +pub fn manual_clone(src: &[String], dst: &mut [String]) { + for i in 0..src.len() { + dst[i] = src[i].clone(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.stderr b/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.stderr new file mode 100644 index 0000000000..54b966f6e5 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.stderr @@ -0,0 +1,115 @@ +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:7:5 + | +LL | / for i in 0..src.len() { +LL | | dst[i] = src[i]; +LL | | } + | |_____^ help: try replacing the loop by: `dst[..src.len()].clone_from_slice(&src[..]);` + | + = note: `-D clippy::manual-memcpy` implied by `-D warnings` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:12:5 + | +LL | / for i in 0..src.len() { +LL | | dst[i + 10] = src[i]; +LL | | } + | |_____^ help: try replacing the loop by: `dst[10..(src.len() + 10)].clone_from_slice(&src[..]);` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:17:5 + | +LL | / for i in 0..src.len() { +LL | | dst[i] = src[i + 10]; +LL | | } + | |_____^ help: try replacing the loop by: `dst[..src.len()].clone_from_slice(&src[10..(src.len() + 10)]);` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:22:5 + | +LL | / for i in 11..src.len() { +LL | | dst[i] = src[i - 10]; +LL | | } + | |_____^ help: try replacing the loop by: `dst[11..src.len()].clone_from_slice(&src[(11 - 10)..(src.len() - 10)]);` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:27:5 + | +LL | / for i in 0..dst.len() { +LL | | dst[i] = src[i]; +LL | | } + | |_____^ help: try replacing the loop by: `dst.clone_from_slice(&src[..dst.len()]);` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:40:5 + | +LL | / for i in 10..256 { +LL | | dst[i] = src[i - 5]; +LL | | dst2[i + 500] = src[i] +LL | | } + | |_____^ + | +help: try replacing the loop by + | +LL | dst[10..256].clone_from_slice(&src[(10 - 5)..(256 - 5)]); +LL | dst2[(10 + 500)..(256 + 500)].clone_from_slice(&src[10..256]); + | + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:52:5 + | +LL | / for i in 10..LOOP_OFFSET { +LL | | dst[i + LOOP_OFFSET] = src[i - some_var]; +LL | | } + | |_____^ help: try replacing the loop by: `dst[(10 + LOOP_OFFSET)..(LOOP_OFFSET + LOOP_OFFSET)].clone_from_slice(&src[(10 - some_var)..(LOOP_OFFSET - some_var)]);` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:65:5 + | +LL | / for i in 0..src_vec.len() { +LL | | dst_vec[i] = src_vec[i]; +LL | | } + | |_____^ help: try replacing the loop by: `dst_vec[..src_vec.len()].clone_from_slice(&src_vec[..]);` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:94:5 + | +LL | / for i in from..from + src.len() { +LL | | dst[i] = src[i - from]; +LL | | } + | |_____^ help: try replacing the loop by: `dst[from..(from + src.len())].clone_from_slice(&src[..(from + src.len() - from)]);` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:98:5 + | +LL | / for i in from..from + 3 { +LL | | dst[i] = src[i - from]; +LL | | } + | |_____^ help: try replacing the loop by: `dst[from..(from + 3)].clone_from_slice(&src[..(from + 3 - from)]);` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:103:5 + | +LL | / for i in 0..5 { +LL | | dst[i - 0] = src[i]; +LL | | } + | |_____^ help: try replacing the loop by: `dst[..5].clone_from_slice(&src[..5]);` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:108:5 + | +LL | / for i in 0..0 { +LL | | dst[i] = src[i]; +LL | | } + | |_____^ help: try replacing the loop by: `dst[..0].clone_from_slice(&src[..0]);` + +error: it looks like you're manually copying between slices + --> $DIR/without_loop_counters.rs:120:5 + | +LL | / for i in 0..src.len() { +LL | | dst[i] = src[i].clone(); +LL | | } + | |_____^ help: try replacing the loop by: `dst[..src.len()].clone_from_slice(&src[..]);` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_non_exhaustive.rs b/src/tools/clippy/tests/ui/manual_non_exhaustive.rs new file mode 100644 index 0000000000..7a788f4852 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_non_exhaustive.rs @@ -0,0 +1,137 @@ +#![warn(clippy::manual_non_exhaustive)] +#![allow(unused)] + +mod enums { + enum E { + A, + B, + #[doc(hidden)] + _C, + } + + // user forgot to remove the marker + #[non_exhaustive] + enum Ep { + A, + B, + #[doc(hidden)] + _C, + } + + // marker variant does not have doc hidden attribute, should be ignored + enum NoDocHidden { + A, + B, + _C, + } + + // name of variant with doc hidden does not start with underscore, should be ignored + enum NoUnderscore { + A, + B, + #[doc(hidden)] + C, + } + + // variant with doc hidden is not unit, should be ignored + enum NotUnit { + A, + B, + #[doc(hidden)] + _C(bool), + } + + // variant with doc hidden is the only one, should be ignored + enum OnlyMarker { + #[doc(hidden)] + _A, + } + + // variant with multiple markers, should be ignored + enum MultipleMarkers { + A, + #[doc(hidden)] + _B, + #[doc(hidden)] + _C, + } + + // already non_exhaustive and no markers, should be ignored + #[non_exhaustive] + enum NonExhaustive { + A, + B, + } +} + +mod structs { + struct S { + pub a: i32, + pub b: i32, + _c: (), + } + + // user forgot to remove the private field + #[non_exhaustive] + struct Sp { + pub a: i32, + pub b: i32, + _c: (), + } + + // some other fields are private, should be ignored + struct PrivateFields { + a: i32, + pub b: i32, + _c: (), + } + + // private field name does not start with underscore, should be ignored + struct NoUnderscore { + pub a: i32, + pub b: i32, + c: (), + } + + // private field is not unit type, should be ignored + struct NotUnit { + pub a: i32, + pub b: i32, + _c: i32, + } + + // private field is the only field, should be ignored + struct OnlyMarker { + _a: (), + } + + // already non exhaustive and no private fields, should be ignored + #[non_exhaustive] + struct NonExhaustive { + pub a: i32, + pub b: i32, + } +} + +mod tuple_structs { + struct T(pub i32, pub i32, ()); + + // user forgot to remove the private field + #[non_exhaustive] + struct Tp(pub i32, pub i32, ()); + + // some other fields are private, should be ignored + struct PrivateFields(pub i32, i32, ()); + + // private field is not unit type, should be ignored + struct NotUnit(pub i32, pub i32, i32); + + // private field is the only field, should be ignored + struct OnlyMarker(()); + + // already non exhaustive and no private fields, should be ignored + #[non_exhaustive] + struct NonExhaustive(pub i32, pub i32); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_non_exhaustive.stderr b/src/tools/clippy/tests/ui/manual_non_exhaustive.stderr new file mode 100644 index 0000000000..613c5e8ca1 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_non_exhaustive.stderr @@ -0,0 +1,103 @@ +error: this seems like a manual implementation of the non-exhaustive pattern + --> $DIR/manual_non_exhaustive.rs:5:5 + | +LL | enum E { + | ^----- + | | + | _____help: add the attribute: `#[non_exhaustive] enum E` + | | +LL | | A, +LL | | B, +LL | | #[doc(hidden)] +LL | | _C, +LL | | } + | |_____^ + | + = note: `-D clippy::manual-non-exhaustive` implied by `-D warnings` +help: remove this variant + --> $DIR/manual_non_exhaustive.rs:9:9 + | +LL | _C, + | ^^ + +error: this seems like a manual implementation of the non-exhaustive pattern + --> $DIR/manual_non_exhaustive.rs:14:5 + | +LL | / enum Ep { +LL | | A, +LL | | B, +LL | | #[doc(hidden)] +LL | | _C, +LL | | } + | |_____^ + | +help: remove this variant + --> $DIR/manual_non_exhaustive.rs:18:9 + | +LL | _C, + | ^^ + +error: this seems like a manual implementation of the non-exhaustive pattern + --> $DIR/manual_non_exhaustive.rs:68:5 + | +LL | struct S { + | ^------- + | | + | _____help: add the attribute: `#[non_exhaustive] struct S` + | | +LL | | pub a: i32, +LL | | pub b: i32, +LL | | _c: (), +LL | | } + | |_____^ + | +help: remove this field + --> $DIR/manual_non_exhaustive.rs:71:9 + | +LL | _c: (), + | ^^^^^^ + +error: this seems like a manual implementation of the non-exhaustive pattern + --> $DIR/manual_non_exhaustive.rs:76:5 + | +LL | / struct Sp { +LL | | pub a: i32, +LL | | pub b: i32, +LL | | _c: (), +LL | | } + | |_____^ + | +help: remove this field + --> $DIR/manual_non_exhaustive.rs:79:9 + | +LL | _c: (), + | ^^^^^^ + +error: this seems like a manual implementation of the non-exhaustive pattern + --> $DIR/manual_non_exhaustive.rs:117:5 + | +LL | struct T(pub i32, pub i32, ()); + | --------^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: add the attribute: `#[non_exhaustive] struct T` + | +help: remove this field + --> $DIR/manual_non_exhaustive.rs:117:32 + | +LL | struct T(pub i32, pub i32, ()); + | ^^ + +error: this seems like a manual implementation of the non-exhaustive pattern + --> $DIR/manual_non_exhaustive.rs:121:5 + | +LL | struct Tp(pub i32, pub i32, ()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove this field + --> $DIR/manual_non_exhaustive.rs:121:33 + | +LL | struct Tp(pub i32, pub i32, ()); + | ^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_ok_or.fixed b/src/tools/clippy/tests/ui/manual_ok_or.fixed new file mode 100644 index 0000000000..887a97d7a0 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_ok_or.fixed @@ -0,0 +1,40 @@ +// run-rustfix +#![warn(clippy::manual_ok_or)] +#![allow(clippy::blacklisted_name)] +#![allow(clippy::redundant_closure)] +#![allow(dead_code)] +#![allow(unused_must_use)] + +fn main() { + // basic case + let foo: Option = None; + foo.ok_or("error"); + + // eta expansion case + foo.ok_or("error"); + + // turbo fish syntax + None::.ok_or("error"); + + // multiline case + #[rustfmt::skip] + foo.ok_or(&format!( + "{}{}{}{}{}{}{}", + "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer")); + + // not applicable, closure isn't direct `Ok` wrapping + foo.map_or(Err("error"), |v| Ok(v + 1)); + + // not applicable, or side isn't `Result::Err` + foo.map_or(Ok::(1), |v| Ok(v)); + + // not applicable, expr is not a `Result` value + foo.map_or(42, |v| v); + + // TODO patterns not covered yet + match foo { + Some(v) => Ok(v), + None => Err("error"), + }; + foo.map_or_else(|| Err("error"), |v| Ok(v)); +} diff --git a/src/tools/clippy/tests/ui/manual_ok_or.rs b/src/tools/clippy/tests/ui/manual_ok_or.rs new file mode 100644 index 0000000000..3c99872f50 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_ok_or.rs @@ -0,0 +1,44 @@ +// run-rustfix +#![warn(clippy::manual_ok_or)] +#![allow(clippy::blacklisted_name)] +#![allow(clippy::redundant_closure)] +#![allow(dead_code)] +#![allow(unused_must_use)] + +fn main() { + // basic case + let foo: Option = None; + foo.map_or(Err("error"), |v| Ok(v)); + + // eta expansion case + foo.map_or(Err("error"), Ok); + + // turbo fish syntax + None::.map_or(Err("error"), |v| Ok(v)); + + // multiline case + #[rustfmt::skip] + foo.map_or(Err::( + &format!( + "{}{}{}{}{}{}{}", + "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer") + ), + |v| Ok(v), + ); + + // not applicable, closure isn't direct `Ok` wrapping + foo.map_or(Err("error"), |v| Ok(v + 1)); + + // not applicable, or side isn't `Result::Err` + foo.map_or(Ok::(1), |v| Ok(v)); + + // not applicable, expr is not a `Result` value + foo.map_or(42, |v| v); + + // TODO patterns not covered yet + match foo { + Some(v) => Ok(v), + None => Err("error"), + }; + foo.map_or_else(|| Err("error"), |v| Ok(v)); +} diff --git a/src/tools/clippy/tests/ui/manual_ok_or.stderr b/src/tools/clippy/tests/ui/manual_ok_or.stderr new file mode 100644 index 0000000000..8ea10ac543 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_ok_or.stderr @@ -0,0 +1,41 @@ +error: this pattern reimplements `Option::ok_or` + --> $DIR/manual_ok_or.rs:11:5 + | +LL | foo.map_or(Err("error"), |v| Ok(v)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")` + | + = note: `-D clippy::manual-ok-or` implied by `-D warnings` + +error: this pattern reimplements `Option::ok_or` + --> $DIR/manual_ok_or.rs:14:5 + | +LL | foo.map_or(Err("error"), Ok); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")` + +error: this pattern reimplements `Option::ok_or` + --> $DIR/manual_ok_or.rs:17:5 + | +LL | None::.map_or(Err("error"), |v| Ok(v)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `None::.ok_or("error")` + +error: this pattern reimplements `Option::ok_or` + --> $DIR/manual_ok_or.rs:21:5 + | +LL | / foo.map_or(Err::( +LL | | &format!( +LL | | "{}{}{}{}{}{}{}", +LL | | "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer") +LL | | ), +LL | | |v| Ok(v), +LL | | ); + | |_____^ + | +help: replace with + | +LL | foo.ok_or(&format!( +LL | "{}{}{}{}{}{}{}", +LL | "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer")); + | + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_saturating_arithmetic.fixed b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.fixed new file mode 100644 index 0000000000..c4f53c446c --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.fixed @@ -0,0 +1,45 @@ +// run-rustfix + +#![allow(unused_imports)] + +use std::{i128, i32, u128, u32}; + +fn main() { + let _ = 1u32.saturating_add(1); + let _ = 1u32.saturating_add(1); + let _ = 1u8.saturating_add(1); + let _ = 1u128.saturating_add(1); + let _ = 1u32.checked_add(1).unwrap_or(1234); // ok + let _ = 1u8.checked_add(1).unwrap_or(0); // ok + let _ = 1u32.saturating_mul(1); + + let _ = 1u32.saturating_sub(1); + let _ = 1u32.saturating_sub(1); + let _ = 1u8.saturating_sub(1); + let _ = 1u32.checked_sub(1).unwrap_or(1234); // ok + let _ = 1u8.checked_sub(1).unwrap_or(255); // ok + + let _ = 1i32.saturating_add(1); + let _ = 1i32.saturating_add(1); + let _ = 1i8.saturating_add(1); + let _ = 1i128.saturating_add(1); + let _ = 1i32.saturating_add(-1); + let _ = 1i32.saturating_add(-1); + let _ = 1i8.saturating_add(-1); + let _ = 1i128.saturating_add(-1); + let _ = 1i32.checked_add(1).unwrap_or(1234); // ok + let _ = 1i8.checked_add(1).unwrap_or(-128); // ok + let _ = 1i8.checked_add(-1).unwrap_or(127); // ok + + let _ = 1i32.saturating_sub(1); + let _ = 1i32.saturating_sub(1); + let _ = 1i8.saturating_sub(1); + let _ = 1i128.saturating_sub(1); + let _ = 1i32.saturating_sub(-1); + let _ = 1i32.saturating_sub(-1); + let _ = 1i8.saturating_sub(-1); + let _ = 1i128.saturating_sub(-1); + let _ = 1i32.checked_sub(1).unwrap_or(1234); // ok + let _ = 1i8.checked_sub(1).unwrap_or(127); // ok + let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok +} diff --git a/src/tools/clippy/tests/ui/manual_saturating_arithmetic.rs b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.rs new file mode 100644 index 0000000000..cd83cf6e65 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.rs @@ -0,0 +1,55 @@ +// run-rustfix + +#![allow(unused_imports)] + +use std::{i128, i32, u128, u32}; + +fn main() { + let _ = 1u32.checked_add(1).unwrap_or(u32::max_value()); + let _ = 1u32.checked_add(1).unwrap_or(u32::MAX); + let _ = 1u8.checked_add(1).unwrap_or(255); + let _ = 1u128 + .checked_add(1) + .unwrap_or(340_282_366_920_938_463_463_374_607_431_768_211_455); + let _ = 1u32.checked_add(1).unwrap_or(1234); // ok + let _ = 1u8.checked_add(1).unwrap_or(0); // ok + let _ = 1u32.checked_mul(1).unwrap_or(u32::MAX); + + let _ = 1u32.checked_sub(1).unwrap_or(u32::min_value()); + let _ = 1u32.checked_sub(1).unwrap_or(u32::MIN); + let _ = 1u8.checked_sub(1).unwrap_or(0); + let _ = 1u32.checked_sub(1).unwrap_or(1234); // ok + let _ = 1u8.checked_sub(1).unwrap_or(255); // ok + + let _ = 1i32.checked_add(1).unwrap_or(i32::max_value()); + let _ = 1i32.checked_add(1).unwrap_or(i32::MAX); + let _ = 1i8.checked_add(1).unwrap_or(127); + let _ = 1i128 + .checked_add(1) + .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727); + let _ = 1i32.checked_add(-1).unwrap_or(i32::min_value()); + let _ = 1i32.checked_add(-1).unwrap_or(i32::MIN); + let _ = 1i8.checked_add(-1).unwrap_or(-128); + let _ = 1i128 + .checked_add(-1) + .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728); + let _ = 1i32.checked_add(1).unwrap_or(1234); // ok + let _ = 1i8.checked_add(1).unwrap_or(-128); // ok + let _ = 1i8.checked_add(-1).unwrap_or(127); // ok + + let _ = 1i32.checked_sub(1).unwrap_or(i32::min_value()); + let _ = 1i32.checked_sub(1).unwrap_or(i32::MIN); + let _ = 1i8.checked_sub(1).unwrap_or(-128); + let _ = 1i128 + .checked_sub(1) + .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728); + let _ = 1i32.checked_sub(-1).unwrap_or(i32::max_value()); + let _ = 1i32.checked_sub(-1).unwrap_or(i32::MAX); + let _ = 1i8.checked_sub(-1).unwrap_or(127); + let _ = 1i128 + .checked_sub(-1) + .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727); + let _ = 1i32.checked_sub(1).unwrap_or(1234); // ok + let _ = 1i8.checked_sub(1).unwrap_or(127); // ok + let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok +} diff --git a/src/tools/clippy/tests/ui/manual_saturating_arithmetic.stderr b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.stderr new file mode 100644 index 0000000000..d985f2e754 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.stderr @@ -0,0 +1,163 @@ +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:8:13 + | +LL | let _ = 1u32.checked_add(1).unwrap_or(u32::max_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1u32.saturating_add(1)` + | + = note: `-D clippy::manual-saturating-arithmetic` implied by `-D warnings` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:9:13 + | +LL | let _ = 1u32.checked_add(1).unwrap_or(u32::MAX); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1u32.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:10:13 + | +LL | let _ = 1u8.checked_add(1).unwrap_or(255); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1u8.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:11:13 + | +LL | let _ = 1u128 + | _____________^ +LL | | .checked_add(1) +LL | | .unwrap_or(340_282_366_920_938_463_463_374_607_431_768_211_455); + | |_______________________________________________________________________^ help: try using `saturating_add`: `1u128.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:16:13 + | +LL | let _ = 1u32.checked_mul(1).unwrap_or(u32::MAX); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_mul`: `1u32.saturating_mul(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:18:13 + | +LL | let _ = 1u32.checked_sub(1).unwrap_or(u32::min_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1u32.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:19:13 + | +LL | let _ = 1u32.checked_sub(1).unwrap_or(u32::MIN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1u32.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:20:13 + | +LL | let _ = 1u8.checked_sub(1).unwrap_or(0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1u8.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:24:13 + | +LL | let _ = 1i32.checked_add(1).unwrap_or(i32::max_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:25:13 + | +LL | let _ = 1i32.checked_add(1).unwrap_or(i32::MAX); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:26:13 + | +LL | let _ = 1i8.checked_add(1).unwrap_or(127); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i8.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:27:13 + | +LL | let _ = 1i128 + | _____________^ +LL | | .checked_add(1) +LL | | .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727); + | |_______________________________________________________________________^ help: try using `saturating_add`: `1i128.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:30:13 + | +LL | let _ = 1i32.checked_add(-1).unwrap_or(i32::min_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:31:13 + | +LL | let _ = 1i32.checked_add(-1).unwrap_or(i32::MIN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:32:13 + | +LL | let _ = 1i8.checked_add(-1).unwrap_or(-128); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i8.saturating_add(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:33:13 + | +LL | let _ = 1i128 + | _____________^ +LL | | .checked_add(-1) +LL | | .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728); + | |________________________________________________________________________^ help: try using `saturating_add`: `1i128.saturating_add(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:40:13 + | +LL | let _ = 1i32.checked_sub(1).unwrap_or(i32::min_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:41:13 + | +LL | let _ = 1i32.checked_sub(1).unwrap_or(i32::MIN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:42:13 + | +LL | let _ = 1i8.checked_sub(1).unwrap_or(-128); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i8.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:43:13 + | +LL | let _ = 1i128 + | _____________^ +LL | | .checked_sub(1) +LL | | .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728); + | |________________________________________________________________________^ help: try using `saturating_sub`: `1i128.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:46:13 + | +LL | let _ = 1i32.checked_sub(-1).unwrap_or(i32::max_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:47:13 + | +LL | let _ = 1i32.checked_sub(-1).unwrap_or(i32::MAX); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:48:13 + | +LL | let _ = 1i8.checked_sub(-1).unwrap_or(127); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i8.saturating_sub(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:49:13 + | +LL | let _ = 1i128 + | _____________^ +LL | | .checked_sub(-1) +LL | | .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727); + | |_______________________________________________________________________^ help: try using `saturating_sub`: `1i128.saturating_sub(-1)` + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_strip.rs b/src/tools/clippy/tests/ui/manual_strip.rs new file mode 100644 index 0000000000..cbb84eb5c7 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_strip.rs @@ -0,0 +1,66 @@ +#![warn(clippy::manual_strip)] + +fn main() { + let s = "abc"; + + if s.starts_with("ab") { + str::to_string(&s["ab".len()..]); + s["ab".len()..].to_string(); + + str::to_string(&s[2..]); + s[2..].to_string(); + } + + if s.ends_with("bc") { + str::to_string(&s[..s.len() - "bc".len()]); + s[..s.len() - "bc".len()].to_string(); + + str::to_string(&s[..s.len() - 2]); + s[..s.len() - 2].to_string(); + } + + // Character patterns + if s.starts_with('a') { + str::to_string(&s[1..]); + s[1..].to_string(); + } + + // Variable prefix + let prefix = "ab"; + if s.starts_with(prefix) { + str::to_string(&s[prefix.len()..]); + } + + // Constant prefix + const PREFIX: &str = "ab"; + if s.starts_with(PREFIX) { + str::to_string(&s[PREFIX.len()..]); + str::to_string(&s[2..]); + } + + // Constant target + const TARGET: &str = "abc"; + if TARGET.starts_with(prefix) { + str::to_string(&TARGET[prefix.len()..]); + } + + // String target - not mutated. + let s1: String = "abc".into(); + if s1.starts_with("ab") { + s1[2..].to_uppercase(); + } + + // String target - mutated. (Don't lint.) + let mut s2: String = "abc".into(); + if s2.starts_with("ab") { + s2.push('d'); + s2[2..].to_uppercase(); + } + + // Target not stripped. (Don't lint.) + let s3 = String::from("abcd"); + let s4 = String::from("efgh"); + if s3.starts_with("ab") { + s4[2..].to_string(); + } +} diff --git a/src/tools/clippy/tests/ui/manual_strip.stderr b/src/tools/clippy/tests/ui/manual_strip.stderr new file mode 100644 index 0000000000..1352a8713d --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_strip.stderr @@ -0,0 +1,132 @@ +error: stripping a prefix manually + --> $DIR/manual_strip.rs:7:24 + | +LL | str::to_string(&s["ab".len()..]); + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::manual-strip` implied by `-D warnings` +note: the prefix was tested here + --> $DIR/manual_strip.rs:6:5 + | +LL | if s.starts_with("ab") { + | ^^^^^^^^^^^^^^^^^^^^^^^ +help: try using the `strip_prefix` method + | +LL | if let Some() = s.strip_prefix("ab") { +LL | str::to_string(); +LL | .to_string(); +LL | +LL | str::to_string(); +LL | .to_string(); + | + +error: stripping a suffix manually + --> $DIR/manual_strip.rs:15:24 + | +LL | str::to_string(&s[..s.len() - "bc".len()]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the suffix was tested here + --> $DIR/manual_strip.rs:14:5 + | +LL | if s.ends_with("bc") { + | ^^^^^^^^^^^^^^^^^^^^^ +help: try using the `strip_suffix` method + | +LL | if let Some() = s.strip_suffix("bc") { +LL | str::to_string(); +LL | .to_string(); +LL | +LL | str::to_string(); +LL | .to_string(); + | + +error: stripping a prefix manually + --> $DIR/manual_strip.rs:24:24 + | +LL | str::to_string(&s[1..]); + | ^^^^^^^ + | +note: the prefix was tested here + --> $DIR/manual_strip.rs:23:5 + | +LL | if s.starts_with('a') { + | ^^^^^^^^^^^^^^^^^^^^^^ +help: try using the `strip_prefix` method + | +LL | if let Some() = s.strip_prefix('a') { +LL | str::to_string(); +LL | .to_string(); + | + +error: stripping a prefix manually + --> $DIR/manual_strip.rs:31:24 + | +LL | str::to_string(&s[prefix.len()..]); + | ^^^^^^^^^^^^^^^^^^ + | +note: the prefix was tested here + --> $DIR/manual_strip.rs:30:5 + | +LL | if s.starts_with(prefix) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try using the `strip_prefix` method + | +LL | if let Some() = s.strip_prefix(prefix) { +LL | str::to_string(); + | + +error: stripping a prefix manually + --> $DIR/manual_strip.rs:37:24 + | +LL | str::to_string(&s[PREFIX.len()..]); + | ^^^^^^^^^^^^^^^^^^ + | +note: the prefix was tested here + --> $DIR/manual_strip.rs:36:5 + | +LL | if s.starts_with(PREFIX) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try using the `strip_prefix` method + | +LL | if let Some() = s.strip_prefix(PREFIX) { +LL | str::to_string(); +LL | str::to_string(); + | + +error: stripping a prefix manually + --> $DIR/manual_strip.rs:44:24 + | +LL | str::to_string(&TARGET[prefix.len()..]); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the prefix was tested here + --> $DIR/manual_strip.rs:43:5 + | +LL | if TARGET.starts_with(prefix) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try using the `strip_prefix` method + | +LL | if let Some() = TARGET.strip_prefix(prefix) { +LL | str::to_string(); + | + +error: stripping a prefix manually + --> $DIR/manual_strip.rs:50:9 + | +LL | s1[2..].to_uppercase(); + | ^^^^^^^ + | +note: the prefix was tested here + --> $DIR/manual_strip.rs:49:5 + | +LL | if s1.starts_with("ab") { + | ^^^^^^^^^^^^^^^^^^^^^^^^ +help: try using the `strip_prefix` method + | +LL | if let Some() = s1.strip_prefix("ab") { +LL | .to_uppercase(); + | + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_unwrap_or.fixed b/src/tools/clippy/tests/ui/manual_unwrap_or.fixed new file mode 100644 index 0000000000..81d903c15d --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_unwrap_or.fixed @@ -0,0 +1,139 @@ +// run-rustfix +#![allow(dead_code)] +#![allow(unused_variables, clippy::unnecessary_wraps)] + +fn option_unwrap_or() { + // int case + Some(1).unwrap_or(42); + + // int case reversed + Some(1).unwrap_or(42); + + // richer none expr + Some(1).unwrap_or(1 + 42); + + // multiline case + #[rustfmt::skip] + Some(1).unwrap_or({ + 42 + 42 + + 42 + 42 + 42 + + 42 + 42 + 42 + }); + + // string case + Some("Bob").unwrap_or("Alice"); + + // don't lint + match Some(1) { + Some(i) => i + 2, + None => 42, + }; + match Some(1) { + Some(i) => i, + None => return, + }; + for j in 0..4 { + match Some(j) { + Some(i) => i, + None => continue, + }; + match Some(j) { + Some(i) => i, + None => break, + }; + } + + // cases where the none arm isn't a constant expression + // are not linted due to potential ownership issues + + // ownership issue example, don't lint + struct NonCopyable; + let mut option: Option = None; + match option { + Some(x) => x, + None => { + option = Some(NonCopyable); + // some more code ... + option.unwrap() + }, + }; + + // ownership issue example, don't lint + let option: Option<&str> = None; + match option { + Some(s) => s, + None => &format!("{} {}!", "hello", "world"), + }; +} + +fn result_unwrap_or() { + // int case + Ok::(1).unwrap_or(42); + + // int case, scrutinee is a binding + let a = Ok::(1); + a.unwrap_or(42); + + // int case, suggestion must surround Result expr with parenthesis + (Ok(1) as Result).unwrap_or(42); + + // method call case, suggestion must not surround Result expr `s.method()` with parenthesis + struct S {} + impl S { + fn method(self) -> Option { + Some(42) + } + } + let s = S {}; + s.method().unwrap_or(42); + + // int case reversed + Ok::(1).unwrap_or(42); + + // richer none expr + Ok::(1).unwrap_or(1 + 42); + + // multiline case + #[rustfmt::skip] + Ok::(1).unwrap_or({ + 42 + 42 + + 42 + 42 + 42 + + 42 + 42 + 42 + }); + + // string case + Ok::<&str, &str>("Bob").unwrap_or("Alice"); + + // don't lint + match Ok::(1) { + Ok(i) => i + 2, + Err(_) => 42, + }; + match Ok::(1) { + Ok(i) => i, + Err(_) => return, + }; + for j in 0..4 { + match Ok::(j) { + Ok(i) => i, + Err(_) => continue, + }; + match Ok::(j) { + Ok(i) => i, + Err(_) => break, + }; + } + + // don't lint, Err value is used + match Ok::<&str, &str>("Alice") { + Ok(s) => s, + Err(s) => s, + }; + // could lint, but unused_variables takes care of it + match Ok::<&str, &str>("Alice") { + Ok(s) => s, + Err(s) => "Bob", + }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_unwrap_or.rs b/src/tools/clippy/tests/ui/manual_unwrap_or.rs new file mode 100644 index 0000000000..16105d379c --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_unwrap_or.rs @@ -0,0 +1,178 @@ +// run-rustfix +#![allow(dead_code)] +#![allow(unused_variables, clippy::unnecessary_wraps)] + +fn option_unwrap_or() { + // int case + match Some(1) { + Some(i) => i, + None => 42, + }; + + // int case reversed + match Some(1) { + None => 42, + Some(i) => i, + }; + + // richer none expr + match Some(1) { + Some(i) => i, + None => 1 + 42, + }; + + // multiline case + #[rustfmt::skip] + match Some(1) { + Some(i) => i, + None => { + 42 + 42 + + 42 + 42 + 42 + + 42 + 42 + 42 + } + }; + + // string case + match Some("Bob") { + Some(i) => i, + None => "Alice", + }; + + // don't lint + match Some(1) { + Some(i) => i + 2, + None => 42, + }; + match Some(1) { + Some(i) => i, + None => return, + }; + for j in 0..4 { + match Some(j) { + Some(i) => i, + None => continue, + }; + match Some(j) { + Some(i) => i, + None => break, + }; + } + + // cases where the none arm isn't a constant expression + // are not linted due to potential ownership issues + + // ownership issue example, don't lint + struct NonCopyable; + let mut option: Option = None; + match option { + Some(x) => x, + None => { + option = Some(NonCopyable); + // some more code ... + option.unwrap() + }, + }; + + // ownership issue example, don't lint + let option: Option<&str> = None; + match option { + Some(s) => s, + None => &format!("{} {}!", "hello", "world"), + }; +} + +fn result_unwrap_or() { + // int case + match Ok::(1) { + Ok(i) => i, + Err(_) => 42, + }; + + // int case, scrutinee is a binding + let a = Ok::(1); + match a { + Ok(i) => i, + Err(_) => 42, + }; + + // int case, suggestion must surround Result expr with parenthesis + match Ok(1) as Result { + Ok(i) => i, + Err(_) => 42, + }; + + // method call case, suggestion must not surround Result expr `s.method()` with parenthesis + struct S {} + impl S { + fn method(self) -> Option { + Some(42) + } + } + let s = S {}; + match s.method() { + Some(i) => i, + None => 42, + }; + + // int case reversed + match Ok::(1) { + Err(_) => 42, + Ok(i) => i, + }; + + // richer none expr + match Ok::(1) { + Ok(i) => i, + Err(_) => 1 + 42, + }; + + // multiline case + #[rustfmt::skip] + match Ok::(1) { + Ok(i) => i, + Err(_) => { + 42 + 42 + + 42 + 42 + 42 + + 42 + 42 + 42 + } + }; + + // string case + match Ok::<&str, &str>("Bob") { + Ok(i) => i, + Err(_) => "Alice", + }; + + // don't lint + match Ok::(1) { + Ok(i) => i + 2, + Err(_) => 42, + }; + match Ok::(1) { + Ok(i) => i, + Err(_) => return, + }; + for j in 0..4 { + match Ok::(j) { + Ok(i) => i, + Err(_) => continue, + }; + match Ok::(j) { + Ok(i) => i, + Err(_) => break, + }; + } + + // don't lint, Err value is used + match Ok::<&str, &str>("Alice") { + Ok(s) => s, + Err(s) => s, + }; + // could lint, but unused_variables takes care of it + match Ok::<&str, &str>("Alice") { + Ok(s) => s, + Err(s) => "Bob", + }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_unwrap_or.stderr b/src/tools/clippy/tests/ui/manual_unwrap_or.stderr new file mode 100644 index 0000000000..fc174c4c27 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_unwrap_or.stderr @@ -0,0 +1,145 @@ +error: this pattern reimplements `Option::unwrap_or` + --> $DIR/manual_unwrap_or.rs:7:5 + | +LL | / match Some(1) { +LL | | Some(i) => i, +LL | | None => 42, +LL | | }; + | |_____^ help: replace with: `Some(1).unwrap_or(42)` + | + = note: `-D clippy::manual-unwrap-or` implied by `-D warnings` + +error: this pattern reimplements `Option::unwrap_or` + --> $DIR/manual_unwrap_or.rs:13:5 + | +LL | / match Some(1) { +LL | | None => 42, +LL | | Some(i) => i, +LL | | }; + | |_____^ help: replace with: `Some(1).unwrap_or(42)` + +error: this pattern reimplements `Option::unwrap_or` + --> $DIR/manual_unwrap_or.rs:19:5 + | +LL | / match Some(1) { +LL | | Some(i) => i, +LL | | None => 1 + 42, +LL | | }; + | |_____^ help: replace with: `Some(1).unwrap_or(1 + 42)` + +error: this pattern reimplements `Option::unwrap_or` + --> $DIR/manual_unwrap_or.rs:26:5 + | +LL | / match Some(1) { +LL | | Some(i) => i, +LL | | None => { +LL | | 42 + 42 +... | +LL | | } +LL | | }; + | |_____^ + | +help: replace with + | +LL | Some(1).unwrap_or({ +LL | 42 + 42 +LL | + 42 + 42 + 42 +LL | + 42 + 42 + 42 +LL | }); + | + +error: this pattern reimplements `Option::unwrap_or` + --> $DIR/manual_unwrap_or.rs:36:5 + | +LL | / match Some("Bob") { +LL | | Some(i) => i, +LL | | None => "Alice", +LL | | }; + | |_____^ help: replace with: `Some("Bob").unwrap_or("Alice")` + +error: this pattern reimplements `Result::unwrap_or` + --> $DIR/manual_unwrap_or.rs:86:5 + | +LL | / match Ok::(1) { +LL | | Ok(i) => i, +LL | | Err(_) => 42, +LL | | }; + | |_____^ help: replace with: `Ok::(1).unwrap_or(42)` + +error: this pattern reimplements `Result::unwrap_or` + --> $DIR/manual_unwrap_or.rs:93:5 + | +LL | / match a { +LL | | Ok(i) => i, +LL | | Err(_) => 42, +LL | | }; + | |_____^ help: replace with: `a.unwrap_or(42)` + +error: this pattern reimplements `Result::unwrap_or` + --> $DIR/manual_unwrap_or.rs:99:5 + | +LL | / match Ok(1) as Result { +LL | | Ok(i) => i, +LL | | Err(_) => 42, +LL | | }; + | |_____^ help: replace with: `(Ok(1) as Result).unwrap_or(42)` + +error: this pattern reimplements `Option::unwrap_or` + --> $DIR/manual_unwrap_or.rs:112:5 + | +LL | / match s.method() { +LL | | Some(i) => i, +LL | | None => 42, +LL | | }; + | |_____^ help: replace with: `s.method().unwrap_or(42)` + +error: this pattern reimplements `Result::unwrap_or` + --> $DIR/manual_unwrap_or.rs:118:5 + | +LL | / match Ok::(1) { +LL | | Err(_) => 42, +LL | | Ok(i) => i, +LL | | }; + | |_____^ help: replace with: `Ok::(1).unwrap_or(42)` + +error: this pattern reimplements `Result::unwrap_or` + --> $DIR/manual_unwrap_or.rs:124:5 + | +LL | / match Ok::(1) { +LL | | Ok(i) => i, +LL | | Err(_) => 1 + 42, +LL | | }; + | |_____^ help: replace with: `Ok::(1).unwrap_or(1 + 42)` + +error: this pattern reimplements `Result::unwrap_or` + --> $DIR/manual_unwrap_or.rs:131:5 + | +LL | / match Ok::(1) { +LL | | Ok(i) => i, +LL | | Err(_) => { +LL | | 42 + 42 +... | +LL | | } +LL | | }; + | |_____^ + | +help: replace with + | +LL | Ok::(1).unwrap_or({ +LL | 42 + 42 +LL | + 42 + 42 + 42 +LL | + 42 + 42 + 42 +LL | }); + | + +error: this pattern reimplements `Result::unwrap_or` + --> $DIR/manual_unwrap_or.rs:141:5 + | +LL | / match Ok::<&str, &str>("Bob") { +LL | | Ok(i) => i, +LL | | Err(_) => "Alice", +LL | | }; + | |_____^ help: replace with: `Ok::<&str, &str>("Bob").unwrap_or("Alice")` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/many_single_char_names.rs b/src/tools/clippy/tests/ui/many_single_char_names.rs new file mode 100644 index 0000000000..80800e4872 --- /dev/null +++ b/src/tools/clippy/tests/ui/many_single_char_names.rs @@ -0,0 +1,73 @@ +#[warn(clippy::many_single_char_names)] + +fn bla() { + let a: i32; + let (b, c, d): (i32, i64, i16); + { + { + let cdefg: i32; + let blar: i32; + } + { + let e: i32; + } + { + let e: i32; + let f: i32; + } + match 5 { + 1 => println!(), + e => panic!(), + } + match 5 { + 1 => println!(), + _ => panic!(), + } + } +} + +fn bindings(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32) {} + +fn bindings2() { + let (a, b, c, d, e, f, g, h): (bool, bool, bool, bool, bool, bool, bool, bool) = unimplemented!(); +} + +fn shadowing() { + let a = 0i32; + let a = 0i32; + let a = 0i32; + let a = 0i32; + let a = 0i32; + let a = 0i32; + { + let a = 0i32; + } +} + +fn patterns() { + enum Z { + A(i32), + B(i32), + C(i32), + D(i32), + E(i32), + F(i32), + } + + // These should not trigger a warning, since the pattern bindings are a new scope. + match Z::A(0) { + Z::A(a) => {}, + Z::B(b) => {}, + Z::C(c) => {}, + Z::D(d) => {}, + Z::E(e) => {}, + Z::F(f) => {}, + } +} + +#[allow(clippy::many_single_char_names)] +fn issue_3198_allow_works() { + let (a, b, c, d, e) = (0, 0, 0, 0, 0); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/many_single_char_names.stderr b/src/tools/clippy/tests/ui/many_single_char_names.stderr new file mode 100644 index 0000000000..27e62e641a --- /dev/null +++ b/src/tools/clippy/tests/ui/many_single_char_names.stderr @@ -0,0 +1,51 @@ +error: 5 bindings with single-character names in scope + --> $DIR/many_single_char_names.rs:4:9 + | +LL | let a: i32; + | ^ +LL | let (b, c, d): (i32, i64, i16); + | ^ ^ ^ +... +LL | let e: i32; + | ^ + | + = note: `-D clippy::many-single-char-names` implied by `-D warnings` + +error: 6 bindings with single-character names in scope + --> $DIR/many_single_char_names.rs:4:9 + | +LL | let a: i32; + | ^ +LL | let (b, c, d): (i32, i64, i16); + | ^ ^ ^ +... +LL | let e: i32; + | ^ +LL | let f: i32; + | ^ + +error: 5 bindings with single-character names in scope + --> $DIR/many_single_char_names.rs:4:9 + | +LL | let a: i32; + | ^ +LL | let (b, c, d): (i32, i64, i16); + | ^ ^ ^ +... +LL | e => panic!(), + | ^ + +error: 8 bindings with single-character names in scope + --> $DIR/many_single_char_names.rs:29:13 + | +LL | fn bindings(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32) {} + | ^ ^ ^ ^ ^ ^ ^ ^ + +error: 8 bindings with single-character names in scope + --> $DIR/many_single_char_names.rs:32:10 + | +LL | let (a, b, c, d, e, f, g, h): (bool, bool, bool, bool, bool, bool, bool, bool) = unimplemented!(); + | ^ ^ ^ ^ ^ ^ ^ ^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/map_clone.fixed b/src/tools/clippy/tests/ui/map_clone.fixed new file mode 100644 index 0000000000..178d8705c2 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_clone.fixed @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::iter_cloned_collect)] +#![allow(clippy::clone_on_copy, clippy::redundant_clone)] +#![allow(clippy::let_underscore_drop)] +#![allow(clippy::missing_docs_in_private_items)] +#![allow(clippy::redundant_closure_for_method_calls)] +#![allow(clippy::many_single_char_names)] + +fn main() { + let _: Vec = vec![5_i8; 6].iter().copied().collect(); + let _: Vec = vec![String::new()].iter().cloned().collect(); + let _: Vec = vec![42, 43].iter().copied().collect(); + let _: Option = Some(Box::new(16)).map(|b| *b); + let _: Option = Some(&16).copied(); + let _: Option = Some(&1).copied(); + + // Don't lint these + let v = vec![5_i8; 6]; + let a = 0; + let b = &a; + let _ = v.iter().map(|_x| *b); + let _ = v.iter().map(|_x| a.clone()); + let _ = v.iter().map(|&_x| a); + + // Issue #498 + let _ = std::env::args(); + + // Issue #4824 item types that aren't references + { + use std::rc::Rc; + + let o: Option> = Some(Rc::new(0_u32)); + let _: Option = o.map(|x| *x); + let v: Vec> = vec![Rc::new(0_u32)]; + let _: Vec = v.into_iter().map(|x| *x).collect(); + } + + // Issue #5524 mutable references + { + let mut c = 42; + let v = vec![&mut c]; + let _: Vec = v.into_iter().map(|x| *x).collect(); + let mut d = 21; + let v = vec![&mut d]; + let _: Vec = v.into_iter().map(|&mut x| x).collect(); + } + + // Issue #6299 + { + let mut aa = 5; + let mut bb = 3; + let items = vec![&mut aa, &mut bb]; + let _: Vec<_> = items.into_iter().map(|x| x.clone()).collect(); + } + + // Issue #6239 deref coercion and clone deref + { + use std::cell::RefCell; + + let _ = Some(RefCell::new(String::new()).borrow()).map(|s| s.clone()); + } +} diff --git a/src/tools/clippy/tests/ui/map_clone.rs b/src/tools/clippy/tests/ui/map_clone.rs new file mode 100644 index 0000000000..c73d81713b --- /dev/null +++ b/src/tools/clippy/tests/ui/map_clone.rs @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::iter_cloned_collect)] +#![allow(clippy::clone_on_copy, clippy::redundant_clone)] +#![allow(clippy::let_underscore_drop)] +#![allow(clippy::missing_docs_in_private_items)] +#![allow(clippy::redundant_closure_for_method_calls)] +#![allow(clippy::many_single_char_names)] + +fn main() { + let _: Vec = vec![5_i8; 6].iter().map(|x| *x).collect(); + let _: Vec = vec![String::new()].iter().map(|x| x.clone()).collect(); + let _: Vec = vec![42, 43].iter().map(|&x| x).collect(); + let _: Option = Some(Box::new(16)).map(|b| *b); + let _: Option = Some(&16).map(|b| *b); + let _: Option = Some(&1).map(|x| x.clone()); + + // Don't lint these + let v = vec![5_i8; 6]; + let a = 0; + let b = &a; + let _ = v.iter().map(|_x| *b); + let _ = v.iter().map(|_x| a.clone()); + let _ = v.iter().map(|&_x| a); + + // Issue #498 + let _ = std::env::args().map(|v| v.clone()); + + // Issue #4824 item types that aren't references + { + use std::rc::Rc; + + let o: Option> = Some(Rc::new(0_u32)); + let _: Option = o.map(|x| *x); + let v: Vec> = vec![Rc::new(0_u32)]; + let _: Vec = v.into_iter().map(|x| *x).collect(); + } + + // Issue #5524 mutable references + { + let mut c = 42; + let v = vec![&mut c]; + let _: Vec = v.into_iter().map(|x| *x).collect(); + let mut d = 21; + let v = vec![&mut d]; + let _: Vec = v.into_iter().map(|&mut x| x).collect(); + } + + // Issue #6299 + { + let mut aa = 5; + let mut bb = 3; + let items = vec![&mut aa, &mut bb]; + let _: Vec<_> = items.into_iter().map(|x| x.clone()).collect(); + } + + // Issue #6239 deref coercion and clone deref + { + use std::cell::RefCell; + + let _ = Some(RefCell::new(String::new()).borrow()).map(|s| s.clone()); + } +} diff --git a/src/tools/clippy/tests/ui/map_clone.stderr b/src/tools/clippy/tests/ui/map_clone.stderr new file mode 100644 index 0000000000..d84a5bf8d4 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_clone.stderr @@ -0,0 +1,40 @@ +error: you are using an explicit closure for copying elements + --> $DIR/map_clone.rs:11:22 + | +LL | let _: Vec = vec![5_i8; 6].iter().map(|x| *x).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `vec![5_i8; 6].iter().copied()` + | + = note: `-D clippy::map-clone` implied by `-D warnings` + +error: you are using an explicit closure for cloning elements + --> $DIR/map_clone.rs:12:26 + | +LL | let _: Vec = vec![String::new()].iter().map(|x| x.clone()).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `vec![String::new()].iter().cloned()` + +error: you are using an explicit closure for copying elements + --> $DIR/map_clone.rs:13:23 + | +LL | let _: Vec = vec![42, 43].iter().map(|&x| x).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `vec![42, 43].iter().copied()` + +error: you are using an explicit closure for copying elements + --> $DIR/map_clone.rs:15:26 + | +LL | let _: Option = Some(&16).map(|b| *b); + | ^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `Some(&16).copied()` + +error: you are using an explicit closure for copying elements + --> $DIR/map_clone.rs:16:25 + | +LL | let _: Option = Some(&1).map(|x| x.clone()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `Some(&1).copied()` + +error: you are needlessly cloning iterator elements + --> $DIR/map_clone.rs:27:29 + | +LL | let _ = std::env::args().map(|v| v.clone()); + | ^^^^^^^^^^^^^^^^^^^ help: remove the `map` call + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/map_collect_result_unit.fixed b/src/tools/clippy/tests/ui/map_collect_result_unit.fixed new file mode 100644 index 0000000000..e66c9cc242 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_collect_result_unit.fixed @@ -0,0 +1,16 @@ +// run-rustfix +#![warn(clippy::map_collect_result_unit)] + +fn main() { + { + let _ = (0..3).try_for_each(|t| Err(t + 1)); + let _: Result<(), _> = (0..3).try_for_each(|t| Err(t + 1)); + + let _ = (0..3).try_for_each(|t| Err(t + 1)); + } +} + +fn _ignore() { + let _ = (0..3).map(|t| Err(t + 1)).collect::, _>>(); + let _ = (0..3).map(|t| Err(t + 1)).collect::>>(); +} diff --git a/src/tools/clippy/tests/ui/map_collect_result_unit.rs b/src/tools/clippy/tests/ui/map_collect_result_unit.rs new file mode 100644 index 0000000000..6f08f4c3c5 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_collect_result_unit.rs @@ -0,0 +1,16 @@ +// run-rustfix +#![warn(clippy::map_collect_result_unit)] + +fn main() { + { + let _ = (0..3).map(|t| Err(t + 1)).collect::>(); + let _: Result<(), _> = (0..3).map(|t| Err(t + 1)).collect(); + + let _ = (0..3).try_for_each(|t| Err(t + 1)); + } +} + +fn _ignore() { + let _ = (0..3).map(|t| Err(t + 1)).collect::, _>>(); + let _ = (0..3).map(|t| Err(t + 1)).collect::>>(); +} diff --git a/src/tools/clippy/tests/ui/map_collect_result_unit.stderr b/src/tools/clippy/tests/ui/map_collect_result_unit.stderr new file mode 100644 index 0000000000..8b06e13baa --- /dev/null +++ b/src/tools/clippy/tests/ui/map_collect_result_unit.stderr @@ -0,0 +1,16 @@ +error: `.map().collect()` can be replaced with `.try_for_each()` + --> $DIR/map_collect_result_unit.rs:6:17 + | +LL | let _ = (0..3).map(|t| Err(t + 1)).collect::>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `(0..3).try_for_each(|t| Err(t + 1))` + | + = note: `-D clippy::map-collect-result-unit` implied by `-D warnings` + +error: `.map().collect()` can be replaced with `.try_for_each()` + --> $DIR/map_collect_result_unit.rs:7:32 + | +LL | let _: Result<(), _> = (0..3).map(|t| Err(t + 1)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `(0..3).try_for_each(|t| Err(t + 1))` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/map_err.rs b/src/tools/clippy/tests/ui/map_err.rs new file mode 100644 index 0000000000..00e037843f --- /dev/null +++ b/src/tools/clippy/tests/ui/map_err.rs @@ -0,0 +1,30 @@ +#![warn(clippy::map_err_ignore)] +#![allow(clippy::unnecessary_wraps)] +use std::convert::TryFrom; +use std::error::Error; +use std::fmt; + +#[derive(Debug)] +enum Errors { + Ignored, +} + +impl Error for Errors {} + +impl fmt::Display for Errors { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error") + } +} + +fn main() -> Result<(), Errors> { + let x = u32::try_from(-123_i32); + + println!("{:?}", x.map_err(|_| Errors::Ignored)); + + // Should not warn you because you explicitly ignore the parameter + // using a named wildcard value + println!("{:?}", x.map_err(|_foo| Errors::Ignored)); + + Ok(()) +} diff --git a/src/tools/clippy/tests/ui/map_err.stderr b/src/tools/clippy/tests/ui/map_err.stderr new file mode 100644 index 0000000000..37e87e64de --- /dev/null +++ b/src/tools/clippy/tests/ui/map_err.stderr @@ -0,0 +1,11 @@ +error: `map_err(|_|...` wildcard pattern discards the original error + --> $DIR/map_err.rs:23:32 + | +LL | println!("{:?}", x.map_err(|_| Errors::Ignored)); + | ^^^ + | + = note: `-D clippy::map-err-ignore` implied by `-D warnings` + = help: consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/map_flatten.fixed b/src/tools/clippy/tests/ui/map_flatten.fixed new file mode 100644 index 0000000000..773b591443 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_flatten.fixed @@ -0,0 +1,26 @@ +// run-rustfix + +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::let_underscore_drop)] +#![allow(clippy::missing_docs_in_private_items)] +#![allow(clippy::map_identity)] +#![allow(clippy::unnecessary_wraps)] + +fn main() { + // mapping to Option on Iterator + fn option_id(x: i8) -> Option { + Some(x) + } + let option_id_ref: fn(i8) -> Option = option_id; + let option_id_closure = |x| Some(x); + let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect(); + + // mapping to Iterator on Iterator + let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect(); + + // mapping to Option on Option + let _: Option<_> = (Some(Some(1))).and_then(|x| x); +} diff --git a/src/tools/clippy/tests/ui/map_flatten.rs b/src/tools/clippy/tests/ui/map_flatten.rs new file mode 100644 index 0000000000..578bd87726 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_flatten.rs @@ -0,0 +1,26 @@ +// run-rustfix + +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::let_underscore_drop)] +#![allow(clippy::missing_docs_in_private_items)] +#![allow(clippy::map_identity)] +#![allow(clippy::unnecessary_wraps)] + +fn main() { + // mapping to Option on Iterator + fn option_id(x: i8) -> Option { + Some(x) + } + let option_id_ref: fn(i8) -> Option = option_id; + let option_id_closure = |x| Some(x); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); + + // mapping to Iterator on Iterator + let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); + + // mapping to Option on Option + let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); +} diff --git a/src/tools/clippy/tests/ui/map_flatten.stderr b/src/tools/clippy/tests/ui/map_flatten.stderr new file mode 100644 index 0000000000..756e6e818a --- /dev/null +++ b/src/tools/clippy/tests/ui/map_flatten.stderr @@ -0,0 +1,40 @@ +error: called `map(..).flatten()` on an `Iterator` + --> $DIR/map_flatten.rs:16:46 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id)` + | + = note: `-D clippy::map-flatten` implied by `-D warnings` + +error: called `map(..).flatten()` on an `Iterator` + --> $DIR/map_flatten.rs:17:46 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_ref)` + +error: called `map(..).flatten()` on an `Iterator` + --> $DIR/map_flatten.rs:18:46 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_closure)` + +error: called `map(..).flatten()` on an `Iterator` + --> $DIR/map_flatten.rs:19:46 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(|x| x.checked_add(1))` + +error: called `map(..).flatten()` on an `Iterator` + --> $DIR/map_flatten.rs:22:46 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `.flat_map(|x| 0..x)` + +error: called `map(..).flatten()` on an `Option` + --> $DIR/map_flatten.rs:25:39 + | +LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); + | ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/map_identity.fixed b/src/tools/clippy/tests/ui/map_identity.fixed new file mode 100644 index 0000000000..4a1452b25f --- /dev/null +++ b/src/tools/clippy/tests/ui/map_identity.fixed @@ -0,0 +1,23 @@ +// run-rustfix +#![warn(clippy::map_identity)] +#![allow(clippy::needless_return)] + +fn main() { + let x: [u16; 3] = [1, 2, 3]; + // should lint + let _: Vec<_> = x.iter().map(not_identity).collect(); + let _: Vec<_> = x.iter().collect(); + let _: Option = Some(3); + let _: Result = Ok(-3); + // should not lint + let _: Vec<_> = x.iter().map(|x| 2 * x).collect(); + let _: Vec<_> = x.iter().map(not_identity).map(|x| return x - 4).collect(); + let _: Option = None.map(|x: u8| x - 1); + let _: Result = Err(2.3).map(|x: i8| { + return x + 3; + }); +} + +fn not_identity(x: &u16) -> u16 { + *x +} diff --git a/src/tools/clippy/tests/ui/map_identity.rs b/src/tools/clippy/tests/ui/map_identity.rs new file mode 100644 index 0000000000..65c7e6e1ea --- /dev/null +++ b/src/tools/clippy/tests/ui/map_identity.rs @@ -0,0 +1,25 @@ +// run-rustfix +#![warn(clippy::map_identity)] +#![allow(clippy::needless_return)] + +fn main() { + let x: [u16; 3] = [1, 2, 3]; + // should lint + let _: Vec<_> = x.iter().map(not_identity).map(|x| return x).collect(); + let _: Vec<_> = x.iter().map(std::convert::identity).map(|y| y).collect(); + let _: Option = Some(3).map(|x| x); + let _: Result = Ok(-3).map(|x| { + return x; + }); + // should not lint + let _: Vec<_> = x.iter().map(|x| 2 * x).collect(); + let _: Vec<_> = x.iter().map(not_identity).map(|x| return x - 4).collect(); + let _: Option = None.map(|x: u8| x - 1); + let _: Result = Err(2.3).map(|x: i8| { + return x + 3; + }); +} + +fn not_identity(x: &u16) -> u16 { + *x +} diff --git a/src/tools/clippy/tests/ui/map_identity.stderr b/src/tools/clippy/tests/ui/map_identity.stderr new file mode 100644 index 0000000000..e4a0320cbd --- /dev/null +++ b/src/tools/clippy/tests/ui/map_identity.stderr @@ -0,0 +1,37 @@ +error: unnecessary map of the identity function + --> $DIR/map_identity.rs:8:47 + | +LL | let _: Vec<_> = x.iter().map(not_identity).map(|x| return x).collect(); + | ^^^^^^^^^^^^^^^^^^ help: remove the call to `map` + | + = note: `-D clippy::map-identity` implied by `-D warnings` + +error: unnecessary map of the identity function + --> $DIR/map_identity.rs:9:57 + | +LL | let _: Vec<_> = x.iter().map(std::convert::identity).map(|y| y).collect(); + | ^^^^^^^^^^^ help: remove the call to `map` + +error: unnecessary map of the identity function + --> $DIR/map_identity.rs:9:29 + | +LL | let _: Vec<_> = x.iter().map(std::convert::identity).map(|y| y).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` + +error: unnecessary map of the identity function + --> $DIR/map_identity.rs:10:32 + | +LL | let _: Option = Some(3).map(|x| x); + | ^^^^^^^^^^^ help: remove the call to `map` + +error: unnecessary map of the identity function + --> $DIR/map_identity.rs:11:36 + | +LL | let _: Result = Ok(-3).map(|x| { + | ____________________________________^ +LL | | return x; +LL | | }); + | |______^ help: remove the call to `map` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/map_unit_fn.rs b/src/tools/clippy/tests/ui/map_unit_fn.rs new file mode 100644 index 0000000000..9a74da4e3b --- /dev/null +++ b/src/tools/clippy/tests/ui/map_unit_fn.rs @@ -0,0 +1,11 @@ +#![allow(unused)] +struct Mappable {} + +impl Mappable { + pub fn map(&self) {} +} + +fn main() { + let m = Mappable {}; + m.map(); +} diff --git a/src/tools/clippy/tests/ui/map_unwrap_or.rs b/src/tools/clippy/tests/ui/map_unwrap_or.rs new file mode 100644 index 0000000000..87e16f5d09 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_unwrap_or.rs @@ -0,0 +1,81 @@ +// aux-build:option_helpers.rs + +#![warn(clippy::map_unwrap_or)] + +#[macro_use] +extern crate option_helpers; + +use std::collections::HashMap; + +#[rustfmt::skip] +fn option_methods() { + let opt = Some(1); + + // Check for `option.map(_).unwrap_or(_)` use. + // Single line case. + let _ = opt.map(|x| x + 1) + // Should lint even though this call is on a separate line. + .unwrap_or(0); + // Multi-line cases. + let _ = opt.map(|x| { + x + 1 + } + ).unwrap_or(0); + let _ = opt.map(|x| x + 1) + .unwrap_or({ + 0 + }); + // Single line `map(f).unwrap_or(None)` case. + let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); + // Multi-line `map(f).unwrap_or(None)` cases. + let _ = opt.map(|x| { + Some(x + 1) + } + ).unwrap_or(None); + let _ = opt + .map(|x| Some(x + 1)) + .unwrap_or(None); + // macro case + let _ = opt_map!(opt, |x| x + 1).unwrap_or(0); // should not lint + + // Should not lint if not copyable + let id: String = "identifier".to_string(); + let _ = Some("prefix").map(|p| format!("{}.{}", p, id)).unwrap_or(id); + // ...but DO lint if the `unwrap_or` argument is not used in the `map` + let id: String = "identifier".to_string(); + let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); + + // Check for `option.map(_).unwrap_or_else(_)` use. + // Multi-line cases. + let _ = opt.map(|x| { + x + 1 + } + ).unwrap_or_else(|| 0); + let _ = opt.map(|x| x + 1) + .unwrap_or_else(|| + 0 + ); +} + +#[rustfmt::skip] +fn result_methods() { + let res: Result = Ok(1); + + // Check for `result.map(_).unwrap_or_else(_)` use. + // multi line cases + let _ = res.map(|x| { + x + 1 + } + ).unwrap_or_else(|_e| 0); + let _ = res.map(|x| x + 1) + .unwrap_or_else(|_e| { + 0 + }); + // macro case + let _ = opt_map!(res, |x| x + 1).unwrap_or_else(|_e| 0); // should not lint +} + +fn main() { + option_methods(); + result_methods(); +} diff --git a/src/tools/clippy/tests/ui/map_unwrap_or.stderr b/src/tools/clippy/tests/ui/map_unwrap_or.stderr new file mode 100644 index 0000000000..96b9d6cc3c --- /dev/null +++ b/src/tools/clippy/tests/ui/map_unwrap_or.stderr @@ -0,0 +1,146 @@ +error: called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead + --> $DIR/map_unwrap_or.rs:16:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | // Should lint even though this call is on a separate line. +LL | | .unwrap_or(0); + | |_____________________^ + | + = note: `-D clippy::map-unwrap-or` implied by `-D warnings` +help: use `map_or(, )` instead + | +LL | let _ = opt.map_or(0, |x| x + 1); + | ^^^^^^ ^^ -- + +error: called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead + --> $DIR/map_unwrap_or.rs:20:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | x + 1 +LL | | } +LL | | ).unwrap_or(0); + | |__________________^ + | +help: use `map_or(, )` instead + | +LL | let _ = opt.map_or(0, |x| { +LL | x + 1 +LL | } +LL | ); + | + +error: called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead + --> $DIR/map_unwrap_or.rs:24:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | .unwrap_or({ +LL | | 0 +LL | | }); + | |__________^ + | +help: use `map_or(, )` instead + | +LL | let _ = opt.map_or({ +LL | 0 +LL | }, |x| x + 1); + | + +error: called `map().unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then()` instead + --> $DIR/map_unwrap_or.rs:29:13 + | +LL | let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `and_then()` instead + | +LL | let _ = opt.and_then(|x| Some(x + 1)); + | ^^^^^^^^ -- + +error: called `map().unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then()` instead + --> $DIR/map_unwrap_or.rs:31:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | Some(x + 1) +LL | | } +LL | | ).unwrap_or(None); + | |_____________________^ + | +help: use `and_then()` instead + | +LL | let _ = opt.and_then(|x| { +LL | Some(x + 1) +LL | } +LL | ); + | + +error: called `map().unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then()` instead + --> $DIR/map_unwrap_or.rs:35:13 + | +LL | let _ = opt + | _____________^ +LL | | .map(|x| Some(x + 1)) +LL | | .unwrap_or(None); + | |________________________^ + | +help: use `and_then()` instead + | +LL | .and_then(|x| Some(x + 1)); + | ^^^^^^^^ -- + +error: called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead + --> $DIR/map_unwrap_or.rs:46:13 + | +LL | let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_or(, )` instead + | +LL | let _ = Some("prefix").map_or(id, |p| format!("{}.", p)); + | ^^^^^^ ^^^ -- + +error: called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling `map_or_else(, )` instead + --> $DIR/map_unwrap_or.rs:50:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | x + 1 +LL | | } +LL | | ).unwrap_or_else(|| 0); + | |__________________________^ + +error: called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling `map_or_else(, )` instead + --> $DIR/map_unwrap_or.rs:54:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | .unwrap_or_else(|| +LL | | 0 +LL | | ); + | |_________^ + +error: called `map().unwrap_or_else()` on a `Result` value. This can be done more directly by calling `.map_or_else(, )` instead + --> $DIR/map_unwrap_or.rs:66:13 + | +LL | let _ = res.map(|x| { + | _____________^ +LL | | x + 1 +LL | | } +LL | | ).unwrap_or_else(|_e| 0); + | |____________________________^ + +error: called `map().unwrap_or_else()` on a `Result` value. This can be done more directly by calling `.map_or_else(, )` instead + --> $DIR/map_unwrap_or.rs:70:13 + | +LL | let _ = res.map(|x| x + 1) + | _____________^ +LL | | .unwrap_or_else(|_e| { +LL | | 0 +LL | | }); + | |__________^ + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/map_unwrap_or_fixable.fixed b/src/tools/clippy/tests/ui/map_unwrap_or_fixable.fixed new file mode 100644 index 0000000000..bd5b4f7165 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_unwrap_or_fixable.fixed @@ -0,0 +1,54 @@ +// run-rustfix +// aux-build:option_helpers.rs + +#![warn(clippy::map_unwrap_or)] + +#[macro_use] +extern crate option_helpers; + +use std::collections::HashMap; + +#[rustfmt::skip] +fn option_methods() { + let opt = Some(1); + + // Check for `option.map(_).unwrap_or_else(_)` use. + // single line case + let _ = opt.map_or_else(|| 0, |x| x + 1); + + // Macro case. + // Should not lint. + let _ = opt_map!(opt, |x| x + 1).unwrap_or_else(|| 0); + + // Issue #4144 + { + let mut frequencies = HashMap::new(); + let word = "foo"; + + frequencies + .get_mut(word) + .map(|count| { + *count += 1; + }) + .unwrap_or_else(|| { + frequencies.insert(word.to_owned(), 1); + }); + } +} + +#[rustfmt::skip] +fn result_methods() { + let res: Result = Ok(1); + + // Check for `result.map(_).unwrap_or_else(_)` use. + // single line case + let _ = res.map_or_else(|_e| 0, |x| x + 1); + + // macro case + let _ = opt_map!(res, |x| x + 1).unwrap_or_else(|_e| 0); // should not lint +} + +fn main() { + option_methods(); + result_methods(); +} diff --git a/src/tools/clippy/tests/ui/map_unwrap_or_fixable.rs b/src/tools/clippy/tests/ui/map_unwrap_or_fixable.rs new file mode 100644 index 0000000000..0b892caf20 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_unwrap_or_fixable.rs @@ -0,0 +1,58 @@ +// run-rustfix +// aux-build:option_helpers.rs + +#![warn(clippy::map_unwrap_or)] + +#[macro_use] +extern crate option_helpers; + +use std::collections::HashMap; + +#[rustfmt::skip] +fn option_methods() { + let opt = Some(1); + + // Check for `option.map(_).unwrap_or_else(_)` use. + // single line case + let _ = opt.map(|x| x + 1) + // Should lint even though this call is on a separate line. + .unwrap_or_else(|| 0); + + // Macro case. + // Should not lint. + let _ = opt_map!(opt, |x| x + 1).unwrap_or_else(|| 0); + + // Issue #4144 + { + let mut frequencies = HashMap::new(); + let word = "foo"; + + frequencies + .get_mut(word) + .map(|count| { + *count += 1; + }) + .unwrap_or_else(|| { + frequencies.insert(word.to_owned(), 1); + }); + } +} + +#[rustfmt::skip] +fn result_methods() { + let res: Result = Ok(1); + + // Check for `result.map(_).unwrap_or_else(_)` use. + // single line case + let _ = res.map(|x| x + 1) + // should lint even though this call is on a separate line + .unwrap_or_else(|_e| 0); + + // macro case + let _ = opt_map!(res, |x| x + 1).unwrap_or_else(|_e| 0); // should not lint +} + +fn main() { + option_methods(); + result_methods(); +} diff --git a/src/tools/clippy/tests/ui/map_unwrap_or_fixable.stderr b/src/tools/clippy/tests/ui/map_unwrap_or_fixable.stderr new file mode 100644 index 0000000000..1837bc2ca3 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_unwrap_or_fixable.stderr @@ -0,0 +1,22 @@ +error: called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling `map_or_else(, )` instead + --> $DIR/map_unwrap_or_fixable.rs:17:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | // Should lint even though this call is on a separate line. +LL | | .unwrap_or_else(|| 0); + | |_____________________________^ help: try this: `opt.map_or_else(|| 0, |x| x + 1)` + | + = note: `-D clippy::map-unwrap-or` implied by `-D warnings` + +error: called `map().unwrap_or_else()` on a `Result` value. This can be done more directly by calling `.map_or_else(, )` instead + --> $DIR/map_unwrap_or_fixable.rs:47:13 + | +LL | let _ = res.map(|x| x + 1) + | _____________^ +LL | | // should lint even though this call is on a separate line +LL | | .unwrap_or_else(|_e| 0); + | |_______________________________^ help: try this: `res.map_or_else(|_e| 0, |x| x + 1)` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/match_as_ref.fixed b/src/tools/clippy/tests/ui/match_as_ref.fixed new file mode 100644 index 0000000000..c61eb92166 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_as_ref.fixed @@ -0,0 +1,35 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::match_as_ref)] + +fn match_as_ref() { + let owned: Option<()> = None; + let borrowed: Option<&()> = owned.as_ref(); + + let mut mut_owned: Option<()> = None; + let borrow_mut: Option<&mut ()> = mut_owned.as_mut(); +} + +mod issue4437 { + use std::{error::Error, fmt, num::ParseIntError}; + + #[derive(Debug)] + struct E { + source: Option, + } + + impl Error for E { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.source.as_ref().map(|x| x as _) + } + } + + impl fmt::Display for E { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_as_ref.rs b/src/tools/clippy/tests/ui/match_as_ref.rs new file mode 100644 index 0000000000..2fbd0b255f --- /dev/null +++ b/src/tools/clippy/tests/ui/match_as_ref.rs @@ -0,0 +1,44 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::match_as_ref)] + +fn match_as_ref() { + let owned: Option<()> = None; + let borrowed: Option<&()> = match owned { + None => None, + Some(ref v) => Some(v), + }; + + let mut mut_owned: Option<()> = None; + let borrow_mut: Option<&mut ()> = match mut_owned { + None => None, + Some(ref mut v) => Some(v), + }; +} + +mod issue4437 { + use std::{error::Error, fmt, num::ParseIntError}; + + #[derive(Debug)] + struct E { + source: Option, + } + + impl Error for E { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self.source { + Some(ref s) => Some(s), + None => None, + } + } + } + + impl fmt::Display for E { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_as_ref.stderr b/src/tools/clippy/tests/ui/match_as_ref.stderr new file mode 100644 index 0000000000..c3b62849cb --- /dev/null +++ b/src/tools/clippy/tests/ui/match_as_ref.stderr @@ -0,0 +1,33 @@ +error: use `as_ref()` instead + --> $DIR/match_as_ref.rs:8:33 + | +LL | let borrowed: Option<&()> = match owned { + | _________________________________^ +LL | | None => None, +LL | | Some(ref v) => Some(v), +LL | | }; + | |_____^ help: try this: `owned.as_ref()` + | + = note: `-D clippy::match-as-ref` implied by `-D warnings` + +error: use `as_mut()` instead + --> $DIR/match_as_ref.rs:14:39 + | +LL | let borrow_mut: Option<&mut ()> = match mut_owned { + | _______________________________________^ +LL | | None => None, +LL | | Some(ref mut v) => Some(v), +LL | | }; + | |_____^ help: try this: `mut_owned.as_mut()` + +error: use `as_ref()` instead + --> $DIR/match_as_ref.rs:30:13 + | +LL | / match self.source { +LL | | Some(ref s) => Some(s), +LL | | None => None, +LL | | } + | |_____________^ help: try this: `self.source.as_ref().map(|x| x as _)` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/match_bool.rs b/src/tools/clippy/tests/ui/match_bool.rs new file mode 100644 index 0000000000..9ed55ca7ae --- /dev/null +++ b/src/tools/clippy/tests/ui/match_bool.rs @@ -0,0 +1,55 @@ +#![deny(clippy::match_bool)] + +fn match_bool() { + let test: bool = true; + + match test { + true => 0, + false => 42, + }; + + let option = 1; + match option == 1 { + true => 1, + false => 0, + }; + + match test { + true => (), + false => { + println!("Noooo!"); + }, + }; + + match test { + false => { + println!("Noooo!"); + }, + _ => (), + }; + + match test && test { + false => { + println!("Noooo!"); + }, + _ => (), + }; + + match test { + false => { + println!("Noooo!"); + }, + true => { + println!("Yes!"); + }, + }; + + // Not linted + match option { + 1..=10 => 1, + 11..=20 => 2, + _ => 3, + }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_bool.stderr b/src/tools/clippy/tests/ui/match_bool.stderr new file mode 100644 index 0000000000..1ad78c740c --- /dev/null +++ b/src/tools/clippy/tests/ui/match_bool.stderr @@ -0,0 +1,117 @@ +error: this boolean expression can be simplified + --> $DIR/match_bool.rs:31:11 + | +LL | match test && test { + | ^^^^^^^^^^^^ help: try: `test` + | + = note: `-D clippy::nonminimal-bool` implied by `-D warnings` + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:6:5 + | +LL | / match test { +LL | | true => 0, +LL | | false => 42, +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if test { 0 } else { 42 }` + | +note: the lint level is defined here + --> $DIR/match_bool.rs:1:9 + | +LL | #![deny(clippy::match_bool)] + | ^^^^^^^^^^^^^^^^^^ + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:12:5 + | +LL | / match option == 1 { +LL | | true => 1, +LL | | false => 0, +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if option == 1 { 1 } else { 0 }` + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:17:5 + | +LL | / match test { +LL | | true => (), +LL | | false => { +LL | | println!("Noooo!"); +LL | | }, +LL | | }; + | |_____^ + | +help: consider using an `if`/`else` expression + | +LL | if !test { +LL | println!("Noooo!"); +LL | }; + | + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:24:5 + | +LL | / match test { +LL | | false => { +LL | | println!("Noooo!"); +LL | | }, +LL | | _ => (), +LL | | }; + | |_____^ + | +help: consider using an `if`/`else` expression + | +LL | if !test { +LL | println!("Noooo!"); +LL | }; + | + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:31:5 + | +LL | / match test && test { +LL | | false => { +LL | | println!("Noooo!"); +LL | | }, +LL | | _ => (), +LL | | }; + | |_____^ + | +help: consider using an `if`/`else` expression + | +LL | if !(test && test) { +LL | println!("Noooo!"); +LL | }; + | + +error: equal expressions as operands to `&&` + --> $DIR/match_bool.rs:31:11 + | +LL | match test && test { + | ^^^^^^^^^^^^ + | + = note: `#[deny(clippy::eq_op)]` on by default + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:38:5 + | +LL | / match test { +LL | | false => { +LL | | println!("Noooo!"); +LL | | }, +... | +LL | | }, +LL | | }; + | |_____^ + | +help: consider using an `if`/`else` expression + | +LL | if test { +LL | println!("Yes!"); +LL | } else { +LL | println!("Noooo!"); +LL | }; + | + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/match_expr_like_matches_macro.fixed b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.fixed new file mode 100644 index 0000000000..319299862a --- /dev/null +++ b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.fixed @@ -0,0 +1,149 @@ +// run-rustfix + +#![warn(clippy::match_like_matches_macro)] +#![allow(unreachable_patterns, dead_code)] + +fn main() { + let x = Some(5); + + // Lint + let _y = matches!(x, Some(0)); + + // Lint + let _w = matches!(x, Some(_)); + + // Turn into is_none + let _z = x.is_none(); + + // Lint + let _zz = !matches!(x, Some(r) if r == 0); + + // Lint + let _zzz = matches!(x, Some(5)); + + // No lint + let _a = match x { + Some(_) => false, + _ => false, + }; + + // No lint + let _ab = match x { + Some(0) => false, + _ => true, + None => false, + }; + + enum E { + A(u32), + B(i32), + C, + D, + } + let x = E::A(2); + { + // lint + let _ans = matches!(x, E::A(_) | E::B(_)); + } + { + // lint + let _ans = !matches!(x, E::B(_) | E::C); + } + { + // no lint + let _ans = match x { + E::A(_) => false, + E::B(_) => false, + E::C => true, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(_) => true, + E::B(_) => false, + E::C => false, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(a) if a < 10 => false, + E::B(a) if a < 10 => false, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(_) => false, + E::B(a) if a < 10 => false, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(a) => a == 10, + E::B(_) => false, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(_) => false, + E::B(_) => true, + _ => false, + }; + } + + { + // should print "z" in suggestion (#6503) + let z = &Some(3); + let _z = matches!(z, Some(3)); + } + + { + // this could also print "z" in suggestion..? + let z = Some(3); + let _z = matches!(&z, Some(3)); + } + + { + enum AnEnum { + X, + Y, + } + + fn foo(_x: AnEnum) {} + + fn main() { + let z = AnEnum::X; + // we can't remove the reference here! + let _ = matches!(&z, AnEnum::X); + foo(z); + } + } + + { + struct S(i32); + + fn fun(_val: Option) {} + let val = Some(S(42)); + // we need the reference here because later val is consumed by fun() + let _res = matches!(&val, &Some(ref _a)); + fun(val); + } + + { + struct S(i32); + + fn fun(_val: Option) {} + let val = Some(S(42)); + let _res = matches!(&val, &Some(ref _a)); + fun(val); + } +} diff --git a/src/tools/clippy/tests/ui/match_expr_like_matches_macro.rs b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.rs new file mode 100644 index 0000000000..2ef6cf4238 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.rs @@ -0,0 +1,184 @@ +// run-rustfix + +#![warn(clippy::match_like_matches_macro)] +#![allow(unreachable_patterns, dead_code)] + +fn main() { + let x = Some(5); + + // Lint + let _y = match x { + Some(0) => true, + _ => false, + }; + + // Lint + let _w = match x { + Some(_) => true, + _ => false, + }; + + // Turn into is_none + let _z = match x { + Some(_) => false, + None => true, + }; + + // Lint + let _zz = match x { + Some(r) if r == 0 => false, + _ => true, + }; + + // Lint + let _zzz = if let Some(5) = x { true } else { false }; + + // No lint + let _a = match x { + Some(_) => false, + _ => false, + }; + + // No lint + let _ab = match x { + Some(0) => false, + _ => true, + None => false, + }; + + enum E { + A(u32), + B(i32), + C, + D, + } + let x = E::A(2); + { + // lint + let _ans = match x { + E::A(_) => true, + E::B(_) => true, + _ => false, + }; + } + { + // lint + let _ans = match x { + E::B(_) => false, + E::C => false, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(_) => false, + E::B(_) => false, + E::C => true, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(_) => true, + E::B(_) => false, + E::C => false, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(a) if a < 10 => false, + E::B(a) if a < 10 => false, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(_) => false, + E::B(a) if a < 10 => false, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(a) => a == 10, + E::B(_) => false, + _ => true, + }; + } + { + // no lint + let _ans = match x { + E::A(_) => false, + E::B(_) => true, + _ => false, + }; + } + + { + // should print "z" in suggestion (#6503) + let z = &Some(3); + let _z = match &z { + Some(3) => true, + _ => false, + }; + } + + { + // this could also print "z" in suggestion..? + let z = Some(3); + let _z = match &z { + Some(3) => true, + _ => false, + }; + } + + { + enum AnEnum { + X, + Y, + } + + fn foo(_x: AnEnum) {} + + fn main() { + let z = AnEnum::X; + // we can't remove the reference here! + let _ = match &z { + AnEnum::X => true, + _ => false, + }; + foo(z); + } + } + + { + struct S(i32); + + fn fun(_val: Option) {} + let val = Some(S(42)); + // we need the reference here because later val is consumed by fun() + let _res = match &val { + &Some(ref _a) => true, + _ => false, + }; + fun(val); + } + + { + struct S(i32); + + fn fun(_val: Option) {} + let val = Some(S(42)); + let _res = match &val { + &Some(ref _a) => true, + _ => false, + }; + fun(val); + } +} diff --git a/src/tools/clippy/tests/ui/match_expr_like_matches_macro.stderr b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.stderr new file mode 100644 index 0000000000..f27b4e9cb2 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.stderr @@ -0,0 +1,157 @@ +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:10:14 + | +LL | let _y = match x { + | ______________^ +LL | | Some(0) => true, +LL | | _ => false, +LL | | }; + | |_____^ help: try this: `matches!(x, Some(0))` + | + = note: `-D clippy::match-like-matches-macro` implied by `-D warnings` + +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:16:14 + | +LL | let _w = match x { + | ______________^ +LL | | Some(_) => true, +LL | | _ => false, +LL | | }; + | |_____^ help: try this: `matches!(x, Some(_))` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/match_expr_like_matches_macro.rs:22:14 + | +LL | let _z = match x { + | ______________^ +LL | | Some(_) => false, +LL | | None => true, +LL | | }; + | |_____^ help: try this: `x.is_none()` + | + = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` + +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:28:15 + | +LL | let _zz = match x { + | _______________^ +LL | | Some(r) if r == 0 => false, +LL | | _ => true, +LL | | }; + | |_____^ help: try this: `!matches!(x, Some(r) if r == 0)` + +error: if let .. else expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:34:16 + | +LL | let _zzz = if let Some(5) = x { true } else { false }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `matches!(x, Some(5))` + +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:58:20 + | +LL | let _ans = match x { + | ____________________^ +LL | | E::A(_) => true, +LL | | E::B(_) => true, +LL | | _ => false, +LL | | }; + | |_________^ help: try this: `matches!(x, E::A(_) | E::B(_))` + +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:66:20 + | +LL | let _ans = match x { + | ____________________^ +LL | | E::B(_) => false, +LL | | E::C => false, +LL | | _ => true, +LL | | }; + | |_________^ help: try this: `!matches!(x, E::B(_) | E::C)` + +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:126:18 + | +LL | let _z = match &z { + | __________________^ +LL | | Some(3) => true, +LL | | _ => false, +LL | | }; + | |_________^ help: try this: `matches!(z, Some(3))` + +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:135:18 + | +LL | let _z = match &z { + | __________________^ +LL | | Some(3) => true, +LL | | _ => false, +LL | | }; + | |_________^ help: try this: `matches!(&z, Some(3))` + +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:152:21 + | +LL | let _ = match &z { + | _____________________^ +LL | | AnEnum::X => true, +LL | | _ => false, +LL | | }; + | |_____________^ help: try this: `matches!(&z, AnEnum::X)` + +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:166:20 + | +LL | let _res = match &val { + | ____________________^ +LL | | &Some(ref _a) => true, +LL | | _ => false, +LL | | }; + | |_________^ help: try this: `matches!(&val, &Some(ref _a))` + +error: you don't need to add `&` to both the expression and the patterns + --> $DIR/match_expr_like_matches_macro.rs:166:20 + | +LL | let _res = match &val { + | ____________________^ +LL | | &Some(ref _a) => true, +LL | | _ => false, +LL | | }; + | |_________^ + | + = note: `-D clippy::match-ref-pats` implied by `-D warnings` +help: try + | +LL | let _res = match val { +LL | Some(ref _a) => true, + | + +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:178:20 + | +LL | let _res = match &val { + | ____________________^ +LL | | &Some(ref _a) => true, +LL | | _ => false, +LL | | }; + | |_________^ help: try this: `matches!(&val, &Some(ref _a))` + +error: you don't need to add `&` to both the expression and the patterns + --> $DIR/match_expr_like_matches_macro.rs:178:20 + | +LL | let _res = match &val { + | ____________________^ +LL | | &Some(ref _a) => true, +LL | | _ => false, +LL | | }; + | |_________^ + | +help: try + | +LL | let _res = match val { +LL | Some(ref _a) => true, + | + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/match_on_vec_items.rs b/src/tools/clippy/tests/ui/match_on_vec_items.rs new file mode 100644 index 0000000000..30415e3b94 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_on_vec_items.rs @@ -0,0 +1,152 @@ +#![warn(clippy::match_on_vec_items)] + +fn match_with_wildcard() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 1; + + // Lint, may panic + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => {}, + } + + // Lint, may panic + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => {}, + } +} + +fn match_without_wildcard() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 2; + + // Lint, may panic + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + num => {}, + } + + // Lint, may panic + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + [ref sub @ ..] => {}, + } +} + +fn match_wildcard_and_action() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Lint, may panic + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => println!("Hello, World!"), + } + + // Lint, may panic + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => println!("Hello, World!"), + } +} + +fn match_vec_ref() { + let arr = &vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Lint, may panic + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => {}, + } + + // Lint, may panic + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => {}, + } +} + +fn match_with_get() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Ok + match arr.get(idx) { + Some(0) => println!("0"), + Some(1) => println!("1"), + _ => {}, + } + + // Ok + match arr.get(range) { + Some(&[0, 1]) => println!("0 1"), + Some(&[1, 2]) => println!("1 2"), + _ => {}, + } +} + +fn match_with_array() { + let arr = [0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Ok + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => {}, + } + + // Ok + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => {}, + } +} + +fn match_with_endless_range() { + let arr = vec![0, 1, 2, 3]; + let range = ..; + + // Ok + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + [0, 1, 2, 3] => println!("0, 1, 2, 3"), + _ => {}, + } + + // Ok + match arr[..] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + [0, 1, 2, 3] => println!("0, 1, 2, 3"), + _ => {}, + } +} + +fn main() { + match_with_wildcard(); + match_without_wildcard(); + match_wildcard_and_action(); + match_vec_ref(); + match_with_get(); + match_with_array(); + match_with_endless_range(); +} diff --git a/src/tools/clippy/tests/ui/match_on_vec_items.stderr b/src/tools/clippy/tests/ui/match_on_vec_items.stderr new file mode 100644 index 0000000000..49446d715a --- /dev/null +++ b/src/tools/clippy/tests/ui/match_on_vec_items.stderr @@ -0,0 +1,52 @@ +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:9:11 + | +LL | match arr[idx] { + | ^^^^^^^^ help: try this: `arr.get(idx)` + | + = note: `-D clippy::match-on-vec-items` implied by `-D warnings` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:16:11 + | +LL | match arr[range] { + | ^^^^^^^^^^ help: try this: `arr.get(range)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:29:11 + | +LL | match arr[idx] { + | ^^^^^^^^ help: try this: `arr.get(idx)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:36:11 + | +LL | match arr[range] { + | ^^^^^^^^^^ help: try this: `arr.get(range)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:49:11 + | +LL | match arr[idx] { + | ^^^^^^^^ help: try this: `arr.get(idx)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:56:11 + | +LL | match arr[range] { + | ^^^^^^^^^^ help: try this: `arr.get(range)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:69:11 + | +LL | match arr[idx] { + | ^^^^^^^^ help: try this: `arr.get(idx)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:76:11 + | +LL | match arr[range] { + | ^^^^^^^^^^ help: try this: `arr.get(range)` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/match_overlapping_arm.rs b/src/tools/clippy/tests/ui/match_overlapping_arm.rs new file mode 100644 index 0000000000..44c51e8112 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_overlapping_arm.rs @@ -0,0 +1,112 @@ +#![feature(exclusive_range_pattern)] +#![feature(half_open_range_patterns)] +#![warn(clippy::match_overlapping_arm)] +#![allow(clippy::redundant_pattern_matching)] + +/// Tests for match_overlapping_arm + +fn overlapping() { + const FOO: u64 = 2; + + match 42 { + 0..=10 => println!("0 ... 10"), + 0..=11 => println!("0 ... 11"), + _ => (), + } + + match 42 { + 0..=5 => println!("0 ... 5"), + 6..=7 => println!("6 ... 7"), + FOO..=11 => println!("0 ... 11"), + _ => (), + } + + match 42 { + 2 => println!("2"), + 0..=5 => println!("0 ... 5"), + _ => (), + } + + match 42 { + 2 => println!("2"), + 0..=2 => println!("0 ... 2"), + _ => (), + } + + match 42 { + 0..=10 => println!("0 ... 10"), + 11..=50 => println!("11 ... 50"), + _ => (), + } + + match 42 { + 2 => println!("2"), + 0..2 => println!("0 .. 2"), + _ => (), + } + + match 42 { + 0..10 => println!("0 .. 10"), + 10..50 => println!("10 .. 50"), + _ => (), + } + + match 42 { + 0..11 => println!("0 .. 11"), + 0..=11 => println!("0 ... 11"), + _ => (), + } + + match 42 { + 5..7 => println!("5 .. 7"), + 0..10 => println!("0 .. 10"), + _ => (), + } + + match 42 { + 5..10 => println!("5 .. 10"), + 0..=10 => println!("0 ... 10"), + _ => (), + } + + match 42 { + 0..14 => println!("0 .. 14"), + 5..10 => println!("5 .. 10"), + _ => (), + } + + match 42 { + 5..14 => println!("5 .. 14"), + 0..=10 => println!("0 ... 10"), + _ => (), + } + + match 42 { + 0..7 => println!("0 .. 7"), + 0..=10 => println!("0 ... 10"), + _ => (), + } + + /* + // FIXME(JohnTitor): uncomment this once rustfmt knows half-open patterns + match 42 { + 0.. => println!("0 .. 42"), + 3.. => println!("3 .. 42"), + _ => (), + } + + match 42 { + ..=23 => println!("0 ... 23"), + ..26 => println!("0 .. 26"), + _ => (), + } + */ + + if let None = Some(42) { + // nothing + } else if let None = Some(42) { + // another nothing :-) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_overlapping_arm.stderr b/src/tools/clippy/tests/ui/match_overlapping_arm.stderr new file mode 100644 index 0000000000..f25a66d634 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_overlapping_arm.stderr @@ -0,0 +1,63 @@ +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:12:9 + | +LL | 0..=10 => println!("0 ... 10"), + | ^^^^^^ + | + = note: `-D clippy::match-overlapping-arm` implied by `-D warnings` +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:13:9 + | +LL | 0..=11 => println!("0 ... 11"), + | ^^^^^^ + +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:18:9 + | +LL | 0..=5 => println!("0 ... 5"), + | ^^^^^ + | +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:20:9 + | +LL | FOO..=11 => println!("0 ... 11"), + | ^^^^^^^^ + +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:55:9 + | +LL | 0..11 => println!("0 .. 11"), + | ^^^^^ + | +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:56:9 + | +LL | 0..=11 => println!("0 ... 11"), + | ^^^^^^ + +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:80:9 + | +LL | 0..=10 => println!("0 ... 10"), + | ^^^^^^ + | +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:79:9 + | +LL | 5..14 => println!("5 .. 14"), + | ^^^^^ + +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:85:9 + | +LL | 0..7 => println!("0 .. 7"), + | ^^^^ + | +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:86:9 + | +LL | 0..=10 => println!("0 ... 10"), + | ^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/match_ref_pats.rs b/src/tools/clippy/tests/ui/match_ref_pats.rs new file mode 100644 index 0000000000..5de43733ad --- /dev/null +++ b/src/tools/clippy/tests/ui/match_ref_pats.rs @@ -0,0 +1,74 @@ +#![warn(clippy::match_ref_pats)] + +fn ref_pats() { + { + let v = &Some(0); + match v { + &Some(v) => println!("{:?}", v), + &None => println!("none"), + } + match v { + // This doesn't trigger; we have a different pattern. + &Some(v) => println!("some"), + other => println!("other"), + } + } + let tup = &(1, 2); + match tup { + &(v, 1) => println!("{}", v), + _ => println!("none"), + } + // Special case: using `&` both in expr and pats. + let w = Some(0); + match &w { + &Some(v) => println!("{:?}", v), + &None => println!("none"), + } + // False positive: only wildcard pattern. + let w = Some(0); + #[allow(clippy::match_single_binding)] + match w { + _ => println!("none"), + } + + let a = &Some(0); + if let &None = a { + println!("none"); + } + + let b = Some(0); + if let &None = &b { + println!("none"); + } +} + +mod ice_3719 { + macro_rules! foo_variant( + ($idx:expr) => (Foo::get($idx).unwrap()) + ); + + enum Foo { + A, + B, + } + + impl Foo { + fn get(idx: u8) -> Option<&'static Self> { + match idx { + 0 => Some(&Foo::A), + 1 => Some(&Foo::B), + _ => None, + } + } + } + + fn ice_3719() { + // ICE #3719 + match foo_variant!(0) { + &Foo::A => println!("A"), + _ => println!("Wild"), + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_ref_pats.stderr b/src/tools/clippy/tests/ui/match_ref_pats.stderr new file mode 100644 index 0000000000..52cb4a14b7 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_ref_pats.stderr @@ -0,0 +1,91 @@ +error: you don't need to add `&` to all patterns + --> $DIR/match_ref_pats.rs:6:9 + | +LL | / match v { +LL | | &Some(v) => println!("{:?}", v), +LL | | &None => println!("none"), +LL | | } + | |_________^ + | + = note: `-D clippy::match-ref-pats` implied by `-D warnings` +help: instead of prefixing all patterns with `&`, you can dereference the expression + | +LL | match *v { +LL | Some(v) => println!("{:?}", v), +LL | None => println!("none"), + | + +error: you don't need to add `&` to all patterns + --> $DIR/match_ref_pats.rs:17:5 + | +LL | / match tup { +LL | | &(v, 1) => println!("{}", v), +LL | | _ => println!("none"), +LL | | } + | |_____^ + | +help: instead of prefixing all patterns with `&`, you can dereference the expression + | +LL | match *tup { +LL | (v, 1) => println!("{}", v), + | + +error: you don't need to add `&` to both the expression and the patterns + --> $DIR/match_ref_pats.rs:23:5 + | +LL | / match &w { +LL | | &Some(v) => println!("{:?}", v), +LL | | &None => println!("none"), +LL | | } + | |_____^ + | +help: try + | +LL | match w { +LL | Some(v) => println!("{:?}", v), +LL | None => println!("none"), + | + +error: you don't need to add `&` to all patterns + --> $DIR/match_ref_pats.rs:35:5 + | +LL | / if let &None = a { +LL | | println!("none"); +LL | | } + | |_____^ + | +help: instead of prefixing all patterns with `&`, you can dereference the expression + | +LL | if let None = *a { + | ^^^^ ^^ + +error: you don't need to add `&` to both the expression and the patterns + --> $DIR/match_ref_pats.rs:40:5 + | +LL | / if let &None = &b { +LL | | println!("none"); +LL | | } + | |_____^ + | +help: try + | +LL | if let None = b { + | ^^^^ ^ + +error: you don't need to add `&` to all patterns + --> $DIR/match_ref_pats.rs:67:9 + | +LL | / match foo_variant!(0) { +LL | | &Foo::A => println!("A"), +LL | | _ => println!("Wild"), +LL | | } + | |_________^ + | +help: instead of prefixing all patterns with `&`, you can dereference the expression + | +LL | match *foo_variant!(0) { +LL | Foo::A => println!("A"), + | + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/match_same_arms.rs b/src/tools/clippy/tests/ui/match_same_arms.rs new file mode 100644 index 0000000000..0b9342c9c4 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_same_arms.rs @@ -0,0 +1,56 @@ +#![warn(clippy::match_same_arms)] + +pub enum Abc { + A, + B, + C, +} + +fn match_same_arms() { + let _ = match Abc::A { + Abc::A => 0, + Abc::B => 1, + _ => 0, //~ ERROR match arms have same body + }; + + match (1, 2, 3) { + (1, .., 3) => 42, + (.., 3) => 42, //~ ERROR match arms have same body + _ => 0, + }; + + let _ = match 42 { + 42 => 1, + 51 => 1, //~ ERROR match arms have same body + 41 => 2, + 52 => 2, //~ ERROR match arms have same body + _ => 0, + }; + + let _ = match 42 { + 1 => 2, + 2 => 2, //~ ERROR 2nd matched arms have same body + 3 => 2, //~ ERROR 3rd matched arms have same body + 4 => 3, + _ => 0, + }; +} + +mod issue4244 { + #[derive(PartialEq, PartialOrd, Eq, Ord)] + pub enum CommandInfo { + BuiltIn { name: String, about: Option }, + External { name: String, path: std::path::PathBuf }, + } + + impl CommandInfo { + pub fn name(&self) -> String { + match self { + CommandInfo::BuiltIn { name, .. } => name.to_string(), + CommandInfo::External { name, .. } => name.to_string(), + } + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_same_arms.stderr b/src/tools/clippy/tests/ui/match_same_arms.stderr new file mode 100644 index 0000000000..0549886a1e --- /dev/null +++ b/src/tools/clippy/tests/ui/match_same_arms.stderr @@ -0,0 +1,139 @@ +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:13:14 + | +LL | _ => 0, //~ ERROR match arms have same body + | ^ + | + = note: `-D clippy::match-same-arms` implied by `-D warnings` +note: same as this + --> $DIR/match_same_arms.rs:11:19 + | +LL | Abc::A => 0, + | ^ +note: `Abc::A` has the same arm body as the `_` wildcard, consider removing it + --> $DIR/match_same_arms.rs:11:19 + | +LL | Abc::A => 0, + | ^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:18:20 + | +LL | (.., 3) => 42, //~ ERROR match arms have same body + | ^^ + | +note: same as this + --> $DIR/match_same_arms.rs:17:23 + | +LL | (1, .., 3) => 42, + | ^^ +help: consider refactoring into `(1, .., 3) | (.., 3)` + --> $DIR/match_same_arms.rs:17:9 + | +LL | (1, .., 3) => 42, + | ^^^^^^^^^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:24:15 + | +LL | 51 => 1, //~ ERROR match arms have same body + | ^ + | +note: same as this + --> $DIR/match_same_arms.rs:23:15 + | +LL | 42 => 1, + | ^ +help: consider refactoring into `42 | 51` + --> $DIR/match_same_arms.rs:23:9 + | +LL | 42 => 1, + | ^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:26:15 + | +LL | 52 => 2, //~ ERROR match arms have same body + | ^ + | +note: same as this + --> $DIR/match_same_arms.rs:25:15 + | +LL | 41 => 2, + | ^ +help: consider refactoring into `41 | 52` + --> $DIR/match_same_arms.rs:25:9 + | +LL | 41 => 2, + | ^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:32:14 + | +LL | 2 => 2, //~ ERROR 2nd matched arms have same body + | ^ + | +note: same as this + --> $DIR/match_same_arms.rs:31:14 + | +LL | 1 => 2, + | ^ +help: consider refactoring into `1 | 2` + --> $DIR/match_same_arms.rs:31:9 + | +LL | 1 => 2, + | ^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:33:14 + | +LL | 3 => 2, //~ ERROR 3rd matched arms have same body + | ^ + | +note: same as this + --> $DIR/match_same_arms.rs:31:14 + | +LL | 1 => 2, + | ^ +help: consider refactoring into `1 | 3` + --> $DIR/match_same_arms.rs:31:9 + | +LL | 1 => 2, + | ^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:33:14 + | +LL | 3 => 2, //~ ERROR 3rd matched arms have same body + | ^ + | +note: same as this + --> $DIR/match_same_arms.rs:32:14 + | +LL | 2 => 2, //~ ERROR 2nd matched arms have same body + | ^ +help: consider refactoring into `2 | 3` + --> $DIR/match_same_arms.rs:32:9 + | +LL | 2 => 2, //~ ERROR 2nd matched arms have same body + | ^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:50:55 + | +LL | CommandInfo::External { name, .. } => name.to_string(), + | ^^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/match_same_arms.rs:49:54 + | +LL | CommandInfo::BuiltIn { name, .. } => name.to_string(), + | ^^^^^^^^^^^^^^^^ +help: consider refactoring into `CommandInfo::BuiltIn { name, .. } | CommandInfo::External { name, .. }` + --> $DIR/match_same_arms.rs:49:17 + | +LL | CommandInfo::BuiltIn { name, .. } => name.to_string(), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/match_same_arms2.rs b/src/tools/clippy/tests/ui/match_same_arms2.rs new file mode 100644 index 0000000000..da4e3020d5 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_same_arms2.rs @@ -0,0 +1,169 @@ +#![warn(clippy::match_same_arms)] +#![allow(clippy::blacklisted_name)] + +fn bar(_: T) {} +fn foo() -> bool { + unimplemented!() +} + +fn match_same_arms() { + let _ = match 42 { + 42 => { + foo(); + let mut a = 42 + [23].len() as i32; + if true { + a += 7; + } + a = -31 - a; + a + }, + _ => { + //~ ERROR match arms have same body + foo(); + let mut a = 42 + [23].len() as i32; + if true { + a += 7; + } + a = -31 - a; + a + }, + }; + + let _ = match 42 { + 42 => foo(), + 51 => foo(), //~ ERROR match arms have same body + _ => true, + }; + + let _ = match Some(42) { + Some(_) => 24, + None => 24, //~ ERROR match arms have same body + }; + + let _ = match Some(42) { + Some(foo) => 24, + None => 24, + }; + + let _ = match Some(42) { + Some(42) => 24, + Some(a) => 24, // bindings are different + None => 0, + }; + + let _ = match Some(42) { + Some(a) if a > 0 => 24, + Some(a) => 24, // one arm has a guard + None => 0, + }; + + match (Some(42), Some(42)) { + (Some(a), None) => bar(a), + (None, Some(a)) => bar(a), //~ ERROR match arms have same body + _ => (), + } + + match (Some(42), Some(42)) { + (Some(a), ..) => bar(a), + (.., Some(a)) => bar(a), //~ ERROR match arms have same body + _ => (), + } + + let _ = match Some(()) { + Some(()) => 0.0, + None => -0.0, + }; + + match (Some(42), Some("")) { + (Some(a), None) => bar(a), + (None, Some(a)) => bar(a), // bindings have different types + _ => (), + } + + let x: Result = Ok(3); + + // No warning because of the guard. + match x { + Ok(x) if x * x == 64 => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => println!("err"), + } + + // This used to be a false positive; see issue #1996. + match x { + Ok(3) => println!("ok"), + Ok(x) if x * x == 64 => println!("ok 64"), + Ok(_) => println!("ok"), + Err(_) => println!("err"), + } + + match (x, Some(1i32)) { + (Ok(x), Some(_)) => println!("ok {}", x), + (Ok(_), Some(x)) => println!("ok {}", x), + _ => println!("err"), + } + + // No warning; different types for `x`. + match (x, Some(1.0f64)) { + (Ok(x), Some(_)) => println!("ok {}", x), + (Ok(_), Some(x)) => println!("ok {}", x), + _ => println!("err"), + } + + // False negative #2251. + match x { + Ok(_tmp) => println!("ok"), + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => { + unreachable!(); + }, + } + + // False positive #1390 + macro_rules! empty { + ($e:expr) => {}; + } + match 0 { + 0 => { + empty!(0); + }, + 1 => { + empty!(1); + }, + x => { + empty!(x); + }, + }; + + // still lint if the tokens are the same + match 0 { + 0 => { + empty!(0); + }, + 1 => { + empty!(0); + }, + x => { + empty!(x); + }, + } + + match_expr_like_matches_macro_priority(); +} + +fn match_expr_like_matches_macro_priority() { + enum E { + A, + B, + C, + } + let x = E::A; + let _ans = match x { + E::A => false, + E::B => false, + _ => true, + }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_same_arms2.stderr b/src/tools/clippy/tests/ui/match_same_arms2.stderr new file mode 100644 index 0000000000..95f9494cdc --- /dev/null +++ b/src/tools/clippy/tests/ui/match_same_arms2.stderr @@ -0,0 +1,181 @@ +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:20:14 + | +LL | _ => { + | ______________^ +LL | | //~ ERROR match arms have same body +LL | | foo(); +LL | | let mut a = 42 + [23].len() as i32; +... | +LL | | a +LL | | }, + | |_________^ + | + = note: `-D clippy::match-same-arms` implied by `-D warnings` +note: same as this + --> $DIR/match_same_arms2.rs:11:15 + | +LL | 42 => { + | _______________^ +LL | | foo(); +LL | | let mut a = 42 + [23].len() as i32; +LL | | if true { +... | +LL | | a +LL | | }, + | |_________^ +note: `42` has the same arm body as the `_` wildcard, consider removing it + --> $DIR/match_same_arms2.rs:11:15 + | +LL | 42 => { + | _______________^ +LL | | foo(); +LL | | let mut a = 42 + [23].len() as i32; +LL | | if true { +... | +LL | | a +LL | | }, + | |_________^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:34:15 + | +LL | 51 => foo(), //~ ERROR match arms have same body + | ^^^^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:33:15 + | +LL | 42 => foo(), + | ^^^^^ +help: consider refactoring into `42 | 51` + --> $DIR/match_same_arms2.rs:33:9 + | +LL | 42 => foo(), + | ^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:40:17 + | +LL | None => 24, //~ ERROR match arms have same body + | ^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:39:20 + | +LL | Some(_) => 24, + | ^^ +help: consider refactoring into `Some(_) | None` + --> $DIR/match_same_arms2.rs:39:9 + | +LL | Some(_) => 24, + | ^^^^^^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:62:28 + | +LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body + | ^^^^^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:61:28 + | +LL | (Some(a), None) => bar(a), + | ^^^^^^ +help: consider refactoring into `(Some(a), None) | (None, Some(a))` + --> $DIR/match_same_arms2.rs:61:9 + | +LL | (Some(a), None) => bar(a), + | ^^^^^^^^^^^^^^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:68:26 + | +LL | (.., Some(a)) => bar(a), //~ ERROR match arms have same body + | ^^^^^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:67:26 + | +LL | (Some(a), ..) => bar(a), + | ^^^^^^ +help: consider refactoring into `(Some(a), ..) | (.., Some(a))` + --> $DIR/match_same_arms2.rs:67:9 + | +LL | (Some(a), ..) => bar(a), + | ^^^^^^^^^^^^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:102:29 + | +LL | (Ok(_), Some(x)) => println!("ok {}", x), + | ^^^^^^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:101:29 + | +LL | (Ok(x), Some(_)) => println!("ok {}", x), + | ^^^^^^^^^^^^^^^^^^^^ +help: consider refactoring into `(Ok(x), Some(_)) | (Ok(_), Some(x))` + --> $DIR/match_same_arms2.rs:101:9 + | +LL | (Ok(x), Some(_)) => println!("ok {}", x), + | ^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:117:18 + | +LL | Ok(_) => println!("ok"), + | ^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:116:18 + | +LL | Ok(3) => println!("ok"), + | ^^^^^^^^^^^^^^ +help: consider refactoring into `Ok(3) | Ok(_)` + --> $DIR/match_same_arms2.rs:116:9 + | +LL | Ok(3) => println!("ok"), + | ^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:144:14 + | +LL | 1 => { + | ______________^ +LL | | empty!(0); +LL | | }, + | |_________^ + | +note: same as this + --> $DIR/match_same_arms2.rs:141:14 + | +LL | 0 => { + | ______________^ +LL | | empty!(0); +LL | | }, + | |_________^ +help: consider refactoring into `0 | 1` + --> $DIR/match_same_arms2.rs:141:9 + | +LL | 0 => { + | ^ + +error: match expression looks like `matches!` macro + --> $DIR/match_same_arms2.rs:162:16 + | +LL | let _ans = match x { + | ________________^ +LL | | E::A => false, +LL | | E::B => false, +LL | | _ => true, +LL | | }; + | |_____^ help: try this: `!matches!(x, E::A | E::B)` + | + = note: `-D clippy::match-like-matches-macro` implied by `-D warnings` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/match_single_binding.fixed b/src/tools/clippy/tests/ui/match_single_binding.fixed new file mode 100644 index 0000000000..526e94b10b --- /dev/null +++ b/src/tools/clippy/tests/ui/match_single_binding.fixed @@ -0,0 +1,118 @@ +// run-rustfix + +#![warn(clippy::match_single_binding)] +#![allow(unused_variables, clippy::many_single_char_names, clippy::toplevel_ref_arg)] + +struct Point { + x: i32, + y: i32, +} + +fn coords() -> Point { + Point { x: 1, y: 2 } +} + +macro_rules! foo { + ($param:expr) => { + match $param { + _ => println!("whatever"), + } + }; +} + +fn main() { + let a = 1; + let b = 2; + let c = 3; + // Lint + let (x, y, z) = (a, b, c); + { + println!("{} {} {}", x, y, z); + } + // Lint + let (x, y, z) = (a, b, c); + println!("{} {} {}", x, y, z); + // Ok + foo!(a); + // Ok + match a { + 2 => println!("2"), + _ => println!("Not 2"), + } + // Ok + let d = Some(5); + match d { + Some(d) => println!("{}", d), + _ => println!("None"), + } + // Lint + println!("whatever"); + // Lint + { + let x = 29; + println!("x has a value of {}", x); + } + // Lint + { + let e = 5 * a; + if e >= 5 { + println!("e is superior to 5"); + } + } + // Lint + let p = Point { x: 0, y: 7 }; + let Point { x, y } = p; + println!("Coords: ({}, {})", x, y); + // Lint + let Point { x: x1, y: y1 } = p; + println!("Coords: ({}, {})", x1, y1); + // Lint + let x = 5; + let ref r = x; + println!("Got a reference to {}", r); + // Lint + let mut x = 5; + let ref mut mr = x; + println!("Got a mutable reference to {}", mr); + // Lint + let Point { x, y } = coords(); + let product = x * y; + // Lint + let v = vec![Some(1), Some(2), Some(3), Some(4)]; + #[allow(clippy::let_and_return)] + let _ = v + .iter() + .map(|i| { + let unwrapped = i.unwrap(); + unwrapped + }) + .collect::>(); + // Ok + let x = 1; + match x { + #[cfg(disabled_feature)] + 0 => println!("Disabled branch"), + _ => println!("Enabled branch"), + } + // Lint + let x = 1; + let y = 1; + println!("Single branch"); + // Ok + let x = 1; + let y = 1; + match match y { + 0 => 1, + _ => 2, + } { + #[cfg(disabled_feature)] + 0 => println!("Array index start"), + _ => println!("Not an array index start"), + } + // False negative + let x = 1; + match x { + // => + _ => println!("Not an array index start"), + } +} diff --git a/src/tools/clippy/tests/ui/match_single_binding.rs b/src/tools/clippy/tests/ui/match_single_binding.rs new file mode 100644 index 0000000000..6a2ca7c5e9 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_single_binding.rs @@ -0,0 +1,135 @@ +// run-rustfix + +#![warn(clippy::match_single_binding)] +#![allow(unused_variables, clippy::many_single_char_names, clippy::toplevel_ref_arg)] + +struct Point { + x: i32, + y: i32, +} + +fn coords() -> Point { + Point { x: 1, y: 2 } +} + +macro_rules! foo { + ($param:expr) => { + match $param { + _ => println!("whatever"), + } + }; +} + +fn main() { + let a = 1; + let b = 2; + let c = 3; + // Lint + match (a, b, c) { + (x, y, z) => { + println!("{} {} {}", x, y, z); + }, + } + // Lint + match (a, b, c) { + (x, y, z) => println!("{} {} {}", x, y, z), + } + // Ok + foo!(a); + // Ok + match a { + 2 => println!("2"), + _ => println!("Not 2"), + } + // Ok + let d = Some(5); + match d { + Some(d) => println!("{}", d), + _ => println!("None"), + } + // Lint + match a { + _ => println!("whatever"), + } + // Lint + match a { + _ => { + let x = 29; + println!("x has a value of {}", x); + }, + } + // Lint + match a { + _ => { + let e = 5 * a; + if e >= 5 { + println!("e is superior to 5"); + } + }, + } + // Lint + let p = Point { x: 0, y: 7 }; + match p { + Point { x, y } => println!("Coords: ({}, {})", x, y), + } + // Lint + match p { + Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1), + } + // Lint + let x = 5; + match x { + ref r => println!("Got a reference to {}", r), + } + // Lint + let mut x = 5; + match x { + ref mut mr => println!("Got a mutable reference to {}", mr), + } + // Lint + let product = match coords() { + Point { x, y } => x * y, + }; + // Lint + let v = vec![Some(1), Some(2), Some(3), Some(4)]; + #[allow(clippy::let_and_return)] + let _ = v + .iter() + .map(|i| match i.unwrap() { + unwrapped => unwrapped, + }) + .collect::>(); + // Ok + let x = 1; + match x { + #[cfg(disabled_feature)] + 0 => println!("Disabled branch"), + _ => println!("Enabled branch"), + } + // Lint + let x = 1; + let y = 1; + match match y { + 0 => 1, + _ => 2, + } { + _ => println!("Single branch"), + } + // Ok + let x = 1; + let y = 1; + match match y { + 0 => 1, + _ => 2, + } { + #[cfg(disabled_feature)] + 0 => println!("Array index start"), + _ => println!("Not an array index start"), + } + // False negative + let x = 1; + match x { + // => + _ => println!("Not an array index start"), + } +} diff --git a/src/tools/clippy/tests/ui/match_single_binding.stderr b/src/tools/clippy/tests/ui/match_single_binding.stderr new file mode 100644 index 0000000000..cbbf5d29c0 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_single_binding.stderr @@ -0,0 +1,182 @@ +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:28:5 + | +LL | / match (a, b, c) { +LL | | (x, y, z) => { +LL | | println!("{} {} {}", x, y, z); +LL | | }, +LL | | } + | |_____^ + | + = note: `-D clippy::match-single-binding` implied by `-D warnings` +help: consider using `let` statement + | +LL | let (x, y, z) = (a, b, c); +LL | { +LL | println!("{} {} {}", x, y, z); +LL | } + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:34:5 + | +LL | / match (a, b, c) { +LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | } + | |_____^ + | +help: consider using `let` statement + | +LL | let (x, y, z) = (a, b, c); +LL | println!("{} {} {}", x, y, z); + | + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:51:5 + | +LL | / match a { +LL | | _ => println!("whatever"), +LL | | } + | |_____^ help: consider using the match body instead: `println!("whatever");` + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:55:5 + | +LL | / match a { +LL | | _ => { +LL | | let x = 29; +LL | | println!("x has a value of {}", x); +LL | | }, +LL | | } + | |_____^ + | +help: consider using the match body instead + | +LL | { +LL | let x = 29; +LL | println!("x has a value of {}", x); +LL | } + | + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:62:5 + | +LL | / match a { +LL | | _ => { +LL | | let e = 5 * a; +LL | | if e >= 5 { +... | +LL | | }, +LL | | } + | |_____^ + | +help: consider using the match body instead + | +LL | { +LL | let e = 5 * a; +LL | if e >= 5 { +LL | println!("e is superior to 5"); +LL | } +LL | } + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:72:5 + | +LL | / match p { +LL | | Point { x, y } => println!("Coords: ({}, {})", x, y), +LL | | } + | |_____^ + | +help: consider using `let` statement + | +LL | let Point { x, y } = p; +LL | println!("Coords: ({}, {})", x, y); + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:76:5 + | +LL | / match p { +LL | | Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1), +LL | | } + | |_____^ + | +help: consider using `let` statement + | +LL | let Point { x: x1, y: y1 } = p; +LL | println!("Coords: ({}, {})", x1, y1); + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:81:5 + | +LL | / match x { +LL | | ref r => println!("Got a reference to {}", r), +LL | | } + | |_____^ + | +help: consider using `let` statement + | +LL | let ref r = x; +LL | println!("Got a reference to {}", r); + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:86:5 + | +LL | / match x { +LL | | ref mut mr => println!("Got a mutable reference to {}", mr), +LL | | } + | |_____^ + | +help: consider using `let` statement + | +LL | let ref mut mr = x; +LL | println!("Got a mutable reference to {}", mr); + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:90:5 + | +LL | / let product = match coords() { +LL | | Point { x, y } => x * y, +LL | | }; + | |______^ + | +help: consider using `let` statement + | +LL | let Point { x, y } = coords(); +LL | let product = x * y; + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:98:18 + | +LL | .map(|i| match i.unwrap() { + | __________________^ +LL | | unwrapped => unwrapped, +LL | | }) + | |_________^ + | +help: consider using `let` statement + | +LL | .map(|i| { +LL | let unwrapped = i.unwrap(); +LL | unwrapped +LL | }) + | + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:112:5 + | +LL | / match match y { +LL | | 0 => 1, +LL | | _ => 2, +LL | | } { +LL | | _ => println!("Single branch"), +LL | | } + | |_____^ help: consider using the match body instead: `println!("Single branch");` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/match_wild_err_arm.rs b/src/tools/clippy/tests/ui/match_wild_err_arm.rs new file mode 100644 index 0000000000..823be65efe --- /dev/null +++ b/src/tools/clippy/tests/ui/match_wild_err_arm.rs @@ -0,0 +1,65 @@ +#![feature(exclusive_range_pattern)] +#![allow(clippy::match_same_arms)] +#![warn(clippy::match_wild_err_arm)] + +fn match_wild_err_arm() { + let x: Result = Ok(3); + + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => panic!("err"), + } + + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => panic!(), + } + + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => { + panic!(); + }, + } + + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_e) => panic!(), + } + + // Allowed when used in `panic!`. + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_e) => panic!("{}", _e), + } + + // Allowed when not with `panic!` block. + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => println!("err"), + } + + // Allowed when used with `unreachable!`. + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => unreachable!(), + } + + // Allowed when used with `unreachable!`. + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => { + unreachable!(); + }, + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_wild_err_arm.stderr b/src/tools/clippy/tests/ui/match_wild_err_arm.stderr new file mode 100644 index 0000000000..6a2a02987d --- /dev/null +++ b/src/tools/clippy/tests/ui/match_wild_err_arm.stderr @@ -0,0 +1,35 @@ +error: `Err(_)` matches all errors + --> $DIR/match_wild_err_arm.rs:11:9 + | +LL | Err(_) => panic!("err"), + | ^^^^^^ + | + = note: `-D clippy::match-wild-err-arm` implied by `-D warnings` + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable + +error: `Err(_)` matches all errors + --> $DIR/match_wild_err_arm.rs:17:9 + | +LL | Err(_) => panic!(), + | ^^^^^^ + | + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable + +error: `Err(_)` matches all errors + --> $DIR/match_wild_err_arm.rs:23:9 + | +LL | Err(_) => { + | ^^^^^^ + | + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable + +error: `Err(_e)` matches all errors + --> $DIR/match_wild_err_arm.rs:31:9 + | +LL | Err(_e) => panic!(), + | ^^^^^^^ + | + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.fixed b/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.fixed new file mode 100644 index 0000000000..519200977a --- /dev/null +++ b/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.fixed @@ -0,0 +1,59 @@ +// run-rustfix + +#![warn(clippy::match_wildcard_for_single_variants)] +#![allow(dead_code)] + +enum Foo { + A, + B, + C, +} + +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), +} + +fn main() { + let f = Foo::A; + match f { + Foo::A => {}, + Foo::B => {}, + Foo::C => {}, + } + + let color = Color::Red; + + // check exhaustive bindings + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_r, _g, _b) => {}, + Color::Blue => {}, + } + + // check exhaustive wild + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(..) => {}, + Color::Blue => {}, + } + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_, _, _) => {}, + Color::Blue => {}, + } + + // shouldn't lint as there is one missing variant + // and one that isn't exhaustively covered + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(255, _, _) => {}, + _ => {}, + } +} diff --git a/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.rs b/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.rs new file mode 100644 index 0000000000..1df917e085 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.rs @@ -0,0 +1,59 @@ +// run-rustfix + +#![warn(clippy::match_wildcard_for_single_variants)] +#![allow(dead_code)] + +enum Foo { + A, + B, + C, +} + +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), +} + +fn main() { + let f = Foo::A; + match f { + Foo::A => {}, + Foo::B => {}, + _ => {}, + } + + let color = Color::Red; + + // check exhaustive bindings + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_r, _g, _b) => {}, + _ => {}, + } + + // check exhaustive wild + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(..) => {}, + _ => {}, + } + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_, _, _) => {}, + _ => {}, + } + + // shouldn't lint as there is one missing variant + // and one that isn't exhaustively covered + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(255, _, _) => {}, + _ => {}, + } +} diff --git a/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.stderr b/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.stderr new file mode 100644 index 0000000000..82790aa9e8 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.stderr @@ -0,0 +1,28 @@ +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:24:9 + | +LL | _ => {}, + | ^ help: try this: `Foo::C` + | + = note: `-D clippy::match-wildcard-for-single-variants` implied by `-D warnings` + +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:34:9 + | +LL | _ => {}, + | ^ help: try this: `Color::Blue` + +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:42:9 + | +LL | _ => {}, + | ^ help: try this: `Color::Blue` + +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:48:9 + | +LL | _ => {}, + | ^ help: try this: `Color::Blue` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/mem_discriminant.fixed b/src/tools/clippy/tests/ui/mem_discriminant.fixed new file mode 100644 index 0000000000..69a8f286d0 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_discriminant.fixed @@ -0,0 +1,45 @@ +// run-rustfix + +#![deny(clippy::mem_discriminant_non_enum)] + +use std::mem; + +enum Foo { + One(usize), + Two(u8), +} + +fn main() { + // bad + mem::discriminant(&Some(2)); + mem::discriminant(&None::); + mem::discriminant(&Foo::One(5)); + mem::discriminant(&Foo::Two(5)); + + let ro = &Some(3); + let rro = &ro; + mem::discriminant(ro); + mem::discriminant(*rro); + mem::discriminant(*rro); + + macro_rules! mem_discriminant_but_in_a_macro { + ($param:expr) => { + mem::discriminant($param) + }; + } + + mem_discriminant_but_in_a_macro!(*rro); + + let rrrrro = &&&rro; + mem::discriminant(****rrrrro); + mem::discriminant(****rrrrro); + + // ok + mem::discriminant(&Some(2)); + mem::discriminant(&None::); + mem::discriminant(&Foo::One(5)); + mem::discriminant(&Foo::Two(5)); + mem::discriminant(ro); + mem::discriminant(*rro); + mem::discriminant(****rrrrro); +} diff --git a/src/tools/clippy/tests/ui/mem_discriminant.rs b/src/tools/clippy/tests/ui/mem_discriminant.rs new file mode 100644 index 0000000000..55db50fcdc --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_discriminant.rs @@ -0,0 +1,45 @@ +// run-rustfix + +#![deny(clippy::mem_discriminant_non_enum)] + +use std::mem; + +enum Foo { + One(usize), + Two(u8), +} + +fn main() { + // bad + mem::discriminant(&&Some(2)); + mem::discriminant(&&None::); + mem::discriminant(&&Foo::One(5)); + mem::discriminant(&&Foo::Two(5)); + + let ro = &Some(3); + let rro = &ro; + mem::discriminant(&ro); + mem::discriminant(rro); + mem::discriminant(&rro); + + macro_rules! mem_discriminant_but_in_a_macro { + ($param:expr) => { + mem::discriminant($param) + }; + } + + mem_discriminant_but_in_a_macro!(&rro); + + let rrrrro = &&&rro; + mem::discriminant(&rrrrro); + mem::discriminant(*rrrrro); + + // ok + mem::discriminant(&Some(2)); + mem::discriminant(&None::); + mem::discriminant(&Foo::One(5)); + mem::discriminant(&Foo::Two(5)); + mem::discriminant(ro); + mem::discriminant(*rro); + mem::discriminant(****rrrrro); +} diff --git a/src/tools/clippy/tests/ui/mem_discriminant.stderr b/src/tools/clippy/tests/ui/mem_discriminant.stderr new file mode 100644 index 0000000000..8d9810970a --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_discriminant.stderr @@ -0,0 +1,94 @@ +error: calling `mem::discriminant` on non-enum type `&std::option::Option` + --> $DIR/mem_discriminant.rs:14:5 + | +LL | mem::discriminant(&&Some(2)); + | ^^^^^^^^^^^^^^^^^^---------^ + | | + | help: try dereferencing: `&Some(2)` + | +note: the lint level is defined here + --> $DIR/mem_discriminant.rs:3:9 + | +LL | #![deny(clippy::mem_discriminant_non_enum)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: calling `mem::discriminant` on non-enum type `&std::option::Option` + --> $DIR/mem_discriminant.rs:15:5 + | +LL | mem::discriminant(&&None::); + | ^^^^^^^^^^^^^^^^^^------------^ + | | + | help: try dereferencing: `&None::` + +error: calling `mem::discriminant` on non-enum type `&Foo` + --> $DIR/mem_discriminant.rs:16:5 + | +LL | mem::discriminant(&&Foo::One(5)); + | ^^^^^^^^^^^^^^^^^^-------------^ + | | + | help: try dereferencing: `&Foo::One(5)` + +error: calling `mem::discriminant` on non-enum type `&Foo` + --> $DIR/mem_discriminant.rs:17:5 + | +LL | mem::discriminant(&&Foo::Two(5)); + | ^^^^^^^^^^^^^^^^^^-------------^ + | | + | help: try dereferencing: `&Foo::Two(5)` + +error: calling `mem::discriminant` on non-enum type `&std::option::Option` + --> $DIR/mem_discriminant.rs:21:5 + | +LL | mem::discriminant(&ro); + | ^^^^^^^^^^^^^^^^^^---^ + | | + | help: try dereferencing: `ro` + +error: calling `mem::discriminant` on non-enum type `&std::option::Option` + --> $DIR/mem_discriminant.rs:22:5 + | +LL | mem::discriminant(rro); + | ^^^^^^^^^^^^^^^^^^---^ + | | + | help: try dereferencing: `*rro` + +error: calling `mem::discriminant` on non-enum type `&&std::option::Option` + --> $DIR/mem_discriminant.rs:23:5 + | +LL | mem::discriminant(&rro); + | ^^^^^^^^^^^^^^^^^^----^ + | | + | help: try dereferencing: `*rro` + +error: calling `mem::discriminant` on non-enum type `&&std::option::Option` + --> $DIR/mem_discriminant.rs:27:13 + | +LL | mem::discriminant($param) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mem_discriminant_but_in_a_macro!(&rro); + | --------------------------------------- + | | | + | | help: try dereferencing: `*rro` + | in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: calling `mem::discriminant` on non-enum type `&&&&&std::option::Option` + --> $DIR/mem_discriminant.rs:34:5 + | +LL | mem::discriminant(&rrrrro); + | ^^^^^^^^^^^^^^^^^^-------^ + | | + | help: try dereferencing: `****rrrrro` + +error: calling `mem::discriminant` on non-enum type `&&&std::option::Option` + --> $DIR/mem_discriminant.rs:35:5 + | +LL | mem::discriminant(*rrrrro); + | ^^^^^^^^^^^^^^^^^^-------^ + | | + | help: try dereferencing: `****rrrrro` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/mem_discriminant_unfixable.rs b/src/tools/clippy/tests/ui/mem_discriminant_unfixable.rs new file mode 100644 index 0000000000..e245d3257d --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_discriminant_unfixable.rs @@ -0,0 +1,16 @@ +#![deny(clippy::mem_discriminant_non_enum)] + +use std::mem; + +enum Foo { + One(usize), + Two(u8), +} + +struct A(Foo); + +fn main() { + // bad + mem::discriminant(&"hello"); + mem::discriminant(&A(Foo::One(0))); +} diff --git a/src/tools/clippy/tests/ui/mem_discriminant_unfixable.stderr b/src/tools/clippy/tests/ui/mem_discriminant_unfixable.stderr new file mode 100644 index 0000000000..e2de3776f2 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_discriminant_unfixable.stderr @@ -0,0 +1,20 @@ +error: calling `mem::discriminant` on non-enum type `&str` + --> $DIR/mem_discriminant_unfixable.rs:14:5 + | +LL | mem::discriminant(&"hello"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/mem_discriminant_unfixable.rs:1:9 + | +LL | #![deny(clippy::mem_discriminant_non_enum)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: calling `mem::discriminant` on non-enum type `A` + --> $DIR/mem_discriminant_unfixable.rs:15:5 + | +LL | mem::discriminant(&A(Foo::One(0))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/mem_forget.rs b/src/tools/clippy/tests/ui/mem_forget.rs new file mode 100644 index 0000000000..e5b35c098a --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_forget.rs @@ -0,0 +1,23 @@ +use std::rc::Rc; +use std::sync::Arc; + +use std::mem as memstuff; +use std::mem::forget as forgetSomething; + +#[warn(clippy::mem_forget)] +#[allow(clippy::forget_copy)] +fn main() { + let five: i32 = 5; + forgetSomething(five); + + let six: Arc = Arc::new(6); + memstuff::forget(six); + + let seven: Rc = Rc::new(7); + std::mem::forget(seven); + + let eight: Vec = vec![8]; + forgetSomething(eight); + + std::mem::forget(7); +} diff --git a/src/tools/clippy/tests/ui/mem_forget.stderr b/src/tools/clippy/tests/ui/mem_forget.stderr new file mode 100644 index 0000000000..a90d8b1655 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_forget.stderr @@ -0,0 +1,22 @@ +error: usage of `mem::forget` on `Drop` type + --> $DIR/mem_forget.rs:14:5 + | +LL | memstuff::forget(six); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::mem-forget` implied by `-D warnings` + +error: usage of `mem::forget` on `Drop` type + --> $DIR/mem_forget.rs:17:5 + | +LL | std::mem::forget(seven); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: usage of `mem::forget` on `Drop` type + --> $DIR/mem_forget.rs:20:5 + | +LL | forgetSomething(eight); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/mem_replace.fixed b/src/tools/clippy/tests/ui/mem_replace.fixed new file mode 100644 index 0000000000..54e962e711 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_replace.fixed @@ -0,0 +1,30 @@ +// run-rustfix +#![allow(unused_imports)] +#![warn( + clippy::all, + clippy::style, + clippy::mem_replace_option_with_none, + clippy::mem_replace_with_default +)] + +use std::mem; + +fn replace_option_with_none() { + let mut an_option = Some(1); + let _ = an_option.take(); + let an_option = &mut Some(1); + let _ = an_option.take(); +} + +fn replace_with_default() { + let mut s = String::from("foo"); + let _ = std::mem::take(&mut s); + let s = &mut String::from("foo"); + let _ = std::mem::take(s); + let _ = std::mem::take(s); +} + +fn main() { + replace_option_with_none(); + replace_with_default(); +} diff --git a/src/tools/clippy/tests/ui/mem_replace.rs b/src/tools/clippy/tests/ui/mem_replace.rs new file mode 100644 index 0000000000..60f5278107 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_replace.rs @@ -0,0 +1,30 @@ +// run-rustfix +#![allow(unused_imports)] +#![warn( + clippy::all, + clippy::style, + clippy::mem_replace_option_with_none, + clippy::mem_replace_with_default +)] + +use std::mem; + +fn replace_option_with_none() { + let mut an_option = Some(1); + let _ = mem::replace(&mut an_option, None); + let an_option = &mut Some(1); + let _ = mem::replace(an_option, None); +} + +fn replace_with_default() { + let mut s = String::from("foo"); + let _ = std::mem::replace(&mut s, String::default()); + let s = &mut String::from("foo"); + let _ = std::mem::replace(s, String::default()); + let _ = std::mem::replace(s, Default::default()); +} + +fn main() { + replace_option_with_none(); + replace_with_default(); +} diff --git a/src/tools/clippy/tests/ui/mem_replace.stderr b/src/tools/clippy/tests/ui/mem_replace.stderr new file mode 100644 index 0000000000..245d33aa4f --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_replace.stderr @@ -0,0 +1,36 @@ +error: replacing an `Option` with `None` + --> $DIR/mem_replace.rs:14:13 + | +LL | let _ = mem::replace(&mut an_option, None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Option::take()` instead: `an_option.take()` + | + = note: `-D clippy::mem-replace-option-with-none` implied by `-D warnings` + +error: replacing an `Option` with `None` + --> $DIR/mem_replace.rs:16:13 + | +LL | let _ = mem::replace(an_option, None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Option::take()` instead: `an_option.take()` + +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> $DIR/mem_replace.rs:21:13 + | +LL | let _ = std::mem::replace(&mut s, String::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::mem::take(&mut s)` + | + = note: `-D clippy::mem-replace-with-default` implied by `-D warnings` + +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> $DIR/mem_replace.rs:23:13 + | +LL | let _ = std::mem::replace(s, String::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::mem::take(s)` + +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> $DIR/mem_replace.rs:24:13 + | +LL | let _ = std::mem::replace(s, Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::mem::take(s)` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/mem_replace_macro.rs b/src/tools/clippy/tests/ui/mem_replace_macro.rs new file mode 100644 index 0000000000..0c09344b80 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_replace_macro.rs @@ -0,0 +1,21 @@ +// aux-build:macro_rules.rs +#![warn(clippy::mem_replace_with_default)] + +#[macro_use] +extern crate macro_rules; + +macro_rules! take { + ($s:expr) => { + std::mem::replace($s, Default::default()) + }; +} + +fn replace_with_default() { + let s = &mut String::from("foo"); + take!(s); + take_external!(s); +} + +fn main() { + replace_with_default(); +} diff --git a/src/tools/clippy/tests/ui/mem_replace_macro.stderr b/src/tools/clippy/tests/ui/mem_replace_macro.stderr new file mode 100644 index 0000000000..4971a91050 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_replace_macro.stderr @@ -0,0 +1,14 @@ +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> $DIR/mem_replace_macro.rs:9:9 + | +LL | std::mem::replace($s, Default::default()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | take!(s); + | --------- in this macro invocation + | + = note: `-D clippy::mem-replace-with-default` implied by `-D warnings` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/methods.rs b/src/tools/clippy/tests/ui/methods.rs new file mode 100644 index 0000000000..513d930e05 --- /dev/null +++ b/src/tools/clippy/tests/ui/methods.rs @@ -0,0 +1,138 @@ +// aux-build:option_helpers.rs +// edition:2018 + +#![warn(clippy::all, clippy::pedantic)] +#![allow( + clippy::blacklisted_name, + clippy::default_trait_access, + clippy::missing_docs_in_private_items, + clippy::missing_safety_doc, + clippy::non_ascii_literal, + clippy::new_without_default, + clippy::needless_pass_by_value, + clippy::needless_lifetimes, + clippy::print_stdout, + clippy::must_use_candidate, + clippy::use_self, + clippy::useless_format, + clippy::wrong_self_convention, + clippy::unused_self, + unused +)] + +#[macro_use] +extern crate option_helpers; + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; +use std::iter::FromIterator; +use std::ops::Mul; +use std::rc::{self, Rc}; +use std::sync::{self, Arc}; + +use option_helpers::IteratorFalsePositives; + +struct Lt<'a> { + foo: &'a u32, +} + +impl<'a> Lt<'a> { + // The lifetime is different, but that’s irrelevant; see issue #734. + #[allow(clippy::needless_lifetimes)] + pub fn new<'b>(s: &'b str) -> Lt<'b> { + unimplemented!() + } +} + +struct Lt2<'a> { + foo: &'a u32, +} + +impl<'a> Lt2<'a> { + // The lifetime is different, but that’s irrelevant; see issue #734. + pub fn new(s: &str) -> Lt2 { + unimplemented!() + } +} + +struct Lt3<'a> { + foo: &'a u32, +} + +impl<'a> Lt3<'a> { + // The lifetime is different, but that’s irrelevant; see issue #734. + pub fn new() -> Lt3<'static> { + unimplemented!() + } +} + +#[derive(Clone, Copy)] +struct U; + +impl U { + fn new() -> Self { + U + } + // Ok because `U` is `Copy`. + fn to_something(self) -> u32 { + 0 + } +} + +struct V { + _dummy: T, +} + +impl V { + fn new() -> Option> { + None + } +} + +struct AsyncNew; + +impl AsyncNew { + async fn new() -> Option { + None + } +} + +struct BadNew; + +impl BadNew { + fn new() -> i32 { + 0 + } +} + +struct T; + +impl Mul for T { + type Output = T; + // No error, obviously. + fn mul(self, other: T) -> T { + self + } +} + +/// Checks implementation of `FILTER_NEXT` lint. +#[rustfmt::skip] +fn filter_next() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + + // Multi-line case. + let _ = v.iter().filter(|&x| { + *x < 0 + } + ).next(); + + // Check that we don't lint if the caller is not an `Iterator`. + let foo = IteratorFalsePositives { foo: 0 }; + let _ = foo.filter().next(); +} + +fn main() { + filter_next(); +} diff --git a/src/tools/clippy/tests/ui/methods.stderr b/src/tools/clippy/tests/ui/methods.stderr new file mode 100644 index 0000000000..4643e09e27 --- /dev/null +++ b/src/tools/clippy/tests/ui/methods.stderr @@ -0,0 +1,24 @@ +error: methods called `new` usually return `Self` + --> $DIR/methods.rs:105:5 + | +LL | / fn new() -> i32 { +LL | | 0 +LL | | } + | |_____^ + | + = note: `-D clippy::new-ret-no-self` implied by `-D warnings` + +error: called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(..)` instead + --> $DIR/methods.rs:126:13 + | +LL | let _ = v.iter().filter(|&x| { + | _____________^ +LL | | *x < 0 +LL | | } +LL | | ).next(); + | |___________________________^ + | + = note: `-D clippy::filter-next` implied by `-D warnings` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/methods_fixable.fixed b/src/tools/clippy/tests/ui/methods_fixable.fixed new file mode 100644 index 0000000000..ee7c1b0da6 --- /dev/null +++ b/src/tools/clippy/tests/ui/methods_fixable.fixed @@ -0,0 +1,11 @@ +// run-rustfix + +#![warn(clippy::filter_next)] + +/// Checks implementation of `FILTER_NEXT` lint. +fn main() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + + // Single-line case. + let _ = v.iter().find(|&x| *x < 0); +} diff --git a/src/tools/clippy/tests/ui/methods_fixable.rs b/src/tools/clippy/tests/ui/methods_fixable.rs new file mode 100644 index 0000000000..6d0f1b7bd5 --- /dev/null +++ b/src/tools/clippy/tests/ui/methods_fixable.rs @@ -0,0 +1,11 @@ +// run-rustfix + +#![warn(clippy::filter_next)] + +/// Checks implementation of `FILTER_NEXT` lint. +fn main() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + + // Single-line case. + let _ = v.iter().filter(|&x| *x < 0).next(); +} diff --git a/src/tools/clippy/tests/ui/methods_fixable.stderr b/src/tools/clippy/tests/ui/methods_fixable.stderr new file mode 100644 index 0000000000..852f48e32d --- /dev/null +++ b/src/tools/clippy/tests/ui/methods_fixable.stderr @@ -0,0 +1,10 @@ +error: called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(..)` instead + --> $DIR/methods_fixable.rs:10:13 + | +LL | let _ = v.iter().filter(|&x| *x < 0).next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `v.iter().find(|&x| *x < 0)` + | + = note: `-D clippy::filter-next` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/min_max.rs b/src/tools/clippy/tests/ui/min_max.rs new file mode 100644 index 0000000000..f7ed72a11c --- /dev/null +++ b/src/tools/clippy/tests/ui/min_max.rs @@ -0,0 +1,65 @@ +#![warn(clippy::all)] + +use std::cmp::max as my_max; +use std::cmp::min as my_min; +use std::cmp::{max, min}; + +const LARGE: usize = 3; + +struct NotOrd(u64); + +impl NotOrd { + fn min(self, x: u64) -> NotOrd { + NotOrd(x) + } + + fn max(self, x: u64) -> NotOrd { + NotOrd(x) + } +} + +fn main() { + let x; + x = 2usize; + min(1, max(3, x)); + min(max(3, x), 1); + max(min(x, 1), 3); + max(3, min(x, 1)); + + my_max(3, my_min(x, 1)); + + min(3, max(1, x)); // ok, could be 1, 2 or 3 depending on x + + min(1, max(LARGE, x)); // no error, we don't lookup consts here + + let y = 2isize; + min(max(y, -1), 3); + + let s; + s = "Hello"; + + min("Apple", max("Zoo", s)); + max(min(s, "Apple"), "Zoo"); + + max("Apple", min(s, "Zoo")); // ok + + let f = 3f32; + x.min(1).max(3); + x.max(3).min(1); + f.max(3f32).min(1f32); + + x.max(1).min(3); // ok + x.min(3).max(1); // ok + f.min(3f32).max(1f32); // ok + + max(x.min(1), 3); + min(x.max(1), 3); // ok + + s.max("Zoo").min("Apple"); + s.min("Apple").max("Zoo"); + + s.min("Zoo").max("Apple"); // ok + + let not_ord = NotOrd(1); + not_ord.min(1).max(3); // ok +} diff --git a/src/tools/clippy/tests/ui/min_max.stderr b/src/tools/clippy/tests/ui/min_max.stderr new file mode 100644 index 0000000000..9f8e26fa40 --- /dev/null +++ b/src/tools/clippy/tests/ui/min_max.stderr @@ -0,0 +1,82 @@ +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:24:5 + | +LL | min(1, max(3, x)); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::min-max` implied by `-D warnings` + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:25:5 + | +LL | min(max(3, x), 1); + | ^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:26:5 + | +LL | max(min(x, 1), 3); + | ^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:27:5 + | +LL | max(3, min(x, 1)); + | ^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:29:5 + | +LL | my_max(3, my_min(x, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:41:5 + | +LL | min("Apple", max("Zoo", s)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:42:5 + | +LL | max(min(s, "Apple"), "Zoo"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:47:5 + | +LL | x.min(1).max(3); + | ^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:48:5 + | +LL | x.max(3).min(1); + | ^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:49:5 + | +LL | f.max(3f32).min(1f32); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:55:5 + | +LL | max(x.min(1), 3); + | ^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:58:5 + | +LL | s.max("Zoo").min("Apple"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:59:5 + | +LL | s.min("Apple").max("Zoo"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/min_rust_version_attr.rs b/src/tools/clippy/tests/ui/min_rust_version_attr.rs new file mode 100644 index 0000000000..0f47f1cbc4 --- /dev/null +++ b/src/tools/clippy/tests/ui/min_rust_version_attr.rs @@ -0,0 +1,177 @@ +#![allow(clippy::redundant_clone)] +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.0.0"] + +use std::ops::{Deref, RangeFrom}; + +fn option_as_ref_deref() { + let mut opt = Some(String::from("123")); + + let _ = opt.as_ref().map(String::as_str); + let _ = opt.as_ref().map(|x| x.as_str()); + let _ = opt.as_mut().map(String::as_mut_str); + let _ = opt.as_mut().map(|x| x.as_mut_str()); +} + +fn match_like_matches() { + let _y = match Some(5) { + Some(0) => true, + _ => false, + }; +} + +fn match_same_arms() { + match (1, 2, 3) { + (1, .., 3) => 42, + (.., 3) => 42, //~ ERROR match arms have same body + _ => 0, + }; +} + +fn match_same_arms2() { + let _ = match Some(42) { + Some(_) => 24, + None => 24, //~ ERROR match arms have same body + }; +} + +pub fn manual_strip_msrv() { + let s = "hello, world!"; + if s.starts_with("hello, ") { + assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); + } +} + +pub fn redundant_fieldnames() { + let start = 0; + let _ = RangeFrom { start: start }; +} + +pub fn redundant_static_lifetime() { + const VAR_ONE: &'static str = "Test constant #1"; +} + +pub fn checked_conversion() { + let value: i64 = 42; + let _ = value <= (u32::max_value() as i64) && value >= 0; + let _ = value <= (u32::MAX as i64) && value >= 0; +} + +pub struct FromOverInto(String); + +impl Into for String { + fn into(self) -> FromOverInto { + FromOverInto(self) + } +} + +pub fn filter_map_next() { + let a = ["1", "lol", "3", "NaN", "5"]; + + #[rustfmt::skip] + let _: Option = vec![1, 2, 3, 4, 5, 6] + .into_iter() + .filter_map(|x| { + if x == 2 { + Some(x * 2) + } else { + None + } + }) + .next(); +} + +#[allow(clippy::no_effect)] +#[allow(clippy::short_circuit_statement)] +#[allow(clippy::unnecessary_operation)] +pub fn manual_range_contains() { + let x = 5; + x >= 8 && x < 12; +} + +pub fn use_self() { + struct Foo {} + + impl Foo { + fn new() -> Foo { + Foo {} + } + fn test() -> Foo { + Foo::new() + } + } +} + +fn replace_with_default() { + let mut s = String::from("foo"); + let _ = std::mem::replace(&mut s, String::default()); +} + +fn map_unwrap_or() { + let opt = Some(1); + + // Check for `option.map(_).unwrap_or(_)` use. + // Single line case. + let _ = opt + .map(|x| x + 1) + // Should lint even though this call is on a separate line. + .unwrap_or(0); +} + +// Could be const +fn missing_const_for_fn() -> i32 { + 1 +} + +fn main() { + filter_map_next(); + checked_conversion(); + redundant_fieldnames(); + redundant_static_lifetime(); + option_as_ref_deref(); + match_like_matches(); + match_same_arms(); + match_same_arms2(); + manual_strip_msrv(); + manual_range_contains(); + use_self(); + replace_with_default(); + map_unwrap_or(); + missing_const_for_fn(); +} + +mod meets_msrv { + #![feature(custom_inner_attributes)] + #![clippy::msrv = "1.45.0"] + + fn main() { + let s = "hello, world!"; + if s.starts_with("hello, ") { + assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); + } + } +} + +mod just_under_msrv { + #![feature(custom_inner_attributes)] + #![clippy::msrv = "1.46.0"] + + fn main() { + let s = "hello, world!"; + if s.starts_with("hello, ") { + assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); + } + } +} + +mod just_above_msrv { + #![feature(custom_inner_attributes)] + #![clippy::msrv = "1.44.0"] + + fn main() { + let s = "hello, world!"; + if s.starts_with("hello, ") { + assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); + } + } +} diff --git a/src/tools/clippy/tests/ui/min_rust_version_attr.stderr b/src/tools/clippy/tests/ui/min_rust_version_attr.stderr new file mode 100644 index 0000000000..e3e3b335cb --- /dev/null +++ b/src/tools/clippy/tests/ui/min_rust_version_attr.stderr @@ -0,0 +1,37 @@ +error: stripping a prefix manually + --> $DIR/min_rust_version_attr.rs:150:24 + | +LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::manual-strip` implied by `-D warnings` +note: the prefix was tested here + --> $DIR/min_rust_version_attr.rs:149:9 + | +LL | if s.starts_with("hello, ") { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try using the `strip_prefix` method + | +LL | if let Some() = s.strip_prefix("hello, ") { +LL | assert_eq!(.to_uppercase(), "WORLD!"); + | + +error: stripping a prefix manually + --> $DIR/min_rust_version_attr.rs:162:24 + | +LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); + | ^^^^^^^^^^^^^^^^^^^^ + | +note: the prefix was tested here + --> $DIR/min_rust_version_attr.rs:161:9 + | +LL | if s.starts_with("hello, ") { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try using the `strip_prefix` method + | +LL | if let Some() = s.strip_prefix("hello, ") { +LL | assert_eq!(.to_uppercase(), "WORLD!"); + | + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/min_rust_version_invalid_attr.rs b/src/tools/clippy/tests/ui/min_rust_version_invalid_attr.rs new file mode 100644 index 0000000000..f20841891a --- /dev/null +++ b/src/tools/clippy/tests/ui/min_rust_version_invalid_attr.rs @@ -0,0 +1,4 @@ +#![feature(custom_inner_attributes)] +#![clippy::msrv = "invalid.version"] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/min_rust_version_invalid_attr.stderr b/src/tools/clippy/tests/ui/min_rust_version_invalid_attr.stderr new file mode 100644 index 0000000000..6ff88ca56f --- /dev/null +++ b/src/tools/clippy/tests/ui/min_rust_version_invalid_attr.stderr @@ -0,0 +1,8 @@ +error: `invalid.version` is not a valid Rust version + --> $DIR/min_rust_version_invalid_attr.rs:2:1 + | +LL | #![clippy::msrv = "invalid.version"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/min_rust_version_multiple_inner_attr.rs b/src/tools/clippy/tests/ui/min_rust_version_multiple_inner_attr.rs new file mode 100644 index 0000000000..e882d5ccf9 --- /dev/null +++ b/src/tools/clippy/tests/ui/min_rust_version_multiple_inner_attr.rs @@ -0,0 +1,11 @@ +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.40"] +#![clippy::msrv = "=1.35.0"] +#![clippy::msrv = "1.10.1"] + +mod foo { + #![clippy::msrv = "1"] + #![clippy::msrv = "1.0.0"] +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/min_rust_version_multiple_inner_attr.stderr b/src/tools/clippy/tests/ui/min_rust_version_multiple_inner_attr.stderr new file mode 100644 index 0000000000..e3ff6605cd --- /dev/null +++ b/src/tools/clippy/tests/ui/min_rust_version_multiple_inner_attr.stderr @@ -0,0 +1,38 @@ +error: `msrv` is defined multiple times + --> $DIR/min_rust_version_multiple_inner_attr.rs:3:1 + | +LL | #![clippy::msrv = "=1.35.0"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: first definition found here + --> $DIR/min_rust_version_multiple_inner_attr.rs:2:1 + | +LL | #![clippy::msrv = "1.40"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `msrv` is defined multiple times + --> $DIR/min_rust_version_multiple_inner_attr.rs:4:1 + | +LL | #![clippy::msrv = "1.10.1"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: first definition found here + --> $DIR/min_rust_version_multiple_inner_attr.rs:2:1 + | +LL | #![clippy::msrv = "1.40"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `msrv` is defined multiple times + --> $DIR/min_rust_version_multiple_inner_attr.rs:8:5 + | +LL | #![clippy::msrv = "1.0.0"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: first definition found here + --> $DIR/min_rust_version_multiple_inner_attr.rs:7:5 + | +LL | #![clippy::msrv = "1"] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/min_rust_version_no_patch.rs b/src/tools/clippy/tests/ui/min_rust_version_no_patch.rs new file mode 100644 index 0000000000..98fffe1e35 --- /dev/null +++ b/src/tools/clippy/tests/ui/min_rust_version_no_patch.rs @@ -0,0 +1,14 @@ +#![allow(clippy::redundant_clone)] +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.0"] + +fn manual_strip_msrv() { + let s = "hello, world!"; + if s.starts_with("hello, ") { + assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); + } +} + +fn main() { + manual_strip_msrv() +} diff --git a/src/tools/clippy/tests/ui/min_rust_version_outer_attr.rs b/src/tools/clippy/tests/ui/min_rust_version_outer_attr.rs new file mode 100644 index 0000000000..551948bd72 --- /dev/null +++ b/src/tools/clippy/tests/ui/min_rust_version_outer_attr.rs @@ -0,0 +1,4 @@ +#![feature(custom_inner_attributes)] + +#[clippy::msrv = "invalid.version"] +fn main() {} diff --git a/src/tools/clippy/tests/ui/min_rust_version_outer_attr.stderr b/src/tools/clippy/tests/ui/min_rust_version_outer_attr.stderr new file mode 100644 index 0000000000..579ee7a87d --- /dev/null +++ b/src/tools/clippy/tests/ui/min_rust_version_outer_attr.stderr @@ -0,0 +1,8 @@ +error: `msrv` cannot be an outer attribute + --> $DIR/min_rust_version_outer_attr.rs:3:1 + | +LL | #[clippy::msrv = "invalid.version"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.fixed b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.fixed new file mode 100644 index 0000000000..f219a570e7 --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.fixed @@ -0,0 +1,27 @@ +// run-rustfix + +#![warn(clippy::mismatched_target_os)] +#![allow(unused)] + +#[cfg(target_os = "hermit")] +fn hermit() {} + +#[cfg(target_os = "wasi")] +fn wasi() {} + +#[cfg(target_os = "none")] +fn none() {} + +// list with conditions +#[cfg(all(not(windows), target_os = "wasi"))] +fn list() {} + +// windows is a valid target family, should be ignored +#[cfg(windows)] +fn windows() {} + +// correct use, should be ignored +#[cfg(target_os = "hermit")] +fn correct() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.rs b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.rs new file mode 100644 index 0000000000..8a8ae756a4 --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.rs @@ -0,0 +1,27 @@ +// run-rustfix + +#![warn(clippy::mismatched_target_os)] +#![allow(unused)] + +#[cfg(hermit)] +fn hermit() {} + +#[cfg(wasi)] +fn wasi() {} + +#[cfg(none)] +fn none() {} + +// list with conditions +#[cfg(all(not(windows), wasi))] +fn list() {} + +// windows is a valid target family, should be ignored +#[cfg(windows)] +fn windows() {} + +// correct use, should be ignored +#[cfg(target_os = "hermit")] +fn correct() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.stderr b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.stderr new file mode 100644 index 0000000000..5f1b090830 --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.stderr @@ -0,0 +1,36 @@ +error: operating system used in target family position + --> $DIR/mismatched_target_os_non_unix.rs:6:1 + | +LL | #[cfg(hermit)] + | ^^^^^^------^^ + | | + | help: try: `target_os = "hermit"` + | + = note: `-D clippy::mismatched-target-os` implied by `-D warnings` + +error: operating system used in target family position + --> $DIR/mismatched_target_os_non_unix.rs:9:1 + | +LL | #[cfg(wasi)] + | ^^^^^^----^^ + | | + | help: try: `target_os = "wasi"` + +error: operating system used in target family position + --> $DIR/mismatched_target_os_non_unix.rs:12:1 + | +LL | #[cfg(none)] + | ^^^^^^----^^ + | | + | help: try: `target_os = "none"` + +error: operating system used in target family position + --> $DIR/mismatched_target_os_non_unix.rs:16:1 + | +LL | #[cfg(all(not(windows), wasi))] + | ^^^^^^^^^^^^^^^^^^^^^^^^----^^^ + | | + | help: try: `target_os = "wasi"` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_unix.fixed b/src/tools/clippy/tests/ui/mismatched_target_os_unix.fixed new file mode 100644 index 0000000000..7d9d406d99 --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_unix.fixed @@ -0,0 +1,62 @@ +// run-rustfix + +#![warn(clippy::mismatched_target_os)] +#![allow(unused)] + +#[cfg(target_os = "linux")] +fn linux() {} + +#[cfg(target_os = "freebsd")] +fn freebsd() {} + +#[cfg(target_os = "dragonfly")] +fn dragonfly() {} + +#[cfg(target_os = "openbsd")] +fn openbsd() {} + +#[cfg(target_os = "netbsd")] +fn netbsd() {} + +#[cfg(target_os = "macos")] +fn macos() {} + +#[cfg(target_os = "ios")] +fn ios() {} + +#[cfg(target_os = "android")] +fn android() {} + +#[cfg(target_os = "emscripten")] +fn emscripten() {} + +#[cfg(target_os = "fuchsia")] +fn fuchsia() {} + +#[cfg(target_os = "haiku")] +fn haiku() {} + +#[cfg(target_os = "illumos")] +fn illumos() {} + +#[cfg(target_os = "l4re")] +fn l4re() {} + +#[cfg(target_os = "redox")] +fn redox() {} + +#[cfg(target_os = "solaris")] +fn solaris() {} + +#[cfg(target_os = "vxworks")] +fn vxworks() {} + +// list with conditions +#[cfg(all(not(any(target_os = "solaris", target_os = "linux")), target_os = "freebsd"))] +fn list() {} + +// correct use, should be ignored +#[cfg(target_os = "freebsd")] +fn correct() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_unix.rs b/src/tools/clippy/tests/ui/mismatched_target_os_unix.rs new file mode 100644 index 0000000000..c1177f1eed --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_unix.rs @@ -0,0 +1,62 @@ +// run-rustfix + +#![warn(clippy::mismatched_target_os)] +#![allow(unused)] + +#[cfg(linux)] +fn linux() {} + +#[cfg(freebsd)] +fn freebsd() {} + +#[cfg(dragonfly)] +fn dragonfly() {} + +#[cfg(openbsd)] +fn openbsd() {} + +#[cfg(netbsd)] +fn netbsd() {} + +#[cfg(macos)] +fn macos() {} + +#[cfg(ios)] +fn ios() {} + +#[cfg(android)] +fn android() {} + +#[cfg(emscripten)] +fn emscripten() {} + +#[cfg(fuchsia)] +fn fuchsia() {} + +#[cfg(haiku)] +fn haiku() {} + +#[cfg(illumos)] +fn illumos() {} + +#[cfg(l4re)] +fn l4re() {} + +#[cfg(redox)] +fn redox() {} + +#[cfg(solaris)] +fn solaris() {} + +#[cfg(vxworks)] +fn vxworks() {} + +// list with conditions +#[cfg(all(not(any(solaris, linux)), freebsd))] +fn list() {} + +// correct use, should be ignored +#[cfg(target_os = "freebsd")] +fn correct() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_unix.stderr b/src/tools/clippy/tests/ui/mismatched_target_os_unix.stderr new file mode 100644 index 0000000000..ea39f5b557 --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_unix.stderr @@ -0,0 +1,183 @@ +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:6:1 + | +LL | #[cfg(linux)] + | ^^^^^^-----^^ + | | + | help: try: `target_os = "linux"` + | + = note: `-D clippy::mismatched-target-os` implied by `-D warnings` + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:9:1 + | +LL | #[cfg(freebsd)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "freebsd"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:12:1 + | +LL | #[cfg(dragonfly)] + | ^^^^^^---------^^ + | | + | help: try: `target_os = "dragonfly"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:15:1 + | +LL | #[cfg(openbsd)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "openbsd"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:18:1 + | +LL | #[cfg(netbsd)] + | ^^^^^^------^^ + | | + | help: try: `target_os = "netbsd"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:21:1 + | +LL | #[cfg(macos)] + | ^^^^^^-----^^ + | | + | help: try: `target_os = "macos"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:24:1 + | +LL | #[cfg(ios)] + | ^^^^^^---^^ + | | + | help: try: `target_os = "ios"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:27:1 + | +LL | #[cfg(android)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "android"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:30:1 + | +LL | #[cfg(emscripten)] + | ^^^^^^----------^^ + | | + | help: try: `target_os = "emscripten"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:33:1 + | +LL | #[cfg(fuchsia)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "fuchsia"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:36:1 + | +LL | #[cfg(haiku)] + | ^^^^^^-----^^ + | | + | help: try: `target_os = "haiku"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:39:1 + | +LL | #[cfg(illumos)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "illumos"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:42:1 + | +LL | #[cfg(l4re)] + | ^^^^^^----^^ + | | + | help: try: `target_os = "l4re"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:45:1 + | +LL | #[cfg(redox)] + | ^^^^^^-----^^ + | | + | help: try: `target_os = "redox"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:48:1 + | +LL | #[cfg(solaris)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "solaris"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:51:1 + | +LL | #[cfg(vxworks)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "vxworks"` + | + = help: did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:55:1 + | +LL | #[cfg(all(not(any(solaris, linux)), freebsd))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: did you mean `unix`? +help: try + | +LL | #[cfg(all(not(any(target_os = "solaris", linux)), freebsd))] + | ^^^^^^^^^^^^^^^^^^^^^ +help: try + | +LL | #[cfg(all(not(any(solaris, target_os = "linux")), freebsd))] + | ^^^^^^^^^^^^^^^^^^^ +help: try + | +LL | #[cfg(all(not(any(solaris, linux)), target_os = "freebsd"))] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/missing-doc-crate-missing.rs b/src/tools/clippy/tests/ui/missing-doc-crate-missing.rs new file mode 100644 index 0000000000..51fd57df8d --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc-crate-missing.rs @@ -0,0 +1,3 @@ +#![warn(clippy::missing_docs_in_private_items)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/missing-doc-crate-missing.stderr b/src/tools/clippy/tests/ui/missing-doc-crate-missing.stderr new file mode 100644 index 0000000000..d56c5cc4c3 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc-crate-missing.stderr @@ -0,0 +1,12 @@ +error: missing documentation for the crate + --> $DIR/missing-doc-crate-missing.rs:1:1 + | +LL | / #![warn(clippy::missing_docs_in_private_items)] +LL | | +LL | | fn main() {} + | |____________^ + | + = note: `-D clippy::missing-docs-in-private-items` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/missing-doc-crate.rs b/src/tools/clippy/tests/ui/missing-doc-crate.rs new file mode 100644 index 0000000000..04711f8648 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc-crate.rs @@ -0,0 +1,5 @@ +#![warn(clippy::missing_docs_in_private_items)] +#![feature(external_doc)] +#![doc(include = "../../README.md")] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/missing-doc-crate.stderr b/src/tools/clippy/tests/ui/missing-doc-crate.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tools/clippy/tests/ui/missing-doc-impl.rs b/src/tools/clippy/tests/ui/missing-doc-impl.rs new file mode 100644 index 0000000000..57af84dcdf --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc-impl.rs @@ -0,0 +1,87 @@ +#![warn(clippy::missing_docs_in_private_items)] +#![allow(dead_code)] +#![feature(associated_type_defaults)] + +//! Some garbage docs for the crate here +#![doc = "More garbage"] + +struct Foo { + a: isize, + b: isize, +} + +pub struct PubFoo { + pub a: isize, + b: isize, +} + +#[allow(clippy::missing_docs_in_private_items)] +pub struct PubFoo2 { + pub a: isize, + pub c: isize, +} + +/// dox +pub trait A { + /// dox + fn foo(&self); + /// dox + fn foo_with_impl(&self) {} +} + +#[allow(clippy::missing_docs_in_private_items)] +trait B { + fn foo(&self); + fn foo_with_impl(&self) {} +} + +pub trait C { + fn foo(&self); + fn foo_with_impl(&self) {} +} + +#[allow(clippy::missing_docs_in_private_items)] +pub trait D { + fn dummy(&self) {} +} + +/// dox +pub trait E: Sized { + type AssociatedType; + type AssociatedTypeDef = Self; + + /// dox + type DocumentedType; + /// dox + type DocumentedTypeDef = Self; + /// dox + fn dummy(&self) {} +} + +impl Foo { + pub fn foo() {} + fn bar() {} +} + +impl PubFoo { + pub fn foo() {} + /// dox + pub fn foo1() {} + fn foo2() {} + #[allow(clippy::missing_docs_in_private_items)] + pub fn foo3() {} +} + +#[allow(clippy::missing_docs_in_private_items)] +trait F { + fn a(); + fn b(&self); +} + +// should need to redefine documentation for implementations of traits +impl F for Foo { + fn a() {} + fn b(&self) {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/missing-doc-impl.stderr b/src/tools/clippy/tests/ui/missing-doc-impl.stderr new file mode 100644 index 0000000000..7e10404ca0 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc-impl.stderr @@ -0,0 +1,103 @@ +error: missing documentation for a struct + --> $DIR/missing-doc-impl.rs:8:1 + | +LL | / struct Foo { +LL | | a: isize, +LL | | b: isize, +LL | | } + | |_^ + | + = note: `-D clippy::missing-docs-in-private-items` implied by `-D warnings` + +error: missing documentation for a struct field + --> $DIR/missing-doc-impl.rs:9:5 + | +LL | a: isize, + | ^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/missing-doc-impl.rs:10:5 + | +LL | b: isize, + | ^^^^^^^^ + +error: missing documentation for a struct + --> $DIR/missing-doc-impl.rs:13:1 + | +LL | / pub struct PubFoo { +LL | | pub a: isize, +LL | | b: isize, +LL | | } + | |_^ + +error: missing documentation for a struct field + --> $DIR/missing-doc-impl.rs:14:5 + | +LL | pub a: isize, + | ^^^^^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/missing-doc-impl.rs:15:5 + | +LL | b: isize, + | ^^^^^^^^ + +error: missing documentation for a trait + --> $DIR/missing-doc-impl.rs:38:1 + | +LL | / pub trait C { +LL | | fn foo(&self); +LL | | fn foo_with_impl(&self) {} +LL | | } + | |_^ + +error: missing documentation for an associated function + --> $DIR/missing-doc-impl.rs:39:5 + | +LL | fn foo(&self); + | ^^^^^^^^^^^^^^ + +error: missing documentation for an associated function + --> $DIR/missing-doc-impl.rs:40:5 + | +LL | fn foo_with_impl(&self) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for an associated type + --> $DIR/missing-doc-impl.rs:50:5 + | +LL | type AssociatedType; + | ^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for an associated type + --> $DIR/missing-doc-impl.rs:51:5 + | +LL | type AssociatedTypeDef = Self; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for an associated function + --> $DIR/missing-doc-impl.rs:62:5 + | +LL | pub fn foo() {} + | ^^^^^^^^^^^^^^^ + +error: missing documentation for an associated function + --> $DIR/missing-doc-impl.rs:63:5 + | +LL | fn bar() {} + | ^^^^^^^^^^^ + +error: missing documentation for an associated function + --> $DIR/missing-doc-impl.rs:67:5 + | +LL | pub fn foo() {} + | ^^^^^^^^^^^^^^^ + +error: missing documentation for an associated function + --> $DIR/missing-doc-impl.rs:70:5 + | +LL | fn foo2() {} + | ^^^^^^^^^^^^ + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/missing-doc.rs b/src/tools/clippy/tests/ui/missing-doc.rs new file mode 100644 index 0000000000..a9bf7140a1 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc.rs @@ -0,0 +1,102 @@ +#![warn(clippy::missing_docs_in_private_items)] +// When denying at the crate level, be sure to not get random warnings from the +// injected intrinsics by the compiler. +#![allow(dead_code)] +#![feature(global_asm)] + +//! Some garbage docs for the crate here +#![doc = "More garbage"] + +type Typedef = String; +pub type PubTypedef = String; + +mod module_no_dox {} +pub mod pub_module_no_dox {} + +/// dox +pub fn foo() {} +pub fn foo2() {} +fn foo3() {} +#[allow(clippy::missing_docs_in_private_items)] +pub fn foo4() {} + +// It sure is nice if doc(hidden) implies allow(missing_docs), and that it +// applies recursively +#[doc(hidden)] +mod a { + pub fn baz() {} + pub mod b { + pub fn baz() {} + } +} + +enum Baz { + BazA { a: isize, b: isize }, + BarB, +} + +pub enum PubBaz { + PubBazA { a: isize }, +} + +/// dox +pub enum PubBaz2 { + /// dox + PubBaz2A { + /// dox + a: isize, + }, +} + +#[allow(clippy::missing_docs_in_private_items)] +pub enum PubBaz3 { + PubBaz3A { b: isize }, +} + +#[doc(hidden)] +pub fn baz() {} + +const FOO: u32 = 0; +/// dox +pub const FOO1: u32 = 0; +#[allow(clippy::missing_docs_in_private_items)] +pub const FOO2: u32 = 0; +#[doc(hidden)] +pub const FOO3: u32 = 0; +pub const FOO4: u32 = 0; + +static BAR: u32 = 0; +/// dox +pub static BAR1: u32 = 0; +#[allow(clippy::missing_docs_in_private_items)] +pub static BAR2: u32 = 0; +#[doc(hidden)] +pub static BAR3: u32 = 0; +pub static BAR4: u32 = 0; + +mod internal_impl { + /// dox + pub fn documented() {} + pub fn undocumented1() {} + pub fn undocumented2() {} + fn undocumented3() {} + /// dox + pub mod globbed { + /// dox + pub fn also_documented() {} + pub fn also_undocumented1() {} + fn also_undocumented2() {} + } +} +/// dox +pub mod public_interface { + pub use internal_impl::documented as foo; + pub use internal_impl::globbed::*; + pub use internal_impl::undocumented1 as bar; + pub use internal_impl::{documented, undocumented2}; +} + +fn main() {} + +// Ensure global asm doesn't require documentation. +global_asm! { "" } diff --git a/src/tools/clippy/tests/ui/missing-doc.stderr b/src/tools/clippy/tests/ui/missing-doc.stderr new file mode 100644 index 0000000000..a876dc078e --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc.stderr @@ -0,0 +1,159 @@ +error: missing documentation for a type alias + --> $DIR/missing-doc.rs:10:1 + | +LL | type Typedef = String; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::missing-docs-in-private-items` implied by `-D warnings` + +error: missing documentation for a type alias + --> $DIR/missing-doc.rs:11:1 + | +LL | pub type PubTypedef = String; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a module + --> $DIR/missing-doc.rs:13:1 + | +LL | mod module_no_dox {} + | ^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a module + --> $DIR/missing-doc.rs:14:1 + | +LL | pub mod pub_module_no_dox {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:18:1 + | +LL | pub fn foo2() {} + | ^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:19:1 + | +LL | fn foo3() {} + | ^^^^^^^^^^^^ + +error: missing documentation for an enum + --> $DIR/missing-doc.rs:33:1 + | +LL | / enum Baz { +LL | | BazA { a: isize, b: isize }, +LL | | BarB, +LL | | } + | |_^ + +error: missing documentation for a variant + --> $DIR/missing-doc.rs:34:5 + | +LL | BazA { a: isize, b: isize }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/missing-doc.rs:34:12 + | +LL | BazA { a: isize, b: isize }, + | ^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/missing-doc.rs:34:22 + | +LL | BazA { a: isize, b: isize }, + | ^^^^^^^^ + +error: missing documentation for a variant + --> $DIR/missing-doc.rs:35:5 + | +LL | BarB, + | ^^^^ + +error: missing documentation for an enum + --> $DIR/missing-doc.rs:38:1 + | +LL | / pub enum PubBaz { +LL | | PubBazA { a: isize }, +LL | | } + | |_^ + +error: missing documentation for a variant + --> $DIR/missing-doc.rs:39:5 + | +LL | PubBazA { a: isize }, + | ^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/missing-doc.rs:39:15 + | +LL | PubBazA { a: isize }, + | ^^^^^^^^ + +error: missing documentation for a constant + --> $DIR/missing-doc.rs:59:1 + | +LL | const FOO: u32 = 0; + | ^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a constant + --> $DIR/missing-doc.rs:66:1 + | +LL | pub const FOO4: u32 = 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a static + --> $DIR/missing-doc.rs:68:1 + | +LL | static BAR: u32 = 0; + | ^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a static + --> $DIR/missing-doc.rs:75:1 + | +LL | pub static BAR4: u32 = 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a module + --> $DIR/missing-doc.rs:77:1 + | +LL | / mod internal_impl { +LL | | /// dox +LL | | pub fn documented() {} +LL | | pub fn undocumented1() {} +... | +LL | | } +LL | | } + | |_^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:80:5 + | +LL | pub fn undocumented1() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:81:5 + | +LL | pub fn undocumented2() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:82:5 + | +LL | fn undocumented3() {} + | ^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:87:9 + | +LL | pub fn also_undocumented1() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:88:9 + | +LL | fn also_undocumented2() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/missing_const_for_fn/cant_be_const.rs b/src/tools/clippy/tests/ui/missing_const_for_fn/cant_be_const.rs new file mode 100644 index 0000000000..ba352ef9ee --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_const_for_fn/cant_be_const.rs @@ -0,0 +1,103 @@ +//! False-positive tests to ensure we don't suggest `const` for things where it would cause a +//! compilation error. +//! The .stderr output of this test should be empty. Otherwise it's a bug somewhere. + +#![warn(clippy::missing_const_for_fn)] +#![allow(incomplete_features)] +#![feature(start, const_generics)] + +struct Game; + +// This should not be linted because it's already const +const fn already_const() -> i32 { + 32 +} + +impl Game { + // This should not be linted because it's already const + pub const fn already_const() -> i32 { + 32 + } +} + +// Allowing on this function, because it would lint, which we don't want in this case. +#[allow(clippy::missing_const_for_fn)] +fn random() -> u32 { + 42 +} + +// We should not suggest to make this function `const` because `random()` is non-const +fn random_caller() -> u32 { + random() +} + +static Y: u32 = 0; + +// We should not suggest to make this function `const` because const functions are not allowed to +// refer to a static variable +fn get_y() -> u32 { + Y + //~^ ERROR E0013 +} + +// Don't lint entrypoint functions +#[start] +fn init(num: isize, something: *const *const u8) -> isize { + 1 +} + +trait Foo { + // This should not be suggested to be made const + // (rustc doesn't allow const trait methods) + fn f() -> u32; + + // This should not be suggested to be made const either + fn g() -> u32 { + 33 + } +} + +// Don't lint in external macros (derive) +#[derive(PartialEq, Eq)] +struct Point(isize, isize); + +impl std::ops::Add for Point { + type Output = Self; + + // Don't lint in trait impls of derived methods + fn add(self, other: Self) -> Self { + Point(self.0 + other.0, self.1 + other.1) + } +} + +mod with_drop { + pub struct A; + pub struct B; + impl Drop for A { + fn drop(&mut self) {} + } + + impl A { + // This can not be const because the type implements `Drop`. + pub fn a(self) -> B { + B + } + } + + impl B { + // This can not be const because `a` implements `Drop`. + pub fn a(self, a: A) -> B { + B + } + } +} + +fn const_generic_params(t: &[T; N]) -> &[T; N] { + t +} + +fn const_generic_return(t: &[T]) -> &[T; N] { + let p = t.as_ptr() as *const [T; N]; + + unsafe { &*p } +} diff --git a/src/tools/clippy/tests/ui/missing_const_for_fn/cant_be_const.stderr b/src/tools/clippy/tests/ui/missing_const_for_fn/cant_be_const.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.rs b/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.rs new file mode 100644 index 0000000000..c6f44b7daa --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.rs @@ -0,0 +1,74 @@ +#![warn(clippy::missing_const_for_fn)] +#![allow(incomplete_features, clippy::let_and_return)] +#![feature(const_generics)] + +use std::mem::transmute; + +struct Game { + guess: i32, +} + +impl Game { + // Could be const + pub fn new() -> Self { + Self { guess: 42 } + } + + fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; N]) -> &'a [T; N] { + b + } +} + +// Could be const +fn one() -> i32 { + 1 +} + +// Could also be const +fn two() -> i32 { + let abc = 2; + abc +} + +// Could be const (since Rust 1.39) +fn string() -> String { + String::new() +} + +// Could be const +unsafe fn four() -> i32 { + 4 +} + +// Could also be const +fn generic(t: T) -> T { + t +} + +fn sub(x: u32) -> usize { + unsafe { transmute(&x) } +} + +// NOTE: This is currently not yet allowed to be const +// Once implemented, Clippy should be able to suggest this as const, too. +fn generic_arr(t: [T; 1]) -> T { + t[0] +} + +mod with_drop { + pub struct A; + pub struct B; + impl Drop for A { + fn drop(&mut self) {} + } + + impl B { + // This can be const, because `a` is passed by reference + pub fn b(self, a: &A) -> B { + B + } + } +} + +// Should not be const +fn main() {} diff --git a/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.stderr b/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.stderr new file mode 100644 index 0000000000..74d32b8a1a --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -0,0 +1,69 @@ +error: this could be a `const fn` + --> $DIR/could_be_const.rs:13:5 + | +LL | / pub fn new() -> Self { +LL | | Self { guess: 42 } +LL | | } + | |_____^ + | + = note: `-D clippy::missing-const-for-fn` implied by `-D warnings` + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:17:5 + | +LL | / fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; N]) -> &'a [T; N] { +LL | | b +LL | | } + | |_____^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:23:1 + | +LL | / fn one() -> i32 { +LL | | 1 +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:28:1 + | +LL | / fn two() -> i32 { +LL | | let abc = 2; +LL | | abc +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:34:1 + | +LL | / fn string() -> String { +LL | | String::new() +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:39:1 + | +LL | / unsafe fn four() -> i32 { +LL | | 4 +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:44:1 + | +LL | / fn generic(t: T) -> T { +LL | | t +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:67:9 + | +LL | / pub fn b(self, a: &A) -> B { +LL | | B +LL | | } + | |_________^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/missing_inline.rs b/src/tools/clippy/tests/ui/missing_inline.rs new file mode 100644 index 0000000000..b73b24b8e0 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_inline.rs @@ -0,0 +1,66 @@ +#![warn(clippy::missing_inline_in_public_items)] +#![crate_type = "dylib"] +// When denying at the crate level, be sure to not get random warnings from the +// injected intrinsics by the compiler. +#![allow(dead_code, non_snake_case)] + +type Typedef = String; +pub type PubTypedef = String; + +struct Foo {} // ok +pub struct PubFoo {} // ok +enum FooE {} // ok +pub enum PubFooE {} // ok + +mod module {} // ok +pub mod pub_module {} // ok + +fn foo() {} +pub fn pub_foo() {} // missing #[inline] +#[inline] +pub fn pub_foo_inline() {} // ok +#[inline(always)] +pub fn pub_foo_inline_always() {} // ok + +#[allow(clippy::missing_inline_in_public_items)] +pub fn pub_foo_no_inline() {} + +trait Bar { + fn Bar_a(); // ok + fn Bar_b() {} // ok +} + +pub trait PubBar { + fn PubBar_a(); // ok + fn PubBar_b() {} // missing #[inline] + #[inline] + fn PubBar_c() {} // ok +} + +// none of these need inline because Foo is not exported +impl PubBar for Foo { + fn PubBar_a() {} // ok + fn PubBar_b() {} // ok + fn PubBar_c() {} // ok +} + +// all of these need inline because PubFoo is exported +impl PubBar for PubFoo { + fn PubBar_a() {} // missing #[inline] + fn PubBar_b() {} // missing #[inline] + fn PubBar_c() {} // missing #[inline] +} + +// do not need inline because Foo is not exported +impl Foo { + fn FooImpl() {} // ok +} + +// need inline because PubFoo is exported +impl PubFoo { + pub fn PubFooImpl() {} // missing #[inline] +} + +// do not lint this since users cannot control the external code +#[derive(Debug)] +pub struct S {} diff --git a/src/tools/clippy/tests/ui/missing_inline.stderr b/src/tools/clippy/tests/ui/missing_inline.stderr new file mode 100644 index 0000000000..40b92b7647 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_inline.stderr @@ -0,0 +1,40 @@ +error: missing `#[inline]` for a function + --> $DIR/missing_inline.rs:19:1 + | +LL | pub fn pub_foo() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::missing-inline-in-public-items` implied by `-D warnings` + +error: missing `#[inline]` for a default trait method + --> $DIR/missing_inline.rs:35:5 + | +LL | fn PubBar_b() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^ + +error: missing `#[inline]` for a method + --> $DIR/missing_inline.rs:49:5 + | +LL | fn PubBar_a() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^ + +error: missing `#[inline]` for a method + --> $DIR/missing_inline.rs:50:5 + | +LL | fn PubBar_b() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^ + +error: missing `#[inline]` for a method + --> $DIR/missing_inline.rs:51:5 + | +LL | fn PubBar_c() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^ + +error: missing `#[inline]` for a method + --> $DIR/missing_inline.rs:61:5 + | +LL | pub fn PubFooImpl() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/missing_inline_executable.rs b/src/tools/clippy/tests/ui/missing_inline_executable.rs new file mode 100644 index 0000000000..6e0400ac93 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_inline_executable.rs @@ -0,0 +1,5 @@ +#![warn(clippy::missing_inline_in_public_items)] + +pub fn foo() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/missing_inline_proc_macro.rs b/src/tools/clippy/tests/ui/missing_inline_proc_macro.rs new file mode 100644 index 0000000000..3c68fb905f --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_inline_proc_macro.rs @@ -0,0 +1,23 @@ +#![warn(clippy::missing_inline_in_public_items)] +#![crate_type = "proc-macro"] + +extern crate proc_macro; + +use proc_macro::TokenStream; + +fn _foo() {} + +#[proc_macro] +pub fn function_like(_: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[proc_macro_attribute] +pub fn attribute(_: TokenStream, _: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[proc_macro_derive(Derive)] +pub fn derive(_: TokenStream) -> TokenStream { + TokenStream::new() +} diff --git a/src/tools/clippy/tests/ui/mistyped_literal_suffix.fixed b/src/tools/clippy/tests/ui/mistyped_literal_suffix.fixed new file mode 100644 index 0000000000..70cdb067d9 --- /dev/null +++ b/src/tools/clippy/tests/ui/mistyped_literal_suffix.fixed @@ -0,0 +1,29 @@ +// run-rustfix + +#![allow( + dead_code, + unused_variables, + clippy::excessive_precision, + clippy::inconsistent_digit_grouping +)] + +fn main() { + let fail14 = 2_i32; + let fail15 = 4_i64; + let fail16 = 7_i8; // + let fail17 = 23_i16; // + let ok18 = 23_128; + + let fail20 = 2_i8; // + let fail21 = 4_i16; // + + let ok24 = 12.34_64; + let fail25 = 1E2_f32; + let fail26 = 43E7_f64; + let fail27 = 243E17_f32; + #[allow(overflowing_literals)] + let fail28 = 241_251_235E723_f64; + let ok29 = 42279.911_32; + + let _ = 1.123_45E1_f32; +} diff --git a/src/tools/clippy/tests/ui/mistyped_literal_suffix.rs b/src/tools/clippy/tests/ui/mistyped_literal_suffix.rs new file mode 100644 index 0000000000..729990af39 --- /dev/null +++ b/src/tools/clippy/tests/ui/mistyped_literal_suffix.rs @@ -0,0 +1,29 @@ +// run-rustfix + +#![allow( + dead_code, + unused_variables, + clippy::excessive_precision, + clippy::inconsistent_digit_grouping +)] + +fn main() { + let fail14 = 2_32; + let fail15 = 4_64; + let fail16 = 7_8; // + let fail17 = 23_16; // + let ok18 = 23_128; + + let fail20 = 2__8; // + let fail21 = 4___16; // + + let ok24 = 12.34_64; + let fail25 = 1E2_32; + let fail26 = 43E7_64; + let fail27 = 243E17_32; + #[allow(overflowing_literals)] + let fail28 = 241251235E723_64; + let ok29 = 42279.911_32; + + let _ = 1.12345E1_32; +} diff --git a/src/tools/clippy/tests/ui/mistyped_literal_suffix.stderr b/src/tools/clippy/tests/ui/mistyped_literal_suffix.stderr new file mode 100644 index 0000000000..b338b8aa62 --- /dev/null +++ b/src/tools/clippy/tests/ui/mistyped_literal_suffix.stderr @@ -0,0 +1,70 @@ +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:11:18 + | +LL | let fail14 = 2_32; + | ^^^^ help: did you mean to write: `2_i32` + | + = note: `#[deny(clippy::mistyped_literal_suffixes)]` on by default + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:12:18 + | +LL | let fail15 = 4_64; + | ^^^^ help: did you mean to write: `4_i64` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:13:18 + | +LL | let fail16 = 7_8; // + | ^^^ help: did you mean to write: `7_i8` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:14:18 + | +LL | let fail17 = 23_16; // + | ^^^^^ help: did you mean to write: `23_i16` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:17:18 + | +LL | let fail20 = 2__8; // + | ^^^^ help: did you mean to write: `2_i8` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:18:18 + | +LL | let fail21 = 4___16; // + | ^^^^^^ help: did you mean to write: `4_i16` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:21:18 + | +LL | let fail25 = 1E2_32; + | ^^^^^^ help: did you mean to write: `1E2_f32` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:22:18 + | +LL | let fail26 = 43E7_64; + | ^^^^^^^ help: did you mean to write: `43E7_f64` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:23:18 + | +LL | let fail27 = 243E17_32; + | ^^^^^^^^^ help: did you mean to write: `243E17_f32` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:25:18 + | +LL | let fail28 = 241251235E723_64; + | ^^^^^^^^^^^^^^^^ help: did you mean to write: `241_251_235E723_f64` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:28:13 + | +LL | let _ = 1.12345E1_32; + | ^^^^^^^^^^^^ help: did you mean to write: `1.123_45E1_f32` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/module_inception.rs b/src/tools/clippy/tests/ui/module_inception.rs new file mode 100644 index 0000000000..a23aba9164 --- /dev/null +++ b/src/tools/clippy/tests/ui/module_inception.rs @@ -0,0 +1,21 @@ +#![warn(clippy::module_inception)] + +mod foo { + mod bar { + mod bar { + mod foo {} + } + mod foo {} + } + mod foo { + mod bar {} + } +} + +// No warning. See . +mod bar { + #[allow(clippy::module_inception)] + mod bar {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/module_inception.stderr b/src/tools/clippy/tests/ui/module_inception.stderr new file mode 100644 index 0000000000..77564dce9e --- /dev/null +++ b/src/tools/clippy/tests/ui/module_inception.stderr @@ -0,0 +1,20 @@ +error: module has the same name as its containing module + --> $DIR/module_inception.rs:5:9 + | +LL | / mod bar { +LL | | mod foo {} +LL | | } + | |_________^ + | + = note: `-D clippy::module-inception` implied by `-D warnings` + +error: module has the same name as its containing module + --> $DIR/module_inception.rs:10:5 + | +LL | / mod foo { +LL | | mod bar {} +LL | | } + | |_____^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/module_name_repetitions.rs b/src/tools/clippy/tests/ui/module_name_repetitions.rs new file mode 100644 index 0000000000..669bf01a84 --- /dev/null +++ b/src/tools/clippy/tests/ui/module_name_repetitions.rs @@ -0,0 +1,26 @@ +// compile-flags: --test + +#![warn(clippy::module_name_repetitions)] +#![allow(dead_code)] + +mod foo { + pub fn foo() {} + pub fn foo_bar() {} + pub fn bar_foo() {} + pub struct FooCake {} + pub enum CakeFoo {} + pub struct Foo7Bar; + + // Should not warn + pub struct Foobar; +} + +#[cfg(test)] +mod test { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/module_name_repetitions.stderr b/src/tools/clippy/tests/ui/module_name_repetitions.stderr new file mode 100644 index 0000000000..bdd217a969 --- /dev/null +++ b/src/tools/clippy/tests/ui/module_name_repetitions.stderr @@ -0,0 +1,34 @@ +error: item name starts with its containing module's name + --> $DIR/module_name_repetitions.rs:8:5 + | +LL | pub fn foo_bar() {} + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::module-name-repetitions` implied by `-D warnings` + +error: item name ends with its containing module's name + --> $DIR/module_name_repetitions.rs:9:5 + | +LL | pub fn bar_foo() {} + | ^^^^^^^^^^^^^^^^^^^ + +error: item name starts with its containing module's name + --> $DIR/module_name_repetitions.rs:10:5 + | +LL | pub struct FooCake {} + | ^^^^^^^^^^^^^^^^^^^^^ + +error: item name ends with its containing module's name + --> $DIR/module_name_repetitions.rs:11:5 + | +LL | pub enum CakeFoo {} + | ^^^^^^^^^^^^^^^^^^^ + +error: item name starts with its containing module's name + --> $DIR/module_name_repetitions.rs:12:5 + | +LL | pub struct Foo7Bar; + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_float.rs b/src/tools/clippy/tests/ui/modulo_arithmetic_float.rs new file mode 100644 index 0000000000..b010b0dbdf --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_float.rs @@ -0,0 +1,36 @@ +#![warn(clippy::modulo_arithmetic)] +#![allow( + unused, + clippy::shadow_reuse, + clippy::shadow_unrelated, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::modulo_one +)] + +fn main() { + // Lint when both sides are const and of the opposite sign + -1.6 % 2.1; + 1.6 % -2.1; + (1.1 - 2.3) % (1.1 + 2.3); + (1.1 + 2.3) % (1.1 - 2.3); + + // Lint on floating point numbers + let a_f32: f32 = -1.6; + let mut b_f32: f32 = 2.1; + a_f32 % b_f32; + b_f32 % a_f32; + b_f32 %= a_f32; + + let a_f64: f64 = -1.6; + let mut b_f64: f64 = 2.1; + a_f64 % b_f64; + b_f64 % a_f64; + b_f64 %= a_f64; + + // No lint when both sides are const and of the same sign + 1.6 % 2.1; + -1.6 % -2.1; + (1.1 + 2.3) % (-1.1 + 2.3); + (-1.1 - 2.3) % (1.1 - 2.3); +} diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_float.stderr b/src/tools/clippy/tests/ui/modulo_arithmetic_float.stderr new file mode 100644 index 0000000000..7bfdb0bde6 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_float.stderr @@ -0,0 +1,83 @@ +error: you are using modulo operator on constants with different signs: `-1.600 % 2.100` + --> $DIR/modulo_arithmetic_float.rs:13:5 + | +LL | -1.6 % 2.1; + | ^^^^^^^^^^ + | + = note: `-D clippy::modulo-arithmetic` implied by `-D warnings` + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on constants with different signs: `1.600 % -2.100` + --> $DIR/modulo_arithmetic_float.rs:14:5 + | +LL | 1.6 % -2.1; + | ^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on constants with different signs: `-1.200 % 3.400` + --> $DIR/modulo_arithmetic_float.rs:15:5 + | +LL | (1.1 - 2.3) % (1.1 + 2.3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on constants with different signs: `3.400 % -1.200` + --> $DIR/modulo_arithmetic_float.rs:16:5 + | +LL | (1.1 + 2.3) % (1.1 - 2.3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:21:5 + | +LL | a_f32 % b_f32; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:22:5 + | +LL | b_f32 % a_f32; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:23:5 + | +LL | b_f32 %= a_f32; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:27:5 + | +LL | a_f64 % b_f64; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:28:5 + | +LL | b_f64 % a_f64; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:29:5 + | +LL | b_f64 %= a_f64; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_integral.rs b/src/tools/clippy/tests/ui/modulo_arithmetic_integral.rs new file mode 100644 index 0000000000..779d035c5f --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_integral.rs @@ -0,0 +1,90 @@ +#![warn(clippy::modulo_arithmetic)] +#![allow( + unused, + clippy::shadow_reuse, + clippy::shadow_unrelated, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::modulo_one +)] + +fn main() { + // Lint on signed integral numbers + let a = -1; + let mut b = 2; + a % b; + b % a; + b %= a; + + let a_i8: i8 = 1; + let mut b_i8: i8 = 2; + a_i8 % b_i8; + b_i8 %= a_i8; + + let a_i16: i16 = 1; + let mut b_i16: i16 = 2; + a_i16 % b_i16; + b_i16 %= a_i16; + + let a_i32: i32 = 1; + let mut b_i32: i32 = 2; + a_i32 % b_i32; + b_i32 %= a_i32; + + let a_i64: i64 = 1; + let mut b_i64: i64 = 2; + a_i64 % b_i64; + b_i64 %= a_i64; + + let a_i128: i128 = 1; + let mut b_i128: i128 = 2; + a_i128 % b_i128; + b_i128 %= a_i128; + + let a_isize: isize = 1; + let mut b_isize: isize = 2; + a_isize % b_isize; + b_isize %= a_isize; + + let a = 1; + let mut b = 2; + a % b; + b %= a; + + // No lint on unsigned integral value + let a_u8: u8 = 17; + let b_u8: u8 = 3; + a_u8 % b_u8; + let mut a_u8: u8 = 1; + a_u8 %= 2; + + let a_u16: u16 = 17; + let b_u16: u16 = 3; + a_u16 % b_u16; + let mut a_u16: u16 = 1; + a_u16 %= 2; + + let a_u32: u32 = 17; + let b_u32: u32 = 3; + a_u32 % b_u32; + let mut a_u32: u32 = 1; + a_u32 %= 2; + + let a_u64: u64 = 17; + let b_u64: u64 = 3; + a_u64 % b_u64; + let mut a_u64: u64 = 1; + a_u64 %= 2; + + let a_u128: u128 = 17; + let b_u128: u128 = 3; + a_u128 % b_u128; + let mut a_u128: u128 = 1; + a_u128 %= 2; + + let a_usize: usize = 17; + let b_usize: usize = 3; + a_usize % b_usize; + let mut a_usize: usize = 1; + a_usize %= 2; +} diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_integral.stderr b/src/tools/clippy/tests/ui/modulo_arithmetic_integral.stderr new file mode 100644 index 0000000000..e863b83869 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_integral.stderr @@ -0,0 +1,156 @@ +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:15:5 + | +LL | a % b; + | ^^^^^ + | + = note: `-D clippy::modulo-arithmetic` implied by `-D warnings` + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:16:5 + | +LL | b % a; + | ^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:17:5 + | +LL | b %= a; + | ^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:21:5 + | +LL | a_i8 % b_i8; + | ^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:22:5 + | +LL | b_i8 %= a_i8; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:26:5 + | +LL | a_i16 % b_i16; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:27:5 + | +LL | b_i16 %= a_i16; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:31:5 + | +LL | a_i32 % b_i32; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:32:5 + | +LL | b_i32 %= a_i32; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:36:5 + | +LL | a_i64 % b_i64; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:37:5 + | +LL | b_i64 %= a_i64; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:41:5 + | +LL | a_i128 % b_i128; + | ^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:42:5 + | +LL | b_i128 %= a_i128; + | ^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:46:5 + | +LL | a_isize % b_isize; + | ^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:47:5 + | +LL | b_isize %= a_isize; + | ^^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:51:5 + | +LL | a % b; + | ^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:52:5 + | +LL | b %= a; + | ^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.rs b/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.rs new file mode 100644 index 0000000000..57a96692c0 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.rs @@ -0,0 +1,44 @@ +#![warn(clippy::modulo_arithmetic)] +#![allow( + unused, + clippy::shadow_reuse, + clippy::shadow_unrelated, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::modulo_one +)] + +fn main() { + // Lint when both sides are const and of the opposite sign + -1 % 2; + 1 % -2; + (1 - 2) % (1 + 2); + (1 + 2) % (1 - 2); + 35 * (7 - 4 * 2) % (-500 * -600); + + -1i8 % 2i8; + 1i8 % -2i8; + -1i16 % 2i16; + 1i16 % -2i16; + -1i32 % 2i32; + 1i32 % -2i32; + -1i64 % 2i64; + 1i64 % -2i64; + -1i128 % 2i128; + 1i128 % -2i128; + -1isize % 2isize; + 1isize % -2isize; + + // No lint when both sides are const and of the same sign + 1 % 2; + -1 % -2; + (1 + 2) % (-1 + 2); + (-1 - 2) % (1 - 2); + + 1u8 % 2u8; + 1u16 % 2u16; + 1u32 % 2u32; + 1u64 % 2u64; + 1u128 % 2u128; + 1usize % 2usize; +} diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.stderr b/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.stderr new file mode 100644 index 0000000000..de328bb75f --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.stderr @@ -0,0 +1,156 @@ +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:13:5 + | +LL | -1 % 2; + | ^^^^^^ + | + = note: `-D clippy::modulo-arithmetic` implied by `-D warnings` + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:14:5 + | +LL | 1 % -2; + | ^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 3` + --> $DIR/modulo_arithmetic_integral_const.rs:15:5 + | +LL | (1 - 2) % (1 + 2); + | ^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `3 % -1` + --> $DIR/modulo_arithmetic_integral_const.rs:16:5 + | +LL | (1 + 2) % (1 - 2); + | ^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-35 % 300000` + --> $DIR/modulo_arithmetic_integral_const.rs:17:5 + | +LL | 35 * (7 - 4 * 2) % (-500 * -600); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:19:5 + | +LL | -1i8 % 2i8; + | ^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:20:5 + | +LL | 1i8 % -2i8; + | ^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:21:5 + | +LL | -1i16 % 2i16; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:22:5 + | +LL | 1i16 % -2i16; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:23:5 + | +LL | -1i32 % 2i32; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:24:5 + | +LL | 1i32 % -2i32; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:25:5 + | +LL | -1i64 % 2i64; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:26:5 + | +LL | 1i64 % -2i64; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:27:5 + | +LL | -1i128 % 2i128; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:28:5 + | +LL | 1i128 % -2i128; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:29:5 + | +LL | -1isize % 2isize; + | ^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:30:5 + | +LL | 1isize % -2isize; + | ^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/modulo_one.rs b/src/tools/clippy/tests/ui/modulo_one.rs new file mode 100644 index 0000000000..678a312f66 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_one.rs @@ -0,0 +1,23 @@ +#![warn(clippy::modulo_one)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +static STATIC_ONE: usize = 2 - 1; +static STATIC_NEG_ONE: i64 = 1 - 2; + +fn main() { + 10 % 1; + 10 % -1; + 10 % 2; + i32::MIN % (-1); // also caught by rustc + + const ONE: u32 = 1 * 1; + const NEG_ONE: i64 = 1 - 2; + const INT_MIN: i64 = i64::MIN; + + 2 % ONE; + 5 % STATIC_ONE; // NOT caught by lint + 2 % NEG_ONE; + 5 % STATIC_NEG_ONE; // NOT caught by lint + INT_MIN % NEG_ONE; // also caught by rustc + INT_MIN % STATIC_NEG_ONE; // ONLY caught by rustc +} diff --git a/src/tools/clippy/tests/ui/modulo_one.stderr b/src/tools/clippy/tests/ui/modulo_one.stderr new file mode 100644 index 0000000000..2b2c699733 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_one.stderr @@ -0,0 +1,74 @@ +error: this arithmetic operation will overflow + --> $DIR/modulo_one.rs:11:5 + | +LL | i32::MIN % (-1); // also caught by rustc + | ^^^^^^^^^^^^^^^ attempt to compute the remainder of `i32::MIN % -1_i32`, which would overflow + | + = note: `#[deny(arithmetic_overflow)]` on by default + +error: this arithmetic operation will overflow + --> $DIR/modulo_one.rs:21:5 + | +LL | INT_MIN % NEG_ONE; // also caught by rustc + | ^^^^^^^^^^^^^^^^^ attempt to compute the remainder of `i64::MIN % -1_i64`, which would overflow + +error: this arithmetic operation will overflow + --> $DIR/modulo_one.rs:22:5 + | +LL | INT_MIN % STATIC_NEG_ONE; // ONLY caught by rustc + | ^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute the remainder of `i64::MIN % -1_i64`, which would overflow + +error: any number modulo 1 will be 0 + --> $DIR/modulo_one.rs:8:5 + | +LL | 10 % 1; + | ^^^^^^ + | + = note: `-D clippy::modulo-one` implied by `-D warnings` + +error: any number modulo -1 will panic/overflow or result in 0 + --> $DIR/modulo_one.rs:9:5 + | +LL | 10 % -1; + | ^^^^^^^ + +error: any number modulo -1 will panic/overflow or result in 0 + --> $DIR/modulo_one.rs:11:5 + | +LL | i32::MIN % (-1); // also caught by rustc + | ^^^^^^^^^^^^^^^ + +error: the operation is ineffective. Consider reducing it to `1` + --> $DIR/modulo_one.rs:13:22 + | +LL | const ONE: u32 = 1 * 1; + | ^^^^^ + | + = note: `-D clippy::identity-op` implied by `-D warnings` + +error: the operation is ineffective. Consider reducing it to `1` + --> $DIR/modulo_one.rs:13:22 + | +LL | const ONE: u32 = 1 * 1; + | ^^^^^ + +error: any number modulo 1 will be 0 + --> $DIR/modulo_one.rs:17:5 + | +LL | 2 % ONE; + | ^^^^^^^ + +error: any number modulo -1 will panic/overflow or result in 0 + --> $DIR/modulo_one.rs:19:5 + | +LL | 2 % NEG_ONE; + | ^^^^^^^^^^^ + +error: any number modulo -1 will panic/overflow or result in 0 + --> $DIR/modulo_one.rs:21:5 + | +LL | INT_MIN % NEG_ONE; // also caught by rustc + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/must_use_candidates.fixed b/src/tools/clippy/tests/ui/must_use_candidates.fixed new file mode 100644 index 0000000000..9556f6f82c --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_candidates.fixed @@ -0,0 +1,93 @@ +// run-rustfix +#![feature(never_type)] +#![allow(unused_mut, clippy::redundant_allocation)] +#![warn(clippy::must_use_candidate)] +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +pub struct MyAtomic(AtomicBool); +pub struct MyPure; + +#[must_use] pub fn pure(i: u8) -> u8 { + i +} + +impl MyPure { + #[must_use] pub fn inherent_pure(&self) -> u8 { + 0 + } +} + +pub trait MyPureTrait { + fn trait_pure(&self, i: u32) -> u32 { + self.trait_impl_pure(i) + 1 + } + + fn trait_impl_pure(&self, i: u32) -> u32; +} + +impl MyPureTrait for MyPure { + fn trait_impl_pure(&self, i: u32) -> u32 { + i + } +} + +pub fn without_result() { + // OK +} + +pub fn impure_primitive(i: &mut u8) -> u8 { + *i +} + +pub fn with_callback bool>(f: &F) -> bool { + f(0) +} + +#[must_use] pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { + true +} + +pub fn quoth_the_raven(_more: !) -> u32 { + unimplemented!(); +} + +pub fn atomics(b: &AtomicBool) -> bool { + b.load(Ordering::SeqCst) +} + +#[must_use] pub fn rcd(_x: Rc) -> bool { + true +} + +pub fn rcmut(_x: Rc<&mut u32>) -> bool { + true +} + +#[must_use] pub fn arcd(_x: Arc) -> bool { + false +} + +pub fn inner_types(_m: &MyAtomic) -> bool { + true +} + +static mut COUNTER: usize = 0; + +/// # Safety +/// +/// Don't ever call this from multiple threads +pub unsafe fn mutates_static() -> usize { + COUNTER += 1; + COUNTER +} + +#[no_mangle] +pub fn unmangled(i: bool) -> bool { + !i +} + +fn main() { + assert_eq!(1, pure(1)); +} diff --git a/src/tools/clippy/tests/ui/must_use_candidates.rs b/src/tools/clippy/tests/ui/must_use_candidates.rs new file mode 100644 index 0000000000..3732422017 --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_candidates.rs @@ -0,0 +1,93 @@ +// run-rustfix +#![feature(never_type)] +#![allow(unused_mut, clippy::redundant_allocation)] +#![warn(clippy::must_use_candidate)] +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +pub struct MyAtomic(AtomicBool); +pub struct MyPure; + +pub fn pure(i: u8) -> u8 { + i +} + +impl MyPure { + pub fn inherent_pure(&self) -> u8 { + 0 + } +} + +pub trait MyPureTrait { + fn trait_pure(&self, i: u32) -> u32 { + self.trait_impl_pure(i) + 1 + } + + fn trait_impl_pure(&self, i: u32) -> u32; +} + +impl MyPureTrait for MyPure { + fn trait_impl_pure(&self, i: u32) -> u32 { + i + } +} + +pub fn without_result() { + // OK +} + +pub fn impure_primitive(i: &mut u8) -> u8 { + *i +} + +pub fn with_callback bool>(f: &F) -> bool { + f(0) +} + +pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { + true +} + +pub fn quoth_the_raven(_more: !) -> u32 { + unimplemented!(); +} + +pub fn atomics(b: &AtomicBool) -> bool { + b.load(Ordering::SeqCst) +} + +pub fn rcd(_x: Rc) -> bool { + true +} + +pub fn rcmut(_x: Rc<&mut u32>) -> bool { + true +} + +pub fn arcd(_x: Arc) -> bool { + false +} + +pub fn inner_types(_m: &MyAtomic) -> bool { + true +} + +static mut COUNTER: usize = 0; + +/// # Safety +/// +/// Don't ever call this from multiple threads +pub unsafe fn mutates_static() -> usize { + COUNTER += 1; + COUNTER +} + +#[no_mangle] +pub fn unmangled(i: bool) -> bool { + !i +} + +fn main() { + assert_eq!(1, pure(1)); +} diff --git a/src/tools/clippy/tests/ui/must_use_candidates.stderr b/src/tools/clippy/tests/ui/must_use_candidates.stderr new file mode 100644 index 0000000000..0fa3849d03 --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_candidates.stderr @@ -0,0 +1,34 @@ +error: this function could have a `#[must_use]` attribute + --> $DIR/must_use_candidates.rs:12:1 + | +LL | pub fn pure(i: u8) -> u8 { + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn pure(i: u8) -> u8` + | + = note: `-D clippy::must-use-candidate` implied by `-D warnings` + +error: this method could have a `#[must_use]` attribute + --> $DIR/must_use_candidates.rs:17:5 + | +LL | pub fn inherent_pure(&self) -> u8 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn inherent_pure(&self) -> u8` + +error: this function could have a `#[must_use]` attribute + --> $DIR/must_use_candidates.rs:48:1 + | +LL | pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool` + +error: this function could have a `#[must_use]` attribute + --> $DIR/must_use_candidates.rs:60:1 + | +LL | pub fn rcd(_x: Rc) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn rcd(_x: Rc) -> bool` + +error: this function could have a `#[must_use]` attribute + --> $DIR/must_use_candidates.rs:68:1 + | +LL | pub fn arcd(_x: Arc) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn arcd(_x: Arc) -> bool` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/must_use_unit.fixed b/src/tools/clippy/tests/ui/must_use_unit.fixed new file mode 100644 index 0000000000..6c9aa434ac --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_unit.fixed @@ -0,0 +1,26 @@ +//run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::must_use_unit)] +#![allow(clippy::unused_unit)] + +#[macro_use] +extern crate macro_rules; + + +pub fn must_use_default() {} + + +pub fn must_use_unit() -> () {} + + +pub fn must_use_with_note() {} + +fn main() { + must_use_default(); + must_use_unit(); + must_use_with_note(); + + // We should not lint in external macros + must_use_unit!(); +} diff --git a/src/tools/clippy/tests/ui/must_use_unit.rs b/src/tools/clippy/tests/ui/must_use_unit.rs new file mode 100644 index 0000000000..8a395dc284 --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_unit.rs @@ -0,0 +1,26 @@ +//run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::must_use_unit)] +#![allow(clippy::unused_unit)] + +#[macro_use] +extern crate macro_rules; + +#[must_use] +pub fn must_use_default() {} + +#[must_use] +pub fn must_use_unit() -> () {} + +#[must_use = "With note"] +pub fn must_use_with_note() {} + +fn main() { + must_use_default(); + must_use_unit(); + must_use_with_note(); + + // We should not lint in external macros + must_use_unit!(); +} diff --git a/src/tools/clippy/tests/ui/must_use_unit.stderr b/src/tools/clippy/tests/ui/must_use_unit.stderr new file mode 100644 index 0000000000..15e0906b66 --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_unit.stderr @@ -0,0 +1,28 @@ +error: this unit-returning function has a `#[must_use]` attribute + --> $DIR/must_use_unit.rs:11:1 + | +LL | #[must_use] + | ----------- help: remove the attribute +LL | pub fn must_use_default() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::must-use-unit` implied by `-D warnings` + +error: this unit-returning function has a `#[must_use]` attribute + --> $DIR/must_use_unit.rs:14:1 + | +LL | #[must_use] + | ----------- help: remove the attribute +LL | pub fn must_use_unit() -> () {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this unit-returning function has a `#[must_use]` attribute + --> $DIR/must_use_unit.rs:17:1 + | +LL | #[must_use = "With note"] + | ------------------------- help: remove the attribute +LL | pub fn must_use_with_note() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_from_ref.rs b/src/tools/clippy/tests/ui/mut_from_ref.rs new file mode 100644 index 0000000000..a9a04c8f56 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_from_ref.rs @@ -0,0 +1,46 @@ +#![allow(unused)] +#![warn(clippy::mut_from_ref)] + +struct Foo; + +impl Foo { + fn this_wont_hurt_a_bit(&self) -> &mut Foo { + unimplemented!() + } +} + +trait Ouch { + fn ouch(x: &Foo) -> &mut Foo; +} + +impl Ouch for Foo { + fn ouch(x: &Foo) -> &mut Foo { + unimplemented!() + } +} + +fn fail(x: &u32) -> &mut u16 { + unimplemented!() +} + +fn fail_lifetime<'a>(x: &'a u32, y: &mut u32) -> &'a mut u32 { + unimplemented!() +} + +fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 { + unimplemented!() +} + +// this is OK, because the result borrows y +fn works<'a>(x: &u32, y: &'a mut u32) -> &'a mut u32 { + unimplemented!() +} + +// this is also OK, because the result could borrow y +fn also_works<'a>(x: &'a u32, y: &'a mut u32) -> &'a mut u32 { + unimplemented!() +} + +fn main() { + //TODO +} diff --git a/src/tools/clippy/tests/ui/mut_from_ref.stderr b/src/tools/clippy/tests/ui/mut_from_ref.stderr new file mode 100644 index 0000000000..4787999920 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_from_ref.stderr @@ -0,0 +1,63 @@ +error: mutable borrow from immutable input(s) + --> $DIR/mut_from_ref.rs:7:39 + | +LL | fn this_wont_hurt_a_bit(&self) -> &mut Foo { + | ^^^^^^^^ + | + = note: `-D clippy::mut-from-ref` implied by `-D warnings` +note: immutable borrow here + --> $DIR/mut_from_ref.rs:7:29 + | +LL | fn this_wont_hurt_a_bit(&self) -> &mut Foo { + | ^^^^^ + +error: mutable borrow from immutable input(s) + --> $DIR/mut_from_ref.rs:13:25 + | +LL | fn ouch(x: &Foo) -> &mut Foo; + | ^^^^^^^^ + | +note: immutable borrow here + --> $DIR/mut_from_ref.rs:13:16 + | +LL | fn ouch(x: &Foo) -> &mut Foo; + | ^^^^ + +error: mutable borrow from immutable input(s) + --> $DIR/mut_from_ref.rs:22:21 + | +LL | fn fail(x: &u32) -> &mut u16 { + | ^^^^^^^^ + | +note: immutable borrow here + --> $DIR/mut_from_ref.rs:22:12 + | +LL | fn fail(x: &u32) -> &mut u16 { + | ^^^^ + +error: mutable borrow from immutable input(s) + --> $DIR/mut_from_ref.rs:26:50 + | +LL | fn fail_lifetime<'a>(x: &'a u32, y: &mut u32) -> &'a mut u32 { + | ^^^^^^^^^^^ + | +note: immutable borrow here + --> $DIR/mut_from_ref.rs:26:25 + | +LL | fn fail_lifetime<'a>(x: &'a u32, y: &mut u32) -> &'a mut u32 { + | ^^^^^^^ + +error: mutable borrow from immutable input(s) + --> $DIR/mut_from_ref.rs:30:67 + | +LL | fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 { + | ^^^^^^^^^^^ + | +note: immutable borrow here + --> $DIR/mut_from_ref.rs:30:27 + | +LL | fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 { + | ^^^^^^^ ^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_key.rs b/src/tools/clippy/tests/ui/mut_key.rs new file mode 100644 index 0000000000..2d227e6654 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_key.rs @@ -0,0 +1,55 @@ +use std::collections::{HashMap, HashSet}; +use std::hash::{Hash, Hasher}; +use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + +struct Key(AtomicUsize); + +impl Clone for Key { + fn clone(&self) -> Self { + Key(AtomicUsize::new(self.0.load(Relaxed))) + } +} + +impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.0.load(Relaxed) == other.0.load(Relaxed) + } +} + +impl Eq for Key {} + +impl Hash for Key { + fn hash(&self, h: &mut H) { + self.0.load(Relaxed).hash(h); + } +} + +fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { + let _other: HashMap = HashMap::new(); + m.keys().cloned().collect() +} + +fn this_is_ok(_m: &mut HashMap) {} + +#[allow(unused)] +trait Trait { + type AssociatedType; + + fn trait_fn(&self, set: std::collections::HashSet); +} + +fn generics_are_ok_too(_m: &mut HashSet) { + // nothing to see here, move along +} + +fn tuples(_m: &mut HashMap<((), U), ()>) {} + +fn tuples_bad(_m: &mut HashMap<(Key, U), bool>) {} + +fn main() { + let _ = should_not_take_this_arg(&mut HashMap::new(), 1); + this_is_ok(&mut HashMap::new()); + tuples::(&mut HashMap::new()); + tuples::<()>(&mut HashMap::new()); + tuples_bad::<()>(&mut HashMap::new()); +} diff --git a/src/tools/clippy/tests/ui/mut_key.stderr b/src/tools/clippy/tests/ui/mut_key.stderr new file mode 100644 index 0000000000..8d6a259c7e --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_key.stderr @@ -0,0 +1,28 @@ +error: mutable key type + --> $DIR/mut_key.rs:27:32 + | +LL | fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::mutable_key_type)]` on by default + +error: mutable key type + --> $DIR/mut_key.rs:27:72 + | +LL | fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { + | ^^^^^^^^^^^^ + +error: mutable key type + --> $DIR/mut_key.rs:28:5 + | +LL | let _other: HashMap = HashMap::new(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: mutable key type + --> $DIR/mut_key.rs:47:22 + | +LL | fn tuples_bad(_m: &mut HashMap<(Key, U), bool>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_mut.rs b/src/tools/clippy/tests/ui/mut_mut.rs new file mode 100644 index 0000000000..8965cef66d --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_mut.rs @@ -0,0 +1,49 @@ +#![allow(unused, clippy::no_effect, clippy::unnecessary_operation)] +#![warn(clippy::mut_mut)] + +fn fun(x: &mut &mut u32) -> bool { + **x > 0 +} + +fn less_fun(x: *mut *mut u32) { + let y = x; +} + +macro_rules! mut_ptr { + ($p:expr) => { + &mut $p + }; +} + +#[allow(unused_mut, unused_variables)] +fn main() { + let mut x = &mut &mut 1u32; + { + let mut y = &mut x; + } + + if fun(x) { + let y: &mut &mut u32 = &mut &mut 2; + **y + **x; + } + + if fun(x) { + let y: &mut &mut &mut u32 = &mut &mut &mut 2; + ***y + **x; + } + + let mut z = mut_ptr!(&mut 3u32); +} + +fn issue939() { + let array = [5, 6, 7, 8, 9]; + let mut args = array.iter().skip(2); + for &arg in &mut args { + println!("{}", arg); + } + + let args = &mut args; + for arg in args { + println!(":{}", arg); + } +} diff --git a/src/tools/clippy/tests/ui/mut_mut.stderr b/src/tools/clippy/tests/ui/mut_mut.stderr new file mode 100644 index 0000000000..44e8142271 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_mut.stderr @@ -0,0 +1,63 @@ +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:4:11 + | +LL | fn fun(x: &mut &mut u32) -> bool { + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::mut-mut` implied by `-D warnings` + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:20:17 + | +LL | let mut x = &mut &mut 1u32; + | ^^^^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:14:9 + | +LL | &mut $p + | ^^^^^^^ +... +LL | let mut z = mut_ptr!(&mut 3u32); + | ------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this expression mutably borrows a mutable reference. Consider reborrowing + --> $DIR/mut_mut.rs:22:21 + | +LL | let mut y = &mut x; + | ^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:26:32 + | +LL | let y: &mut &mut u32 = &mut &mut 2; + | ^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:26:16 + | +LL | let y: &mut &mut u32 = &mut &mut 2; + | ^^^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:31:37 + | +LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; + | ^^^^^^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:31:16 + | +LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; + | ^^^^^^^^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:31:21 + | +LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; + | ^^^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_mutex_lock.fixed b/src/tools/clippy/tests/ui/mut_mutex_lock.fixed new file mode 100644 index 0000000000..36bc52e337 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_mutex_lock.fixed @@ -0,0 +1,21 @@ +// run-rustfix +#![allow(dead_code, unused_mut)] +#![warn(clippy::mut_mutex_lock)] + +use std::sync::{Arc, Mutex}; + +fn mut_mutex_lock() { + let mut value_rc = Arc::new(Mutex::new(42_u8)); + let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + + let mut value = value_mutex.get_mut().unwrap(); + *value += 1; +} + +fn no_owned_mutex_lock() { + let mut value_rc = Arc::new(Mutex::new(42_u8)); + let mut value = value_rc.lock().unwrap(); + *value += 1; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/mut_mutex_lock.rs b/src/tools/clippy/tests/ui/mut_mutex_lock.rs new file mode 100644 index 0000000000..ea60df5ae1 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_mutex_lock.rs @@ -0,0 +1,21 @@ +// run-rustfix +#![allow(dead_code, unused_mut)] +#![warn(clippy::mut_mutex_lock)] + +use std::sync::{Arc, Mutex}; + +fn mut_mutex_lock() { + let mut value_rc = Arc::new(Mutex::new(42_u8)); + let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + + let mut value = value_mutex.lock().unwrap(); + *value += 1; +} + +fn no_owned_mutex_lock() { + let mut value_rc = Arc::new(Mutex::new(42_u8)); + let mut value = value_rc.lock().unwrap(); + *value += 1; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/mut_mutex_lock.stderr b/src/tools/clippy/tests/ui/mut_mutex_lock.stderr new file mode 100644 index 0000000000..21c1b3486c --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_mutex_lock.stderr @@ -0,0 +1,10 @@ +error: calling `&mut Mutex::lock` unnecessarily locks an exclusive (mutable) reference + --> $DIR/mut_mutex_lock.rs:11:33 + | +LL | let mut value = value_mutex.lock().unwrap(); + | ^^^^ help: change this to: `get_mut` + | + = note: `-D clippy::mut-mutex-lock` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/mut_range_bound.rs b/src/tools/clippy/tests/ui/mut_range_bound.rs new file mode 100644 index 0000000000..1348dd2a3d --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_range_bound.rs @@ -0,0 +1,63 @@ +#![allow(unused)] + +fn main() { + mut_range_bound_upper(); + mut_range_bound_lower(); + mut_range_bound_both(); + mut_range_bound_no_mutation(); + immut_range_bound(); + mut_borrow_range_bound(); + immut_borrow_range_bound(); +} + +fn mut_range_bound_upper() { + let mut m = 4; + for i in 0..m { + m = 5; + } // warning +} + +fn mut_range_bound_lower() { + let mut m = 4; + for i in m..10 { + m *= 2; + } // warning +} + +fn mut_range_bound_both() { + let mut m = 4; + let mut n = 6; + for i in m..n { + m = 5; + n = 7; + } // warning (1 for each mutated bound) +} + +fn mut_range_bound_no_mutation() { + let mut m = 4; + for i in 0..m { + continue; + } // no warning +} + +fn mut_borrow_range_bound() { + let mut m = 4; + for i in 0..m { + let n = &mut m; // warning + *n += 1; + } +} + +fn immut_borrow_range_bound() { + let mut m = 4; + for i in 0..m { + let n = &m; // should be no warning? + } +} + +fn immut_range_bound() { + let m = 4; + for i in 0..m { + continue; + } // no warning +} diff --git a/src/tools/clippy/tests/ui/mut_range_bound.stderr b/src/tools/clippy/tests/ui/mut_range_bound.stderr new file mode 100644 index 0000000000..0eeb76e0ec --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_range_bound.stderr @@ -0,0 +1,34 @@ +error: attempt to mutate range bound within loop; note that the range of the loop is unchanged + --> $DIR/mut_range_bound.rs:16:9 + | +LL | m = 5; + | ^ + | + = note: `-D clippy::mut-range-bound` implied by `-D warnings` + +error: attempt to mutate range bound within loop; note that the range of the loop is unchanged + --> $DIR/mut_range_bound.rs:23:9 + | +LL | m *= 2; + | ^ + +error: attempt to mutate range bound within loop; note that the range of the loop is unchanged + --> $DIR/mut_range_bound.rs:31:9 + | +LL | m = 5; + | ^ + +error: attempt to mutate range bound within loop; note that the range of the loop is unchanged + --> $DIR/mut_range_bound.rs:32:9 + | +LL | n = 7; + | ^ + +error: attempt to mutate range bound within loop; note that the range of the loop is unchanged + --> $DIR/mut_range_bound.rs:46:22 + | +LL | let n = &mut m; // warning + | ^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_reference.rs b/src/tools/clippy/tests/ui/mut_reference.rs new file mode 100644 index 0000000000..73906121c4 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_reference.rs @@ -0,0 +1,43 @@ +#![allow(unused_variables)] + +fn takes_an_immutable_reference(a: &i32) {} +fn takes_a_mutable_reference(a: &mut i32) {} + +struct MyStruct; + +impl MyStruct { + fn takes_an_immutable_reference(&self, a: &i32) {} + + fn takes_a_mutable_reference(&self, a: &mut i32) {} +} + +#[warn(clippy::unnecessary_mut_passed)] +fn main() { + // Functions + takes_an_immutable_reference(&mut 42); + let as_ptr: fn(&i32) = takes_an_immutable_reference; + as_ptr(&mut 42); + + // Methods + let my_struct = MyStruct; + my_struct.takes_an_immutable_reference(&mut 42); + + // No error + + // Functions + takes_an_immutable_reference(&42); + let as_ptr: fn(&i32) = takes_an_immutable_reference; + as_ptr(&42); + + takes_a_mutable_reference(&mut 42); + let as_ptr: fn(&mut i32) = takes_a_mutable_reference; + as_ptr(&mut 42); + + let a = &mut 42; + takes_an_immutable_reference(a); + + // Methods + my_struct.takes_an_immutable_reference(&42); + my_struct.takes_a_mutable_reference(&mut 42); + my_struct.takes_an_immutable_reference(a); +} diff --git a/src/tools/clippy/tests/ui/mut_reference.stderr b/src/tools/clippy/tests/ui/mut_reference.stderr new file mode 100644 index 0000000000..062d30b262 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_reference.stderr @@ -0,0 +1,22 @@ +error: the function `takes_an_immutable_reference` doesn't need a mutable reference + --> $DIR/mut_reference.rs:17:34 + | +LL | takes_an_immutable_reference(&mut 42); + | ^^^^^^^ + | + = note: `-D clippy::unnecessary-mut-passed` implied by `-D warnings` + +error: the function `as_ptr` doesn't need a mutable reference + --> $DIR/mut_reference.rs:19:12 + | +LL | as_ptr(&mut 42); + | ^^^^^^^ + +error: the method `takes_an_immutable_reference` doesn't need a mutable reference + --> $DIR/mut_reference.rs:23:44 + | +LL | my_struct.takes_an_immutable_reference(&mut 42); + | ^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/mutex_atomic.rs b/src/tools/clippy/tests/ui/mutex_atomic.rs new file mode 100644 index 0000000000..b9d78b7f47 --- /dev/null +++ b/src/tools/clippy/tests/ui/mutex_atomic.rs @@ -0,0 +1,15 @@ +#![warn(clippy::all)] +#![warn(clippy::mutex_integer)] + +fn main() { + use std::sync::Mutex; + Mutex::new(true); + Mutex::new(5usize); + Mutex::new(9isize); + let mut x = 4u32; + Mutex::new(&x as *const u32); + Mutex::new(&mut x as *mut u32); + Mutex::new(0u32); + Mutex::new(0i32); + Mutex::new(0f32); // there are no float atomics, so this should not lint +} diff --git a/src/tools/clippy/tests/ui/mutex_atomic.stderr b/src/tools/clippy/tests/ui/mutex_atomic.stderr new file mode 100644 index 0000000000..a3511ba708 --- /dev/null +++ b/src/tools/clippy/tests/ui/mutex_atomic.stderr @@ -0,0 +1,48 @@ +error: consider using an `AtomicBool` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:6:5 + | +LL | Mutex::new(true); + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::mutex-atomic` implied by `-D warnings` + +error: consider using an `AtomicUsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:7:5 + | +LL | Mutex::new(5usize); + | ^^^^^^^^^^^^^^^^^^ + +error: consider using an `AtomicIsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:8:5 + | +LL | Mutex::new(9isize); + | ^^^^^^^^^^^^^^^^^^ + +error: consider using an `AtomicPtr` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:10:5 + | +LL | Mutex::new(&x as *const u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: consider using an `AtomicPtr` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:11:5 + | +LL | Mutex::new(&mut x as *mut u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: consider using an `AtomicUsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:12:5 + | +LL | Mutex::new(0u32); + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::mutex-integer` implied by `-D warnings` + +error: consider using an `AtomicIsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:13:5 + | +LL | Mutex::new(0i32); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_arbitrary_self_type.fixed b/src/tools/clippy/tests/ui/needless_arbitrary_self_type.fixed new file mode 100644 index 0000000000..9da21eb6b2 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_arbitrary_self_type.fixed @@ -0,0 +1,69 @@ +// run-rustfix + +#![warn(clippy::needless_arbitrary_self_type)] +#![allow(unused_mut, clippy::needless_lifetimes)] + +pub enum ValType { + A, + B, +} + +impl ValType { + pub fn bad(self) { + unimplemented!(); + } + + pub fn good(self) { + unimplemented!(); + } + + pub fn mut_bad(mut self) { + unimplemented!(); + } + + pub fn mut_good(mut self) { + unimplemented!(); + } + + pub fn ref_bad(&self) { + unimplemented!(); + } + + pub fn ref_good(&self) { + unimplemented!(); + } + + pub fn ref_bad_with_lifetime<'a>(&'a self) { + unimplemented!(); + } + + pub fn ref_good_with_lifetime<'a>(&'a self) { + unimplemented!(); + } + + pub fn mut_ref_bad(&mut self) { + unimplemented!(); + } + + pub fn mut_ref_good(&mut self) { + unimplemented!(); + } + + pub fn mut_ref_bad_with_lifetime<'a>(&'a mut self) { + unimplemented!(); + } + + pub fn mut_ref_good_with_lifetime<'a>(&'a mut self) { + unimplemented!(); + } + + pub fn mut_ref_mut_good(mut self: &mut Self) { + unimplemented!(); + } + + pub fn mut_ref_mut_ref_good(self: &&mut &mut Self) { + unimplemented!(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_arbitrary_self_type.rs b/src/tools/clippy/tests/ui/needless_arbitrary_self_type.rs new file mode 100644 index 0000000000..17aeaaf97a --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_arbitrary_self_type.rs @@ -0,0 +1,69 @@ +// run-rustfix + +#![warn(clippy::needless_arbitrary_self_type)] +#![allow(unused_mut, clippy::needless_lifetimes)] + +pub enum ValType { + A, + B, +} + +impl ValType { + pub fn bad(self: Self) { + unimplemented!(); + } + + pub fn good(self) { + unimplemented!(); + } + + pub fn mut_bad(mut self: Self) { + unimplemented!(); + } + + pub fn mut_good(mut self) { + unimplemented!(); + } + + pub fn ref_bad(self: &Self) { + unimplemented!(); + } + + pub fn ref_good(&self) { + unimplemented!(); + } + + pub fn ref_bad_with_lifetime<'a>(self: &'a Self) { + unimplemented!(); + } + + pub fn ref_good_with_lifetime<'a>(&'a self) { + unimplemented!(); + } + + pub fn mut_ref_bad(self: &mut Self) { + unimplemented!(); + } + + pub fn mut_ref_good(&mut self) { + unimplemented!(); + } + + pub fn mut_ref_bad_with_lifetime<'a>(self: &'a mut Self) { + unimplemented!(); + } + + pub fn mut_ref_good_with_lifetime<'a>(&'a mut self) { + unimplemented!(); + } + + pub fn mut_ref_mut_good(mut self: &mut Self) { + unimplemented!(); + } + + pub fn mut_ref_mut_ref_good(self: &&mut &mut Self) { + unimplemented!(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_arbitrary_self_type.stderr b/src/tools/clippy/tests/ui/needless_arbitrary_self_type.stderr new file mode 100644 index 0000000000..f4c645d35c --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_arbitrary_self_type.stderr @@ -0,0 +1,40 @@ +error: the type of the `self` parameter does not need to be arbitrary + --> $DIR/needless_arbitrary_self_type.rs:12:16 + | +LL | pub fn bad(self: Self) { + | ^^^^^^^^^^ help: consider to change this parameter to: `self` + | + = note: `-D clippy::needless-arbitrary-self-type` implied by `-D warnings` + +error: the type of the `self` parameter does not need to be arbitrary + --> $DIR/needless_arbitrary_self_type.rs:20:20 + | +LL | pub fn mut_bad(mut self: Self) { + | ^^^^^^^^^^^^^^ help: consider to change this parameter to: `mut self` + +error: the type of the `self` parameter does not need to be arbitrary + --> $DIR/needless_arbitrary_self_type.rs:28:20 + | +LL | pub fn ref_bad(self: &Self) { + | ^^^^^^^^^^^ help: consider to change this parameter to: `&self` + +error: the type of the `self` parameter does not need to be arbitrary + --> $DIR/needless_arbitrary_self_type.rs:36:38 + | +LL | pub fn ref_bad_with_lifetime<'a>(self: &'a Self) { + | ^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'a self` + +error: the type of the `self` parameter does not need to be arbitrary + --> $DIR/needless_arbitrary_self_type.rs:44:24 + | +LL | pub fn mut_ref_bad(self: &mut Self) { + | ^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&mut self` + +error: the type of the `self` parameter does not need to be arbitrary + --> $DIR/needless_arbitrary_self_type.rs:52:42 + | +LL | pub fn mut_ref_bad_with_lifetime<'a>(self: &'a mut Self) { + | ^^^^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'a mut self` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_arbitrary_self_type_unfixable.rs b/src/tools/clippy/tests/ui/needless_arbitrary_self_type_unfixable.rs new file mode 100644 index 0000000000..a39d96109f --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_arbitrary_self_type_unfixable.rs @@ -0,0 +1,45 @@ +// aux-build:proc_macro_attr.rs + +#![warn(clippy::needless_arbitrary_self_type)] + +#[macro_use] +extern crate proc_macro_attr; + +mod issue_6089 { + // Check that we don't lint if the `self` parameter comes from expansion + + macro_rules! test_from_expansion { + () => { + trait T1 { + fn test(self: &Self); + } + + struct S1 {} + + impl T1 for S1 { + fn test(self: &Self) {} + } + }; + } + + test_from_expansion!(); + + // If only the lifetime name comes from expansion we will lint, but the suggestion will have + // placeholders and will not be applied automatically, as we can't reliably know the original name. + // This specific case happened with async_trait. + + trait T2 { + fn call_with_mut_self(&mut self); + } + + struct S2 {} + + // The method's signature will be expanded to: + // fn call_with_mut_self<'life0>(self: &'life0 mut Self) {} + #[rename_my_lifetimes] + impl T2 for S2 { + fn call_with_mut_self(self: &mut Self) {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_arbitrary_self_type_unfixable.stderr b/src/tools/clippy/tests/ui/needless_arbitrary_self_type_unfixable.stderr new file mode 100644 index 0000000000..44a0e6ddea --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_arbitrary_self_type_unfixable.stderr @@ -0,0 +1,10 @@ +error: the type of the `self` parameter does not need to be arbitrary + --> $DIR/needless_arbitrary_self_type_unfixable.rs:41:31 + | +LL | fn call_with_mut_self(self: &mut Self) {} + | ^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'_ mut self` + | + = note: `-D clippy::needless-arbitrary-self-type` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/needless_bool/fixable.fixed b/src/tools/clippy/tests/ui/needless_bool/fixable.fixed new file mode 100644 index 0000000000..567dbc5410 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_bool/fixable.fixed @@ -0,0 +1,98 @@ +// run-rustfix + +#![warn(clippy::needless_bool)] +#![allow( + unused, + dead_code, + clippy::no_effect, + clippy::if_same_then_else, + clippy::needless_return +)] + +use std::cell::Cell; + +macro_rules! bool_comparison_trigger { + ($($i:ident: $def:expr, $stb:expr );+ $(;)*) => ( + + #[derive(Clone)] + pub struct Trigger { + $($i: (Cell, bool, bool)),+ + } + + #[allow(dead_code)] + impl Trigger { + pub fn trigger(&self, key: &str) -> bool { + $( + if let stringify!($i) = key { + return self.$i.1 && self.$i.2 == $def; + } + )+ + false + } + } + ) +} + +fn main() { + let x = true; + let y = false; + x; + !x; + !(x && y); + if x { + x + } else { + false + }; // would also be questionable, but we don't catch this yet + bool_ret3(x); + bool_ret4(x); + bool_ret5(x, x); + bool_ret6(x, x); + needless_bool(x); + needless_bool2(x); + needless_bool3(x); +} + +fn bool_ret3(x: bool) -> bool { + return x; +} + +fn bool_ret4(x: bool) -> bool { + return !x; +} + +fn bool_ret5(x: bool, y: bool) -> bool { + return x && y; +} + +fn bool_ret6(x: bool, y: bool) -> bool { + return !(x && y); +} + +fn needless_bool(x: bool) { + if x {}; +} + +fn needless_bool2(x: bool) { + if !x {}; +} + +fn needless_bool3(x: bool) { + bool_comparison_trigger! { + test_one: false, false; + test_three: false, false; + test_two: true, true; + } + + if x {}; + if !x {}; +} + +fn needless_bool_in_the_suggestion_wraps_the_predicate_of_if_else_statement_in_brackets() { + let b = false; + let returns_bool = || false; + + let x = if b { + true + } else { !returns_bool() }; +} diff --git a/src/tools/clippy/tests/ui/needless_bool/fixable.rs b/src/tools/clippy/tests/ui/needless_bool/fixable.rs new file mode 100644 index 0000000000..10126ad4db --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_bool/fixable.rs @@ -0,0 +1,130 @@ +// run-rustfix + +#![warn(clippy::needless_bool)] +#![allow( + unused, + dead_code, + clippy::no_effect, + clippy::if_same_then_else, + clippy::needless_return +)] + +use std::cell::Cell; + +macro_rules! bool_comparison_trigger { + ($($i:ident: $def:expr, $stb:expr );+ $(;)*) => ( + + #[derive(Clone)] + pub struct Trigger { + $($i: (Cell, bool, bool)),+ + } + + #[allow(dead_code)] + impl Trigger { + pub fn trigger(&self, key: &str) -> bool { + $( + if let stringify!($i) = key { + return self.$i.1 && self.$i.2 == $def; + } + )+ + false + } + } + ) +} + +fn main() { + let x = true; + let y = false; + if x { + true + } else { + false + }; + if x { + false + } else { + true + }; + if x && y { + false + } else { + true + }; + if x { + x + } else { + false + }; // would also be questionable, but we don't catch this yet + bool_ret3(x); + bool_ret4(x); + bool_ret5(x, x); + bool_ret6(x, x); + needless_bool(x); + needless_bool2(x); + needless_bool3(x); +} + +fn bool_ret3(x: bool) -> bool { + if x { + return true; + } else { + return false; + }; +} + +fn bool_ret4(x: bool) -> bool { + if x { + return false; + } else { + return true; + }; +} + +fn bool_ret5(x: bool, y: bool) -> bool { + if x && y { + return true; + } else { + return false; + }; +} + +fn bool_ret6(x: bool, y: bool) -> bool { + if x && y { + return false; + } else { + return true; + }; +} + +fn needless_bool(x: bool) { + if x == true {}; +} + +fn needless_bool2(x: bool) { + if x == false {}; +} + +fn needless_bool3(x: bool) { + bool_comparison_trigger! { + test_one: false, false; + test_three: false, false; + test_two: true, true; + } + + if x == true {}; + if x == false {}; +} + +fn needless_bool_in_the_suggestion_wraps_the_predicate_of_if_else_statement_in_brackets() { + let b = false; + let returns_bool = || false; + + let x = if b { + true + } else if returns_bool() { + false + } else { + true + }; +} diff --git a/src/tools/clippy/tests/ui/needless_bool/fixable.stderr b/src/tools/clippy/tests/ui/needless_bool/fixable.stderr new file mode 100644 index 0000000000..25abfb2a47 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_bool/fixable.stderr @@ -0,0 +1,111 @@ +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:39:5 + | +LL | / if x { +LL | | true +LL | | } else { +LL | | false +LL | | }; + | |_____^ help: you can reduce it to: `x` + | + = note: `-D clippy::needless-bool` implied by `-D warnings` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:44:5 + | +LL | / if x { +LL | | false +LL | | } else { +LL | | true +LL | | }; + | |_____^ help: you can reduce it to: `!x` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:49:5 + | +LL | / if x && y { +LL | | false +LL | | } else { +LL | | true +LL | | }; + | |_____^ help: you can reduce it to: `!(x && y)` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:69:5 + | +LL | / if x { +LL | | return true; +LL | | } else { +LL | | return false; +LL | | }; + | |_____^ help: you can reduce it to: `return x` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:77:5 + | +LL | / if x { +LL | | return false; +LL | | } else { +LL | | return true; +LL | | }; + | |_____^ help: you can reduce it to: `return !x` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:85:5 + | +LL | / if x && y { +LL | | return true; +LL | | } else { +LL | | return false; +LL | | }; + | |_____^ help: you can reduce it to: `return x && y` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:93:5 + | +LL | / if x && y { +LL | | return false; +LL | | } else { +LL | | return true; +LL | | }; + | |_____^ help: you can reduce it to: `return !(x && y)` + +error: equality checks against true are unnecessary + --> $DIR/fixable.rs:101:8 + | +LL | if x == true {}; + | ^^^^^^^^^ help: try simplifying it as shown: `x` + | + = note: `-D clippy::bool-comparison` implied by `-D warnings` + +error: equality checks against false can be replaced by a negation + --> $DIR/fixable.rs:105:8 + | +LL | if x == false {}; + | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: equality checks against true are unnecessary + --> $DIR/fixable.rs:115:8 + | +LL | if x == true {}; + | ^^^^^^^^^ help: try simplifying it as shown: `x` + +error: equality checks against false can be replaced by a negation + --> $DIR/fixable.rs:116:8 + | +LL | if x == false {}; + | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:125:12 + | +LL | } else if returns_bool() { + | ____________^ +LL | | false +LL | | } else { +LL | | true +LL | | }; + | |_____^ help: you can reduce it to: `{ !returns_bool() }` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_bool/simple.rs b/src/tools/clippy/tests/ui/needless_bool/simple.rs new file mode 100644 index 0000000000..e9f1428fc3 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_bool/simple.rs @@ -0,0 +1,46 @@ +#![warn(clippy::needless_bool)] +#![allow( + unused, + dead_code, + clippy::no_effect, + clippy::if_same_then_else, + clippy::needless_return +)] + +fn main() { + let x = true; + let y = false; + if x { + true + } else { + true + }; + if x { + false + } else { + false + }; + if x { + x + } else { + false + }; // would also be questionable, but we don't catch this yet + bool_ret(x); + bool_ret2(x); +} + +fn bool_ret(x: bool) -> bool { + if x { + return true; + } else { + return true; + }; +} + +fn bool_ret2(x: bool) -> bool { + if x { + return false; + } else { + return false; + }; +} diff --git a/src/tools/clippy/tests/ui/needless_bool/simple.stderr b/src/tools/clippy/tests/ui/needless_bool/simple.stderr new file mode 100644 index 0000000000..c57a8a042f --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_bool/simple.stderr @@ -0,0 +1,44 @@ +error: this if-then-else expression will always return true + --> $DIR/simple.rs:13:5 + | +LL | / if x { +LL | | true +LL | | } else { +LL | | true +LL | | }; + | |_____^ + | + = note: `-D clippy::needless-bool` implied by `-D warnings` + +error: this if-then-else expression will always return false + --> $DIR/simple.rs:18:5 + | +LL | / if x { +LL | | false +LL | | } else { +LL | | false +LL | | }; + | |_____^ + +error: this if-then-else expression will always return true + --> $DIR/simple.rs:33:5 + | +LL | / if x { +LL | | return true; +LL | | } else { +LL | | return true; +LL | | }; + | |_____^ + +error: this if-then-else expression will always return false + --> $DIR/simple.rs:41:5 + | +LL | / if x { +LL | | return false; +LL | | } else { +LL | | return false; +LL | | }; + | |_____^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_borrow.fixed b/src/tools/clippy/tests/ui/needless_borrow.fixed new file mode 100644 index 0000000000..5ae4a0e79b --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrow.fixed @@ -0,0 +1,61 @@ +// run-rustfix + +#![allow(clippy::needless_borrowed_reference)] + +fn x(y: &i32) -> i32 { + *y +} + +#[warn(clippy::all, clippy::needless_borrow)] +#[allow(unused_variables)] +fn main() { + let a = 5; + let b = x(&a); + let c = x(&a); + let s = &String::from("hi"); + let s_ident = f(&s); // should not error, because `&String` implements Copy, but `String` does not + let g_val = g(&Vec::new()); // should not error, because `&Vec` derefs to `&[T]` + let vec = Vec::new(); + let vec_val = g(&vec); // should not error, because `&Vec` derefs to `&[T]` + h(&"foo"); // should not error, because the `&&str` is required, due to `&Trait` + if let Some(cake) = Some(&5) {} + let garbl = match 42 { + 44 => &a, + 45 => { + println!("foo"); + &&a // FIXME: this should lint, too + }, + 46 => &a, + _ => panic!(), + }; +} + +fn f(y: &T) -> T { + *y +} + +fn g(y: &[u8]) -> u8 { + y[0] +} + +trait Trait {} + +impl<'a> Trait for &'a str {} + +fn h(_: &dyn Trait) {} +#[warn(clippy::needless_borrow)] +#[allow(dead_code)] +fn issue_1432() { + let mut v = Vec::::new(); + let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + let _ = v.iter().filter(|&a| a.is_empty()); + + let _ = v.iter().filter(|&a| a.is_empty()); +} + +#[allow(dead_code)] +#[warn(clippy::needless_borrow)] +#[derive(Debug)] +enum Foo<'a> { + Str(&'a str), +} diff --git a/src/tools/clippy/tests/ui/needless_borrow.rs b/src/tools/clippy/tests/ui/needless_borrow.rs new file mode 100644 index 0000000000..1e281316c8 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrow.rs @@ -0,0 +1,61 @@ +// run-rustfix + +#![allow(clippy::needless_borrowed_reference)] + +fn x(y: &i32) -> i32 { + *y +} + +#[warn(clippy::all, clippy::needless_borrow)] +#[allow(unused_variables)] +fn main() { + let a = 5; + let b = x(&a); + let c = x(&&a); + let s = &String::from("hi"); + let s_ident = f(&s); // should not error, because `&String` implements Copy, but `String` does not + let g_val = g(&Vec::new()); // should not error, because `&Vec` derefs to `&[T]` + let vec = Vec::new(); + let vec_val = g(&vec); // should not error, because `&Vec` derefs to `&[T]` + h(&"foo"); // should not error, because the `&&str` is required, due to `&Trait` + if let Some(ref cake) = Some(&5) {} + let garbl = match 42 { + 44 => &a, + 45 => { + println!("foo"); + &&a // FIXME: this should lint, too + }, + 46 => &&a, + _ => panic!(), + }; +} + +fn f(y: &T) -> T { + *y +} + +fn g(y: &[u8]) -> u8 { + y[0] +} + +trait Trait {} + +impl<'a> Trait for &'a str {} + +fn h(_: &dyn Trait) {} +#[warn(clippy::needless_borrow)] +#[allow(dead_code)] +fn issue_1432() { + let mut v = Vec::::new(); + let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + let _ = v.iter().filter(|&ref a| a.is_empty()); + + let _ = v.iter().filter(|&a| a.is_empty()); +} + +#[allow(dead_code)] +#[warn(clippy::needless_borrow)] +#[derive(Debug)] +enum Foo<'a> { + Str(&'a str), +} diff --git a/src/tools/clippy/tests/ui/needless_borrow.stderr b/src/tools/clippy/tests/ui/needless_borrow.stderr new file mode 100644 index 0000000000..bea4b41b80 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrow.stderr @@ -0,0 +1,28 @@ +error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:14:15 + | +LL | let c = x(&&a); + | ^^^ help: change this to: `&a` + | + = note: `-D clippy::needless-borrow` implied by `-D warnings` + +error: this pattern creates a reference to a reference + --> $DIR/needless_borrow.rs:21:17 + | +LL | if let Some(ref cake) = Some(&5) {} + | ^^^^^^^^ help: change this to: `cake` + +error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:28:15 + | +LL | 46 => &&a, + | ^^^ help: change this to: `&a` + +error: this pattern creates a reference to a reference + --> $DIR/needless_borrow.rs:51:31 + | +LL | let _ = v.iter().filter(|&ref a| a.is_empty()); + | ^^^^^ help: change this to: `a` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_borrowed_ref.fixed b/src/tools/clippy/tests/ui/needless_borrowed_ref.fixed new file mode 100644 index 0000000000..a0937a2c5f --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrowed_ref.fixed @@ -0,0 +1,45 @@ +// run-rustfix + +#[warn(clippy::needless_borrowed_reference)] +#[allow(unused_variables)] +fn main() { + let mut v = Vec::::new(); + let _ = v.iter_mut().filter(|a| a.is_empty()); + // ^ should be linted + + let var = 3; + let thingy = Some(&var); + if let Some(&ref v) = thingy { + // ^ should be linted + } + + let mut var2 = 5; + let thingy2 = Some(&mut var2); + if let Some(&mut ref mut v) = thingy2 { + // ^ should **not** be linted + // v is borrowed as mutable. + *v = 10; + } + if let Some(&mut ref v) = thingy2 { + // ^ should **not** be linted + // here, v is borrowed as immutable. + // can't do that: + //*v = 15; + } +} + +#[allow(dead_code)] +enum Animal { + Cat(u64), + Dog(u64), +} + +#[allow(unused_variables)] +#[allow(dead_code)] +fn foo(a: &Animal, b: &Animal) { + match (a, b) { + (&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (), // lifetime mismatch error if there is no '&ref' + // ^ and ^ should **not** be linted + (&Animal::Dog(ref a), &Animal::Dog(_)) => (), // ^ should **not** be linted + } +} diff --git a/src/tools/clippy/tests/ui/needless_borrowed_ref.rs b/src/tools/clippy/tests/ui/needless_borrowed_ref.rs new file mode 100644 index 0000000000..500ac448f0 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrowed_ref.rs @@ -0,0 +1,45 @@ +// run-rustfix + +#[warn(clippy::needless_borrowed_reference)] +#[allow(unused_variables)] +fn main() { + let mut v = Vec::::new(); + let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + // ^ should be linted + + let var = 3; + let thingy = Some(&var); + if let Some(&ref v) = thingy { + // ^ should be linted + } + + let mut var2 = 5; + let thingy2 = Some(&mut var2); + if let Some(&mut ref mut v) = thingy2 { + // ^ should **not** be linted + // v is borrowed as mutable. + *v = 10; + } + if let Some(&mut ref v) = thingy2 { + // ^ should **not** be linted + // here, v is borrowed as immutable. + // can't do that: + //*v = 15; + } +} + +#[allow(dead_code)] +enum Animal { + Cat(u64), + Dog(u64), +} + +#[allow(unused_variables)] +#[allow(dead_code)] +fn foo(a: &Animal, b: &Animal) { + match (a, b) { + (&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (), // lifetime mismatch error if there is no '&ref' + // ^ and ^ should **not** be linted + (&Animal::Dog(ref a), &Animal::Dog(_)) => (), // ^ should **not** be linted + } +} diff --git a/src/tools/clippy/tests/ui/needless_borrowed_ref.stderr b/src/tools/clippy/tests/ui/needless_borrowed_ref.stderr new file mode 100644 index 0000000000..0a5cfb3db0 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrowed_ref.stderr @@ -0,0 +1,10 @@ +error: this pattern takes a reference on something that is being de-referenced + --> $DIR/needless_borrowed_ref.rs:7:34 + | +LL | let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + | ^^^^^^ help: try removing the `&ref` part and just keep: `a` + | + = note: `-D clippy::needless-borrowed-reference` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/needless_collect.fixed b/src/tools/clippy/tests/ui/needless_collect.fixed new file mode 100644 index 0000000000..af6c7bf15e --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_collect.fixed @@ -0,0 +1,21 @@ +// run-rustfix + +#![allow(unused, clippy::suspicious_map, clippy::iter_count)] + +use std::collections::{BTreeSet, HashMap, HashSet}; + +#[warn(clippy::needless_collect)] +#[allow(unused_variables, clippy::iter_cloned_collect, clippy::iter_next_slice)] +fn main() { + let sample = [1; 5]; + let len = sample.iter().count(); + if sample.iter().next().is_none() { + // Empty + } + sample.iter().cloned().any(|x| x == 1); + sample.iter().map(|x| (x, x)).count(); + // Notice the `HashSet`--this should not be linted + sample.iter().collect::>().len(); + // Neither should this + sample.iter().collect::>().len(); +} diff --git a/src/tools/clippy/tests/ui/needless_collect.rs b/src/tools/clippy/tests/ui/needless_collect.rs new file mode 100644 index 0000000000..6ae14f370b --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_collect.rs @@ -0,0 +1,21 @@ +// run-rustfix + +#![allow(unused, clippy::suspicious_map, clippy::iter_count)] + +use std::collections::{BTreeSet, HashMap, HashSet}; + +#[warn(clippy::needless_collect)] +#[allow(unused_variables, clippy::iter_cloned_collect, clippy::iter_next_slice)] +fn main() { + let sample = [1; 5]; + let len = sample.iter().collect::>().len(); + if sample.iter().collect::>().is_empty() { + // Empty + } + sample.iter().cloned().collect::>().contains(&1); + sample.iter().map(|x| (x, x)).collect::>().len(); + // Notice the `HashSet`--this should not be linted + sample.iter().collect::>().len(); + // Neither should this + sample.iter().collect::>().len(); +} diff --git a/src/tools/clippy/tests/ui/needless_collect.stderr b/src/tools/clippy/tests/ui/needless_collect.stderr new file mode 100644 index 0000000000..2a9539d597 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_collect.stderr @@ -0,0 +1,28 @@ +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:11:29 + | +LL | let len = sample.iter().collect::>().len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` + | + = note: `-D clippy::needless-collect` implied by `-D warnings` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:12:22 + | +LL | if sample.iter().collect::>().is_empty() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:15:28 + | +LL | sample.iter().cloned().collect::>().contains(&1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:16:35 + | +LL | sample.iter().map(|x| (x, x)).collect::>().len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_collect_indirect.rs b/src/tools/clippy/tests/ui/needless_collect_indirect.rs new file mode 100644 index 0000000000..0918a6868a --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_collect_indirect.rs @@ -0,0 +1,45 @@ +use std::collections::{HashMap, VecDeque}; + +fn main() { + let sample = [1; 5]; + let indirect_iter = sample.iter().collect::>(); + indirect_iter.into_iter().map(|x| (x, x + 1)).collect::>(); + let indirect_len = sample.iter().collect::>(); + indirect_len.len(); + let indirect_empty = sample.iter().collect::>(); + indirect_empty.is_empty(); + let indirect_contains = sample.iter().collect::>(); + indirect_contains.contains(&&5); + let indirect_negative = sample.iter().collect::>(); + indirect_negative.len(); + indirect_negative + .into_iter() + .map(|x| (*x, *x + 1)) + .collect::>(); + + // #6202 + let a = "a".to_string(); + let sample = vec![a.clone(), "b".to_string(), "c".to_string()]; + let non_copy_contains = sample.into_iter().collect::>(); + non_copy_contains.contains(&a); + + // Fix #5991 + let vec_a = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let vec_b = vec_a.iter().collect::>(); + if vec_b.len() > 3 {} + let other_vec = vec![1, 3, 12, 4, 16, 2]; + let we_got_the_same_numbers = other_vec.iter().filter(|item| vec_b.contains(item)).collect::>(); + + // Fix #6297 + let sample = [1; 5]; + let multiple_indirect = sample.iter().collect::>(); + let sample2 = vec![2, 3]; + if multiple_indirect.is_empty() { + // do something + } else { + let found = sample2 + .iter() + .filter(|i| multiple_indirect.iter().any(|s| **s % **i == 0)) + .collect::>(); + } +} diff --git a/src/tools/clippy/tests/ui/needless_collect_indirect.stderr b/src/tools/clippy/tests/ui/needless_collect_indirect.stderr new file mode 100644 index 0000000000..76e789d905 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_collect_indirect.stderr @@ -0,0 +1,68 @@ +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:5:5 + | +LL | / let indirect_iter = sample.iter().collect::>(); +LL | | indirect_iter.into_iter().map(|x| (x, x + 1)).collect::>(); + | |____^ + | + = note: `-D clippy::needless-collect` implied by `-D warnings` +help: use the original Iterator instead of collecting it and then producing a new one + | +LL | +LL | sample.iter().map(|x| (x, x + 1)).collect::>(); + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:7:5 + | +LL | / let indirect_len = sample.iter().collect::>(); +LL | | indirect_len.len(); + | |____^ + | +help: take the original Iterator's count instead of collecting it and finding the length + | +LL | +LL | sample.iter().count(); + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:9:5 + | +LL | / let indirect_empty = sample.iter().collect::>(); +LL | | indirect_empty.is_empty(); + | |____^ + | +help: check if the original Iterator has anything instead of collecting it and seeing if it's empty + | +LL | +LL | sample.iter().next().is_none(); + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:11:5 + | +LL | / let indirect_contains = sample.iter().collect::>(); +LL | | indirect_contains.contains(&&5); + | |____^ + | +help: check if the original Iterator contains an element instead of collecting then checking + | +LL | +LL | sample.iter().any(|x| x == &5); + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:23:5 + | +LL | / let non_copy_contains = sample.into_iter().collect::>(); +LL | | non_copy_contains.contains(&a); + | |____^ + | +help: check if the original Iterator contains an element instead of collecting then checking + | +LL | +LL | sample.into_iter().any(|x| x == a); + | + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_continue.rs b/src/tools/clippy/tests/ui/needless_continue.rs new file mode 100644 index 0000000000..5da95647f2 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_continue.rs @@ -0,0 +1,115 @@ +#![warn(clippy::needless_continue)] + +macro_rules! zero { + ($x:expr) => { + $x == 0 + }; +} + +macro_rules! nonzero { + ($x:expr) => { + !zero!($x) + }; +} + +fn main() { + let mut i = 1; + while i < 10 { + i += 1; + + if i % 2 == 0 && i % 3 == 0 { + println!("{}", i); + println!("{}", i + 1); + if i % 5 == 0 { + println!("{}", i + 2); + } + let i = 0; + println!("bar {} ", i); + } else { + continue; + } + + println!("bleh"); + { + println!("blah"); + } + + // some comments that also should ideally be included in the + // output of the lint suggestion if possible. + if !(!(i == 2) || !(i == 5)) { + println!("lama"); + } + + if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 { + continue; + } else { + println!("Blabber"); + println!("Jabber"); + } + + println!("bleh"); + } +} + +mod issue_2329 { + fn condition() -> bool { + unimplemented!() + } + fn update_condition() {} + + // only the outer loop has a label + fn foo() { + 'outer: loop { + println!("Entry"); + while condition() { + update_condition(); + if condition() { + println!("foo-1"); + } else { + continue 'outer; // should not lint here + } + println!("foo-2"); + + update_condition(); + if condition() { + continue 'outer; // should not lint here + } else { + println!("foo-3"); + } + println!("foo-4"); + } + } + } + + // both loops have labels + fn bar() { + 'outer: loop { + println!("Entry"); + 'inner: while condition() { + update_condition(); + if condition() { + println!("bar-1"); + } else { + continue 'outer; // should not lint here + } + println!("bar-2"); + + update_condition(); + if condition() { + println!("bar-3"); + } else { + continue 'inner; // should lint here + } + println!("bar-4"); + + update_condition(); + if condition() { + continue; // should lint here + } else { + println!("bar-5"); + } + println!("bar-6"); + } + } + } +} diff --git a/src/tools/clippy/tests/ui/needless_continue.stderr b/src/tools/clippy/tests/ui/needless_continue.stderr new file mode 100644 index 0000000000..8d6a37df96 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_continue.stderr @@ -0,0 +1,99 @@ +error: this `else` block is redundant + --> $DIR/needless_continue.rs:28:16 + | +LL | } else { + | ________________^ +LL | | continue; +LL | | } + | |_________^ + | + = note: `-D clippy::needless-continue` implied by `-D warnings` + = help: consider dropping the `else` clause and merging the code that follows (in the loop) with the `if` block + if i % 2 == 0 && i % 3 == 0 { + println!("{}", i); + println!("{}", i + 1); + if i % 5 == 0 { + println!("{}", i + 2); + } + let i = 0; + println!("bar {} ", i); + // merged code follows: + println!("bleh"); + { + println!("blah"); + } + if !(!(i == 2) || !(i == 5)) { + println!("lama"); + } + if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 { + continue; + } else { + println!("Blabber"); + println!("Jabber"); + } + println!("bleh"); + } + +error: there is no need for an explicit `else` block for this `if` expression + --> $DIR/needless_continue.rs:43:9 + | +LL | / if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 { +LL | | continue; +LL | | } else { +LL | | println!("Blabber"); +LL | | println!("Jabber"); +LL | | } + | |_________^ + | + = help: consider dropping the `else` clause + if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 { + continue; + } + { + println!("Blabber"); + println!("Jabber"); + } + +error: this `else` block is redundant + --> $DIR/needless_continue.rs:100:24 + | +LL | } else { + | ________________________^ +LL | | continue 'inner; // should lint here +LL | | } + | |_________________^ + | + = help: consider dropping the `else` clause and merging the code that follows (in the loop) with the `if` block + if condition() { + println!("bar-3"); + // merged code follows: + println!("bar-4"); + update_condition(); + if condition() { + continue; // should lint here + } else { + println!("bar-5"); + } + println!("bar-6"); + } + +error: there is no need for an explicit `else` block for this `if` expression + --> $DIR/needless_continue.rs:106:17 + | +LL | / if condition() { +LL | | continue; // should lint here +LL | | } else { +LL | | println!("bar-5"); +LL | | } + | |_________________^ + | + = help: consider dropping the `else` clause + if condition() { + continue; // should lint here + } + { + println!("bar-5"); + } + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_doc_main.rs b/src/tools/clippy/tests/ui/needless_doc_main.rs new file mode 100644 index 0000000000..83e9bbaa3a --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_doc_main.rs @@ -0,0 +1,140 @@ +/// This is a test for needless `fn main()` in doctests. +/// +/// # Examples +/// +/// This should lint +/// ``` +/// fn main() { +/// unimplemented!(); +/// } +/// ``` +/// +/// With an explicit return type it should lint too +/// ```edition2015 +/// fn main() -> () { +/// unimplemented!(); +/// } +/// ``` +/// +/// This should, too. +/// ```rust +/// fn main() { +/// unimplemented!(); +/// } +/// ``` +/// +/// This one too. +/// ```no_run +/// fn main() { +/// unimplemented!(); +/// } +/// ``` +fn bad_doctests() {} + +/// # Examples +/// +/// This shouldn't lint, because the `main` is empty: +/// ``` +/// fn main(){} +/// ``` +/// +/// This shouldn't lint either, because main is async: +/// ```edition2018 +/// async fn main() { +/// assert_eq!(42, ANSWER); +/// } +/// ``` +/// +/// Same here, because the return type is not the unit type: +/// ``` +/// fn main() -> Result<()> { +/// Ok(()) +/// } +/// ``` +/// +/// This shouldn't lint either, because there's a `static`: +/// ``` +/// static ANSWER: i32 = 42; +/// +/// fn main() { +/// assert_eq!(42, ANSWER); +/// } +/// ``` +/// +/// This shouldn't lint either, because there's a `const`: +/// ``` +/// fn main() { +/// assert_eq!(42, ANSWER); +/// } +/// +/// const ANSWER: i32 = 42; +/// ``` +/// +/// Neither should this lint because of `extern crate`: +/// ``` +/// #![feature(test)] +/// extern crate test; +/// fn main() { +/// assert_eq(1u8, test::black_box(1)); +/// } +/// ``` +/// +/// Neither should this lint because it has an extern block: +/// ``` +/// extern {} +/// fn main() { +/// unimplemented!(); +/// } +/// ``` +/// +/// This should not lint because there is another function defined: +/// ``` +/// fn fun() {} +/// +/// fn main() { +/// unimplemented!(); +/// } +/// ``` +/// +/// We should not lint inside raw strings ... +/// ``` +/// let string = r#" +/// fn main() { +/// unimplemented!(); +/// } +/// "#; +/// ``` +/// +/// ... or comments +/// ``` +/// // fn main() { +/// // let _inception = 42; +/// // } +/// let _inception = 42; +/// ``` +/// +/// We should not lint ignored examples: +/// ```rust,ignore +/// fn main() { +/// unimplemented!(); +/// } +/// ``` +/// +/// Or even non-rust examples: +/// ```text +/// fn main() { +/// is what starts the program +/// } +/// ``` +fn no_false_positives() {} + +/// Yields a parse error when interpreted as rust code: +/// ``` +/// r#"hi" +/// ``` +fn issue_6022() {} + +fn main() { + bad_doctests(); + no_false_positives(); +} diff --git a/src/tools/clippy/tests/ui/needless_doc_main.stderr b/src/tools/clippy/tests/ui/needless_doc_main.stderr new file mode 100644 index 0000000000..05c7f9d33a --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_doc_main.stderr @@ -0,0 +1,28 @@ +error: needless `fn main` in doctest + --> $DIR/needless_doc_main.rs:7:4 + | +LL | /// fn main() { + | ^^^^^^^^^^^^ + | + = note: `-D clippy::needless-doctest-main` implied by `-D warnings` + +error: needless `fn main` in doctest + --> $DIR/needless_doc_main.rs:14:4 + | +LL | /// fn main() -> () { + | ^^^^^^^^^^^^^^^^^^ + +error: needless `fn main` in doctest + --> $DIR/needless_doc_main.rs:21:4 + | +LL | /// fn main() { + | ^^^^^^^^^^^^ + +error: needless `fn main` in doctest + --> $DIR/needless_doc_main.rs:28:4 + | +LL | /// fn main() { + | ^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_lifetimes.rs b/src/tools/clippy/tests/ui/needless_lifetimes.rs new file mode 100644 index 0000000000..bda0801e51 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_lifetimes.rs @@ -0,0 +1,367 @@ +#![warn(clippy::needless_lifetimes)] +#![allow(dead_code, clippy::needless_pass_by_value, clippy::unnecessary_wraps)] + +fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} + +fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {} + +// No error; same lifetime on two params. +fn same_lifetime_on_input<'a>(_x: &'a u8, _y: &'a u8) {} + +// No error; static involved. +fn only_static_on_input(_x: &u8, _y: &u8, _z: &'static u8) {} + +fn mut_and_static_input(_x: &mut u8, _y: &'static str) {} + +fn in_and_out<'a>(x: &'a u8, _y: u8) -> &'a u8 { + x +} + +// No error; multiple input refs. +fn multiple_in_and_out_1<'a>(x: &'a u8, _y: &'a u8) -> &'a u8 { + x +} + +// No error; multiple input refs. +fn multiple_in_and_out_2<'a, 'b>(x: &'a u8, _y: &'b u8) -> &'a u8 { + x +} + +// No error; static involved. +fn in_static_and_out<'a>(x: &'a u8, _y: &'static u8) -> &'a u8 { + x +} + +// No error. +fn deep_reference_1<'a, 'b>(x: &'a u8, _y: &'b u8) -> Result<&'a u8, ()> { + Ok(x) +} + +// No error; two input refs. +fn deep_reference_2<'a>(x: Result<&'a u8, &'a u8>) -> &'a u8 { + x.unwrap() +} + +fn deep_reference_3<'a>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> { + Ok(x) +} + +// Where-clause, but without lifetimes. +fn where_clause_without_lt<'a, T>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> +where + T: Copy, +{ + Ok(x) +} + +type Ref<'r> = &'r u8; + +// No error; same lifetime on two params. +fn lifetime_param_1<'a>(_x: Ref<'a>, _y: &'a u8) {} + +fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {} + +// No error; bounded lifetime. +fn lifetime_param_3<'a, 'b: 'a>(_x: Ref<'a>, _y: &'b u8) {} + +// No error; bounded lifetime. +fn lifetime_param_4<'a, 'b>(_x: Ref<'a>, _y: &'b u8) +where + 'b: 'a, +{ +} + +struct Lt<'a, I: 'static> { + x: &'a I, +} + +// No error; fn bound references `'a`. +fn fn_bound<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> +where + F: Fn(Lt<'a, I>) -> Lt<'a, I>, +{ + unreachable!() +} + +fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> +where + for<'x> F: Fn(Lt<'x, I>) -> Lt<'x, I>, +{ + unreachable!() +} + +// No error; see below. +fn fn_bound_3<'a, F: FnOnce(&'a i32)>(x: &'a i32, f: F) { + f(x); +} + +fn fn_bound_3_cannot_elide() { + let x = 42; + let p = &x; + let mut q = &x; + // This will fail if we elide lifetimes of `fn_bound_3`. + fn_bound_3(p, |y| q = y); +} + +// No error; multiple input refs. +fn fn_bound_4<'a, F: FnOnce() -> &'a ()>(cond: bool, x: &'a (), f: F) -> &'a () { + if cond { x } else { f() } +} + +struct X { + x: u8, +} + +impl X { + fn self_and_out<'s>(&'s self) -> &'s u8 { + &self.x + } + + // No error; multiple input refs. + fn self_and_in_out<'s, 't>(&'s self, _x: &'t u8) -> &'s u8 { + &self.x + } + + fn distinct_self_and_in<'s, 't>(&'s self, _x: &'t u8) {} + + // No error; same lifetimes on two params. + fn self_and_same_in<'s>(&'s self, _x: &'s u8) {} +} + +struct Foo<'a>(&'a u8); + +impl<'a> Foo<'a> { + // No error; lifetime `'a` not defined in method. + fn self_shared_lifetime(&self, _: &'a u8) {} + // No error; bounds exist. + fn self_bound_lifetime<'b: 'a>(&self, _: &'b u8) {} +} + +fn already_elided<'a>(_: &u8, _: &'a u8) -> &'a u8 { + unimplemented!() +} + +fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes (named on the reference, anonymous on `Foo`). +fn struct_with_lt2<'a>(_foo: &'a Foo) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes (anonymous on the reference, named on `Foo`). +fn struct_with_lt3<'a>(_foo: &Foo<'a>) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes. +fn struct_with_lt4<'a, 'b>(_foo: &'a Foo<'b>) -> &'a str { + unimplemented!() +} + +trait WithLifetime<'a> {} + +type WithLifetimeAlias<'a> = dyn WithLifetime<'a>; + +// Should not warn because it won't build without the lifetime. +fn trait_obj_elided<'a>(_arg: &'a dyn WithLifetime) -> &'a str { + unimplemented!() +} + +// Should warn because there is no lifetime on `Drop`, so this would be +// unambiguous if we elided the lifetime. +fn trait_obj_elided2<'a>(_arg: &'a dyn Drop) -> &'a str { + unimplemented!() +} + +type FooAlias<'a> = Foo<'a>; + +fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes (named on the reference, anonymous on `FooAlias`). +fn alias_with_lt2<'a>(_foo: &'a FooAlias) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes (anonymous on the reference, named on `FooAlias`). +fn alias_with_lt3<'a>(_foo: &FooAlias<'a>) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes. +fn alias_with_lt4<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'a str { + unimplemented!() +} + +fn named_input_elided_output<'a>(_arg: &'a str) -> &str { + unimplemented!() +} + +fn elided_input_named_output<'a>(_arg: &str) -> &'a str { + unimplemented!() +} + +fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) { + unimplemented!() +} +fn trait_bound<'a, T: WithLifetime<'a>>(_: &'a u8, _: T) { + unimplemented!() +} + +// Don't warn on these; see issue #292. +fn trait_bound_bug<'a, T: WithLifetime<'a>>() { + unimplemented!() +} + +// See issue #740. +struct Test { + vec: Vec, +} + +impl Test { + fn iter<'a>(&'a self) -> Box + 'a> { + unimplemented!() + } +} + +trait LintContext<'a> {} + +fn f<'a, T: LintContext<'a>>(_: &T) {} + +fn test<'a>(x: &'a [u8]) -> u8 { + let y: &'a u8 = &x[5]; + *y +} + +// Issue #3284: give hint regarding lifetime in return type. +struct Cow<'a> { + x: &'a str, +} +fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> { + unimplemented!() +} + +// Make sure we still warn on implementations +mod issue4291 { + trait BadTrait { + fn needless_lt<'a>(x: &'a u8) {} + } + + impl BadTrait for () { + fn needless_lt<'a>(_x: &'a u8) {} + } +} + +mod issue2944 { + trait Foo {} + struct Bar {} + struct Baz<'a> { + bar: &'a Bar, + } + + impl<'a> Foo for Baz<'a> {} + impl Bar { + fn baz<'a>(&'a self) -> impl Foo + 'a { + Baz { bar: self } + } + } +} + +mod nested_elision_sites { + // issue #issue2944 + + // closure trait bounds subject to nested elision + // don't lint because they refer to outer lifetimes + fn trait_fn<'a>(i: &'a i32) -> impl Fn() -> &'a i32 { + move || i + } + fn trait_fn_mut<'a>(i: &'a i32) -> impl FnMut() -> &'a i32 { + move || i + } + fn trait_fn_once<'a>(i: &'a i32) -> impl FnOnce() -> &'a i32 { + move || i + } + + // don't lint + fn impl_trait_in_input_position<'a>(f: impl Fn() -> &'a i32) -> &'a i32 { + f() + } + fn impl_trait_in_output_position<'a>(i: &'a i32) -> impl Fn() -> &'a i32 { + move || i + } + // lint + fn impl_trait_elidable_nested_named_lifetimes<'a>(i: &'a i32, f: impl for<'b> Fn(&'b i32) -> &'b i32) -> &'a i32 { + f(i) + } + fn impl_trait_elidable_nested_anonymous_lifetimes<'a>(i: &'a i32, f: impl Fn(&i32) -> &i32) -> &'a i32 { + f(i) + } + + // don't lint + fn generics_not_elidable<'a, T: Fn() -> &'a i32>(f: T) -> &'a i32 { + f() + } + // lint + fn generics_elidable<'a, T: Fn(&i32) -> &i32>(i: &'a i32, f: T) -> &'a i32 { + f(i) + } + + // don't lint + fn where_clause_not_elidable<'a, T>(f: T) -> &'a i32 + where + T: Fn() -> &'a i32, + { + f() + } + // lint + fn where_clause_elidadable<'a, T>(i: &'a i32, f: T) -> &'a i32 + where + T: Fn(&i32) -> &i32, + { + f(i) + } + + // don't lint + fn pointer_fn_in_input_position<'a>(f: fn(&'a i32) -> &'a i32, i: &'a i32) -> &'a i32 { + f(i) + } + fn pointer_fn_in_output_position<'a>(_: &'a i32) -> fn(&'a i32) -> &'a i32 { + |i| i + } + // lint + fn pointer_fn_elidable<'a>(i: &'a i32, f: fn(&i32) -> &i32) -> &'a i32 { + f(i) + } + + // don't lint + fn nested_fn_pointer_1<'a>(_: &'a i32) -> fn(fn(&'a i32) -> &'a i32) -> i32 { + |f| 42 + } + fn nested_fn_pointer_2<'a>(_: &'a i32) -> impl Fn(fn(&'a i32)) { + |f| () + } + + // lint + fn nested_fn_pointer_3<'a>(_: &'a i32) -> fn(fn(&i32) -> &i32) -> i32 { + |f| 42 + } + fn nested_fn_pointer_4<'a>(_: &'a i32) -> impl Fn(fn(&i32)) { + |f| () + } +} + +mod issue6159 { + use std::ops::Deref; + pub fn apply_deref<'a, T, F, R>(x: &'a T, f: F) -> R + where + T: Deref, + F: FnOnce(&'a T::Target) -> R, + { + f(x.deref()) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_lifetimes.stderr b/src/tools/clippy/tests/ui/needless_lifetimes.stderr new file mode 100644 index 0000000000..33a6de1618 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_lifetimes.stderr @@ -0,0 +1,154 @@ +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:4:1 + | +LL | fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::needless-lifetimes` implied by `-D warnings` + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:6:1 + | +LL | fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:16:1 + | +LL | fn in_and_out<'a>(x: &'a u8, _y: u8) -> &'a u8 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:45:1 + | +LL | fn deep_reference_3<'a>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:50:1 + | +LL | fn where_clause_without_lt<'a, T>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:62:1 + | +LL | fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:86:1 + | +LL | fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:116:5 + | +LL | fn self_and_out<'s>(&'s self) -> &'s u8 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:125:5 + | +LL | fn distinct_self_and_in<'s, 't>(&'s self, _x: &'t u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:144:1 + | +LL | fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:174:1 + | +LL | fn trait_obj_elided2<'a>(_arg: &'a dyn Drop) -> &'a str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:180:1 + | +LL | fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:199:1 + | +LL | fn named_input_elided_output<'a>(_arg: &'a str) -> &str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:207:1 + | +LL | fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:243:1 + | +LL | fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:250:9 + | +LL | fn needless_lt<'a>(x: &'a u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:254:9 + | +LL | fn needless_lt<'a>(_x: &'a u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:267:9 + | +LL | fn baz<'a>(&'a self) -> impl Foo + 'a { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:296:5 + | +LL | fn impl_trait_elidable_nested_named_lifetimes<'a>(i: &'a i32, f: impl for<'b> Fn(&'b i32) -> &'b i32) -> &'a i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:299:5 + | +LL | fn impl_trait_elidable_nested_anonymous_lifetimes<'a>(i: &'a i32, f: impl Fn(&i32) -> &i32) -> &'a i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:308:5 + | +LL | fn generics_elidable<'a, T: Fn(&i32) -> &i32>(i: &'a i32, f: T) -> &'a i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:320:5 + | +LL | fn where_clause_elidadable<'a, T>(i: &'a i32, f: T) -> &'a i32 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:335:5 + | +LL | fn pointer_fn_elidable<'a>(i: &'a i32, f: fn(&i32) -> &i32) -> &'a i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:348:5 + | +LL | fn nested_fn_pointer_3<'a>(_: &'a i32) -> fn(fn(&i32) -> &i32) -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:351:5 + | +LL | fn nested_fn_pointer_4<'a>(_: &'a i32) -> impl Fn(fn(&i32)) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 25 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_pass_by_value.rs b/src/tools/clippy/tests/ui/needless_pass_by_value.rs new file mode 100644 index 0000000000..7a9ba55590 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_pass_by_value.rs @@ -0,0 +1,161 @@ +#![warn(clippy::needless_pass_by_value)] +#![allow( + dead_code, + clippy::single_match, + clippy::redundant_pattern_matching, + clippy::many_single_char_names, + clippy::option_option, + clippy::redundant_clone +)] + +use std::borrow::Borrow; +use std::collections::HashSet; +use std::convert::AsRef; +use std::mem::MaybeUninit; + +// `v` should be warned +// `w`, `x` and `y` are allowed (moved or mutated) +fn foo(v: Vec, w: Vec, mut x: Vec, y: Vec) -> Vec { + assert_eq!(v.len(), 42); + + consume(w); + + x.push(T::default()); + + y +} + +fn consume(_: T) {} + +struct Wrapper(String); + +fn bar(x: String, y: Wrapper) { + assert_eq!(x.len(), 42); + assert_eq!(y.0.len(), 42); +} + +// V implements `Borrow`, but should be warned correctly +fn test_borrow_trait, U: AsRef, V>(t: T, u: U, v: V) { + println!("{}", t.borrow()); + println!("{}", u.as_ref()); + consume(&v); +} + +// ok +fn test_fn i32>(f: F) { + f(1); +} + +// x should be warned, but y is ok +fn test_match(x: Option>, y: Option>) { + match x { + Some(Some(_)) => 1, // not moved + _ => 0, + }; + + match y { + Some(Some(s)) => consume(s), // moved + _ => (), + }; +} + +// x and y should be warned, but z is ok +fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) { + let Wrapper(s) = z; // moved + let Wrapper(ref t) = y; // not moved + let Wrapper(_) = y; // still not moved + + assert_eq!(x.0.len(), s.len()); + println!("{}", t); +} + +trait Foo {} + +// `S: Serialize` is allowed to be passed by value, since a caller can pass `&S` instead +trait Serialize {} +impl<'a, T> Serialize for &'a T where T: Serialize {} +impl Serialize for i32 {} + +fn test_blanket_ref(_foo: T, _serializable: S) {} + +fn issue_2114(s: String, t: String, u: Vec, v: Vec) { + s.capacity(); + let _ = t.clone(); + u.capacity(); + let _ = v.clone(); +} + +struct S(T, U); + +impl S { + fn foo( + self, + // taking `self` by value is always allowed + s: String, + t: String, + ) -> usize { + s.len() + t.capacity() + } + + fn bar(_t: T, // Ok, since `&T: Serialize` too + ) { + } + + fn baz(&self, _u: U, _s: Self) {} +} + +trait FalsePositive { + fn visit_str(s: &str); + fn visit_string(s: String) { + Self::visit_str(&s); + } +} + +// shouldn't warn on extern funcs +extern "C" fn ext(x: MaybeUninit) -> usize { + unsafe { x.assume_init() } +} + +// exempt RangeArgument +fn range>(range: T) { + let _ = range.start_bound(); +} + +struct CopyWrapper(u32); + +fn bar_copy(x: u32, y: CopyWrapper) { + assert_eq!(x, 42); + assert_eq!(y.0, 42); +} + +// x and y should be warned, but z is ok +fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) { + let CopyWrapper(s) = z; // moved + let CopyWrapper(ref t) = y; // not moved + let CopyWrapper(_) = y; // still not moved + + assert_eq!(x.0, s); + println!("{}", t); +} + +// The following 3 lines should not cause an ICE. See #2831 +trait Bar<'a, A> {} +impl<'b, T> Bar<'b, T> for T {} +fn some_fun<'b, S: Bar<'b, ()>>(_item: S) {} + +// Also this should not cause an ICE. See #2831 +trait Club<'a, A> {} +impl Club<'static, T> for T {} +fn more_fun(_item: impl Club<'static, i32>) {} + +fn is_sync(_: T) +where + T: Sync, +{ +} + +fn main() { + // This should not cause an ICE either + // https://github.com/rust-lang/rust-clippy/issues/3144 + is_sync(HashSet::::new()); +} diff --git a/src/tools/clippy/tests/ui/needless_pass_by_value.stderr b/src/tools/clippy/tests/ui/needless_pass_by_value.stderr new file mode 100644 index 0000000000..9aa783bf90 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_pass_by_value.stderr @@ -0,0 +1,178 @@ +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:18:23 + | +LL | fn foo(v: Vec, w: Vec, mut x: Vec, y: Vec) -> Vec { + | ^^^^^^ help: consider changing the type to: `&[T]` + | + = note: `-D clippy::needless-pass-by-value` implied by `-D warnings` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:32:11 + | +LL | fn bar(x: String, y: Wrapper) { + | ^^^^^^ help: consider changing the type to: `&str` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:32:22 + | +LL | fn bar(x: String, y: Wrapper) { + | ^^^^^^^ help: consider taking a reference instead: `&Wrapper` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:38:71 + | +LL | fn test_borrow_trait, U: AsRef, V>(t: T, u: U, v: V) { + | ^ help: consider taking a reference instead: `&V` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:50:18 + | +LL | fn test_match(x: Option>, y: Option>) { + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `&Option>` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:63:24 + | +LL | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) { + | ^^^^^^^ help: consider taking a reference instead: `&Wrapper` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:63:36 + | +LL | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) { + | ^^^^^^^ help: consider taking a reference instead: `&Wrapper` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:79:49 + | +LL | fn test_blanket_ref(_foo: T, _serializable: S) {} + | ^ help: consider taking a reference instead: `&T` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:81:18 + | +LL | fn issue_2114(s: String, t: String, u: Vec, v: Vec) { + | ^^^^^^ help: consider taking a reference instead: `&String` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:81:29 + | +LL | fn issue_2114(s: String, t: String, u: Vec, v: Vec) { + | ^^^^^^ + | +help: consider changing the type to + | +LL | fn issue_2114(s: String, t: &str, u: Vec, v: Vec) { + | ^^^^ +help: change `t.clone()` to + | +LL | let _ = t.to_string(); + | ^^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:81:40 + | +LL | fn issue_2114(s: String, t: String, u: Vec, v: Vec) { + | ^^^^^^^^ help: consider taking a reference instead: `&Vec` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:81:53 + | +LL | fn issue_2114(s: String, t: String, u: Vec, v: Vec) { + | ^^^^^^^^ + | +help: consider changing the type to + | +LL | fn issue_2114(s: String, t: String, u: Vec, v: &[i32]) { + | ^^^^^^ +help: change `v.clone()` to + | +LL | let _ = v.to_owned(); + | ^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:94:12 + | +LL | s: String, + | ^^^^^^ help: consider changing the type to: `&str` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:95:12 + | +LL | t: String, + | ^^^^^^ help: consider taking a reference instead: `&String` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:104:23 + | +LL | fn baz(&self, _u: U, _s: Self) {} + | ^ help: consider taking a reference instead: `&U` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:104:30 + | +LL | fn baz(&self, _u: U, _s: Self) {} + | ^^^^ help: consider taking a reference instead: `&Self` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:126:24 + | +LL | fn bar_copy(x: u32, y: CopyWrapper) { + | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper` + | +help: consider marking this type as `Copy` + --> $DIR/needless_pass_by_value.rs:124:1 + | +LL | struct CopyWrapper(u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:132:29 + | +LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) { + | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper` + | +help: consider marking this type as `Copy` + --> $DIR/needless_pass_by_value.rs:124:1 + | +LL | struct CopyWrapper(u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:132:45 + | +LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) { + | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper` + | +help: consider marking this type as `Copy` + --> $DIR/needless_pass_by_value.rs:124:1 + | +LL | struct CopyWrapper(u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:132:61 + | +LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) { + | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper` + | +help: consider marking this type as `Copy` + --> $DIR/needless_pass_by_value.rs:124:1 + | +LL | struct CopyWrapper(u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:144:40 + | +LL | fn some_fun<'b, S: Bar<'b, ()>>(_item: S) {} + | ^ help: consider taking a reference instead: `&S` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:149:20 + | +LL | fn more_fun(_item: impl Club<'static, i32>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `&impl Club<'static, i32>` + +error: aborting due to 22 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_pass_by_value_proc_macro.rs b/src/tools/clippy/tests/ui/needless_pass_by_value_proc_macro.rs new file mode 100644 index 0000000000..78a0e92d17 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_pass_by_value_proc_macro.rs @@ -0,0 +1,21 @@ +#![crate_type = "proc-macro"] +#![warn(clippy::needless_pass_by_value)] + +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[proc_macro_derive(Foo)] +pub fn foo(_input: TokenStream) -> TokenStream { + unimplemented!() +} + +#[proc_macro] +pub fn bar(_input: TokenStream) -> TokenStream { + unimplemented!() +} + +#[proc_macro_attribute] +pub fn baz(_args: TokenStream, _input: TokenStream) -> TokenStream { + unimplemented!() +} diff --git a/src/tools/clippy/tests/ui/needless_question_mark.fixed b/src/tools/clippy/tests/ui/needless_question_mark.fixed new file mode 100644 index 0000000000..71fb356522 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_question_mark.fixed @@ -0,0 +1,169 @@ +// run-rustfix + +#![warn(clippy::needless_question_mark)] +#![allow( + clippy::needless_return, + clippy::unnecessary_unwrap, + clippy::upper_case_acronyms, + dead_code, + unused_must_use +)] +#![feature(custom_inner_attributes)] + +struct TO { + magic: Option, +} + +struct TR { + magic: Result, +} + +fn simple_option_bad1(to: TO) -> Option { + // return as a statement + return to.magic; +} + +// formatting will add a semi-colon, which would make +// this identical to the test case above +#[rustfmt::skip] +fn simple_option_bad2(to: TO) -> Option { + // return as an expression + return to.magic +} + +fn simple_option_bad3(to: TO) -> Option { + // block value "return" + to.magic +} + +fn simple_option_bad4(to: Option) -> Option { + // single line closure + to.and_then(|t| t.magic) +} + +// formatting this will remove the block brackets, making +// this test identical to the one above +#[rustfmt::skip] +fn simple_option_bad5(to: Option) -> Option { + // closure with body + to.and_then(|t| { + t.magic + }) +} + +fn simple_result_bad1(tr: TR) -> Result { + return tr.magic; +} + +// formatting will add a semi-colon, which would make +// this identical to the test case above +#[rustfmt::skip] +fn simple_result_bad2(tr: TR) -> Result { + return tr.magic +} + +fn simple_result_bad3(tr: TR) -> Result { + tr.magic +} + +fn simple_result_bad4(tr: Result) -> Result { + tr.and_then(|t| t.magic) +} + +// formatting this will remove the block brackets, making +// this test identical to the one above +#[rustfmt::skip] +fn simple_result_bad5(tr: Result) -> Result { + tr.and_then(|t| { + t.magic + }) +} + +fn also_bad(tr: Result) -> Result { + if tr.is_ok() { + let t = tr.unwrap(); + return t.magic; + } + Err(false) +} + +fn false_positive_test(x: Result<(), U>) -> Result<(), T> +where + T: From, +{ + Ok(x?) +} + +fn main() {} + +mod question_mark_none { + #![clippy::msrv = "1.12.0"] + fn needless_question_mark_option() -> Option { + struct TO { + magic: Option, + } + let to = TO { magic: None }; + Some(to.magic?) // should not be triggered + } + + fn needless_question_mark_result() -> Result { + struct TO { + magic: Result, + } + let to = TO { magic: Ok(1_usize) }; + Ok(to.magic?) // should not be triggered + } + + fn main() { + needless_question_mark_option(); + needless_question_mark_result(); + } +} + +mod question_mark_result { + #![clippy::msrv = "1.21.0"] + fn needless_question_mark_option() -> Option { + struct TO { + magic: Option, + } + let to = TO { magic: None }; + Some(to.magic?) // should not be triggered + } + + fn needless_question_mark_result() -> Result { + struct TO { + magic: Result, + } + let to = TO { magic: Ok(1_usize) }; + to.magic // should be triggered + } + + fn main() { + needless_question_mark_option(); + needless_question_mark_result(); + } +} + +mod question_mark_both { + #![clippy::msrv = "1.22.0"] + fn needless_question_mark_option() -> Option { + struct TO { + magic: Option, + } + let to = TO { magic: None }; + to.magic // should be triggered + } + + fn needless_question_mark_result() -> Result { + struct TO { + magic: Result, + } + let to = TO { magic: Ok(1_usize) }; + to.magic // should be triggered + } + + fn main() { + needless_question_mark_option(); + needless_question_mark_result(); + } +} diff --git a/src/tools/clippy/tests/ui/needless_question_mark.rs b/src/tools/clippy/tests/ui/needless_question_mark.rs new file mode 100644 index 0000000000..e31f6f48fa --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_question_mark.rs @@ -0,0 +1,169 @@ +// run-rustfix + +#![warn(clippy::needless_question_mark)] +#![allow( + clippy::needless_return, + clippy::unnecessary_unwrap, + clippy::upper_case_acronyms, + dead_code, + unused_must_use +)] +#![feature(custom_inner_attributes)] + +struct TO { + magic: Option, +} + +struct TR { + magic: Result, +} + +fn simple_option_bad1(to: TO) -> Option { + // return as a statement + return Some(to.magic?); +} + +// formatting will add a semi-colon, which would make +// this identical to the test case above +#[rustfmt::skip] +fn simple_option_bad2(to: TO) -> Option { + // return as an expression + return Some(to.magic?) +} + +fn simple_option_bad3(to: TO) -> Option { + // block value "return" + Some(to.magic?) +} + +fn simple_option_bad4(to: Option) -> Option { + // single line closure + to.and_then(|t| Some(t.magic?)) +} + +// formatting this will remove the block brackets, making +// this test identical to the one above +#[rustfmt::skip] +fn simple_option_bad5(to: Option) -> Option { + // closure with body + to.and_then(|t| { + Some(t.magic?) + }) +} + +fn simple_result_bad1(tr: TR) -> Result { + return Ok(tr.magic?); +} + +// formatting will add a semi-colon, which would make +// this identical to the test case above +#[rustfmt::skip] +fn simple_result_bad2(tr: TR) -> Result { + return Ok(tr.magic?) +} + +fn simple_result_bad3(tr: TR) -> Result { + Ok(tr.magic?) +} + +fn simple_result_bad4(tr: Result) -> Result { + tr.and_then(|t| Ok(t.magic?)) +} + +// formatting this will remove the block brackets, making +// this test identical to the one above +#[rustfmt::skip] +fn simple_result_bad5(tr: Result) -> Result { + tr.and_then(|t| { + Ok(t.magic?) + }) +} + +fn also_bad(tr: Result) -> Result { + if tr.is_ok() { + let t = tr.unwrap(); + return Ok(t.magic?); + } + Err(false) +} + +fn false_positive_test(x: Result<(), U>) -> Result<(), T> +where + T: From, +{ + Ok(x?) +} + +fn main() {} + +mod question_mark_none { + #![clippy::msrv = "1.12.0"] + fn needless_question_mark_option() -> Option { + struct TO { + magic: Option, + } + let to = TO { magic: None }; + Some(to.magic?) // should not be triggered + } + + fn needless_question_mark_result() -> Result { + struct TO { + magic: Result, + } + let to = TO { magic: Ok(1_usize) }; + Ok(to.magic?) // should not be triggered + } + + fn main() { + needless_question_mark_option(); + needless_question_mark_result(); + } +} + +mod question_mark_result { + #![clippy::msrv = "1.21.0"] + fn needless_question_mark_option() -> Option { + struct TO { + magic: Option, + } + let to = TO { magic: None }; + Some(to.magic?) // should not be triggered + } + + fn needless_question_mark_result() -> Result { + struct TO { + magic: Result, + } + let to = TO { magic: Ok(1_usize) }; + Ok(to.magic?) // should be triggered + } + + fn main() { + needless_question_mark_option(); + needless_question_mark_result(); + } +} + +mod question_mark_both { + #![clippy::msrv = "1.22.0"] + fn needless_question_mark_option() -> Option { + struct TO { + magic: Option, + } + let to = TO { magic: None }; + Some(to.magic?) // should be triggered + } + + fn needless_question_mark_result() -> Result { + struct TO { + magic: Result, + } + let to = TO { magic: Ok(1_usize) }; + Ok(to.magic?) // should be triggered + } + + fn main() { + needless_question_mark_option(); + needless_question_mark_result(); + } +} diff --git a/src/tools/clippy/tests/ui/needless_question_mark.stderr b/src/tools/clippy/tests/ui/needless_question_mark.stderr new file mode 100644 index 0000000000..983c56031d --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_question_mark.stderr @@ -0,0 +1,88 @@ +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:23:12 + | +LL | return Some(to.magic?); + | ^^^^^^^^^^^^^^^ help: try: `to.magic` + | + = note: `-D clippy::needless-question-mark` implied by `-D warnings` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:31:12 + | +LL | return Some(to.magic?) + | ^^^^^^^^^^^^^^^ help: try: `to.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:36:5 + | +LL | Some(to.magic?) + | ^^^^^^^^^^^^^^^ help: try: `to.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:41:21 + | +LL | to.and_then(|t| Some(t.magic?)) + | ^^^^^^^^^^^^^^ help: try: `t.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:50:9 + | +LL | Some(t.magic?) + | ^^^^^^^^^^^^^^ help: try: `t.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:55:12 + | +LL | return Ok(tr.magic?); + | ^^^^^^^^^^^^^ help: try: `tr.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:62:12 + | +LL | return Ok(tr.magic?) + | ^^^^^^^^^^^^^ help: try: `tr.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:66:5 + | +LL | Ok(tr.magic?) + | ^^^^^^^^^^^^^ help: try: `tr.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:70:21 + | +LL | tr.and_then(|t| Ok(t.magic?)) + | ^^^^^^^^^^^^ help: try: `t.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:78:9 + | +LL | Ok(t.magic?) + | ^^^^^^^^^^^^ help: try: `t.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:85:16 + | +LL | return Ok(t.magic?); + | ^^^^^^^^^^^^ help: try: `t.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:138:9 + | +LL | Ok(to.magic?) // should be triggered + | ^^^^^^^^^^^^^ help: try: `to.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:154:9 + | +LL | Some(to.magic?) // should be triggered + | ^^^^^^^^^^^^^^^ help: try: `to.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:162:9 + | +LL | Ok(to.magic?) // should be triggered + | ^^^^^^^^^^^^^ help: try: `to.magic` + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_range_loop.rs b/src/tools/clippy/tests/ui/needless_range_loop.rs new file mode 100644 index 0000000000..3fce34367a --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_range_loop.rs @@ -0,0 +1,95 @@ +#![warn(clippy::needless_range_loop)] + +static STATIC: [usize; 4] = [0, 1, 8, 16]; +const CONST: [usize; 4] = [0, 1, 8, 16]; +const MAX_LEN: usize = 42; + +fn main() { + let mut vec = vec![1, 2, 3, 4]; + let vec2 = vec![1, 2, 3, 4]; + for i in 0..vec.len() { + println!("{}", vec[i]); + } + + for i in 0..vec.len() { + let i = 42; // make a different `i` + println!("{}", vec[i]); // ok, not the `i` of the for-loop + } + + for i in 0..vec.len() { + let _ = vec[i]; + } + + // ICE #746 + for j in 0..4 { + println!("{:?}", STATIC[j]); + } + + for j in 0..4 { + println!("{:?}", CONST[j]); + } + + for i in 0..vec.len() { + println!("{} {}", vec[i], i); + } + for i in 0..vec.len() { + // not an error, indexing more than one variable + println!("{} {}", vec[i], vec2[i]); + } + + for i in 0..vec.len() { + println!("{}", vec2[i]); + } + + for i in 5..vec.len() { + println!("{}", vec[i]); + } + + for i in 0..MAX_LEN { + println!("{}", vec[i]); + } + + for i in 0..=MAX_LEN { + println!("{}", vec[i]); + } + + for i in 5..10 { + println!("{}", vec[i]); + } + + for i in 5..=10 { + println!("{}", vec[i]); + } + + for i in 5..vec.len() { + println!("{} {}", vec[i], i); + } + + for i in 5..10 { + println!("{} {}", vec[i], i); + } + + // #2542 + for i in 0..vec.len() { + vec[i] = Some(1).unwrap_or_else(|| panic!("error on {}", i)); + } + + // #3788 + let test = Test { + inner: vec![1, 2, 3, 4], + }; + for i in 0..2 { + println!("{}", test[i]); + } +} + +struct Test { + inner: Vec, +} + +impl std::ops::Index for Test { + type Output = usize; + fn index(&self, index: usize) -> &Self::Output { + &self.inner[index] + } +} diff --git a/src/tools/clippy/tests/ui/needless_range_loop.stderr b/src/tools/clippy/tests/ui/needless_range_loop.stderr new file mode 100644 index 0000000000..c898cd64a9 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_range_loop.stderr @@ -0,0 +1,157 @@ +error: the loop variable `i` is only used to index `vec` + --> $DIR/needless_range_loop.rs:10:14 + | +LL | for i in 0..vec.len() { + | ^^^^^^^^^^^^ + | + = note: `-D clippy::needless-range-loop` implied by `-D warnings` +help: consider using an iterator + | +LL | for in &vec { + | ^^^^^^ ^^^^ + +error: the loop variable `i` is only used to index `vec` + --> $DIR/needless_range_loop.rs:19:14 + | +LL | for i in 0..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in &vec { + | ^^^^^^ ^^^^ + +error: the loop variable `j` is only used to index `STATIC` + --> $DIR/needless_range_loop.rs:24:14 + | +LL | for j in 0..4 { + | ^^^^ + | +help: consider using an iterator + | +LL | for in &STATIC { + | ^^^^^^ ^^^^^^^ + +error: the loop variable `j` is only used to index `CONST` + --> $DIR/needless_range_loop.rs:28:14 + | +LL | for j in 0..4 { + | ^^^^ + | +help: consider using an iterator + | +LL | for in &CONST { + | ^^^^^^ ^^^^^^ + +error: the loop variable `i` is used to index `vec` + --> $DIR/needless_range_loop.rs:32:14 + | +LL | for i in 0..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for (i, ) in vec.iter().enumerate() { + | ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec2` + --> $DIR/needless_range_loop.rs:40:14 + | +LL | for i in 0..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec2.iter().take(vec.len()) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec` + --> $DIR/needless_range_loop.rs:44:14 + | +LL | for i in 5..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter().skip(5) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec` + --> $DIR/needless_range_loop.rs:48:14 + | +LL | for i in 0..MAX_LEN { + | ^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter().take(MAX_LEN) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec` + --> $DIR/needless_range_loop.rs:52:14 + | +LL | for i in 0..=MAX_LEN { + | ^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter().take(MAX_LEN + 1) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec` + --> $DIR/needless_range_loop.rs:56:14 + | +LL | for i in 5..10 { + | ^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter().take(10).skip(5) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec` + --> $DIR/needless_range_loop.rs:60:14 + | +LL | for i in 5..=10 { + | ^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter().take(10 + 1).skip(5) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is used to index `vec` + --> $DIR/needless_range_loop.rs:64:14 + | +LL | for i in 5..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for (i, ) in vec.iter().enumerate().skip(5) { + | ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is used to index `vec` + --> $DIR/needless_range_loop.rs:68:14 + | +LL | for i in 5..10 { + | ^^^^^ + | +help: consider using an iterator + | +LL | for (i, ) in vec.iter().enumerate().take(10).skip(5) { + | ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is used to index `vec` + --> $DIR/needless_range_loop.rs:73:14 + | +LL | for i in 0..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for (i, ) in vec.iter_mut().enumerate() { + | ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_range_loop2.rs b/src/tools/clippy/tests/ui/needless_range_loop2.rs new file mode 100644 index 0000000000..7633316e0f --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_range_loop2.rs @@ -0,0 +1,109 @@ +#![warn(clippy::needless_range_loop)] + +fn calc_idx(i: usize) -> usize { + (i + i + 20) % 4 +} + +fn main() { + let ns = vec![2, 3, 5, 7]; + + for i in 3..10 { + println!("{}", ns[i]); + } + + for i in 3..10 { + println!("{}", ns[i % 4]); + } + + for i in 3..10 { + println!("{}", ns[i % ns.len()]); + } + + for i in 3..10 { + println!("{}", ns[calc_idx(i)]); + } + + for i in 3..10 { + println!("{}", ns[calc_idx(i) % 4]); + } + + let mut ms = vec![1, 2, 3, 4, 5, 6]; + for i in 0..ms.len() { + ms[i] *= 2; + } + assert_eq!(ms, vec![2, 4, 6, 8, 10, 12]); + + let mut ms = vec![1, 2, 3, 4, 5, 6]; + for i in 0..ms.len() { + let x = &mut ms[i]; + *x *= 2; + } + assert_eq!(ms, vec![2, 4, 6, 8, 10, 12]); + + let g = vec![1, 2, 3, 4, 5, 6]; + let glen = g.len(); + for i in 0..glen { + let x: u32 = g[i + 1..].iter().sum(); + println!("{}", g[i] + x); + } + assert_eq!(g, vec![20, 18, 15, 11, 6, 0]); + + let mut g = vec![1, 2, 3, 4, 5, 6]; + let glen = g.len(); + for i in 0..glen { + g[i] = g[i + 1..].iter().sum(); + } + assert_eq!(g, vec![20, 18, 15, 11, 6, 0]); + + let x = 5; + let mut vec = vec![0; 9]; + + for i in x..x + 4 { + vec[i] += 1; + } + + let x = 5; + let mut vec = vec![0; 10]; + + for i in x..=x + 4 { + vec[i] += 1; + } + + let arr = [1, 2, 3]; + + for i in 0..3 { + println!("{}", arr[i]); + } + + for i in 0..2 { + println!("{}", arr[i]); + } + + for i in 1..3 { + println!("{}", arr[i]); + } + + // Fix #5945 + let mut vec = vec![1, 2, 3, 4]; + for i in 0..vec.len() - 1 { + vec[i] += 1; + } + let mut vec = vec![1, 2, 3, 4]; + for i in vec.len() - 3..vec.len() { + vec[i] += 1; + } + let mut vec = vec![1, 2, 3, 4]; + for i in vec.len() - 3..vec.len() - 1 { + vec[i] += 1; + } +} + +mod issue2277 { + pub fn example(list: &[[f64; 3]]) { + let mut x: [f64; 3] = [10.; 3]; + + for i in 0..3 { + x[i] = list.iter().map(|item| item[i]).sum::(); + } + } +} diff --git a/src/tools/clippy/tests/ui/needless_range_loop2.stderr b/src/tools/clippy/tests/ui/needless_range_loop2.stderr new file mode 100644 index 0000000000..2e1f0fd029 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_range_loop2.stderr @@ -0,0 +1,91 @@ +error: the loop variable `i` is only used to index `ns` + --> $DIR/needless_range_loop2.rs:10:14 + | +LL | for i in 3..10 { + | ^^^^^ + | + = note: `-D clippy::needless-range-loop` implied by `-D warnings` +help: consider using an iterator + | +LL | for in ns.iter().take(10).skip(3) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `ms` + --> $DIR/needless_range_loop2.rs:31:14 + | +LL | for i in 0..ms.len() { + | ^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in &mut ms { + | ^^^^^^ ^^^^^^^ + +error: the loop variable `i` is only used to index `ms` + --> $DIR/needless_range_loop2.rs:37:14 + | +LL | for i in 0..ms.len() { + | ^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in &mut ms { + | ^^^^^^ ^^^^^^^ + +error: the loop variable `i` is only used to index `vec` + --> $DIR/needless_range_loop2.rs:61:14 + | +LL | for i in x..x + 4 { + | ^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter_mut().skip(x).take(4) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec` + --> $DIR/needless_range_loop2.rs:68:14 + | +LL | for i in x..=x + 4 { + | ^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter_mut().skip(x).take(4 + 1) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `arr` + --> $DIR/needless_range_loop2.rs:74:14 + | +LL | for i in 0..3 { + | ^^^^ + | +help: consider using an iterator + | +LL | for in &arr { + | ^^^^^^ ^^^^ + +error: the loop variable `i` is only used to index `arr` + --> $DIR/needless_range_loop2.rs:78:14 + | +LL | for i in 0..2 { + | ^^^^ + | +help: consider using an iterator + | +LL | for in arr.iter().take(2) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `arr` + --> $DIR/needless_range_loop2.rs:82:14 + | +LL | for i in 1..3 { + | ^^^^ + | +help: consider using an iterator + | +LL | for in arr.iter().skip(1) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_return.fixed b/src/tools/clippy/tests/ui/needless_return.fixed new file mode 100644 index 0000000000..990475fcb5 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_return.fixed @@ -0,0 +1,129 @@ +// run-rustfix + +#![allow(unused, clippy::needless_bool)] +#![allow(clippy::if_same_then_else, clippy::single_match)] +#![warn(clippy::needless_return)] + +macro_rules! the_answer { + () => { + 42 + }; +} + +fn test_end_of_fn() -> bool { + if true { + // no error! + return true; + } + true +} + +fn test_no_semicolon() -> bool { + true +} + +fn test_if_block() -> bool { + if true { + true + } else { + false + } +} + +fn test_match(x: bool) -> bool { + match x { + true => false, + false => { + true + }, + } +} + +fn test_closure() { + let _ = || { + true + }; + let _ = || true; +} + +fn test_macro_call() -> i32 { + return the_answer!(); +} + +fn test_void_fun() { + +} + +fn test_void_if_fun(b: bool) { + if b { + + } else { + + } +} + +fn test_void_match(x: u32) { + match x { + 0 => (), + _ => {}, + } +} + +fn read_line() -> String { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + return stdin.lock().lines().next().unwrap().unwrap(); +} + +fn borrows_but_not_last(value: bool) -> String { + if value { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + let _a = stdin.lock().lines().next().unwrap().unwrap(); + String::from("test") + } else { + String::new() + } +} + +macro_rules! needed_return { + ($e:expr) => { + if $e > 3 { + return; + } + }; +} + +fn test_return_in_macro() { + // This will return and the macro below won't be executed. Removing the `return` from the macro + // will change semantics. + needed_return!(10); + needed_return!(0); +} + +mod issue6501 { + fn foo(bar: Result<(), ()>) { + bar.unwrap_or_else(|_| {}) + } + + fn test_closure() { + let _ = || { + + }; + let _ = || {}; + } + + struct Foo; + #[allow(clippy::unnecessary_lazy_evaluations)] + fn bar(res: Result) -> Foo { + res.unwrap_or_else(|_| Foo) + } +} + +fn main() { + let _ = test_end_of_fn(); + let _ = test_no_semicolon(); + let _ = test_if_block(); + let _ = test_match(true); + test_closure(); +} diff --git a/src/tools/clippy/tests/ui/needless_return.rs b/src/tools/clippy/tests/ui/needless_return.rs new file mode 100644 index 0000000000..dec3d84a02 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_return.rs @@ -0,0 +1,129 @@ +// run-rustfix + +#![allow(unused, clippy::needless_bool)] +#![allow(clippy::if_same_then_else, clippy::single_match)] +#![warn(clippy::needless_return)] + +macro_rules! the_answer { + () => { + 42 + }; +} + +fn test_end_of_fn() -> bool { + if true { + // no error! + return true; + } + return true; +} + +fn test_no_semicolon() -> bool { + return true; +} + +fn test_if_block() -> bool { + if true { + return true; + } else { + return false; + } +} + +fn test_match(x: bool) -> bool { + match x { + true => return false, + false => { + return true; + }, + } +} + +fn test_closure() { + let _ = || { + return true; + }; + let _ = || return true; +} + +fn test_macro_call() -> i32 { + return the_answer!(); +} + +fn test_void_fun() { + return; +} + +fn test_void_if_fun(b: bool) { + if b { + return; + } else { + return; + } +} + +fn test_void_match(x: u32) { + match x { + 0 => (), + _ => return, + } +} + +fn read_line() -> String { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + return stdin.lock().lines().next().unwrap().unwrap(); +} + +fn borrows_but_not_last(value: bool) -> String { + if value { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + let _a = stdin.lock().lines().next().unwrap().unwrap(); + return String::from("test"); + } else { + return String::new(); + } +} + +macro_rules! needed_return { + ($e:expr) => { + if $e > 3 { + return; + } + }; +} + +fn test_return_in_macro() { + // This will return and the macro below won't be executed. Removing the `return` from the macro + // will change semantics. + needed_return!(10); + needed_return!(0); +} + +mod issue6501 { + fn foo(bar: Result<(), ()>) { + bar.unwrap_or_else(|_| return) + } + + fn test_closure() { + let _ = || { + return; + }; + let _ = || return; + } + + struct Foo; + #[allow(clippy::unnecessary_lazy_evaluations)] + fn bar(res: Result) -> Foo { + res.unwrap_or_else(|_| return Foo) + } +} + +fn main() { + let _ = test_end_of_fn(); + let _ = test_no_semicolon(); + let _ = test_if_block(); + let _ = test_match(true); + test_closure(); +} diff --git a/src/tools/clippy/tests/ui/needless_return.stderr b/src/tools/clippy/tests/ui/needless_return.stderr new file mode 100644 index 0000000000..ae31d60754 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_return.stderr @@ -0,0 +1,112 @@ +error: unneeded `return` statement + --> $DIR/needless_return.rs:18:5 + | +LL | return true; + | ^^^^^^^^^^^^ help: remove `return`: `true` + | + = note: `-D clippy::needless-return` implied by `-D warnings` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:22:5 + | +LL | return true; + | ^^^^^^^^^^^^ help: remove `return`: `true` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:27:9 + | +LL | return true; + | ^^^^^^^^^^^^ help: remove `return`: `true` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:29:9 + | +LL | return false; + | ^^^^^^^^^^^^^ help: remove `return`: `false` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:35:17 + | +LL | true => return false, + | ^^^^^^^^^^^^ help: remove `return`: `false` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:37:13 + | +LL | return true; + | ^^^^^^^^^^^^ help: remove `return`: `true` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:44:9 + | +LL | return true; + | ^^^^^^^^^^^^ help: remove `return`: `true` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:46:16 + | +LL | let _ = || return true; + | ^^^^^^^^^^^ help: remove `return`: `true` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:54:5 + | +LL | return; + | ^^^^^^^ help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:59:9 + | +LL | return; + | ^^^^^^^ help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:61:9 + | +LL | return; + | ^^^^^^^ help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:68:14 + | +LL | _ => return, + | ^^^^^^ help: replace `return` with an empty block: `{}` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:83:9 + | +LL | return String::from("test"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:85:9 + | +LL | return String::new(); + | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:106:32 + | +LL | bar.unwrap_or_else(|_| return) + | ^^^^^^ help: replace `return` with an empty block: `{}` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:111:13 + | +LL | return; + | ^^^^^^^ help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:113:20 + | +LL | let _ = || return; + | ^^^^^^ help: replace `return` with an empty block: `{}` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:119:32 + | +LL | res.unwrap_or_else(|_| return Foo) + | ^^^^^^^^^^ help: remove `return`: `Foo` + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_update.rs b/src/tools/clippy/tests/ui/needless_update.rs new file mode 100644 index 0000000000..b93ff048a6 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_update.rs @@ -0,0 +1,25 @@ +#![warn(clippy::needless_update)] +#![allow(clippy::no_effect)] + +struct S { + pub a: i32, + pub b: i32, +} + +#[non_exhaustive] +struct T { + pub x: i32, + pub y: i32, +} + +fn main() { + let base = S { a: 0, b: 0 }; + S { ..base }; // no error + S { a: 1, ..base }; // no error + S { a: 1, b: 1, ..base }; + + let base = T { x: 0, y: 0 }; + T { ..base }; // no error + T { x: 1, ..base }; // no error + T { x: 1, y: 1, ..base }; // no error +} diff --git a/src/tools/clippy/tests/ui/needless_update.stderr b/src/tools/clippy/tests/ui/needless_update.stderr new file mode 100644 index 0000000000..b154b3b306 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_update.stderr @@ -0,0 +1,10 @@ +error: struct update has no effect, all the fields in the struct have already been specified + --> $DIR/needless_update.rs:19:23 + | +LL | S { a: 1, b: 1, ..base }; + | ^^^^ + | + = note: `-D clippy::needless-update` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.rs b/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.rs new file mode 100644 index 0000000000..2d392c593b --- /dev/null +++ b/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.rs @@ -0,0 +1,62 @@ +//! This test case utilizes `f64` an easy example for `PartialOrd` only types +//! but the lint itself actually validates any expression where the left +//! operand implements `PartialOrd` but not `Ord`. + +use std::cmp::Ordering; + +#[allow(clippy::unnested_or_patterns, clippy::match_like_matches_macro)] +#[warn(clippy::neg_cmp_op_on_partial_ord)] +fn main() { + let a_value = 1.0; + let another_value = 7.0; + + // --- Bad --- + + // Not Less but potentially Greater, Equal or Uncomparable. + let _not_less = !(a_value < another_value); + + // Not Less or Equal but potentially Greater or Uncomparable. + let _not_less_or_equal = !(a_value <= another_value); + + // Not Greater but potentially Less, Equal or Uncomparable. + let _not_greater = !(a_value > another_value); + + // Not Greater or Equal but potentially Less or Uncomparable. + let _not_greater_or_equal = !(a_value >= another_value); + + // --- Good --- + + let _not_less = match a_value.partial_cmp(&another_value) { + None | Some(Ordering::Greater) | Some(Ordering::Equal) => true, + _ => false, + }; + let _not_less_or_equal = match a_value.partial_cmp(&another_value) { + None | Some(Ordering::Greater) => true, + _ => false, + }; + let _not_greater = match a_value.partial_cmp(&another_value) { + None | Some(Ordering::Less) | Some(Ordering::Equal) => true, + _ => false, + }; + let _not_greater_or_equal = match a_value.partial_cmp(&another_value) { + None | Some(Ordering::Less) => true, + _ => false, + }; + + // --- Should not trigger --- + + let _ = a_value < another_value; + let _ = a_value <= another_value; + let _ = a_value > another_value; + let _ = a_value >= another_value; + + // --- regression tests --- + + // Issue 2856: False positive on assert!() + // + // The macro always negates the result of the given comparison in its + // internal check which automatically triggered the lint. As it's an + // external macro there was no chance to do anything about it which led + // to an exempting of all external macros. + assert!(a_value < another_value); +} diff --git a/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.stderr b/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.stderr new file mode 100644 index 0000000000..c785600072 --- /dev/null +++ b/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.stderr @@ -0,0 +1,28 @@ +error: the use of negated comparison operators on partially ordered types produces code that is hard to read and refactor, please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable + --> $DIR/neg_cmp_op_on_partial_ord.rs:16:21 + | +LL | let _not_less = !(a_value < another_value); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::neg-cmp-op-on-partial-ord` implied by `-D warnings` + +error: the use of negated comparison operators on partially ordered types produces code that is hard to read and refactor, please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable + --> $DIR/neg_cmp_op_on_partial_ord.rs:19:30 + | +LL | let _not_less_or_equal = !(a_value <= another_value); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the use of negated comparison operators on partially ordered types produces code that is hard to read and refactor, please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable + --> $DIR/neg_cmp_op_on_partial_ord.rs:22:24 + | +LL | let _not_greater = !(a_value > another_value); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the use of negated comparison operators on partially ordered types produces code that is hard to read and refactor, please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable + --> $DIR/neg_cmp_op_on_partial_ord.rs:25:33 + | +LL | let _not_greater_or_equal = !(a_value >= another_value); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/neg_multiply.rs b/src/tools/clippy/tests/ui/neg_multiply.rs new file mode 100644 index 0000000000..d4a20ce9db --- /dev/null +++ b/src/tools/clippy/tests/ui/neg_multiply.rs @@ -0,0 +1,35 @@ +#![warn(clippy::neg_multiply)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +use std::ops::Mul; + +struct X; + +impl Mul for X { + type Output = X; + + fn mul(self, _r: isize) -> Self { + self + } +} + +impl Mul for isize { + type Output = X; + + fn mul(self, _r: X) -> X { + X + } +} + +fn main() { + let x = 0; + + x * -1; + + -1 * x; + + -1 * -1; // should be ok + + X * -1; // should be ok + -1 * X; // should also be ok +} diff --git a/src/tools/clippy/tests/ui/neg_multiply.stderr b/src/tools/clippy/tests/ui/neg_multiply.stderr new file mode 100644 index 0000000000..ad677f6d6f --- /dev/null +++ b/src/tools/clippy/tests/ui/neg_multiply.stderr @@ -0,0 +1,16 @@ +error: negation by multiplying with `-1` + --> $DIR/neg_multiply.rs:27:5 + | +LL | x * -1; + | ^^^^^^ + | + = note: `-D clippy::neg-multiply` implied by `-D warnings` + +error: negation by multiplying with `-1` + --> $DIR/neg_multiply.rs:29:5 + | +LL | -1 * x; + | ^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/never_loop.rs b/src/tools/clippy/tests/ui/never_loop.rs new file mode 100644 index 0000000000..2770eb2b2a --- /dev/null +++ b/src/tools/clippy/tests/ui/never_loop.rs @@ -0,0 +1,204 @@ +#![allow( + clippy::single_match, + unused_assignments, + unused_variables, + clippy::while_immutable_condition +)] + +fn test1() { + let mut x = 0; + loop { + // clippy::never_loop + x += 1; + if x == 1 { + return; + } + break; + } +} + +fn test2() { + let mut x = 0; + loop { + x += 1; + if x == 1 { + break; + } + } +} + +fn test3() { + let mut x = 0; + loop { + // never loops + x += 1; + break; + } +} + +fn test4() { + let mut x = 1; + loop { + x += 1; + match x { + 5 => return, + _ => (), + } + } +} + +fn test5() { + let i = 0; + loop { + // never loops + while i == 0 { + // never loops + break; + } + return; + } +} + +fn test6() { + let mut x = 0; + 'outer: loop { + x += 1; + loop { + // never loops + if x == 5 { + break; + } + continue 'outer; + } + return; + } +} + +fn test7() { + let mut x = 0; + loop { + x += 1; + match x { + 1 => continue, + _ => (), + } + return; + } +} + +fn test8() { + let mut x = 0; + loop { + x += 1; + match x { + 5 => return, + _ => continue, + } + } +} + +fn test9() { + let x = Some(1); + while let Some(y) = x { + // never loops + return; + } +} + +fn test10() { + for x in 0..10 { + // never loops + match x { + 1 => break, + _ => return, + } + } +} + +fn test11 i32>(mut f: F) { + loop { + return match f() { + 1 => continue, + _ => (), + }; + } +} + +pub fn test12(a: bool, b: bool) { + 'label: loop { + loop { + if a { + continue 'label; + } + if b { + break; + } + } + break; + } +} + +pub fn test13() { + let mut a = true; + loop { + // infinite loop + while a { + if true { + a = false; + continue; + } + return; + } + } +} + +pub fn test14() { + let mut a = true; + 'outer: while a { + // never loops + while a { + if a { + a = false; + continue; + } + } + break 'outer; + } +} + +// Issue #1991: the outer loop should not warn. +pub fn test15() { + 'label: loop { + while false { + break 'label; + } + } +} + +// Issue #4058: `continue` in `break` expression +pub fn test16() { + let mut n = 1; + loop { + break if n != 5 { + n += 1; + continue; + }; + } +} + +fn main() { + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + test7(); + test8(); + test9(); + test10(); + test11(|| 0); + test12(true, false); + test13(); + test14(); +} diff --git a/src/tools/clippy/tests/ui/never_loop.stderr b/src/tools/clippy/tests/ui/never_loop.stderr new file mode 100644 index 0000000000..c00b4c78cf --- /dev/null +++ b/src/tools/clippy/tests/ui/never_loop.stderr @@ -0,0 +1,100 @@ +error: this loop never actually loops + --> $DIR/never_loop.rs:10:5 + | +LL | / loop { +LL | | // clippy::never_loop +LL | | x += 1; +LL | | if x == 1 { +... | +LL | | break; +LL | | } + | |_____^ + | + = note: `#[deny(clippy::never_loop)]` on by default + +error: this loop never actually loops + --> $DIR/never_loop.rs:32:5 + | +LL | / loop { +LL | | // never loops +LL | | x += 1; +LL | | break; +LL | | } + | |_____^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:52:5 + | +LL | / loop { +LL | | // never loops +LL | | while i == 0 { +LL | | // never loops +... | +LL | | return; +LL | | } + | |_____^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:54:9 + | +LL | / while i == 0 { +LL | | // never loops +LL | | break; +LL | | } + | |_________^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:66:9 + | +LL | / loop { +LL | | // never loops +LL | | if x == 5 { +LL | | break; +LL | | } +LL | | continue 'outer; +LL | | } + | |_________^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:102:5 + | +LL | / while let Some(y) = x { +LL | | // never loops +LL | | return; +LL | | } + | |_____^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:109:5 + | +LL | / for x in 0..10 { +LL | | // never loops +LL | | match x { +LL | | 1 => break, +LL | | _ => return, +LL | | } +LL | | } + | |_____^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:157:5 + | +LL | / 'outer: while a { +LL | | // never loops +LL | | while a { +LL | | if a { +... | +LL | | break 'outer; +LL | | } + | |_____^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:172:9 + | +LL | / while false { +LL | | break 'label; +LL | | } + | |_________^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/new_ret_no_self.rs b/src/tools/clippy/tests/ui/new_ret_no_self.rs new file mode 100644 index 0000000000..e82873629a --- /dev/null +++ b/src/tools/clippy/tests/ui/new_ret_no_self.rs @@ -0,0 +1,342 @@ +#![warn(clippy::new_ret_no_self)] +#![allow(dead_code)] + +fn main() {} + +trait R { + type Item; +} + +trait Q { + type Item; + type Item2; +} + +struct S; + +impl R for S { + type Item = Self; +} + +impl S { + // should not trigger the lint + pub fn new() -> impl R { + S + } +} + +struct S2; + +impl R for S2 { + type Item = Self; +} + +impl S2 { + // should not trigger the lint + pub fn new(_: String) -> impl R { + S2 + } +} + +struct S3; + +impl R for S3 { + type Item = u32; +} + +impl S3 { + // should trigger the lint + pub fn new(_: String) -> impl R { + S3 + } +} + +struct S4; + +impl Q for S4 { + type Item = u32; + type Item2 = Self; +} + +impl S4 { + // should not trigger the lint + pub fn new(_: String) -> impl Q { + S4 + } +} + +struct T; + +impl T { + // should not trigger lint + pub fn new() -> Self { + unimplemented!(); + } +} + +struct U; + +impl U { + // should trigger lint + pub fn new() -> u32 { + unimplemented!(); + } +} + +struct V; + +impl V { + // should trigger lint + pub fn new(_: String) -> u32 { + unimplemented!(); + } +} + +struct TupleReturnerOk; + +impl TupleReturnerOk { + // should not trigger lint + pub fn new() -> (Self, u32) { + unimplemented!(); + } +} + +struct TupleReturnerOk2; + +impl TupleReturnerOk2 { + // should not trigger lint (it doesn't matter which element in the tuple is Self) + pub fn new() -> (u32, Self) { + unimplemented!(); + } +} + +struct TupleReturnerOk3; + +impl TupleReturnerOk3 { + // should not trigger lint (tuple can contain multiple Self) + pub fn new() -> (Self, Self) { + unimplemented!(); + } +} + +struct TupleReturnerBad; + +impl TupleReturnerBad { + // should trigger lint + pub fn new() -> (u32, u32) { + unimplemented!(); + } +} + +struct MutPointerReturnerOk; + +impl MutPointerReturnerOk { + // should not trigger lint + pub fn new() -> *mut Self { + unimplemented!(); + } +} + +struct ConstPointerReturnerOk2; + +impl ConstPointerReturnerOk2 { + // should not trigger lint + pub fn new() -> *const Self { + unimplemented!(); + } +} + +struct MutPointerReturnerBad; + +impl MutPointerReturnerBad { + // should trigger lint + pub fn new() -> *mut V { + unimplemented!(); + } +} + +struct GenericReturnerOk; + +impl GenericReturnerOk { + // should not trigger lint + pub fn new() -> Option { + unimplemented!(); + } +} + +struct GenericReturnerBad; + +impl GenericReturnerBad { + // should trigger lint + pub fn new() -> Option { + unimplemented!(); + } +} + +struct NestedReturnerOk; + +impl NestedReturnerOk { + // should not trigger lint + pub fn new() -> (Option, u32) { + unimplemented!(); + } +} + +struct NestedReturnerOk2; + +impl NestedReturnerOk2 { + // should not trigger lint + pub fn new() -> ((Self, u32), u32) { + unimplemented!(); + } +} + +struct NestedReturnerOk3; + +impl NestedReturnerOk3 { + // should not trigger lint + pub fn new() -> Option<(Self, u32)> { + unimplemented!(); + } +} + +struct WithLifetime<'a> { + cat: &'a str, +} + +impl<'a> WithLifetime<'a> { + // should not trigger the lint, because the lifetimes are different + pub fn new<'b: 'a>(s: &'b str) -> WithLifetime<'b> { + unimplemented!(); + } +} + +mod issue5435 { + struct V; + + pub trait TraitRetSelf { + // should not trigger lint + fn new() -> Self; + } + + pub trait TraitRet { + // should trigger lint as we are in trait definition + fn new() -> String; + } + pub struct StructRet; + impl TraitRet for StructRet { + // should not trigger lint as we are in the impl block + fn new() -> String { + unimplemented!(); + } + } + + pub trait TraitRet2 { + // should trigger lint + fn new(_: String) -> String; + } + + trait TupleReturnerOk { + // should not trigger lint + fn new() -> (Self, u32) + where + Self: Sized, + { + unimplemented!(); + } + } + + trait TupleReturnerOk2 { + // should not trigger lint (it doesn't matter which element in the tuple is Self) + fn new() -> (u32, Self) + where + Self: Sized, + { + unimplemented!(); + } + } + + trait TupleReturnerOk3 { + // should not trigger lint (tuple can contain multiple Self) + fn new() -> (Self, Self) + where + Self: Sized, + { + unimplemented!(); + } + } + + trait TupleReturnerBad { + // should trigger lint + fn new() -> (u32, u32) { + unimplemented!(); + } + } + + trait MutPointerReturnerOk { + // should not trigger lint + fn new() -> *mut Self + where + Self: Sized, + { + unimplemented!(); + } + } + + trait ConstPointerReturnerOk2 { + // should not trigger lint + fn new() -> *const Self + where + Self: Sized, + { + unimplemented!(); + } + } + + trait MutPointerReturnerBad { + // should trigger lint + fn new() -> *mut V { + unimplemented!(); + } + } + + trait GenericReturnerOk { + // should not trigger lint + fn new() -> Option + where + Self: Sized, + { + unimplemented!(); + } + } + + trait NestedReturnerOk { + // should not trigger lint + fn new() -> (Option, u32) + where + Self: Sized, + { + unimplemented!(); + } + } + + trait NestedReturnerOk2 { + // should not trigger lint + fn new() -> ((Self, u32), u32) + where + Self: Sized, + { + unimplemented!(); + } + } + + trait NestedReturnerOk3 { + // should not trigger lint + fn new() -> Option<(Self, u32)> + where + Self: Sized, + { + unimplemented!(); + } + } +} diff --git a/src/tools/clippy/tests/ui/new_ret_no_self.stderr b/src/tools/clippy/tests/ui/new_ret_no_self.stderr new file mode 100644 index 0000000000..8217bc6187 --- /dev/null +++ b/src/tools/clippy/tests/ui/new_ret_no_self.stderr @@ -0,0 +1,80 @@ +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:49:5 + | +LL | / pub fn new(_: String) -> impl R { +LL | | S3 +LL | | } + | |_____^ + | + = note: `-D clippy::new-ret-no-self` implied by `-D warnings` + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:81:5 + | +LL | / pub fn new() -> u32 { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:90:5 + | +LL | / pub fn new(_: String) -> u32 { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:126:5 + | +LL | / pub fn new() -> (u32, u32) { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:153:5 + | +LL | / pub fn new() -> *mut V { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:171:5 + | +LL | / pub fn new() -> Option { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:224:9 + | +LL | fn new() -> String; + | ^^^^^^^^^^^^^^^^^^^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:236:9 + | +LL | fn new(_: String) -> String; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:271:9 + | +LL | / fn new() -> (u32, u32) { +LL | | unimplemented!(); +LL | | } + | |_________^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:298:9 + | +LL | / fn new() -> *mut V { +LL | | unimplemented!(); +LL | | } + | |_________^ + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/new_without_default.rs b/src/tools/clippy/tests/ui/new_without_default.rs new file mode 100644 index 0000000000..3b6041823d --- /dev/null +++ b/src/tools/clippy/tests/ui/new_without_default.rs @@ -0,0 +1,162 @@ +#![feature(const_fn)] +#![allow(dead_code, clippy::missing_safety_doc)] +#![warn(clippy::new_without_default)] + +pub struct Foo; + +impl Foo { + pub fn new() -> Foo { + Foo + } +} + +pub struct Bar; + +impl Bar { + pub fn new() -> Self { + Bar + } +} + +pub struct Ok; + +impl Ok { + pub fn new() -> Self { + Ok + } +} + +impl Default for Ok { + fn default() -> Self { + Ok + } +} + +pub struct Params; + +impl Params { + pub fn new(_: u32) -> Self { + Params + } +} + +pub struct GenericsOk { + bar: T, +} + +impl Default for GenericsOk { + fn default() -> Self { + unimplemented!(); + } +} + +impl<'c, V> GenericsOk { + pub fn new() -> GenericsOk { + unimplemented!() + } +} + +pub struct LtOk<'a> { + foo: &'a bool, +} + +impl<'b> Default for LtOk<'b> { + fn default() -> Self { + unimplemented!(); + } +} + +impl<'c> LtOk<'c> { + pub fn new() -> LtOk<'c> { + unimplemented!() + } +} + +pub struct LtKo<'a> { + foo: &'a bool, +} + +impl<'c> LtKo<'c> { + pub fn new() -> LtKo<'c> { + unimplemented!() + } + // FIXME: that suggestion is missing lifetimes +} + +struct Private; + +impl Private { + fn new() -> Private { + unimplemented!() + } // We don't lint private items +} + +struct Const; + +impl Const { + pub const fn new() -> Const { + Const + } // const fns can't be implemented via Default +} + +pub struct IgnoreGenericNew; + +impl IgnoreGenericNew { + pub fn new() -> Self { + IgnoreGenericNew + } // the derived Default does not make sense here as the result depends on T +} + +pub trait TraitWithNew: Sized { + fn new() -> Self { + panic!() + } +} + +pub struct IgnoreUnsafeNew; + +impl IgnoreUnsafeNew { + pub unsafe fn new() -> Self { + IgnoreUnsafeNew + } +} + +#[derive(Default)] +pub struct OptionRefWrapper<'a, T>(Option<&'a T>); + +impl<'a, T> OptionRefWrapper<'a, T> { + pub fn new() -> Self { + OptionRefWrapper(None) + } +} + +pub struct Allow(Foo); + +impl Allow { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + unimplemented!() + } +} + +pub struct AllowDerive; + +impl AllowDerive { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + unimplemented!() + } +} + +pub struct NewNotEqualToDerive { + foo: i32, +} + +impl NewNotEqualToDerive { + // This `new` implementation is not equal to a derived `Default`, so do not suggest deriving. + pub fn new() -> Self { + NewNotEqualToDerive { foo: 1 } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/new_without_default.stderr b/src/tools/clippy/tests/ui/new_without_default.stderr new file mode 100644 index 0000000000..e529e441eb --- /dev/null +++ b/src/tools/clippy/tests/ui/new_without_default.stderr @@ -0,0 +1,71 @@ +error: you should consider adding a `Default` implementation for `Foo` + --> $DIR/new_without_default.rs:8:5 + | +LL | / pub fn new() -> Foo { +LL | | Foo +LL | | } + | |_____^ + | + = note: `-D clippy::new-without-default` implied by `-D warnings` +help: try this + | +LL | impl Default for Foo { +LL | fn default() -> Self { +LL | Self::new() +LL | } +LL | } + | + +error: you should consider adding a `Default` implementation for `Bar` + --> $DIR/new_without_default.rs:16:5 + | +LL | / pub fn new() -> Self { +LL | | Bar +LL | | } + | |_____^ + | +help: try this + | +LL | impl Default for Bar { +LL | fn default() -> Self { +LL | Self::new() +LL | } +LL | } + | + +error: you should consider adding a `Default` implementation for `LtKo<'c>` + --> $DIR/new_without_default.rs:80:5 + | +LL | / pub fn new() -> LtKo<'c> { +LL | | unimplemented!() +LL | | } + | |_____^ + | +help: try this + | +LL | impl Default for LtKo<'c> { +LL | fn default() -> Self { +LL | Self::new() +LL | } +LL | } + | + +error: you should consider adding a `Default` implementation for `NewNotEqualToDerive` + --> $DIR/new_without_default.rs:157:5 + | +LL | / pub fn new() -> Self { +LL | | NewNotEqualToDerive { foo: 1 } +LL | | } + | |_____^ + | +help: try this + | +LL | impl Default for NewNotEqualToDerive { +LL | fn default() -> Self { +LL | Self::new() +LL | } +LL | } + | + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/no_effect.rs b/src/tools/clippy/tests/ui/no_effect.rs new file mode 100644 index 0000000000..8fbfcb7986 --- /dev/null +++ b/src/tools/clippy/tests/ui/no_effect.rs @@ -0,0 +1,102 @@ +#![feature(box_syntax)] +#![warn(clippy::no_effect)] +#![allow(dead_code)] +#![allow(path_statements)] +#![allow(clippy::deref_addrof)] +#![allow(clippy::redundant_field_names)] +#![feature(untagged_unions)] + +struct Unit; +struct Tuple(i32); +struct Struct { + field: i32, +} +enum Enum { + Tuple(i32), + Struct { field: i32 }, +} +struct DropUnit; +impl Drop for DropUnit { + fn drop(&mut self) {} +} +struct DropStruct { + field: i32, +} +impl Drop for DropStruct { + fn drop(&mut self) {} +} +struct DropTuple(i32); +impl Drop for DropTuple { + fn drop(&mut self) {} +} +enum DropEnum { + Tuple(i32), + Struct { field: i32 }, +} +impl Drop for DropEnum { + fn drop(&mut self) {} +} +struct FooString { + s: String, +} +union Union { + a: u8, + b: f64, +} + +fn get_number() -> i32 { + 0 +} +fn get_struct() -> Struct { + Struct { field: 0 } +} +fn get_drop_struct() -> DropStruct { + DropStruct { field: 0 } +} + +unsafe fn unsafe_fn() -> i32 { + 0 +} + +fn main() { + let s = get_struct(); + let s2 = get_struct(); + + 0; + s2; + Unit; + Tuple(0); + Struct { field: 0 }; + Struct { ..s }; + Union { a: 0 }; + Enum::Tuple(0); + Enum::Struct { field: 0 }; + 5 + 6; + *&42; + &6; + (5, 6, 7); + box 42; + ..; + 5..; + ..5; + 5..6; + 5..=6; + [42, 55]; + [42, 55][1]; + (42, 55).1; + [42; 55]; + [42; 55][13]; + let mut x = 0; + || x += 5; + let s: String = "foo".into(); + FooString { s: s }; + + // Do not warn + get_number(); + unsafe { unsafe_fn() }; + DropUnit; + DropStruct { field: 0 }; + DropTuple(0); + DropEnum::Tuple(0); + DropEnum::Struct { field: 0 }; +} diff --git a/src/tools/clippy/tests/ui/no_effect.stderr b/src/tools/clippy/tests/ui/no_effect.stderr new file mode 100644 index 0000000000..834b9056e3 --- /dev/null +++ b/src/tools/clippy/tests/ui/no_effect.stderr @@ -0,0 +1,154 @@ +error: statement with no effect + --> $DIR/no_effect.rs:65:5 + | +LL | 0; + | ^^ + | + = note: `-D clippy::no-effect` implied by `-D warnings` + +error: statement with no effect + --> $DIR/no_effect.rs:66:5 + | +LL | s2; + | ^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:67:5 + | +LL | Unit; + | ^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:68:5 + | +LL | Tuple(0); + | ^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:69:5 + | +LL | Struct { field: 0 }; + | ^^^^^^^^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:70:5 + | +LL | Struct { ..s }; + | ^^^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:71:5 + | +LL | Union { a: 0 }; + | ^^^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:72:5 + | +LL | Enum::Tuple(0); + | ^^^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:73:5 + | +LL | Enum::Struct { field: 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:74:5 + | +LL | 5 + 6; + | ^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:75:5 + | +LL | *&42; + | ^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:76:5 + | +LL | &6; + | ^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:77:5 + | +LL | (5, 6, 7); + | ^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:78:5 + | +LL | box 42; + | ^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:79:5 + | +LL | ..; + | ^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:80:5 + | +LL | 5..; + | ^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:81:5 + | +LL | ..5; + | ^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:82:5 + | +LL | 5..6; + | ^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:84:5 + | +LL | [42, 55]; + | ^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:85:5 + | +LL | [42, 55][1]; + | ^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:86:5 + | +LL | (42, 55).1; + | ^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:87:5 + | +LL | [42; 55]; + | ^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:88:5 + | +LL | [42; 55][13]; + | ^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:90:5 + | +LL | || x += 5; + | ^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:92:5 + | +LL | FooString { s: s }; + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 25 previous errors + diff --git a/src/tools/clippy/tests/ui/non_expressive_names.rs b/src/tools/clippy/tests/ui/non_expressive_names.rs new file mode 100644 index 0000000000..58415b4aed --- /dev/null +++ b/src/tools/clippy/tests/ui/non_expressive_names.rs @@ -0,0 +1,56 @@ +#![warn(clippy::all)] +#![allow(unused, clippy::println_empty_string)] + +#[derive(Clone, Debug)] +enum MaybeInst { + Split, + Split1(usize), + Split2(usize), +} + +struct InstSplit { + uiae: usize, +} + +impl MaybeInst { + fn fill(&mut self) { + let filled = match *self { + MaybeInst::Split1(goto1) => panic!(1), + MaybeInst::Split2(goto2) => panic!(2), + _ => unimplemented!(), + }; + unimplemented!() + } +} + +fn underscores_and_numbers() { + let _1 = 1; //~ERROR Consider a more descriptive name + let ____1 = 1; //~ERROR Consider a more descriptive name + let __1___2 = 12; //~ERROR Consider a more descriptive name + let _1_ok = 1; +} + +fn issue2927() { + let args = 1; + format!("{:?}", 2); +} + +fn issue3078() { + match "a" { + stringify!(a) => {}, + _ => {}, + } +} + +struct Bar; + +impl Bar { + fn bar() { + let _1 = 1; + let ____1 = 1; + let __1___2 = 12; + let _1_ok = 1; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/non_expressive_names.stderr b/src/tools/clippy/tests/ui/non_expressive_names.stderr new file mode 100644 index 0000000000..a0ca46f0ef --- /dev/null +++ b/src/tools/clippy/tests/ui/non_expressive_names.stderr @@ -0,0 +1,40 @@ +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:27:9 + | +LL | let _1 = 1; //~ERROR Consider a more descriptive name + | ^^ + | + = note: `-D clippy::just-underscores-and-digits` implied by `-D warnings` + +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:28:9 + | +LL | let ____1 = 1; //~ERROR Consider a more descriptive name + | ^^^^^ + +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:29:9 + | +LL | let __1___2 = 12; //~ERROR Consider a more descriptive name + | ^^^^^^^ + +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:49:13 + | +LL | let _1 = 1; + | ^^ + +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:50:13 + | +LL | let ____1 = 1; + | ^^^^^ + +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:51:13 + | +LL | let __1___2 = 12; + | ^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/non_expressive_names.stdout b/src/tools/clippy/tests/ui/non_expressive_names.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tools/clippy/tests/ui/nonminimal_bool.rs b/src/tools/clippy/tests/ui/nonminimal_bool.rs new file mode 100644 index 0000000000..971be26278 --- /dev/null +++ b/src/tools/clippy/tests/ui/nonminimal_bool.rs @@ -0,0 +1,52 @@ +#![allow(unused, clippy::many_single_char_names, clippy::diverging_sub_expression)] +#![warn(clippy::nonminimal_bool)] + +fn main() { + let a: bool = unimplemented!(); + let b: bool = unimplemented!(); + let c: bool = unimplemented!(); + let d: bool = unimplemented!(); + let e: bool = unimplemented!(); + let _ = !true; + let _ = !false; + let _ = !!a; + let _ = false || a; + // don't lint on cfgs + let _ = cfg!(you_shall_not_not_pass) && a; + let _ = a || !b || !c || !d || !e; + let _ = !(!a && b); + let _ = !(!a || b); + let _ = !a && !(b && c); +} + +fn equality_stuff() { + let a: i32 = unimplemented!(); + let b: i32 = unimplemented!(); + let c: i32 = unimplemented!(); + let d: i32 = unimplemented!(); + let _ = a == b && c == 5 && a == b; + let _ = a == b || c == 5 || a == b; + let _ = a == b && c == 5 && b == a; + let _ = a != b || !(a != b || c == d); + let _ = a != b && !(a != b && c == d); +} + +fn issue3847(a: u32, b: u32) -> bool { + const THRESHOLD: u32 = 1_000; + + if a < THRESHOLD && b >= THRESHOLD || a >= THRESHOLD && b < THRESHOLD { + return false; + } + true +} + +fn issue4548() { + fn f(_i: u32, _j: u32) -> u32 { + unimplemented!(); + } + + let i = 0; + let j = 0; + + if i != j && f(i, j) != 0 || i == j && f(i, j) != 1 {} +} diff --git a/src/tools/clippy/tests/ui/nonminimal_bool.stderr b/src/tools/clippy/tests/ui/nonminimal_bool.stderr new file mode 100644 index 0000000000..d34d106cb2 --- /dev/null +++ b/src/tools/clippy/tests/ui/nonminimal_bool.stderr @@ -0,0 +1,111 @@ +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:10:13 + | +LL | let _ = !true; + | ^^^^^ help: try: `false` + | + = note: `-D clippy::nonminimal-bool` implied by `-D warnings` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:11:13 + | +LL | let _ = !false; + | ^^^^^^ help: try: `true` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:12:13 + | +LL | let _ = !!a; + | ^^^ help: try: `a` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:13:13 + | +LL | let _ = false || a; + | ^^^^^^^^^^ help: try: `a` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:17:13 + | +LL | let _ = !(!a && b); + | ^^^^^^^^^^ help: try: `a || !b` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:18:13 + | +LL | let _ = !(!a || b); + | ^^^^^^^^^^ help: try: `a && !b` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:19:13 + | +LL | let _ = !a && !(b && c); + | ^^^^^^^^^^^^^^^ help: try: `!(a || b && c)` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:27:13 + | +LL | let _ = a == b && c == 5 && a == b; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | let _ = a == b && c == 5; + | ^^^^^^^^^^^^^^^^ +LL | let _ = !(a != b || c != 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:28:13 + | +LL | let _ = a == b || c == 5 || a == b; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | let _ = a == b || c == 5; + | ^^^^^^^^^^^^^^^^ +LL | let _ = !(a != b && c != 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:29:13 + | +LL | let _ = a == b && c == 5 && b == a; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | let _ = a == b && c == 5; + | ^^^^^^^^^^^^^^^^ +LL | let _ = !(a != b || c != 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:30:13 + | +LL | let _ = a != b || !(a != b || c == d); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | let _ = a != b || c != d; + | ^^^^^^^^^^^^^^^^ +LL | let _ = !(a == b && c == d); + | ^^^^^^^^^^^^^^^^^^^ + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:31:13 + | +LL | let _ = a != b && !(a != b && c == d); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | let _ = a != b && c != d; + | ^^^^^^^^^^^^^^^^ +LL | let _ = !(a == b || c == d); + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/nonminimal_bool_methods.rs b/src/tools/clippy/tests/ui/nonminimal_bool_methods.rs new file mode 100644 index 0000000000..9075874029 --- /dev/null +++ b/src/tools/clippy/tests/ui/nonminimal_bool_methods.rs @@ -0,0 +1,110 @@ +#![allow(unused, clippy::many_single_char_names, clippy::diverging_sub_expression)] +#![warn(clippy::nonminimal_bool)] + +fn methods_with_negation() { + let a: Option = unimplemented!(); + let b: Result = unimplemented!(); + let _ = a.is_some(); + let _ = !a.is_some(); + let _ = a.is_none(); + let _ = !a.is_none(); + let _ = b.is_err(); + let _ = !b.is_err(); + let _ = b.is_ok(); + let _ = !b.is_ok(); + let c = false; + let _ = !(a.is_some() && !c); + let _ = !(a.is_some() || !c); + let _ = !(!c ^ c) || !a.is_some(); + let _ = (!c ^ c) || !a.is_some(); + let _ = !c ^ c || !a.is_some(); +} + +// Simplified versions of https://github.com/rust-lang/rust-clippy/issues/2638 +// clippy::nonminimal_bool should only check the built-in Result and Some type, not +// any other types like the following. +enum CustomResultOk { + Ok, + Err(E), +} +enum CustomResultErr { + Ok, + Err(E), +} +enum CustomSomeSome { + Some(T), + None, +} +enum CustomSomeNone { + Some(T), + None, +} + +impl CustomResultOk { + pub fn is_ok(&self) -> bool { + true + } +} + +impl CustomResultErr { + pub fn is_err(&self) -> bool { + true + } +} + +impl CustomSomeSome { + pub fn is_some(&self) -> bool { + true + } +} + +impl CustomSomeNone { + pub fn is_none(&self) -> bool { + true + } +} + +fn dont_warn_for_custom_methods_with_negation() { + let res = CustomResultOk::Err("Error"); + // Should not warn and suggest 'is_err()' because the type does not + // implement is_err(). + if !res.is_ok() {} + + let res = CustomResultErr::Err("Error"); + // Should not warn and suggest 'is_ok()' because the type does not + // implement is_ok(). + if !res.is_err() {} + + let res = CustomSomeSome::Some("thing"); + // Should not warn and suggest 'is_none()' because the type does not + // implement is_none(). + if !res.is_some() {} + + let res = CustomSomeNone::Some("thing"); + // Should not warn and suggest 'is_some()' because the type does not + // implement is_some(). + if !res.is_none() {} +} + +// Only Built-in Result and Some types should suggest the negated alternative +fn warn_for_built_in_methods_with_negation() { + let res: Result = Ok(1); + if !res.is_ok() {} + if !res.is_err() {} + + let res = Some(1); + if !res.is_some() {} + if !res.is_none() {} +} + +#[allow(clippy::neg_cmp_op_on_partial_ord)] +fn dont_warn_for_negated_partial_ord_comparison() { + let a: f64 = unimplemented!(); + let b: f64 = unimplemented!(); + let _ = !(a < b); + let _ = !(a <= b); + let _ = !(a > b); + let _ = !(a >= b); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/nonminimal_bool_methods.stderr b/src/tools/clippy/tests/ui/nonminimal_bool_methods.stderr new file mode 100644 index 0000000000..a2df889d62 --- /dev/null +++ b/src/tools/clippy/tests/ui/nonminimal_bool_methods.stderr @@ -0,0 +1,82 @@ +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:8:13 + | +LL | let _ = !a.is_some(); + | ^^^^^^^^^^^^ help: try: `a.is_none()` + | + = note: `-D clippy::nonminimal-bool` implied by `-D warnings` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:10:13 + | +LL | let _ = !a.is_none(); + | ^^^^^^^^^^^^ help: try: `a.is_some()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:12:13 + | +LL | let _ = !b.is_err(); + | ^^^^^^^^^^^ help: try: `b.is_ok()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:14:13 + | +LL | let _ = !b.is_ok(); + | ^^^^^^^^^^ help: try: `b.is_err()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:16:13 + | +LL | let _ = !(a.is_some() && !c); + | ^^^^^^^^^^^^^^^^^^^^ help: try: `a.is_none() || c` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:17:13 + | +LL | let _ = !(a.is_some() || !c); + | ^^^^^^^^^^^^^^^^^^^^ help: try: `a.is_none() && c` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:18:26 + | +LL | let _ = !(!c ^ c) || !a.is_some(); + | ^^^^^^^^^^^^ help: try: `a.is_none()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:19:25 + | +LL | let _ = (!c ^ c) || !a.is_some(); + | ^^^^^^^^^^^^ help: try: `a.is_none()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:20:23 + | +LL | let _ = !c ^ c || !a.is_some(); + | ^^^^^^^^^^^^ help: try: `a.is_none()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:92:8 + | +LL | if !res.is_ok() {} + | ^^^^^^^^^^^^ help: try: `res.is_err()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:93:8 + | +LL | if !res.is_err() {} + | ^^^^^^^^^^^^^ help: try: `res.is_ok()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:96:8 + | +LL | if !res.is_some() {} + | ^^^^^^^^^^^^^^ help: try: `res.is_none()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:97:8 + | +LL | if !res.is_none() {} + | ^^^^^^^^^^^^^^ help: try: `res.is_some()` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/ok_expect.rs b/src/tools/clippy/tests/ui/ok_expect.rs new file mode 100644 index 0000000000..ff68d38c73 --- /dev/null +++ b/src/tools/clippy/tests/ui/ok_expect.rs @@ -0,0 +1,27 @@ +use std::io; + +struct MyError(()); // doesn't implement Debug + +#[derive(Debug)] +struct MyErrorWithParam { + x: T, +} + +fn main() { + let res: Result = Ok(0); + let _ = res.unwrap(); + + res.ok().expect("disaster!"); + // the following should not warn, since `expect` isn't implemented unless + // the error type implements `Debug` + let res2: Result = Ok(0); + res2.ok().expect("oh noes!"); + let res3: Result> = Ok(0); + res3.ok().expect("whoof"); + let res4: Result = Ok(0); + res4.ok().expect("argh"); + let res5: io::Result = Ok(0); + res5.ok().expect("oops"); + let res6: Result = Ok(0); + res6.ok().expect("meh"); +} diff --git a/src/tools/clippy/tests/ui/ok_expect.stderr b/src/tools/clippy/tests/ui/ok_expect.stderr new file mode 100644 index 0000000000..b02b28e7f6 --- /dev/null +++ b/src/tools/clippy/tests/ui/ok_expect.stderr @@ -0,0 +1,43 @@ +error: called `ok().expect()` on a `Result` value + --> $DIR/ok_expect.rs:14:5 + | +LL | res.ok().expect("disaster!"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::ok-expect` implied by `-D warnings` + = help: you can call `expect()` directly on the `Result` + +error: called `ok().expect()` on a `Result` value + --> $DIR/ok_expect.rs:20:5 + | +LL | res3.ok().expect("whoof"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: you can call `expect()` directly on the `Result` + +error: called `ok().expect()` on a `Result` value + --> $DIR/ok_expect.rs:22:5 + | +LL | res4.ok().expect("argh"); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: you can call `expect()` directly on the `Result` + +error: called `ok().expect()` on a `Result` value + --> $DIR/ok_expect.rs:24:5 + | +LL | res5.ok().expect("oops"); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: you can call `expect()` directly on the `Result` + +error: called `ok().expect()` on a `Result` value + --> $DIR/ok_expect.rs:26:5 + | +LL | res6.ok().expect("meh"); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: you can call `expect()` directly on the `Result` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/op_ref.rs b/src/tools/clippy/tests/ui/op_ref.rs new file mode 100644 index 0000000000..6605c967c8 --- /dev/null +++ b/src/tools/clippy/tests/ui/op_ref.rs @@ -0,0 +1,58 @@ +#![allow(unused_variables, clippy::blacklisted_name)] +#![warn(clippy::op_ref)] +#![allow(clippy::many_single_char_names)] +use std::collections::HashSet; +use std::ops::BitAnd; + +fn main() { + let tracked_fds: HashSet = HashSet::new(); + let new_fds = HashSet::new(); + let unwanted = &tracked_fds - &new_fds; + + let foo = &5 - &6; + + let bar = String::new(); + let bar = "foo" == &bar; + + let a = "a".to_string(); + let b = "a"; + + if b < &a { + println!("OK"); + } + + struct X(i32); + impl BitAnd for X { + type Output = X; + fn bitand(self, rhs: X) -> X { + X(self.0 & rhs.0) + } + } + impl<'a> BitAnd<&'a X> for X { + type Output = X; + fn bitand(self, rhs: &'a X) -> X { + X(self.0 & rhs.0) + } + } + let x = X(1); + let y = X(2); + let z = x & &y; + + #[derive(Copy, Clone)] + struct Y(i32); + impl BitAnd for Y { + type Output = Y; + fn bitand(self, rhs: Y) -> Y { + Y(self.0 & rhs.0) + } + } + impl<'a> BitAnd<&'a Y> for Y { + type Output = Y; + fn bitand(self, rhs: &'a Y) -> Y { + Y(self.0 & rhs.0) + } + } + let x = Y(1); + let y = Y(2); + let z = x & &y; +} diff --git a/src/tools/clippy/tests/ui/op_ref.stderr b/src/tools/clippy/tests/ui/op_ref.stderr new file mode 100644 index 0000000000..a3a9adcc48 --- /dev/null +++ b/src/tools/clippy/tests/ui/op_ref.stderr @@ -0,0 +1,22 @@ +error: needlessly taken reference of both operands + --> $DIR/op_ref.rs:12:15 + | +LL | let foo = &5 - &6; + | ^^^^^^^ + | + = note: `-D clippy::op-ref` implied by `-D warnings` +help: use the values directly + | +LL | let foo = 5 - 6; + | ^ ^ + +error: taken reference of right operand + --> $DIR/op_ref.rs:57:13 + | +LL | let z = x & &y; + | ^^^^-- + | | + | help: use the right value directly: `y` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/open_options.rs b/src/tools/clippy/tests/ui/open_options.rs new file mode 100644 index 0000000000..9063fafbcd --- /dev/null +++ b/src/tools/clippy/tests/ui/open_options.rs @@ -0,0 +1,14 @@ +use std::fs::OpenOptions; + +#[allow(unused_must_use)] +#[warn(clippy::nonsensical_open_options)] +fn main() { + OpenOptions::new().read(true).truncate(true).open("foo.txt"); + OpenOptions::new().append(true).truncate(true).open("foo.txt"); + + OpenOptions::new().read(true).read(false).open("foo.txt"); + OpenOptions::new().create(true).create(false).open("foo.txt"); + OpenOptions::new().write(true).write(false).open("foo.txt"); + OpenOptions::new().append(true).append(false).open("foo.txt"); + OpenOptions::new().truncate(true).truncate(false).open("foo.txt"); +} diff --git a/src/tools/clippy/tests/ui/open_options.stderr b/src/tools/clippy/tests/ui/open_options.stderr new file mode 100644 index 0000000000..26fe9f6fb2 --- /dev/null +++ b/src/tools/clippy/tests/ui/open_options.stderr @@ -0,0 +1,46 @@ +error: file opened with `truncate` and `read` + --> $DIR/open_options.rs:6:5 + | +LL | OpenOptions::new().read(true).truncate(true).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::nonsensical-open-options` implied by `-D warnings` + +error: file opened with `append` and `truncate` + --> $DIR/open_options.rs:7:5 + | +LL | OpenOptions::new().append(true).truncate(true).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the method `read` is called more than once + --> $DIR/open_options.rs:9:5 + | +LL | OpenOptions::new().read(true).read(false).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the method `create` is called more than once + --> $DIR/open_options.rs:10:5 + | +LL | OpenOptions::new().create(true).create(false).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the method `write` is called more than once + --> $DIR/open_options.rs:11:5 + | +LL | OpenOptions::new().write(true).write(false).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the method `append` is called more than once + --> $DIR/open_options.rs:12:5 + | +LL | OpenOptions::new().append(true).append(false).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the method `truncate` is called more than once + --> $DIR/open_options.rs:13:5 + | +LL | OpenOptions::new().truncate(true).truncate(false).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/option_as_ref_deref.fixed b/src/tools/clippy/tests/ui/option_as_ref_deref.fixed new file mode 100644 index 0000000000..07d7f0b45b --- /dev/null +++ b/src/tools/clippy/tests/ui/option_as_ref_deref.fixed @@ -0,0 +1,44 @@ +// run-rustfix + +#![allow(unused_imports, clippy::redundant_clone)] +#![warn(clippy::option_as_ref_deref)] + +use std::ffi::{CString, OsString}; +use std::ops::{Deref, DerefMut}; +use std::path::PathBuf; + +fn main() { + let mut opt = Some(String::from("123")); + + let _ = opt.clone().as_deref().map(str::len); + + #[rustfmt::skip] + let _ = opt.clone().as_deref() + .map(str::len); + + let _ = opt.as_deref_mut(); + + let _ = opt.as_deref(); + let _ = opt.as_deref(); + let _ = opt.as_deref_mut(); + let _ = opt.as_deref_mut(); + let _ = Some(CString::new(vec![]).unwrap()).as_deref(); + let _ = Some(OsString::new()).as_deref(); + let _ = Some(PathBuf::new()).as_deref(); + let _ = Some(Vec::<()>::new()).as_deref(); + let _ = Some(Vec::<()>::new()).as_deref_mut(); + + let _ = opt.as_deref(); + let _ = opt.clone().as_deref_mut().map(|x| x.len()); + + let vc = vec![String::new()]; + let _ = Some(1_usize).as_ref().map(|x| vc[*x].as_str()); // should not be linted + + let _: Option<&str> = Some(&String::new()).as_ref().map(|x| x.as_str()); // should not be linted + + let _ = opt.as_deref(); + let _ = opt.as_deref_mut(); + + // Issue #5927 + let _ = opt.as_deref(); +} diff --git a/src/tools/clippy/tests/ui/option_as_ref_deref.rs b/src/tools/clippy/tests/ui/option_as_ref_deref.rs new file mode 100644 index 0000000000..6ae059c942 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_as_ref_deref.rs @@ -0,0 +1,47 @@ +// run-rustfix + +#![allow(unused_imports, clippy::redundant_clone)] +#![warn(clippy::option_as_ref_deref)] + +use std::ffi::{CString, OsString}; +use std::ops::{Deref, DerefMut}; +use std::path::PathBuf; + +fn main() { + let mut opt = Some(String::from("123")); + + let _ = opt.clone().as_ref().map(Deref::deref).map(str::len); + + #[rustfmt::skip] + let _ = opt.clone() + .as_ref().map( + Deref::deref + ) + .map(str::len); + + let _ = opt.as_mut().map(DerefMut::deref_mut); + + let _ = opt.as_ref().map(String::as_str); + let _ = opt.as_ref().map(|x| x.as_str()); + let _ = opt.as_mut().map(String::as_mut_str); + let _ = opt.as_mut().map(|x| x.as_mut_str()); + let _ = Some(CString::new(vec![]).unwrap()).as_ref().map(CString::as_c_str); + let _ = Some(OsString::new()).as_ref().map(OsString::as_os_str); + let _ = Some(PathBuf::new()).as_ref().map(PathBuf::as_path); + let _ = Some(Vec::<()>::new()).as_ref().map(Vec::as_slice); + let _ = Some(Vec::<()>::new()).as_mut().map(Vec::as_mut_slice); + + let _ = opt.as_ref().map(|x| x.deref()); + let _ = opt.clone().as_mut().map(|x| x.deref_mut()).map(|x| x.len()); + + let vc = vec![String::new()]; + let _ = Some(1_usize).as_ref().map(|x| vc[*x].as_str()); // should not be linted + + let _: Option<&str> = Some(&String::new()).as_ref().map(|x| x.as_str()); // should not be linted + + let _ = opt.as_ref().map(|x| &**x); + let _ = opt.as_mut().map(|x| &mut **x); + + // Issue #5927 + let _ = opt.as_ref().map(std::ops::Deref::deref); +} diff --git a/src/tools/clippy/tests/ui/option_as_ref_deref.stderr b/src/tools/clippy/tests/ui/option_as_ref_deref.stderr new file mode 100644 index 0000000000..62f2823247 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_as_ref_deref.stderr @@ -0,0 +1,110 @@ +error: called `.as_ref().map(Deref::deref)` on an Option value. This can be done more directly by calling `opt.clone().as_deref()` instead + --> $DIR/option_as_ref_deref.rs:13:13 + | +LL | let _ = opt.clone().as_ref().map(Deref::deref).map(str::len); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.clone().as_deref()` + | + = note: `-D clippy::option-as-ref-deref` implied by `-D warnings` + +error: called `.as_ref().map(Deref::deref)` on an Option value. This can be done more directly by calling `opt.clone().as_deref()` instead + --> $DIR/option_as_ref_deref.rs:16:13 + | +LL | let _ = opt.clone() + | _____________^ +LL | | .as_ref().map( +LL | | Deref::deref +LL | | ) + | |_________^ help: try using as_deref instead: `opt.clone().as_deref()` + +error: called `.as_mut().map(DerefMut::deref_mut)` on an Option value. This can be done more directly by calling `opt.as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:22:13 + | +LL | let _ = opt.as_mut().map(DerefMut::deref_mut); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.as_deref_mut()` + +error: called `.as_ref().map(String::as_str)` on an Option value. This can be done more directly by calling `opt.as_deref()` instead + --> $DIR/option_as_ref_deref.rs:24:13 + | +LL | let _ = opt.as_ref().map(String::as_str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.as_deref()` + +error: called `.as_ref().map(|x| x.as_str())` on an Option value. This can be done more directly by calling `opt.as_deref()` instead + --> $DIR/option_as_ref_deref.rs:25:13 + | +LL | let _ = opt.as_ref().map(|x| x.as_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.as_deref()` + +error: called `.as_mut().map(String::as_mut_str)` on an Option value. This can be done more directly by calling `opt.as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:26:13 + | +LL | let _ = opt.as_mut().map(String::as_mut_str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.as_deref_mut()` + +error: called `.as_mut().map(|x| x.as_mut_str())` on an Option value. This can be done more directly by calling `opt.as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:27:13 + | +LL | let _ = opt.as_mut().map(|x| x.as_mut_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.as_deref_mut()` + +error: called `.as_ref().map(CString::as_c_str)` on an Option value. This can be done more directly by calling `Some(CString::new(vec![]).unwrap()).as_deref()` instead + --> $DIR/option_as_ref_deref.rs:28:13 + | +LL | let _ = Some(CString::new(vec![]).unwrap()).as_ref().map(CString::as_c_str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `Some(CString::new(vec![]).unwrap()).as_deref()` + +error: called `.as_ref().map(OsString::as_os_str)` on an Option value. This can be done more directly by calling `Some(OsString::new()).as_deref()` instead + --> $DIR/option_as_ref_deref.rs:29:13 + | +LL | let _ = Some(OsString::new()).as_ref().map(OsString::as_os_str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `Some(OsString::new()).as_deref()` + +error: called `.as_ref().map(PathBuf::as_path)` on an Option value. This can be done more directly by calling `Some(PathBuf::new()).as_deref()` instead + --> $DIR/option_as_ref_deref.rs:30:13 + | +LL | let _ = Some(PathBuf::new()).as_ref().map(PathBuf::as_path); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `Some(PathBuf::new()).as_deref()` + +error: called `.as_ref().map(Vec::as_slice)` on an Option value. This can be done more directly by calling `Some(Vec::<()>::new()).as_deref()` instead + --> $DIR/option_as_ref_deref.rs:31:13 + | +LL | let _ = Some(Vec::<()>::new()).as_ref().map(Vec::as_slice); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `Some(Vec::<()>::new()).as_deref()` + +error: called `.as_mut().map(Vec::as_mut_slice)` on an Option value. This can be done more directly by calling `Some(Vec::<()>::new()).as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:32:13 + | +LL | let _ = Some(Vec::<()>::new()).as_mut().map(Vec::as_mut_slice); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `Some(Vec::<()>::new()).as_deref_mut()` + +error: called `.as_ref().map(|x| x.deref())` on an Option value. This can be done more directly by calling `opt.as_deref()` instead + --> $DIR/option_as_ref_deref.rs:34:13 + | +LL | let _ = opt.as_ref().map(|x| x.deref()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.as_deref()` + +error: called `.as_mut().map(|x| x.deref_mut())` on an Option value. This can be done more directly by calling `opt.clone().as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:35:13 + | +LL | let _ = opt.clone().as_mut().map(|x| x.deref_mut()).map(|x| x.len()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.clone().as_deref_mut()` + +error: called `.as_ref().map(|x| &**x)` on an Option value. This can be done more directly by calling `opt.as_deref()` instead + --> $DIR/option_as_ref_deref.rs:42:13 + | +LL | let _ = opt.as_ref().map(|x| &**x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.as_deref()` + +error: called `.as_mut().map(|x| &mut **x)` on an Option value. This can be done more directly by calling `opt.as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:43:13 + | +LL | let _ = opt.as_mut().map(|x| &mut **x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.as_deref_mut()` + +error: called `.as_ref().map(std::ops::Deref::deref)` on an Option value. This can be done more directly by calling `opt.as_deref()` instead + --> $DIR/option_as_ref_deref.rs:46:13 + | +LL | let _ = opt.as_ref().map(std::ops::Deref::deref); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.as_deref()` + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/option_env_unwrap.rs b/src/tools/clippy/tests/ui/option_env_unwrap.rs new file mode 100644 index 0000000000..642c77460a --- /dev/null +++ b/src/tools/clippy/tests/ui/option_env_unwrap.rs @@ -0,0 +1,23 @@ +// aux-build:macro_rules.rs +#![warn(clippy::option_env_unwrap)] + +#[macro_use] +extern crate macro_rules; + +macro_rules! option_env_unwrap { + ($env: expr) => { + option_env!($env).unwrap() + }; + ($env: expr, $message: expr) => { + option_env!($env).expect($message) + }; +} + +fn main() { + let _ = option_env!("PATH").unwrap(); + let _ = option_env!("PATH").expect("environment variable PATH isn't set"); + let _ = option_env_unwrap!("PATH"); + let _ = option_env_unwrap!("PATH", "environment variable PATH isn't set"); + let _ = option_env_unwrap_external!("PATH"); + let _ = option_env_unwrap_external!("PATH", "environment variable PATH isn't set"); +} diff --git a/src/tools/clippy/tests/ui/option_env_unwrap.stderr b/src/tools/clippy/tests/ui/option_env_unwrap.stderr new file mode 100644 index 0000000000..8de9c8a9d2 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_env_unwrap.stderr @@ -0,0 +1,61 @@ +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:17:13 + | +LL | let _ = option_env!("PATH").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::option-env-unwrap` implied by `-D warnings` + = help: consider using the `env!` macro instead + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:18:13 + | +LL | let _ = option_env!("PATH").expect("environment variable PATH isn't set"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the `env!` macro instead + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:9:9 + | +LL | option_env!($env).unwrap() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | let _ = option_env_unwrap!("PATH"); + | -------------------------- in this macro invocation + | + = help: consider using the `env!` macro instead + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:12:9 + | +LL | option_env!($env).expect($message) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | let _ = option_env_unwrap!("PATH", "environment variable PATH isn't set"); + | ----------------------------------------------------------------- in this macro invocation + | + = help: consider using the `env!` macro instead + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:21:13 + | +LL | let _ = option_env_unwrap_external!("PATH"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the `env!` macro instead + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:22:13 + | +LL | let _ = option_env_unwrap_external!("PATH", "environment variable PATH isn't set"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the `env!` macro instead + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/option_if_let_else.fixed b/src/tools/clippy/tests/ui/option_if_let_else.fixed new file mode 100644 index 0000000000..47e7460fa7 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_if_let_else.fixed @@ -0,0 +1,85 @@ +// run-rustfix +#![warn(clippy::option_if_let_else)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::ref_option_ref)] + +fn bad1(string: Option<&str>) -> (bool, &str) { + string.map_or((false, "hello"), |x| (true, x)) +} + +fn else_if_option(string: Option<&str>) -> Option<(bool, &str)> { + if string.is_none() { + None + } else { string.map_or(Some((false, "")), |x| Some((true, x))) } +} + +fn unop_bad(string: &Option<&str>, mut num: Option) { + let _ = string.map_or(0, |s| s.len()); + let _ = num.as_ref().map_or(&0, |s| s); + let _ = num.as_mut().map_or(&mut 0, |s| { + *s += 1; + s + }); + let _ = num.as_ref().map_or(&0, |s| s); + let _ = num.map_or(0, |mut s| { + s += 1; + s + }); + let _ = num.as_mut().map_or(&mut 0, |s| { + *s += 1; + s + }); +} + +fn longer_body(arg: Option) -> u32 { + arg.map_or(13, |x| { + let y = x * x; + y * y + }) +} + +fn impure_else(arg: Option) { + let side_effect = || { + println!("return 1"); + 1 + }; + let _ = arg.map_or_else(|| side_effect(), |x| x); +} + +fn test_map_or_else(arg: Option) { + let _ = arg.map_or_else(|| { + let mut y = 1; + y = (y + 2 / y) / 2; + y = (y + 2 / y) / 2; + y + }, |x| x * x * x * x); +} + +fn negative_tests(arg: Option) -> u32 { + let _ = if let Some(13) = arg { "unlucky" } else { "lucky" }; + for _ in 0..10 { + let _ = if let Some(x) = arg { + x + } else { + continue; + }; + } + let _ = if let Some(x) = arg { + return x; + } else { + 5 + }; + 7 +} + +fn main() { + let optional = Some(5); + let _ = optional.map_or(5, |x| x + 2); + let _ = bad1(None); + let _ = else_if_option(None); + unop_bad(&None, None); + let _ = longer_body(None); + test_map_or_else(None); + let _ = negative_tests(None); + let _ = impure_else(None); +} diff --git a/src/tools/clippy/tests/ui/option_if_let_else.rs b/src/tools/clippy/tests/ui/option_if_let_else.rs new file mode 100644 index 0000000000..e2f8dec3b9 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_if_let_else.rs @@ -0,0 +1,108 @@ +// run-rustfix +#![warn(clippy::option_if_let_else)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::ref_option_ref)] + +fn bad1(string: Option<&str>) -> (bool, &str) { + if let Some(x) = string { + (true, x) + } else { + (false, "hello") + } +} + +fn else_if_option(string: Option<&str>) -> Option<(bool, &str)> { + if string.is_none() { + None + } else if let Some(x) = string { + Some((true, x)) + } else { + Some((false, "")) + } +} + +fn unop_bad(string: &Option<&str>, mut num: Option) { + let _ = if let Some(s) = *string { s.len() } else { 0 }; + let _ = if let Some(s) = &num { s } else { &0 }; + let _ = if let Some(s) = &mut num { + *s += 1; + s + } else { + &mut 0 + }; + let _ = if let Some(ref s) = num { s } else { &0 }; + let _ = if let Some(mut s) = num { + s += 1; + s + } else { + 0 + }; + let _ = if let Some(ref mut s) = num { + *s += 1; + s + } else { + &mut 0 + }; +} + +fn longer_body(arg: Option) -> u32 { + if let Some(x) = arg { + let y = x * x; + y * y + } else { + 13 + } +} + +fn impure_else(arg: Option) { + let side_effect = || { + println!("return 1"); + 1 + }; + let _ = if let Some(x) = arg { + x + } else { + // map_or_else must be suggested + side_effect() + }; +} + +fn test_map_or_else(arg: Option) { + let _ = if let Some(x) = arg { + x * x * x * x + } else { + let mut y = 1; + y = (y + 2 / y) / 2; + y = (y + 2 / y) / 2; + y + }; +} + +fn negative_tests(arg: Option) -> u32 { + let _ = if let Some(13) = arg { "unlucky" } else { "lucky" }; + for _ in 0..10 { + let _ = if let Some(x) = arg { + x + } else { + continue; + }; + } + let _ = if let Some(x) = arg { + return x; + } else { + 5 + }; + 7 +} + +fn main() { + let optional = Some(5); + let _ = if let Some(x) = optional { x + 2 } else { 5 }; + let _ = bad1(None); + let _ = else_if_option(None); + unop_bad(&None, None); + let _ = longer_body(None); + test_map_or_else(None); + let _ = negative_tests(None); + let _ = impure_else(None); +} diff --git a/src/tools/clippy/tests/ui/option_if_let_else.stderr b/src/tools/clippy/tests/ui/option_if_let_else.stderr new file mode 100644 index 0000000000..7aab068800 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_if_let_else.stderr @@ -0,0 +1,163 @@ +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:7:5 + | +LL | / if let Some(x) = string { +LL | | (true, x) +LL | | } else { +LL | | (false, "hello") +LL | | } + | |_____^ help: try: `string.map_or((false, "hello"), |x| (true, x))` + | + = note: `-D clippy::option-if-let-else` implied by `-D warnings` + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:17:12 + | +LL | } else if let Some(x) = string { + | ____________^ +LL | | Some((true, x)) +LL | | } else { +LL | | Some((false, "")) +LL | | } + | |_____^ help: try: `{ string.map_or(Some((false, "")), |x| Some((true, x))) }` + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:25:13 + | +LL | let _ = if let Some(s) = *string { s.len() } else { 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.map_or(0, |s| s.len())` + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:26:13 + | +LL | let _ = if let Some(s) = &num { s } else { &0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:27:13 + | +LL | let _ = if let Some(s) = &mut num { + | _____________^ +LL | | *s += 1; +LL | | s +LL | | } else { +LL | | &mut 0 +LL | | }; + | |_____^ + | +help: try + | +LL | let _ = num.as_mut().map_or(&mut 0, |s| { +LL | *s += 1; +LL | s +LL | }); + | + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:33:13 + | +LL | let _ = if let Some(ref s) = num { s } else { &0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:34:13 + | +LL | let _ = if let Some(mut s) = num { + | _____________^ +LL | | s += 1; +LL | | s +LL | | } else { +LL | | 0 +LL | | }; + | |_____^ + | +help: try + | +LL | let _ = num.map_or(0, |mut s| { +LL | s += 1; +LL | s +LL | }); + | + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:40:13 + | +LL | let _ = if let Some(ref mut s) = num { + | _____________^ +LL | | *s += 1; +LL | | s +LL | | } else { +LL | | &mut 0 +LL | | }; + | |_____^ + | +help: try + | +LL | let _ = num.as_mut().map_or(&mut 0, |s| { +LL | *s += 1; +LL | s +LL | }); + | + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:49:5 + | +LL | / if let Some(x) = arg { +LL | | let y = x * x; +LL | | y * y +LL | | } else { +LL | | 13 +LL | | } + | |_____^ + | +help: try + | +LL | arg.map_or(13, |x| { +LL | let y = x * x; +LL | y * y +LL | }) + | + +error: use Option::map_or_else instead of an if let/else + --> $DIR/option_if_let_else.rs:62:13 + | +LL | let _ = if let Some(x) = arg { + | _____________^ +LL | | x +LL | | } else { +LL | | // map_or_else must be suggested +LL | | side_effect() +LL | | }; + | |_____^ help: try: `arg.map_or_else(|| side_effect(), |x| x)` + +error: use Option::map_or_else instead of an if let/else + --> $DIR/option_if_let_else.rs:71:13 + | +LL | let _ = if let Some(x) = arg { + | _____________^ +LL | | x * x * x * x +LL | | } else { +LL | | let mut y = 1; +... | +LL | | y +LL | | }; + | |_____^ + | +help: try + | +LL | let _ = arg.map_or_else(|| { +LL | let mut y = 1; +LL | y = (y + 2 / y) / 2; +LL | y = (y + 2 / y) / 2; +LL | y +LL | }, |x| x * x * x * x); + | + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:100:13 + | +LL | let _ = if let Some(x) = optional { x + 2 } else { 5 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/option_map_or_none.fixed b/src/tools/clippy/tests/ui/option_map_or_none.fixed new file mode 100644 index 0000000000..d80c3c7c1b --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_or_none.fixed @@ -0,0 +1,16 @@ +// run-rustfix + +#![allow(clippy::bind_instead_of_map)] + +fn main() { + let opt = Some(1); + + // Check `OPTION_MAP_OR_NONE`. + // Single line case. + let _ = opt.and_then(|x| Some(x + 1)); + // Multi-line case. + #[rustfmt::skip] + let _ = opt.and_then(|x| { + Some(x + 1) + }); +} diff --git a/src/tools/clippy/tests/ui/option_map_or_none.rs b/src/tools/clippy/tests/ui/option_map_or_none.rs new file mode 100644 index 0000000000..629842419e --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_or_none.rs @@ -0,0 +1,16 @@ +// run-rustfix + +#![allow(clippy::bind_instead_of_map)] + +fn main() { + let opt = Some(1); + + // Check `OPTION_MAP_OR_NONE`. + // Single line case. + let _ = opt.map_or(None, |x| Some(x + 1)); + // Multi-line case. + #[rustfmt::skip] + let _ = opt.map_or(None, |x| { + Some(x + 1) + }); +} diff --git a/src/tools/clippy/tests/ui/option_map_or_none.stderr b/src/tools/clippy/tests/ui/option_map_or_none.stderr new file mode 100644 index 0000000000..1cba29412b --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_or_none.stderr @@ -0,0 +1,26 @@ +error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `and_then(..)` instead + --> $DIR/option_map_or_none.rs:10:13 + | +LL | let _ = opt.map_or(None, |x| Some(x + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `opt.and_then(|x| Some(x + 1))` + | + = note: `-D clippy::option-map-or-none` implied by `-D warnings` + +error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `and_then(..)` instead + --> $DIR/option_map_or_none.rs:13:13 + | +LL | let _ = opt.map_or(None, |x| { + | _____________^ +LL | | Some(x + 1) +LL | | }); + | |_________________________^ + | +help: try using `and_then` instead + | +LL | let _ = opt.and_then(|x| { +LL | Some(x + 1) +LL | }); + | + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.fixed b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.fixed new file mode 100644 index 0000000000..7d29445e66 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.fixed @@ -0,0 +1,85 @@ +// run-rustfix + +#![warn(clippy::option_map_unit_fn)] +#![allow(unused)] +#![allow(clippy::unnecessary_wraps)] + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +fn option() -> Option { + Some(10) +} + +struct HasOption { + field: Option, +} + +impl HasOption { + fn do_option_nothing(&self, value: usize) {} + + fn do_option_plus_one(&self, value: usize) -> usize { + value + 1 + } +} +#[rustfmt::skip] +fn option_map_unit_fn() { + let x = HasOption { field: Some(10) }; + + x.field.map(plus_one); + let _ : Option<()> = x.field.map(do_nothing); + + if let Some(x_field) = x.field { do_nothing(x_field) } + + if let Some(x_field) = x.field { do_nothing(x_field) } + + if let Some(x_field) = x.field { diverge(x_field) } + + let captured = 10; + if let Some(value) = x.field { do_nothing(value + captured) }; + let _ : Option<()> = x.field.map(|value| do_nothing(value + captured)); + + if let Some(value) = x.field { x.do_option_nothing(value + captured) } + + if let Some(value) = x.field { x.do_option_plus_one(value + captured); } + + + if let Some(value) = x.field { do_nothing(value + captured) } + + if let Some(value) = x.field { do_nothing(value + captured) } + + if let Some(value) = x.field { do_nothing(value + captured); } + + if let Some(value) = x.field { do_nothing(value + captured); } + + + if let Some(value) = x.field { diverge(value + captured) } + + if let Some(value) = x.field { diverge(value + captured) } + + if let Some(value) = x.field { diverge(value + captured); } + + if let Some(value) = x.field { diverge(value + captured); } + + + x.field.map(|value| plus_one(value + captured)); + x.field.map(|value| { plus_one(value + captured) }); + if let Some(value) = x.field { let y = plus_one(value + captured); } + + if let Some(value) = x.field { plus_one(value + captured); } + + if let Some(value) = x.field { plus_one(value + captured); } + + + if let Some(ref value) = x.field { do_nothing(value + captured) } + + if let Some(a) = option() { do_nothing(a) }} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.rs b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.rs new file mode 100644 index 0000000000..b6f834f686 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.rs @@ -0,0 +1,85 @@ +// run-rustfix + +#![warn(clippy::option_map_unit_fn)] +#![allow(unused)] +#![allow(clippy::unnecessary_wraps)] + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +fn option() -> Option { + Some(10) +} + +struct HasOption { + field: Option, +} + +impl HasOption { + fn do_option_nothing(&self, value: usize) {} + + fn do_option_plus_one(&self, value: usize) -> usize { + value + 1 + } +} +#[rustfmt::skip] +fn option_map_unit_fn() { + let x = HasOption { field: Some(10) }; + + x.field.map(plus_one); + let _ : Option<()> = x.field.map(do_nothing); + + x.field.map(do_nothing); + + x.field.map(do_nothing); + + x.field.map(diverge); + + let captured = 10; + if let Some(value) = x.field { do_nothing(value + captured) }; + let _ : Option<()> = x.field.map(|value| do_nothing(value + captured)); + + x.field.map(|value| x.do_option_nothing(value + captured)); + + x.field.map(|value| { x.do_option_plus_one(value + captured); }); + + + x.field.map(|value| do_nothing(value + captured)); + + x.field.map(|value| { do_nothing(value + captured) }); + + x.field.map(|value| { do_nothing(value + captured); }); + + x.field.map(|value| { { do_nothing(value + captured); } }); + + + x.field.map(|value| diverge(value + captured)); + + x.field.map(|value| { diverge(value + captured) }); + + x.field.map(|value| { diverge(value + captured); }); + + x.field.map(|value| { { diverge(value + captured); } }); + + + x.field.map(|value| plus_one(value + captured)); + x.field.map(|value| { plus_one(value + captured) }); + x.field.map(|value| { let y = plus_one(value + captured); }); + + x.field.map(|value| { plus_one(value + captured); }); + + x.field.map(|value| { { plus_one(value + captured); } }); + + + x.field.map(|ref value| { do_nothing(value + captured) }); + + option().map(do_nothing);} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.stderr b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.stderr new file mode 100644 index 0000000000..8abdbcafb6 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.stderr @@ -0,0 +1,148 @@ +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:39:5 + | +LL | x.field.map(do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(x_field) = x.field { do_nothing(x_field) }` + | + = note: `-D clippy::option-map-unit-fn` implied by `-D warnings` + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:41:5 + | +LL | x.field.map(do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(x_field) = x.field { do_nothing(x_field) }` + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:43:5 + | +LL | x.field.map(diverge); + | ^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(x_field) = x.field { diverge(x_field) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:49:5 + | +LL | x.field.map(|value| x.do_option_nothing(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { x.do_option_nothing(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:51:5 + | +LL | x.field.map(|value| { x.do_option_plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { x.do_option_plus_one(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:54:5 + | +LL | x.field.map(|value| do_nothing(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { do_nothing(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:56:5 + | +LL | x.field.map(|value| { do_nothing(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { do_nothing(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:58:5 + | +LL | x.field.map(|value| { do_nothing(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { do_nothing(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:60:5 + | +LL | x.field.map(|value| { { do_nothing(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { do_nothing(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:63:5 + | +LL | x.field.map(|value| diverge(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { diverge(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:65:5 + | +LL | x.field.map(|value| { diverge(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { diverge(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:67:5 + | +LL | x.field.map(|value| { diverge(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { diverge(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:69:5 + | +LL | x.field.map(|value| { { diverge(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { diverge(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:74:5 + | +LL | x.field.map(|value| { let y = plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { let y = plus_one(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:76:5 + | +LL | x.field.map(|value| { plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { plus_one(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:78:5 + | +LL | x.field.map(|value| { { plus_one(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { plus_one(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:81:5 + | +LL | x.field.map(|ref value| { do_nothing(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(ref value) = x.field { do_nothing(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> $DIR/option_map_unit_fn_fixable.rs:83:5 + | +LL | option().map(do_nothing);} + | ^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(a) = option() { do_nothing(a) }` + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.rs b/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.rs new file mode 100644 index 0000000000..20e6c15b18 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.rs @@ -0,0 +1,39 @@ +#![warn(clippy::option_map_unit_fn)] +#![allow(unused)] + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +#[rustfmt::skip] +fn option_map_unit_fn() { + + x.field.map(|value| { do_nothing(value); do_nothing(value) }); + + x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); + + // Suggestion for the let block should be `{ ... }` as it's too difficult to build a + // proper suggestion for these cases + x.field.map(|value| { + do_nothing(value); + do_nothing(value) + }); + x.field.map(|value| { do_nothing(value); do_nothing(value); }); + + // The following should suggest `if let Some(_X) ...` as it's difficult to generate a proper let variable name for them + Some(42).map(diverge); + "12".parse::().ok().map(diverge); + Some(plus_one(1)).map(do_nothing); + + // Should suggest `if let Some(_y) ...` to not override the existing foo variable + let y = Some(42); + y.map(do_nothing); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.stderr b/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.stderr new file mode 100644 index 0000000000..a53f5889c5 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.stderr @@ -0,0 +1,27 @@ +error[E0425]: cannot find value `x` in this scope + --> $DIR/option_map_unit_fn_unfixable.rs:17:5 + | +LL | x.field.map(|value| { do_nothing(value); do_nothing(value) }); + | ^ not found in this scope + +error[E0425]: cannot find value `x` in this scope + --> $DIR/option_map_unit_fn_unfixable.rs:19:5 + | +LL | x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); + | ^ not found in this scope + +error[E0425]: cannot find value `x` in this scope + --> $DIR/option_map_unit_fn_unfixable.rs:23:5 + | +LL | x.field.map(|value| { + | ^ not found in this scope + +error[E0425]: cannot find value `x` in this scope + --> $DIR/option_map_unit_fn_unfixable.rs:27:5 + | +LL | x.field.map(|value| { do_nothing(value); do_nothing(value); }); + | ^ not found in this scope + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/src/tools/clippy/tests/ui/option_option.rs b/src/tools/clippy/tests/ui/option_option.rs new file mode 100644 index 0000000000..6859ba8e5b --- /dev/null +++ b/src/tools/clippy/tests/ui/option_option.rs @@ -0,0 +1,86 @@ +#![deny(clippy::option_option)] +#![allow(clippy::unnecessary_wraps)] + +fn input(_: Option>) {} + +fn output() -> Option> { + None +} + +fn output_nested() -> Vec>> { + vec![None] +} + +// The lint only generates one warning for this +fn output_nested_nested() -> Option>> { + None +} + +struct Struct { + x: Option>, +} + +impl Struct { + fn struct_fn() -> Option> { + None + } +} + +trait Trait { + fn trait_fn() -> Option>; +} + +enum Enum { + Tuple(Option>), + Struct { x: Option> }, +} + +// The lint allows this +type OptionOption = Option>; + +// The lint allows this +fn output_type_alias() -> OptionOption { + None +} + +// The line allows this +impl Trait for Struct { + fn trait_fn() -> Option> { + None + } +} + +fn main() { + input(None); + output(); + output_nested(); + + // The lint allows this + let local: Option> = None; + + // The lint allows this + let expr = Some(Some(true)); +} + +extern crate serde; +mod issue_4298 { + use serde::{Deserialize, Deserializer, Serialize}; + use std::borrow::Cow; + + #[derive(Serialize, Deserialize)] + struct Foo<'a> { + #[serde(deserialize_with = "func")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(borrow)] + foo: Option>>, + } + + #[allow(clippy::option_option)] + fn func<'a, D>(_: D) -> Result>>, D::Error> + where + D: Deserializer<'a>, + { + Ok(Some(Some(Cow::Borrowed("hi")))) + } +} diff --git a/src/tools/clippy/tests/ui/option_option.stderr b/src/tools/clippy/tests/ui/option_option.stderr new file mode 100644 index 0000000000..ad7f081c71 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_option.stderr @@ -0,0 +1,68 @@ +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:4:13 + | +LL | fn input(_: Option>) {} + | ^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/option_option.rs:1:9 + | +LL | #![deny(clippy::option_option)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:6:16 + | +LL | fn output() -> Option> { + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:10:27 + | +LL | fn output_nested() -> Vec>> { + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:15:30 + | +LL | fn output_nested_nested() -> Option>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:20:8 + | +LL | x: Option>, + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:24:23 + | +LL | fn struct_fn() -> Option> { + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:30:22 + | +LL | fn trait_fn() -> Option>; + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:34:11 + | +LL | Tuple(Option>), + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:35:17 + | +LL | Struct { x: Option> }, + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:76:14 + | +LL | foo: Option>>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/or_fun_call.fixed b/src/tools/clippy/tests/ui/or_fun_call.fixed new file mode 100644 index 0000000000..64347cae5d --- /dev/null +++ b/src/tools/clippy/tests/ui/or_fun_call.fixed @@ -0,0 +1,135 @@ +// run-rustfix + +#![warn(clippy::or_fun_call)] +#![allow(dead_code)] +#![allow(clippy::unnecessary_wraps)] + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::time::Duration; + +/// Checks implementation of the `OR_FUN_CALL` lint. +fn or_fun_call() { + struct Foo; + + impl Foo { + fn new() -> Foo { + Foo + } + } + + enum Enum { + A(i32), + } + + fn make() -> T { + unimplemented!(); + } + + let with_enum = Some(Enum::A(1)); + with_enum.unwrap_or(Enum::A(5)); + + let with_const_fn = Some(Duration::from_secs(1)); + with_const_fn.unwrap_or_else(|| Duration::from_secs(5)); + + let with_constructor = Some(vec![1]); + with_constructor.unwrap_or_else(make); + + let with_new = Some(vec![1]); + with_new.unwrap_or_default(); + + let with_const_args = Some(vec![1]); + with_const_args.unwrap_or_else(|| Vec::with_capacity(12)); + + let with_err: Result<_, ()> = Ok(vec![1]); + with_err.unwrap_or_else(|_| make()); + + let with_err_args: Result<_, ()> = Ok(vec![1]); + with_err_args.unwrap_or_else(|_| Vec::with_capacity(12)); + + let with_default_trait = Some(1); + with_default_trait.unwrap_or_default(); + + let with_default_type = Some(1); + with_default_type.unwrap_or_default(); + + let with_vec = Some(vec![1]); + with_vec.unwrap_or_default(); + + let without_default = Some(Foo); + without_default.unwrap_or_else(Foo::new); + + let mut map = HashMap::::new(); + map.entry(42).or_insert_with(String::new); + + let mut map_vec = HashMap::>::new(); + map_vec.entry(42).or_insert_with(Vec::new); + + let mut btree = BTreeMap::::new(); + btree.entry(42).or_insert_with(String::new); + + let mut btree_vec = BTreeMap::>::new(); + btree_vec.entry(42).or_insert_with(Vec::new); + + let stringy = Some(String::from("")); + let _ = stringy.unwrap_or_else(|| "".to_owned()); + + let opt = Some(1); + let hello = "Hello"; + let _ = opt.ok_or(format!("{} world.", hello)); + + // index + let map = HashMap::::new(); + let _ = Some(1).unwrap_or_else(|| map[&1]); + let map = BTreeMap::::new(); + let _ = Some(1).unwrap_or_else(|| map[&1]); + // don't lint index vec + let vec = vec![1]; + let _ = Some(1).unwrap_or(vec[1]); +} + +struct Foo(u8); +struct Bar(String, Duration); +#[rustfmt::skip] +fn test_or_with_ctors() { + let opt = Some(1); + let opt_opt = Some(Some(1)); + // we also test for const promotion, this makes sure we don't hit that + let two = 2; + + let _ = opt_opt.unwrap_or(Some(2)); + let _ = opt_opt.unwrap_or(Some(two)); + let _ = opt.ok_or(Some(2)); + let _ = opt.ok_or(Some(two)); + let _ = opt.ok_or(Foo(2)); + let _ = opt.ok_or(Foo(two)); + let _ = opt.or(Some(2)); + let _ = opt.or(Some(two)); + + let _ = Some("a".to_string()).or_else(|| Some("b".to_string())); + + let b = "b".to_string(); + let _ = Some(Bar("a".to_string(), Duration::from_secs(1))) + .or_else(|| Some(Bar(b, Duration::from_secs(2)))); + + let vec = vec!["foo"]; + let _ = opt.ok_or(vec.len()); + + let array = ["foo"]; + let _ = opt.ok_or(array.len()); + + let slice = &["foo"][..]; + let _ = opt.ok_or(slice.len()); +} + +// Issue 4514 - early return +fn f() -> Option<()> { + let a = Some(1); + let b = 1i32; + + let _ = a.unwrap_or(b.checked_mul(3)?.min(240)); + + Some(()) +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/or_fun_call.rs b/src/tools/clippy/tests/ui/or_fun_call.rs new file mode 100644 index 0000000000..7faab0017b --- /dev/null +++ b/src/tools/clippy/tests/ui/or_fun_call.rs @@ -0,0 +1,135 @@ +// run-rustfix + +#![warn(clippy::or_fun_call)] +#![allow(dead_code)] +#![allow(clippy::unnecessary_wraps)] + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::time::Duration; + +/// Checks implementation of the `OR_FUN_CALL` lint. +fn or_fun_call() { + struct Foo; + + impl Foo { + fn new() -> Foo { + Foo + } + } + + enum Enum { + A(i32), + } + + fn make() -> T { + unimplemented!(); + } + + let with_enum = Some(Enum::A(1)); + with_enum.unwrap_or(Enum::A(5)); + + let with_const_fn = Some(Duration::from_secs(1)); + with_const_fn.unwrap_or(Duration::from_secs(5)); + + let with_constructor = Some(vec![1]); + with_constructor.unwrap_or(make()); + + let with_new = Some(vec![1]); + with_new.unwrap_or(Vec::new()); + + let with_const_args = Some(vec![1]); + with_const_args.unwrap_or(Vec::with_capacity(12)); + + let with_err: Result<_, ()> = Ok(vec![1]); + with_err.unwrap_or(make()); + + let with_err_args: Result<_, ()> = Ok(vec![1]); + with_err_args.unwrap_or(Vec::with_capacity(12)); + + let with_default_trait = Some(1); + with_default_trait.unwrap_or(Default::default()); + + let with_default_type = Some(1); + with_default_type.unwrap_or(u64::default()); + + let with_vec = Some(vec![1]); + with_vec.unwrap_or(vec![]); + + let without_default = Some(Foo); + without_default.unwrap_or(Foo::new()); + + let mut map = HashMap::::new(); + map.entry(42).or_insert(String::new()); + + let mut map_vec = HashMap::>::new(); + map_vec.entry(42).or_insert(vec![]); + + let mut btree = BTreeMap::::new(); + btree.entry(42).or_insert(String::new()); + + let mut btree_vec = BTreeMap::>::new(); + btree_vec.entry(42).or_insert(vec![]); + + let stringy = Some(String::from("")); + let _ = stringy.unwrap_or("".to_owned()); + + let opt = Some(1); + let hello = "Hello"; + let _ = opt.ok_or(format!("{} world.", hello)); + + // index + let map = HashMap::::new(); + let _ = Some(1).unwrap_or(map[&1]); + let map = BTreeMap::::new(); + let _ = Some(1).unwrap_or(map[&1]); + // don't lint index vec + let vec = vec![1]; + let _ = Some(1).unwrap_or(vec[1]); +} + +struct Foo(u8); +struct Bar(String, Duration); +#[rustfmt::skip] +fn test_or_with_ctors() { + let opt = Some(1); + let opt_opt = Some(Some(1)); + // we also test for const promotion, this makes sure we don't hit that + let two = 2; + + let _ = opt_opt.unwrap_or(Some(2)); + let _ = opt_opt.unwrap_or(Some(two)); + let _ = opt.ok_or(Some(2)); + let _ = opt.ok_or(Some(two)); + let _ = opt.ok_or(Foo(2)); + let _ = opt.ok_or(Foo(two)); + let _ = opt.or(Some(2)); + let _ = opt.or(Some(two)); + + let _ = Some("a".to_string()).or(Some("b".to_string())); + + let b = "b".to_string(); + let _ = Some(Bar("a".to_string(), Duration::from_secs(1))) + .or(Some(Bar(b, Duration::from_secs(2)))); + + let vec = vec!["foo"]; + let _ = opt.ok_or(vec.len()); + + let array = ["foo"]; + let _ = opt.ok_or(array.len()); + + let slice = &["foo"][..]; + let _ = opt.ok_or(slice.len()); +} + +// Issue 4514 - early return +fn f() -> Option<()> { + let a = Some(1); + let b = 1i32; + + let _ = a.unwrap_or(b.checked_mul(3)?.min(240)); + + Some(()) +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/or_fun_call.stderr b/src/tools/clippy/tests/ui/or_fun_call.stderr new file mode 100644 index 0000000000..1e2bfd490e --- /dev/null +++ b/src/tools/clippy/tests/ui/or_fun_call.stderr @@ -0,0 +1,118 @@ +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:33:19 + | +LL | with_const_fn.unwrap_or(Duration::from_secs(5)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| Duration::from_secs(5))` + | + = note: `-D clippy::or-fun-call` implied by `-D warnings` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:36:22 + | +LL | with_constructor.unwrap_or(make()); + | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(make)` + +error: use of `unwrap_or` followed by a call to `new` + --> $DIR/or_fun_call.rs:39:5 + | +LL | with_new.unwrap_or(Vec::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_new.unwrap_or_default()` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:42:21 + | +LL | with_const_args.unwrap_or(Vec::with_capacity(12)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| Vec::with_capacity(12))` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:45:14 + | +LL | with_err.unwrap_or(make()); + | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| make())` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:48:19 + | +LL | with_err_args.unwrap_or(Vec::with_capacity(12)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| Vec::with_capacity(12))` + +error: use of `unwrap_or` followed by a call to `default` + --> $DIR/or_fun_call.rs:51:5 + | +LL | with_default_trait.unwrap_or(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_default_trait.unwrap_or_default()` + +error: use of `unwrap_or` followed by a call to `default` + --> $DIR/or_fun_call.rs:54:5 + | +LL | with_default_type.unwrap_or(u64::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_default_type.unwrap_or_default()` + +error: use of `unwrap_or` followed by a call to `new` + --> $DIR/or_fun_call.rs:57:5 + | +LL | with_vec.unwrap_or(vec![]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_vec.unwrap_or_default()` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:60:21 + | +LL | without_default.unwrap_or(Foo::new()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(Foo::new)` + +error: use of `or_insert` followed by a function call + --> $DIR/or_fun_call.rs:63:19 + | +LL | map.entry(42).or_insert(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(String::new)` + +error: use of `or_insert` followed by a function call + --> $DIR/or_fun_call.rs:66:23 + | +LL | map_vec.entry(42).or_insert(vec![]); + | ^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(Vec::new)` + +error: use of `or_insert` followed by a function call + --> $DIR/or_fun_call.rs:69:21 + | +LL | btree.entry(42).or_insert(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(String::new)` + +error: use of `or_insert` followed by a function call + --> $DIR/or_fun_call.rs:72:25 + | +LL | btree_vec.entry(42).or_insert(vec![]); + | ^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(Vec::new)` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:75:21 + | +LL | let _ = stringy.unwrap_or("".to_owned()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "".to_owned())` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:83:21 + | +LL | let _ = Some(1).unwrap_or(map[&1]); + | ^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| map[&1])` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:85:21 + | +LL | let _ = Some(1).unwrap_or(map[&1]); + | ^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| map[&1])` + +error: use of `or` followed by a function call + --> $DIR/or_fun_call.rs:109:35 + | +LL | let _ = Some("a".to_string()).or(Some("b".to_string())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_else(|| Some("b".to_string()))` + +error: use of `or` followed by a function call + --> $DIR/or_fun_call.rs:113:10 + | +LL | .or(Some(Bar(b, Duration::from_secs(2)))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_else(|| Some(Bar(b, Duration::from_secs(2))))` + +error: aborting due to 19 previous errors + diff --git a/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.rs b/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.rs new file mode 100644 index 0000000000..f20a0ede11 --- /dev/null +++ b/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.rs @@ -0,0 +1,11 @@ +#![warn(clippy::out_of_bounds_indexing)] +#![allow(clippy::no_effect, const_err)] + +fn main() { + let x = [1, 2, 3, 4]; + + // issue 3102 + let num = 1; + &x[num..10]; // should trigger out of bounds error + &x[10..num]; // should trigger out of bounds error +} diff --git a/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.stderr b/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.stderr new file mode 100644 index 0000000000..516c1df40b --- /dev/null +++ b/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.stderr @@ -0,0 +1,16 @@ +error: range is out of bounds + --> $DIR/issue-3102.rs:9:13 + | +LL | &x[num..10]; // should trigger out of bounds error + | ^^ + | + = note: `-D clippy::out-of-bounds-indexing` implied by `-D warnings` + +error: range is out of bounds + --> $DIR/issue-3102.rs:10:8 + | +LL | &x[10..num]; // should trigger out of bounds error + | ^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.rs b/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.rs new file mode 100644 index 0000000000..590e578d75 --- /dev/null +++ b/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.rs @@ -0,0 +1,22 @@ +#![warn(clippy::out_of_bounds_indexing)] +#![allow(clippy::no_effect, clippy::unnecessary_operation, const_err)] + +fn main() { + let x = [1, 2, 3, 4]; + + &x[..=4]; + &x[1..5]; + &x[5..]; + &x[..5]; + &x[5..].iter().map(|x| 2 * x).collect::>(); + &x[0..=4]; + + &x[4..]; // Ok, should not produce stderr. + &x[..4]; // Ok, should not produce stderr. + &x[..]; // Ok, should not produce stderr. + &x[1..]; // Ok, should not produce stderr. + &x[2..].iter().map(|x| 2 * x).collect::>(); // Ok, should not produce stderr. + + &x[0..].get(..3); // Ok, should not produce stderr. + &x[0..3]; // Ok, should not produce stderr. +} diff --git a/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.stderr b/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.stderr new file mode 100644 index 0000000000..3d95afcdab --- /dev/null +++ b/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.stderr @@ -0,0 +1,40 @@ +error: range is out of bounds + --> $DIR/simple.rs:7:11 + | +LL | &x[..=4]; + | ^ + | + = note: `-D clippy::out-of-bounds-indexing` implied by `-D warnings` + +error: range is out of bounds + --> $DIR/simple.rs:8:11 + | +LL | &x[1..5]; + | ^ + +error: range is out of bounds + --> $DIR/simple.rs:9:8 + | +LL | &x[5..]; + | ^ + +error: range is out of bounds + --> $DIR/simple.rs:10:10 + | +LL | &x[..5]; + | ^ + +error: range is out of bounds + --> $DIR/simple.rs:11:8 + | +LL | &x[5..].iter().map(|x| 2 * x).collect::>(); + | ^ + +error: range is out of bounds + --> $DIR/simple.rs:12:12 + | +LL | &x[0..=4]; + | ^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/overflow_check_conditional.rs b/src/tools/clippy/tests/ui/overflow_check_conditional.rs new file mode 100644 index 0000000000..84332040db --- /dev/null +++ b/src/tools/clippy/tests/ui/overflow_check_conditional.rs @@ -0,0 +1,26 @@ +#![allow(clippy::many_single_char_names)] +#![warn(clippy::overflow_check_conditional)] + +fn main() { + let a: u32 = 1; + let b: u32 = 2; + let c: u32 = 3; + if a + b < a {} + if a > a + b {} + if a + b < b {} + if b > a + b {} + if a - b > b {} + if b < a - b {} + if a - b > a {} + if a < a - b {} + if a + b < c {} + if c > a + b {} + if a - b < c {} + if c > a - b {} + let i = 1.1; + let j = 2.2; + if i + j < i {} + if i - j < i {} + if i > i + j {} + if i - j < i {} +} diff --git a/src/tools/clippy/tests/ui/overflow_check_conditional.stderr b/src/tools/clippy/tests/ui/overflow_check_conditional.stderr new file mode 100644 index 0000000000..19e843c2c0 --- /dev/null +++ b/src/tools/clippy/tests/ui/overflow_check_conditional.stderr @@ -0,0 +1,52 @@ +error: you are trying to use classic C overflow conditions that will fail in Rust + --> $DIR/overflow_check_conditional.rs:8:8 + | +LL | if a + b < a {} + | ^^^^^^^^^ + | + = note: `-D clippy::overflow-check-conditional` implied by `-D warnings` + +error: you are trying to use classic C overflow conditions that will fail in Rust + --> $DIR/overflow_check_conditional.rs:9:8 + | +LL | if a > a + b {} + | ^^^^^^^^^ + +error: you are trying to use classic C overflow conditions that will fail in Rust + --> $DIR/overflow_check_conditional.rs:10:8 + | +LL | if a + b < b {} + | ^^^^^^^^^ + +error: you are trying to use classic C overflow conditions that will fail in Rust + --> $DIR/overflow_check_conditional.rs:11:8 + | +LL | if b > a + b {} + | ^^^^^^^^^ + +error: you are trying to use classic C underflow conditions that will fail in Rust + --> $DIR/overflow_check_conditional.rs:12:8 + | +LL | if a - b > b {} + | ^^^^^^^^^ + +error: you are trying to use classic C underflow conditions that will fail in Rust + --> $DIR/overflow_check_conditional.rs:13:8 + | +LL | if b < a - b {} + | ^^^^^^^^^ + +error: you are trying to use classic C underflow conditions that will fail in Rust + --> $DIR/overflow_check_conditional.rs:14:8 + | +LL | if a - b > a {} + | ^^^^^^^^^ + +error: you are trying to use classic C underflow conditions that will fail in Rust + --> $DIR/overflow_check_conditional.rs:15:8 + | +LL | if a < a - b {} + | ^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/panic_in_result_fn.rs b/src/tools/clippy/tests/ui/panic_in_result_fn.rs new file mode 100644 index 0000000000..3d3c19a1be --- /dev/null +++ b/src/tools/clippy/tests/ui/panic_in_result_fn.rs @@ -0,0 +1,71 @@ +#![warn(clippy::panic_in_result_fn)] +#![allow(clippy::unnecessary_wraps)] + +struct A; + +impl A { + fn result_with_panic() -> Result // should emit lint + { + panic!("error"); + } + + fn result_with_unimplemented() -> Result // should emit lint + { + unimplemented!(); + } + + fn result_with_unreachable() -> Result // should emit lint + { + unreachable!(); + } + + fn result_with_todo() -> Result // should emit lint + { + todo!("Finish this"); + } + + fn other_with_panic() // should not emit lint + { + panic!(""); + } + + fn other_with_unreachable() // should not emit lint + { + unreachable!(); + } + + fn other_with_unimplemented() // should not emit lint + { + unimplemented!(); + } + + fn other_with_todo() // should not emit lint + { + todo!("finish this") + } + + fn result_without_banned_functions() -> Result // should not emit lint + { + Ok(true) + } +} + +fn function_result_with_panic() -> Result // should emit lint +{ + panic!("error"); +} + +fn todo() { + println!("something"); +} + +fn function_result_with_custom_todo() -> Result // should not emit lint +{ + todo(); + Ok(true) +} + +fn main() -> Result<(), String> { + todo!("finish main method"); + Ok(()) +} diff --git a/src/tools/clippy/tests/ui/panic_in_result_fn.stderr b/src/tools/clippy/tests/ui/panic_in_result_fn.stderr new file mode 100644 index 0000000000..eb744b0c19 --- /dev/null +++ b/src/tools/clippy/tests/ui/panic_in_result_fn.stderr @@ -0,0 +1,105 @@ +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn.rs:7:5 + | +LL | / fn result_with_panic() -> Result // should emit lint +LL | | { +LL | | panic!("error"); +LL | | } + | |_____^ + | + = note: `-D clippy::panic-in-result-fn` implied by `-D warnings` + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn.rs:9:9 + | +LL | panic!("error"); + | ^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn.rs:12:5 + | +LL | / fn result_with_unimplemented() -> Result // should emit lint +LL | | { +LL | | unimplemented!(); +LL | | } + | |_____^ + | + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn.rs:14:9 + | +LL | unimplemented!(); + | ^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn.rs:17:5 + | +LL | / fn result_with_unreachable() -> Result // should emit lint +LL | | { +LL | | unreachable!(); +LL | | } + | |_____^ + | + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn.rs:19:9 + | +LL | unreachable!(); + | ^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn.rs:22:5 + | +LL | / fn result_with_todo() -> Result // should emit lint +LL | | { +LL | | todo!("Finish this"); +LL | | } + | |_____^ + | + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn.rs:24:9 + | +LL | todo!("Finish this"); + | ^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn.rs:53:1 + | +LL | / fn function_result_with_panic() -> Result // should emit lint +LL | | { +LL | | panic!("error"); +LL | | } + | |_^ + | + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn.rs:55:5 + | +LL | panic!("error"); + | ^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn.rs:68:1 + | +LL | / fn main() -> Result<(), String> { +LL | | todo!("finish main method"); +LL | | Ok(()) +LL | | } + | |_^ + | + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn.rs:69:5 + | +LL | todo!("finish main method"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/panic_in_result_fn_assertions.rs b/src/tools/clippy/tests/ui/panic_in_result_fn_assertions.rs new file mode 100644 index 0000000000..ffdf8288ad --- /dev/null +++ b/src/tools/clippy/tests/ui/panic_in_result_fn_assertions.rs @@ -0,0 +1,48 @@ +#![warn(clippy::panic_in_result_fn)] +#![allow(clippy::unnecessary_wraps)] + +struct A; + +impl A { + fn result_with_assert_with_message(x: i32) -> Result // should emit lint + { + assert!(x == 5, "wrong argument"); + Ok(true) + } + + fn result_with_assert_eq(x: i32) -> Result // should emit lint + { + assert_eq!(x, 5); + Ok(true) + } + + fn result_with_assert_ne(x: i32) -> Result // should emit lint + { + assert_ne!(x, 1); + Ok(true) + } + + fn other_with_assert_with_message(x: i32) // should not emit lint + { + assert!(x == 5, "wrong argument"); + } + + fn other_with_assert_eq(x: i32) // should not emit lint + { + assert_eq!(x, 5); + } + + fn other_with_assert_ne(x: i32) // should not emit lint + { + assert_ne!(x, 1); + } + + fn result_without_banned_functions() -> Result // should not emit lint + { + let assert = "assert!"; + println!("No {}", assert); + Ok(true) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/panic_in_result_fn_assertions.stderr b/src/tools/clippy/tests/ui/panic_in_result_fn_assertions.stderr new file mode 100644 index 0000000000..a17f043737 --- /dev/null +++ b/src/tools/clippy/tests/ui/panic_in_result_fn_assertions.stderr @@ -0,0 +1,57 @@ +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn_assertions.rs:7:5 + | +LL | / fn result_with_assert_with_message(x: i32) -> Result // should emit lint +LL | | { +LL | | assert!(x == 5, "wrong argument"); +LL | | Ok(true) +LL | | } + | |_____^ + | + = note: `-D clippy::panic-in-result-fn` implied by `-D warnings` + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn_assertions.rs:9:9 + | +LL | assert!(x == 5, "wrong argument"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn_assertions.rs:13:5 + | +LL | / fn result_with_assert_eq(x: i32) -> Result // should emit lint +LL | | { +LL | | assert_eq!(x, 5); +LL | | Ok(true) +LL | | } + | |_____^ + | + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn_assertions.rs:15:9 + | +LL | assert_eq!(x, 5); + | ^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn_assertions.rs:19:5 + | +LL | / fn result_with_assert_ne(x: i32) -> Result // should emit lint +LL | | { +LL | | assert_ne!(x, 1); +LL | | Ok(true) +LL | | } + | |_____^ + | + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn_assertions.rs:21:9 + | +LL | assert_ne!(x, 1); + | ^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/panic_in_result_fn_debug_assertions.rs b/src/tools/clippy/tests/ui/panic_in_result_fn_debug_assertions.rs new file mode 100644 index 0000000000..b60c79f97c --- /dev/null +++ b/src/tools/clippy/tests/ui/panic_in_result_fn_debug_assertions.rs @@ -0,0 +1,48 @@ +#![warn(clippy::panic_in_result_fn)] +#![allow(clippy::unnecessary_wraps)] + +struct A; + +impl A { + fn result_with_debug_assert_with_message(x: i32) -> Result // should emit lint + { + debug_assert!(x == 5, "wrong argument"); + Ok(true) + } + + fn result_with_debug_assert_eq(x: i32) -> Result // should emit lint + { + debug_assert_eq!(x, 5); + Ok(true) + } + + fn result_with_debug_assert_ne(x: i32) -> Result // should emit lint + { + debug_assert_ne!(x, 1); + Ok(true) + } + + fn other_with_debug_assert_with_message(x: i32) // should not emit lint + { + debug_assert!(x == 5, "wrong argument"); + } + + fn other_with_debug_assert_eq(x: i32) // should not emit lint + { + debug_assert_eq!(x, 5); + } + + fn other_with_debug_assert_ne(x: i32) // should not emit lint + { + debug_assert_ne!(x, 1); + } + + fn result_without_banned_functions() -> Result // should not emit lint + { + let debug_assert = "debug_assert!"; + println!("No {}", debug_assert); + Ok(true) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/panic_in_result_fn_debug_assertions.stderr b/src/tools/clippy/tests/ui/panic_in_result_fn_debug_assertions.stderr new file mode 100644 index 0000000000..ec18e89698 --- /dev/null +++ b/src/tools/clippy/tests/ui/panic_in_result_fn_debug_assertions.stderr @@ -0,0 +1,57 @@ +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn_debug_assertions.rs:7:5 + | +LL | / fn result_with_debug_assert_with_message(x: i32) -> Result // should emit lint +LL | | { +LL | | debug_assert!(x == 5, "wrong argument"); +LL | | Ok(true) +LL | | } + | |_____^ + | + = note: `-D clippy::panic-in-result-fn` implied by `-D warnings` + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn_debug_assertions.rs:9:9 + | +LL | debug_assert!(x == 5, "wrong argument"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn_debug_assertions.rs:13:5 + | +LL | / fn result_with_debug_assert_eq(x: i32) -> Result // should emit lint +LL | | { +LL | | debug_assert_eq!(x, 5); +LL | | Ok(true) +LL | | } + | |_____^ + | + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn_debug_assertions.rs:15:9 + | +LL | debug_assert_eq!(x, 5); + | ^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` + --> $DIR/panic_in_result_fn_debug_assertions.rs:19:5 + | +LL | / fn result_with_debug_assert_ne(x: i32) -> Result // should emit lint +LL | | { +LL | | debug_assert_ne!(x, 1); +LL | | Ok(true) +LL | | } + | |_____^ + | + = help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing +note: return Err() instead of panicking + --> $DIR/panic_in_result_fn_debug_assertions.rs:21:9 + | +LL | debug_assert_ne!(x, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/panicking_macros.rs b/src/tools/clippy/tests/ui/panicking_macros.rs new file mode 100644 index 0000000000..77fcb8dfd0 --- /dev/null +++ b/src/tools/clippy/tests/ui/panicking_macros.rs @@ -0,0 +1,52 @@ +#![warn(clippy::unimplemented, clippy::unreachable, clippy::todo, clippy::panic)] +#![allow(clippy::assertions_on_constants)] + +extern crate core; + +fn panic() { + let a = 2; + panic!(); + panic!("message"); + panic!("{} {}", "panic with", "multiple arguments"); + let b = a + 2; +} + +fn todo() { + let a = 2; + todo!(); + todo!("message"); + todo!("{} {}", "panic with", "multiple arguments"); + let b = a + 2; +} + +fn unimplemented() { + let a = 2; + unimplemented!(); + unimplemented!("message"); + unimplemented!("{} {}", "panic with", "multiple arguments"); + let b = a + 2; +} + +fn unreachable() { + let a = 2; + unreachable!(); + unreachable!("message"); + unreachable!("{} {}", "panic with", "multiple arguments"); + let b = a + 2; +} + +fn core_versions() { + use core::{panic, todo, unimplemented, unreachable}; + panic!(); + todo!(); + unimplemented!(); + unreachable!(); +} + +fn main() { + panic(); + todo(); + unimplemented(); + unreachable(); + core_versions(); +} diff --git a/src/tools/clippy/tests/ui/panicking_macros.stderr b/src/tools/clippy/tests/ui/panicking_macros.stderr new file mode 100644 index 0000000000..ffced49690 --- /dev/null +++ b/src/tools/clippy/tests/ui/panicking_macros.stderr @@ -0,0 +1,127 @@ +error: `panic` should not be present in production code + --> $DIR/panicking_macros.rs:8:5 + | +LL | panic!(); + | ^^^^^^^^^ + | + = note: `-D clippy::panic` implied by `-D warnings` + +error: `panic` should not be present in production code + --> $DIR/panicking_macros.rs:9:5 + | +LL | panic!("message"); + | ^^^^^^^^^^^^^^^^^^ + +error: `panic` should not be present in production code + --> $DIR/panicking_macros.rs:10:5 + | +LL | panic!("{} {}", "panic with", "multiple arguments"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `todo` should not be present in production code + --> $DIR/panicking_macros.rs:16:5 + | +LL | todo!(); + | ^^^^^^^^ + | + = note: `-D clippy::todo` implied by `-D warnings` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `todo` should not be present in production code + --> $DIR/panicking_macros.rs:17:5 + | +LL | todo!("message"); + | ^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `todo` should not be present in production code + --> $DIR/panicking_macros.rs:18:5 + | +LL | todo!("{} {}", "panic with", "multiple arguments"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `unimplemented` should not be present in production code + --> $DIR/panicking_macros.rs:24:5 + | +LL | unimplemented!(); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unimplemented` implied by `-D warnings` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `unimplemented` should not be present in production code + --> $DIR/panicking_macros.rs:25:5 + | +LL | unimplemented!("message"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `unimplemented` should not be present in production code + --> $DIR/panicking_macros.rs:26:5 + | +LL | unimplemented!("{} {}", "panic with", "multiple arguments"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: usage of the `unreachable!` macro + --> $DIR/panicking_macros.rs:32:5 + | +LL | unreachable!(); + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unreachable` implied by `-D warnings` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: usage of the `unreachable!` macro + --> $DIR/panicking_macros.rs:33:5 + | +LL | unreachable!("message"); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: usage of the `unreachable!` macro + --> $DIR/panicking_macros.rs:34:5 + | +LL | unreachable!("{} {}", "panic with", "multiple arguments"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `panic` should not be present in production code + --> $DIR/panicking_macros.rs:40:5 + | +LL | panic!(); + | ^^^^^^^^^ + +error: `todo` should not be present in production code + --> $DIR/panicking_macros.rs:41:5 + | +LL | todo!(); + | ^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `unimplemented` should not be present in production code + --> $DIR/panicking_macros.rs:42:5 + | +LL | unimplemented!(); + | ^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: usage of the `unreachable!` macro + --> $DIR/panicking_macros.rs:43:5 + | +LL | unreachable!(); + | ^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/partialeq_ne_impl.rs b/src/tools/clippy/tests/ui/partialeq_ne_impl.rs new file mode 100644 index 0000000000..1338d3c74d --- /dev/null +++ b/src/tools/clippy/tests/ui/partialeq_ne_impl.rs @@ -0,0 +1,26 @@ +#![allow(dead_code)] + +struct Foo; + +impl PartialEq for Foo { + fn eq(&self, _: &Foo) -> bool { + true + } + fn ne(&self, _: &Foo) -> bool { + false + } +} + +struct Bar; + +impl PartialEq for Bar { + fn eq(&self, _: &Bar) -> bool { + true + } + #[allow(clippy::partialeq_ne_impl)] + fn ne(&self, _: &Bar) -> bool { + false + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/partialeq_ne_impl.stderr b/src/tools/clippy/tests/ui/partialeq_ne_impl.stderr new file mode 100644 index 0000000000..b92da4511b --- /dev/null +++ b/src/tools/clippy/tests/ui/partialeq_ne_impl.stderr @@ -0,0 +1,12 @@ +error: re-implementing `PartialEq::ne` is unnecessary + --> $DIR/partialeq_ne_impl.rs:9:5 + | +LL | / fn ne(&self, _: &Foo) -> bool { +LL | | false +LL | | } + | |_____^ + | + = note: `-D clippy::partialeq-ne-impl` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/path_buf_push_overwrite.fixed b/src/tools/clippy/tests/ui/path_buf_push_overwrite.fixed new file mode 100644 index 0000000000..ef8856830f --- /dev/null +++ b/src/tools/clippy/tests/ui/path_buf_push_overwrite.fixed @@ -0,0 +1,8 @@ +// run-rustfix +use std::path::PathBuf; + +#[warn(clippy::all, clippy::path_buf_push_overwrite)] +fn main() { + let mut x = PathBuf::from("/foo"); + x.push("bar"); +} diff --git a/src/tools/clippy/tests/ui/path_buf_push_overwrite.rs b/src/tools/clippy/tests/ui/path_buf_push_overwrite.rs new file mode 100644 index 0000000000..6e2d483f45 --- /dev/null +++ b/src/tools/clippy/tests/ui/path_buf_push_overwrite.rs @@ -0,0 +1,8 @@ +// run-rustfix +use std::path::PathBuf; + +#[warn(clippy::all, clippy::path_buf_push_overwrite)] +fn main() { + let mut x = PathBuf::from("/foo"); + x.push("/bar"); +} diff --git a/src/tools/clippy/tests/ui/path_buf_push_overwrite.stderr b/src/tools/clippy/tests/ui/path_buf_push_overwrite.stderr new file mode 100644 index 0000000000..bb8dce2bbb --- /dev/null +++ b/src/tools/clippy/tests/ui/path_buf_push_overwrite.stderr @@ -0,0 +1,10 @@ +error: calling `push` with '/' or '/' (file system root) will overwrite the previous path definition + --> $DIR/path_buf_push_overwrite.rs:7:12 + | +LL | x.push("/bar"); + | ^^^^^^ help: try: `"bar"` + | + = note: `-D clippy::path-buf-push-overwrite` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/mutability.rs b/src/tools/clippy/tests/ui/pattern_type_mismatch/mutability.rs new file mode 100644 index 0000000000..9b4f2f1f57 --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/mutability.rs @@ -0,0 +1,40 @@ +#![allow(clippy::all)] +#![warn(clippy::pattern_type_mismatch)] + +fn main() {} + +fn should_lint() { + let value = &Some(23); + match value { + Some(_) => (), + _ => (), + } + + let value = &mut Some(23); + match value { + Some(_) => (), + _ => (), + } +} + +fn should_not_lint() { + let value = &Some(23); + match value { + &Some(_) => (), + _ => (), + } + match *value { + Some(_) => (), + _ => (), + } + + let value = &mut Some(23); + match value { + &mut Some(_) => (), + _ => (), + } + match *value { + Some(_) => (), + _ => (), + } +} diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/mutability.stderr b/src/tools/clippy/tests/ui/pattern_type_mismatch/mutability.stderr new file mode 100644 index 0000000000..3421d56836 --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/mutability.stderr @@ -0,0 +1,19 @@ +error: type of pattern does not match the expression type + --> $DIR/mutability.rs:9:9 + | +LL | Some(_) => (), + | ^^^^^^^ + | + = note: `-D clippy::pattern-type-mismatch` implied by `-D warnings` + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/mutability.rs:15:9 + | +LL | Some(_) => (), + | ^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&mut _` pattern and adjust the enclosed variable bindings + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_alternatives.rs b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_alternatives.rs new file mode 100644 index 0000000000..065ea9fb9b --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_alternatives.rs @@ -0,0 +1,24 @@ +#![allow(clippy::all)] +#![warn(clippy::pattern_type_mismatch)] + +fn main() {} + +fn alternatives() { + enum Value<'a> { + Unused, + A(&'a Option), + B, + } + let ref_value = &Value::A(&Some(23)); + + // not ok + if let Value::B | Value::A(_) = ref_value {} + if let &Value::B | &Value::A(Some(_)) = ref_value {} + if let Value::B | Value::A(Some(_)) = *ref_value {} + + // ok + if let &Value::B | &Value::A(_) = ref_value {} + if let Value::B | Value::A(_) = *ref_value {} + if let &Value::B | &Value::A(&Some(_)) = ref_value {} + if let Value::B | Value::A(&Some(_)) = *ref_value {} +} diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_alternatives.stderr b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_alternatives.stderr new file mode 100644 index 0000000000..d285c93782 --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_alternatives.stderr @@ -0,0 +1,27 @@ +error: type of pattern does not match the expression type + --> $DIR/pattern_alternatives.rs:15:12 + | +LL | if let Value::B | Value::A(_) = ref_value {} + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::pattern-type-mismatch` implied by `-D warnings` + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_alternatives.rs:16:34 + | +LL | if let &Value::B | &Value::A(Some(_)) = ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_alternatives.rs:17:32 + | +LL | if let Value::B | Value::A(Some(_)) = *ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_structs.rs b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_structs.rs new file mode 100644 index 0000000000..417b1c107c --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_structs.rs @@ -0,0 +1,45 @@ +#![allow(clippy::all)] +#![warn(clippy::pattern_type_mismatch)] + +fn main() {} + +fn struct_types() { + struct Struct<'a> { + ref_inner: &'a Option, + } + let ref_value = &Struct { ref_inner: &Some(42) }; + + // not ok + let Struct { .. } = ref_value; + if let &Struct { ref_inner: Some(_) } = ref_value {} + if let Struct { ref_inner: Some(_) } = *ref_value {} + + // ok + let &Struct { .. } = ref_value; + let Struct { .. } = *ref_value; + if let &Struct { ref_inner: &Some(_) } = ref_value {} + if let Struct { ref_inner: &Some(_) } = *ref_value {} +} + +fn struct_enum_variants() { + enum StructEnum<'a> { + Empty, + Var { inner_ref: &'a Option }, + } + let ref_value = &StructEnum::Var { inner_ref: &Some(42) }; + + // not ok + if let StructEnum::Var { .. } = ref_value {} + if let StructEnum::Var { inner_ref: Some(_) } = ref_value {} + if let &StructEnum::Var { inner_ref: Some(_) } = ref_value {} + if let StructEnum::Var { inner_ref: Some(_) } = *ref_value {} + if let StructEnum::Empty = ref_value {} + + // ok + if let &StructEnum::Var { .. } = ref_value {} + if let StructEnum::Var { .. } = *ref_value {} + if let &StructEnum::Var { inner_ref: &Some(_) } = ref_value {} + if let StructEnum::Var { inner_ref: &Some(_) } = *ref_value {} + if let &StructEnum::Empty = ref_value {} + if let StructEnum::Empty = *ref_value {} +} diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_structs.stderr b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_structs.stderr new file mode 100644 index 0000000000..d428e85b0c --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_structs.stderr @@ -0,0 +1,67 @@ +error: type of pattern does not match the expression type + --> $DIR/pattern_structs.rs:13:9 + | +LL | let Struct { .. } = ref_value; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::pattern-type-mismatch` implied by `-D warnings` + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_structs.rs:14:33 + | +LL | if let &Struct { ref_inner: Some(_) } = ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_structs.rs:15:32 + | +LL | if let Struct { ref_inner: Some(_) } = *ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_structs.rs:32:12 + | +LL | if let StructEnum::Var { .. } = ref_value {} + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_structs.rs:33:12 + | +LL | if let StructEnum::Var { inner_ref: Some(_) } = ref_value {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_structs.rs:34:42 + | +LL | if let &StructEnum::Var { inner_ref: Some(_) } = ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_structs.rs:35:41 + | +LL | if let StructEnum::Var { inner_ref: Some(_) } = *ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_structs.rs:36:12 + | +LL | if let StructEnum::Empty = ref_value {} + | ^^^^^^^^^^^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_tuples.rs b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_tuples.rs new file mode 100644 index 0000000000..19504a051d --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_tuples.rs @@ -0,0 +1,57 @@ +#![allow(clippy::all)] +#![warn(clippy::pattern_type_mismatch)] + +fn main() {} + +fn tuple_types() { + struct TupleStruct<'a>(&'a Option); + let ref_value = &TupleStruct(&Some(42)); + + // not ok + let TupleStruct(_) = ref_value; + if let &TupleStruct(Some(_)) = ref_value {} + if let TupleStruct(Some(_)) = *ref_value {} + + // ok + let &TupleStruct(_) = ref_value; + let TupleStruct(_) = *ref_value; + if let &TupleStruct(&Some(_)) = ref_value {} + if let TupleStruct(&Some(_)) = *ref_value {} +} + +fn tuple_enum_variants() { + enum TupleEnum<'a> { + Empty, + Var(&'a Option), + } + let ref_value = &TupleEnum::Var(&Some(42)); + + // not ok + if let TupleEnum::Var(_) = ref_value {} + if let &TupleEnum::Var(Some(_)) = ref_value {} + if let TupleEnum::Var(Some(_)) = *ref_value {} + if let TupleEnum::Empty = ref_value {} + + // ok + if let &TupleEnum::Var(_) = ref_value {} + if let TupleEnum::Var(_) = *ref_value {} + if let &TupleEnum::Var(&Some(_)) = ref_value {} + if let TupleEnum::Var(&Some(_)) = *ref_value {} + if let &TupleEnum::Empty = ref_value {} + if let TupleEnum::Empty = *ref_value {} +} + +fn plain_tuples() { + let ref_value = &(&Some(23), &Some(42)); + + // not ok + let (_a, _b) = ref_value; + if let &(_a, Some(_)) = ref_value {} + if let (_a, Some(_)) = *ref_value {} + + // ok + let &(_a, _b) = ref_value; + let (_a, _b) = *ref_value; + if let &(_a, &Some(_)) = ref_value {} + if let (_a, &Some(_)) = *ref_value {} +} diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_tuples.stderr b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_tuples.stderr new file mode 100644 index 0000000000..edd0074d00 --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/pattern_tuples.stderr @@ -0,0 +1,83 @@ +error: type of pattern does not match the expression type + --> $DIR/pattern_tuples.rs:11:9 + | +LL | let TupleStruct(_) = ref_value; + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::pattern-type-mismatch` implied by `-D warnings` + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_tuples.rs:12:25 + | +LL | if let &TupleStruct(Some(_)) = ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_tuples.rs:13:24 + | +LL | if let TupleStruct(Some(_)) = *ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_tuples.rs:30:12 + | +LL | if let TupleEnum::Var(_) = ref_value {} + | ^^^^^^^^^^^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_tuples.rs:31:28 + | +LL | if let &TupleEnum::Var(Some(_)) = ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_tuples.rs:32:27 + | +LL | if let TupleEnum::Var(Some(_)) = *ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_tuples.rs:33:12 + | +LL | if let TupleEnum::Empty = ref_value {} + | ^^^^^^^^^^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_tuples.rs:48:9 + | +LL | let (_a, _b) = ref_value; + | ^^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_tuples.rs:49:18 + | +LL | if let &(_a, Some(_)) = ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/pattern_tuples.rs:50:17 + | +LL | if let (_a, Some(_)) = *ref_value {} + | ^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.rs b/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.rs new file mode 100644 index 0000000000..e89917c41e --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.rs @@ -0,0 +1,146 @@ +#![allow(clippy::all)] +#![warn(clippy::pattern_type_mismatch)] + +fn main() {} + +fn syntax_match() { + let ref_value = &Some(&Some(42)); + + // not ok + match ref_value { + Some(_) => (), + None => (), + } + + // ok + match ref_value { + &Some(_) => (), + &None => (), + } + match *ref_value { + Some(_) => (), + None => (), + } +} + +fn syntax_if_let() { + let ref_value = &Some(42); + + // not ok + if let Some(_) = ref_value {} + + // ok + if let &Some(_) = ref_value {} + if let Some(_) = *ref_value {} +} + +fn syntax_while_let() { + let ref_value = &Some(42); + + // not ok + while let Some(_) = ref_value { + break; + } + + // ok + while let &Some(_) = ref_value { + break; + } + while let Some(_) = *ref_value { + break; + } +} + +fn syntax_for() { + let ref_value = &Some(23); + let slice = &[(2, 3), (4, 2)]; + + // not ok + for (_a, _b) in slice.iter() {} + + // ok + for &(_a, _b) in slice.iter() {} +} + +fn syntax_let() { + let ref_value = &(2, 3); + + // not ok + let (_n, _m) = ref_value; + + // ok + let &(_n, _m) = ref_value; + let (_n, _m) = *ref_value; +} + +fn syntax_fn() { + // not ok + fn foo((_a, _b): &(i32, i32)) {} + + // ok + fn foo_ok_1(&(_a, _b): &(i32, i32)) {} +} + +fn syntax_closure() { + fn foo(f: F) + where + F: FnOnce(&(i32, i32)), + { + } + + // not ok + foo(|(_a, _b)| ()); + + // ok + foo(|&(_a, _b)| ()); +} + +fn macro_with_expression() { + macro_rules! matching_macro { + ($e:expr) => { + $e + }; + } + let value = &Some(23); + + // not ok + matching_macro!(match value { + Some(_) => (), + _ => (), + }); + + // ok + matching_macro!(match value { + &Some(_) => (), + _ => (), + }); + matching_macro!(match *value { + Some(_) => (), + _ => (), + }); +} + +fn macro_expansion() { + macro_rules! matching_macro { + ($e:expr) => { + // not ok + match $e { + Some(_) => (), + _ => (), + } + + // ok + match $e { + &Some(_) => (), + _ => (), + } + match *$e { + Some(_) => (), + _ => (), + } + }; + } + + let value = &Some(23); + matching_macro!(value); +} diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.stderr b/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.stderr new file mode 100644 index 0000000000..5a5186bd4f --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.stderr @@ -0,0 +1,79 @@ +error: type of pattern does not match the expression type + --> $DIR/syntax.rs:11:9 + | +LL | Some(_) => (), + | ^^^^^^^ + | + = note: `-D clippy::pattern-type-mismatch` implied by `-D warnings` + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/syntax.rs:30:12 + | +LL | if let Some(_) = ref_value {} + | ^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/syntax.rs:41:15 + | +LL | while let Some(_) = ref_value { + | ^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/syntax.rs:59:9 + | +LL | for (_a, _b) in slice.iter() {} + | ^^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/syntax.rs:69:9 + | +LL | let (_n, _m) = ref_value; + | ^^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/syntax.rs:78:12 + | +LL | fn foo((_a, _b): &(i32, i32)) {} + | ^^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/syntax.rs:92:10 + | +LL | foo(|(_a, _b)| ()); + | ^^^^^^^^ + | + = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/syntax.rs:108:9 + | +LL | Some(_) => (), + | ^^^^^^^ + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + +error: type of pattern does not match the expression type + --> $DIR/syntax.rs:128:17 + | +LL | Some(_) => (), + | ^^^^^^^ +... +LL | matching_macro!(value); + | ----------------------- in this macro invocation + | + = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/patterns.fixed b/src/tools/clippy/tests/ui/patterns.fixed new file mode 100644 index 0000000000..f223881544 --- /dev/null +++ b/src/tools/clippy/tests/ui/patterns.fixed @@ -0,0 +1,36 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::all)] + +fn main() { + let v = Some(true); + let s = [0, 1, 2, 3, 4]; + match v { + Some(x) => (), + y => (), + } + match v { + Some(x) => (), + y @ None => (), // no error + } + match s { + [x, inside @ .., y] => (), // no error + [..] => (), + } + + let mut mutv = vec![1, 2, 3]; + + // required "ref" left out in suggestion: #5271 + match mutv { + ref mut x => { + x.push(4); + println!("vec: {:?}", x); + }, + ref y if y == &vec![0] => (), + } + + match mutv { + ref x => println!("vec: {:?}", x), + ref y if y == &vec![0] => (), + } +} diff --git a/src/tools/clippy/tests/ui/patterns.rs b/src/tools/clippy/tests/ui/patterns.rs new file mode 100644 index 0000000000..5848ecd38d --- /dev/null +++ b/src/tools/clippy/tests/ui/patterns.rs @@ -0,0 +1,36 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::all)] + +fn main() { + let v = Some(true); + let s = [0, 1, 2, 3, 4]; + match v { + Some(x) => (), + y @ _ => (), + } + match v { + Some(x) => (), + y @ None => (), // no error + } + match s { + [x, inside @ .., y] => (), // no error + [..] => (), + } + + let mut mutv = vec![1, 2, 3]; + + // required "ref" left out in suggestion: #5271 + match mutv { + ref mut x @ _ => { + x.push(4); + println!("vec: {:?}", x); + }, + ref y if y == &vec![0] => (), + } + + match mutv { + ref x @ _ => println!("vec: {:?}", x), + ref y if y == &vec![0] => (), + } +} diff --git a/src/tools/clippy/tests/ui/patterns.stderr b/src/tools/clippy/tests/ui/patterns.stderr new file mode 100644 index 0000000000..af06758068 --- /dev/null +++ b/src/tools/clippy/tests/ui/patterns.stderr @@ -0,0 +1,22 @@ +error: the `y @ _` pattern can be written as just `y` + --> $DIR/patterns.rs:10:9 + | +LL | y @ _ => (), + | ^^^^^ help: try: `y` + | + = note: `-D clippy::redundant-pattern` implied by `-D warnings` + +error: the `x @ _` pattern can be written as just `x` + --> $DIR/patterns.rs:25:9 + | +LL | ref mut x @ _ => { + | ^^^^^^^^^^^^^ help: try: `ref mut x` + +error: the `x @ _` pattern can be written as just `x` + --> $DIR/patterns.rs:33:9 + | +LL | ref x @ _ => println!("vec: {:?}", x), + | ^^^^^^^^^ help: try: `ref x` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/precedence.fixed b/src/tools/clippy/tests/ui/precedence.fixed new file mode 100644 index 0000000000..163bd044c1 --- /dev/null +++ b/src/tools/clippy/tests/ui/precedence.fixed @@ -0,0 +1,61 @@ +// run-rustfix +#![warn(clippy::precedence)] +#![allow(unused_must_use, clippy::no_effect, clippy::unnecessary_operation)] +#![allow(clippy::identity_op)] +#![allow(clippy::eq_op)] + +macro_rules! trip { + ($a:expr) => { + match $a & 0b1111_1111u8 { + 0 => println!("a is zero ({})", $a), + _ => println!("a is {}", $a), + } + }; +} + +fn main() { + 1 << (2 + 3); + (1 + 2) << 3; + 4 >> (1 + 1); + (1 + 3) >> 2; + 1 ^ (1 - 1); + 3 | (2 - 1); + 3 & (5 - 2); + -(1i32.abs()); + -(1f32.abs()); + + // These should not trigger an error + let _ = (-1i32).abs(); + let _ = (-1f32).abs(); + let _ = -(1i32).abs(); + let _ = -(1f32).abs(); + let _ = -(1i32.abs()); + let _ = -(1f32.abs()); + + // Odd functions should not trigger an error + let _ = -1f64.asin(); + let _ = -1f64.asinh(); + let _ = -1f64.atan(); + let _ = -1f64.atanh(); + let _ = -1f64.cbrt(); + let _ = -1f64.fract(); + let _ = -1f64.round(); + let _ = -1f64.signum(); + let _ = -1f64.sin(); + let _ = -1f64.sinh(); + let _ = -1f64.tan(); + let _ = -1f64.tanh(); + let _ = -1f64.to_degrees(); + let _ = -1f64.to_radians(); + + // Chains containing any non-odd function should trigger (issue #5924) + let _ = -(1.0_f64.cos().cos()); + let _ = -(1.0_f64.cos().sin()); + let _ = -(1.0_f64.sin().cos()); + + // Chains of odd functions shouldn't trigger + let _ = -1f64.sin().sin(); + + let b = 3; + trip!(b * 8); +} diff --git a/src/tools/clippy/tests/ui/precedence.rs b/src/tools/clippy/tests/ui/precedence.rs new file mode 100644 index 0000000000..8c849e3209 --- /dev/null +++ b/src/tools/clippy/tests/ui/precedence.rs @@ -0,0 +1,61 @@ +// run-rustfix +#![warn(clippy::precedence)] +#![allow(unused_must_use, clippy::no_effect, clippy::unnecessary_operation)] +#![allow(clippy::identity_op)] +#![allow(clippy::eq_op)] + +macro_rules! trip { + ($a:expr) => { + match $a & 0b1111_1111u8 { + 0 => println!("a is zero ({})", $a), + _ => println!("a is {}", $a), + } + }; +} + +fn main() { + 1 << 2 + 3; + 1 + 2 << 3; + 4 >> 1 + 1; + 1 + 3 >> 2; + 1 ^ 1 - 1; + 3 | 2 - 1; + 3 & 5 - 2; + -1i32.abs(); + -1f32.abs(); + + // These should not trigger an error + let _ = (-1i32).abs(); + let _ = (-1f32).abs(); + let _ = -(1i32).abs(); + let _ = -(1f32).abs(); + let _ = -(1i32.abs()); + let _ = -(1f32.abs()); + + // Odd functions should not trigger an error + let _ = -1f64.asin(); + let _ = -1f64.asinh(); + let _ = -1f64.atan(); + let _ = -1f64.atanh(); + let _ = -1f64.cbrt(); + let _ = -1f64.fract(); + let _ = -1f64.round(); + let _ = -1f64.signum(); + let _ = -1f64.sin(); + let _ = -1f64.sinh(); + let _ = -1f64.tan(); + let _ = -1f64.tanh(); + let _ = -1f64.to_degrees(); + let _ = -1f64.to_radians(); + + // Chains containing any non-odd function should trigger (issue #5924) + let _ = -1.0_f64.cos().cos(); + let _ = -1.0_f64.cos().sin(); + let _ = -1.0_f64.sin().cos(); + + // Chains of odd functions shouldn't trigger + let _ = -1f64.sin().sin(); + + let b = 3; + trip!(b * 8); +} diff --git a/src/tools/clippy/tests/ui/precedence.stderr b/src/tools/clippy/tests/ui/precedence.stderr new file mode 100644 index 0000000000..03d585b397 --- /dev/null +++ b/src/tools/clippy/tests/ui/precedence.stderr @@ -0,0 +1,76 @@ +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:17:5 + | +LL | 1 << 2 + 3; + | ^^^^^^^^^^ help: consider parenthesizing your expression: `1 << (2 + 3)` + | + = note: `-D clippy::precedence` implied by `-D warnings` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:18:5 + | +LL | 1 + 2 << 3; + | ^^^^^^^^^^ help: consider parenthesizing your expression: `(1 + 2) << 3` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:19:5 + | +LL | 4 >> 1 + 1; + | ^^^^^^^^^^ help: consider parenthesizing your expression: `4 >> (1 + 1)` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:20:5 + | +LL | 1 + 3 >> 2; + | ^^^^^^^^^^ help: consider parenthesizing your expression: `(1 + 3) >> 2` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:21:5 + | +LL | 1 ^ 1 - 1; + | ^^^^^^^^^ help: consider parenthesizing your expression: `1 ^ (1 - 1)` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:22:5 + | +LL | 3 | 2 - 1; + | ^^^^^^^^^ help: consider parenthesizing your expression: `3 | (2 - 1)` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:23:5 + | +LL | 3 & 5 - 2; + | ^^^^^^^^^ help: consider parenthesizing your expression: `3 & (5 - 2)` + +error: unary minus has lower precedence than method call + --> $DIR/precedence.rs:24:5 + | +LL | -1i32.abs(); + | ^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1i32.abs())` + +error: unary minus has lower precedence than method call + --> $DIR/precedence.rs:25:5 + | +LL | -1f32.abs(); + | ^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1f32.abs())` + +error: unary minus has lower precedence than method call + --> $DIR/precedence.rs:52:13 + | +LL | let _ = -1.0_f64.cos().cos(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1.0_f64.cos().cos())` + +error: unary minus has lower precedence than method call + --> $DIR/precedence.rs:53:13 + | +LL | let _ = -1.0_f64.cos().sin(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1.0_f64.cos().sin())` + +error: unary minus has lower precedence than method call + --> $DIR/precedence.rs:54:13 + | +LL | let _ = -1.0_f64.sin().cos(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1.0_f64.sin().cos())` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/print.rs b/src/tools/clippy/tests/ui/print.rs new file mode 100644 index 0000000000..366ccc2b3b --- /dev/null +++ b/src/tools/clippy/tests/ui/print.rs @@ -0,0 +1,35 @@ +#![allow(clippy::print_literal, clippy::write_literal)] +#![warn(clippy::print_stdout, clippy::use_debug)] + +use std::fmt::{Debug, Display, Formatter, Result}; + +#[allow(dead_code)] +struct Foo; + +impl Display for Foo { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{:?}", 43.1415) + } +} + +impl Debug for Foo { + fn fmt(&self, f: &mut Formatter) -> Result { + // ok, we can use `Debug` formatting in `Debug` implementations + write!(f, "{:?}", 42.718) + } +} + +fn main() { + println!("Hello"); + print!("Hello"); + + print!("Hello {}", "World"); + + print!("Hello {:?}", "World"); + + print!("Hello {:#?}", "#orld"); + + assert_eq!(42, 1337); + + vec![1, 2]; +} diff --git a/src/tools/clippy/tests/ui/print.stderr b/src/tools/clippy/tests/ui/print.stderr new file mode 100644 index 0000000000..208d953262 --- /dev/null +++ b/src/tools/clippy/tests/ui/print.stderr @@ -0,0 +1,54 @@ +error: use of `Debug`-based formatting + --> $DIR/print.rs:11:19 + | +LL | write!(f, "{:?}", 43.1415) + | ^^^^^^ + | + = note: `-D clippy::use-debug` implied by `-D warnings` + +error: use of `println!` + --> $DIR/print.rs:23:5 + | +LL | println!("Hello"); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::print-stdout` implied by `-D warnings` + +error: use of `print!` + --> $DIR/print.rs:24:5 + | +LL | print!("Hello"); + | ^^^^^^^^^^^^^^^ + +error: use of `print!` + --> $DIR/print.rs:26:5 + | +LL | print!("Hello {}", "World"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of `print!` + --> $DIR/print.rs:28:5 + | +LL | print!("Hello {:?}", "World"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of `Debug`-based formatting + --> $DIR/print.rs:28:12 + | +LL | print!("Hello {:?}", "World"); + | ^^^^^^^^^^^^ + +error: use of `print!` + --> $DIR/print.rs:30:5 + | +LL | print!("Hello {:#?}", "#orld"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of `Debug`-based formatting + --> $DIR/print.rs:30:12 + | +LL | print!("Hello {:#?}", "#orld"); + | ^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/print_literal.rs b/src/tools/clippy/tests/ui/print_literal.rs new file mode 100644 index 0000000000..8665a3bb28 --- /dev/null +++ b/src/tools/clippy/tests/ui/print_literal.rs @@ -0,0 +1,38 @@ +#![warn(clippy::print_literal)] + +fn main() { + // these should be fine + print!("Hello"); + println!("Hello"); + let world = "world"; + println!("Hello {}", world); + println!("Hello {world}", world = world); + println!("3 in hex is {:X}", 3); + println!("2 + 1 = {:.4}", 3); + println!("2 + 1 = {:5.4}", 3); + println!("Debug test {:?}", "hello, world"); + println!("{0:8} {1:>8}", "hello", "world"); + println!("{1:8} {0:>8}", "hello", "world"); + println!("{foo:8} {bar:>8}", foo = "hello", bar = "world"); + println!("{bar:8} {foo:>8}", foo = "hello", bar = "world"); + println!("{number:>width$}", number = 1, width = 6); + println!("{number:>0width$}", number = 1, width = 6); + println!("{} of {:b} people know binary, the other half doesn't", 1, 2); + println!("10 / 4 is {}", 2.5); + println!("2 + 1 = {}", 3); + + // these should throw warnings + print!("Hello {}", "world"); + println!("Hello {} {}", world, "world"); + println!("Hello {}", "world"); + + // positional args don't change the fact + // that we're using a literal -- this should + // throw a warning + println!("{0} {1}", "hello", "world"); + println!("{1} {0}", "hello", "world"); + + // named args shouldn't change anything either + println!("{foo} {bar}", foo = "hello", bar = "world"); + println!("{bar} {foo}", foo = "hello", bar = "world"); +} diff --git a/src/tools/clippy/tests/ui/print_literal.stderr b/src/tools/clippy/tests/ui/print_literal.stderr new file mode 100644 index 0000000000..e284aece23 --- /dev/null +++ b/src/tools/clippy/tests/ui/print_literal.stderr @@ -0,0 +1,70 @@ +error: literal with an empty format string + --> $DIR/print_literal.rs:25:24 + | +LL | print!("Hello {}", "world"); + | ^^^^^^^ + | + = note: `-D clippy::print-literal` implied by `-D warnings` + +error: literal with an empty format string + --> $DIR/print_literal.rs:26:36 + | +LL | println!("Hello {} {}", world, "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:27:26 + | +LL | println!("Hello {}", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:32:25 + | +LL | println!("{0} {1}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:32:34 + | +LL | println!("{0} {1}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:33:25 + | +LL | println!("{1} {0}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:33:34 + | +LL | println!("{1} {0}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:36:35 + | +LL | println!("{foo} {bar}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:36:50 + | +LL | println!("{foo} {bar}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:37:35 + | +LL | println!("{bar} {foo}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:37:50 + | +LL | println!("{bar} {foo}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/print_stderr.rs b/src/tools/clippy/tests/ui/print_stderr.rs new file mode 100644 index 0000000000..fa07e74a7b --- /dev/null +++ b/src/tools/clippy/tests/ui/print_stderr.rs @@ -0,0 +1,8 @@ +#![warn(clippy::print_stderr)] + +fn main() { + eprintln!("Hello"); + println!("This should not do anything"); + eprint!("World"); + print!("Nor should this"); +} diff --git a/src/tools/clippy/tests/ui/print_stderr.stderr b/src/tools/clippy/tests/ui/print_stderr.stderr new file mode 100644 index 0000000000..5af735af65 --- /dev/null +++ b/src/tools/clippy/tests/ui/print_stderr.stderr @@ -0,0 +1,16 @@ +error: use of `eprintln!` + --> $DIR/print_stderr.rs:4:5 + | +LL | eprintln!("Hello"); + | ^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::print-stderr` implied by `-D warnings` + +error: use of `eprint!` + --> $DIR/print_stderr.rs:6:5 + | +LL | eprint!("World"); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/print_stdout_build_script.rs b/src/tools/clippy/tests/ui/print_stdout_build_script.rs new file mode 100644 index 0000000000..997ebef8a6 --- /dev/null +++ b/src/tools/clippy/tests/ui/print_stdout_build_script.rs @@ -0,0 +1,12 @@ +// compile-flags: --crate-name=build_script_build + +#![warn(clippy::print_stdout)] + +fn main() { + // Fix #6041 + // + // The `print_stdout` lint shouldn't emit in `build.rs` + // as these methods are used for the build script. + println!("Hello"); + print!("Hello"); +} diff --git a/src/tools/clippy/tests/ui/print_with_newline.rs b/src/tools/clippy/tests/ui/print_with_newline.rs new file mode 100644 index 0000000000..a43a1fc4f5 --- /dev/null +++ b/src/tools/clippy/tests/ui/print_with_newline.rs @@ -0,0 +1,52 @@ +// FIXME: Ideally these suggestions would be fixed via rustfix. Blocked by rust-lang/rust#53934 +// // run-rustfix + +#![allow(clippy::print_literal)] +#![warn(clippy::print_with_newline)] + +fn main() { + print!("Hello\n"); + print!("Hello {}\n", "world"); + print!("Hello {} {}\n", "world", "#2"); + print!("{}\n", 1265); + print!("\n"); + + // these are all fine + print!(""); + print!("Hello"); + println!("Hello"); + println!("Hello\n"); + println!("Hello {}\n", "world"); + print!("Issue\n{}", 1265); + print!("{}", 1265); + print!("\n{}", 1275); + print!("\n\n"); + print!("like eof\n\n"); + print!("Hello {} {}\n\n", "world", "#2"); + println!("\ndon't\nwarn\nfor\nmultiple\nnewlines\n"); // #3126 + println!("\nbla\n\n"); // #3126 + + // Escaping + print!("\\n"); // #3514 + print!("\\\n"); // should fail + print!("\\\\n"); + + // Raw strings + print!(r"\n"); // #3778 + + // Literal newlines should also fail + print!( + " +" + ); + print!( + r" +" + ); + + // Don't warn on CRLF (#4208) + print!("\r\n"); + print!("foo\r\n"); + print!("\\r\n"); //~ ERROR + print!("foo\rbar\n") // ~ ERROR +} diff --git a/src/tools/clippy/tests/ui/print_with_newline.stderr b/src/tools/clippy/tests/ui/print_with_newline.stderr new file mode 100644 index 0000000000..54b3ad75b3 --- /dev/null +++ b/src/tools/clippy/tests/ui/print_with_newline.stderr @@ -0,0 +1,121 @@ +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:8:5 + | +LL | print!("Hello/n"); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::print-with-newline` implied by `-D warnings` +help: use `println!` instead + | +LL | println!("Hello"); + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:9:5 + | +LL | print!("Hello {}/n", "world"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("Hello {}", "world"); + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:10:5 + | +LL | print!("Hello {} {}/n", "world", "#2"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("Hello {} {}", "world", "#2"); + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:11:5 + | +LL | print!("{}/n", 1265); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("{}", 1265); + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:12:5 + | +LL | print!("/n"); + | ^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!(); + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:31:5 + | +LL | print!("//n"); // should fail + | ^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("/"); // should fail + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:38:5 + | +LL | / print!( +LL | | " +LL | | " +LL | | ); + | |_____^ + | +help: use `println!` instead + | +LL | println!( +LL | "" + | + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:42:5 + | +LL | / print!( +LL | | r" +LL | | " +LL | | ); + | |_____^ + | +help: use `println!` instead + | +LL | println!( +LL | r"" + | + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:50:5 + | +LL | print!("/r/n"); //~ ERROR + | ^^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("/r"); //~ ERROR + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:51:5 + | +LL | print!("foo/rbar/n") // ~ ERROR + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("foo/rbar") // ~ ERROR + | ^^^^^^^ -- + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/println_empty_string.fixed b/src/tools/clippy/tests/ui/println_empty_string.fixed new file mode 100644 index 0000000000..9760680927 --- /dev/null +++ b/src/tools/clippy/tests/ui/println_empty_string.fixed @@ -0,0 +1,18 @@ +// run-rustfix +#![allow(clippy::match_single_binding)] + +fn main() { + println!(); + println!(); + + match "a" { + _ => println!(), + } + + eprintln!(); + eprintln!(); + + match "a" { + _ => eprintln!(), + } +} diff --git a/src/tools/clippy/tests/ui/println_empty_string.rs b/src/tools/clippy/tests/ui/println_empty_string.rs new file mode 100644 index 0000000000..80fdb3e6e2 --- /dev/null +++ b/src/tools/clippy/tests/ui/println_empty_string.rs @@ -0,0 +1,18 @@ +// run-rustfix +#![allow(clippy::match_single_binding)] + +fn main() { + println!(); + println!(""); + + match "a" { + _ => println!(""), + } + + eprintln!(); + eprintln!(""); + + match "a" { + _ => eprintln!(""), + } +} diff --git a/src/tools/clippy/tests/ui/println_empty_string.stderr b/src/tools/clippy/tests/ui/println_empty_string.stderr new file mode 100644 index 0000000000..17fe4ea747 --- /dev/null +++ b/src/tools/clippy/tests/ui/println_empty_string.stderr @@ -0,0 +1,28 @@ +error: using `println!("")` + --> $DIR/println_empty_string.rs:6:5 + | +LL | println!(""); + | ^^^^^^^^^^^^ help: replace it with: `println!()` + | + = note: `-D clippy::println-empty-string` implied by `-D warnings` + +error: using `println!("")` + --> $DIR/println_empty_string.rs:9:14 + | +LL | _ => println!(""), + | ^^^^^^^^^^^^ help: replace it with: `println!()` + +error: using `eprintln!("")` + --> $DIR/println_empty_string.rs:13:5 + | +LL | eprintln!(""); + | ^^^^^^^^^^^^^ help: replace it with: `eprintln!()` + +error: using `eprintln!("")` + --> $DIR/println_empty_string.rs:16:14 + | +LL | _ => eprintln!(""), + | ^^^^^^^^^^^^^ help: replace it with: `eprintln!()` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/proc_macro.rs b/src/tools/clippy/tests/ui/proc_macro.rs new file mode 100644 index 0000000000..59914b8b8f --- /dev/null +++ b/src/tools/clippy/tests/ui/proc_macro.rs @@ -0,0 +1,26 @@ +//! Check that we correctly lint procedural macros. +#![crate_type = "proc-macro"] + +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[allow(dead_code)] +fn f() { + let _x = 3.14; +} + +#[proc_macro] +pub fn mybangmacro(t: TokenStream) -> TokenStream { + t +} + +#[proc_macro_derive(MyDerivedTrait)] +pub fn myderive(t: TokenStream) -> TokenStream { + t +} + +#[proc_macro_attribute] +pub fn myattribute(t: TokenStream, a: TokenStream) -> TokenStream { + t +} diff --git a/src/tools/clippy/tests/ui/proc_macro.stderr b/src/tools/clippy/tests/ui/proc_macro.stderr new file mode 100644 index 0000000000..872cbc66af --- /dev/null +++ b/src/tools/clippy/tests/ui/proc_macro.stderr @@ -0,0 +1,10 @@ +error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly + --> $DIR/proc_macro.rs:10:14 + | +LL | let _x = 3.14; + | ^^^^ + | + = note: `#[deny(clippy::approx_constant)]` on by default + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/ptr_arg.rs b/src/tools/clippy/tests/ui/ptr_arg.rs new file mode 100644 index 0000000000..06370dfce6 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_arg.rs @@ -0,0 +1,157 @@ +#![allow(unused, clippy::many_single_char_names, clippy::redundant_clone)] +#![warn(clippy::ptr_arg)] + +use std::borrow::Cow; +use std::path::PathBuf; + +fn do_vec(x: &Vec) { + //Nothing here +} + +fn do_vec_mut(x: &mut Vec) { + // no error here + //Nothing here +} + +fn do_str(x: &String) { + //Nothing here either +} + +fn do_str_mut(x: &mut String) { + // no error here + //Nothing here either +} + +fn do_path(x: &PathBuf) { + //Nothing here either +} + +fn do_path_mut(x: &mut PathBuf) { + // no error here + //Nothing here either +} + +fn main() {} + +trait Foo { + type Item; + fn do_vec(x: &Vec); + fn do_item(x: &Self::Item); +} + +struct Bar; + +// no error, in trait impl (#425) +impl Foo for Bar { + type Item = Vec; + fn do_vec(x: &Vec) {} + fn do_item(x: &Vec) {} +} + +fn cloned(x: &Vec) -> Vec { + let e = x.clone(); + let f = e.clone(); // OK + let g = x; + let h = g.clone(); // Alas, we cannot reliably detect this without following data. + let i = (e).clone(); + x.clone() +} + +fn str_cloned(x: &String) -> String { + let a = x.clone(); + let b = x.clone(); + let c = b.clone(); + let d = a.clone().clone().clone(); + x.clone() +} + +fn path_cloned(x: &PathBuf) -> PathBuf { + let a = x.clone(); + let b = x.clone(); + let c = b.clone(); + let d = a.clone().clone().clone(); + x.clone() +} + +fn false_positive_capacity(x: &Vec, y: &String) { + let a = x.capacity(); + let b = y.clone(); + let c = y.as_str(); +} + +fn false_positive_capacity_too(x: &String) -> String { + if x.capacity() > 1024 { + panic!("Too large!"); + } + x.clone() +} + +#[allow(dead_code)] +fn test_cow_with_ref(c: &Cow<[i32]>) {} + +fn test_cow(c: Cow<[i32]>) { + let _c = c; +} + +trait Foo2 { + fn do_string(&self); +} + +// no error for &self references where self is of type String (#2293) +impl Foo2 for String { + fn do_string(&self) {} +} + +// Check that the allow attribute on parameters is honored +mod issue_5644 { + use std::borrow::Cow; + use std::path::PathBuf; + + fn allowed( + #[allow(clippy::ptr_arg)] _v: &Vec, + #[allow(clippy::ptr_arg)] _s: &String, + #[allow(clippy::ptr_arg)] _p: &PathBuf, + #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + ) { + } + + struct S {} + impl S { + fn allowed( + #[allow(clippy::ptr_arg)] _v: &Vec, + #[allow(clippy::ptr_arg)] _s: &String, + #[allow(clippy::ptr_arg)] _p: &PathBuf, + #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + ) { + } + } + + trait T { + fn allowed( + #[allow(clippy::ptr_arg)] _v: &Vec, + #[allow(clippy::ptr_arg)] _s: &String, + #[allow(clippy::ptr_arg)] _p: &PathBuf, + #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + ) { + } + } +} + +mod issue6509 { + use std::path::PathBuf; + + fn foo_vec(vec: &Vec) { + let _ = vec.clone().pop(); + let _ = vec.clone().clone(); + } + + fn foo_path(path: &PathBuf) { + let _ = path.clone().pop(); + let _ = path.clone().clone(); + } + + fn foo_str(str: &PathBuf) { + let _ = str.clone().pop(); + let _ = str.clone().clone(); + } +} diff --git a/src/tools/clippy/tests/ui/ptr_arg.stderr b/src/tools/clippy/tests/ui/ptr_arg.stderr new file mode 100644 index 0000000000..d302b16d4b --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_arg.stderr @@ -0,0 +1,175 @@ +error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices + --> $DIR/ptr_arg.rs:7:14 + | +LL | fn do_vec(x: &Vec) { + | ^^^^^^^^^ help: change this to: `&[i64]` + | + = note: `-D clippy::ptr-arg` implied by `-D warnings` + +error: writing `&String` instead of `&str` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:16:14 + | +LL | fn do_str(x: &String) { + | ^^^^^^^ help: change this to: `&str` + +error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:25:15 + | +LL | fn do_path(x: &PathBuf) { + | ^^^^^^^^ help: change this to: `&Path` + +error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices + --> $DIR/ptr_arg.rs:38:18 + | +LL | fn do_vec(x: &Vec); + | ^^^^^^^^^ help: change this to: `&[i64]` + +error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices + --> $DIR/ptr_arg.rs:51:14 + | +LL | fn cloned(x: &Vec) -> Vec { + | ^^^^^^^^ + | +help: change this to + | +LL | fn cloned(x: &[u8]) -> Vec { + | ^^^^^ +help: change `x.clone()` to + | +LL | let e = x.to_owned(); + | ^^^^^^^^^^^^ +help: change `x.clone()` to + | +LL | x.to_owned() + | + +error: writing `&String` instead of `&str` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:60:18 + | +LL | fn str_cloned(x: &String) -> String { + | ^^^^^^^ + | +help: change this to + | +LL | fn str_cloned(x: &str) -> String { + | ^^^^ +help: change `x.clone()` to + | +LL | let a = x.to_string(); + | ^^^^^^^^^^^^^ +help: change `x.clone()` to + | +LL | let b = x.to_string(); + | ^^^^^^^^^^^^^ +help: change `x.clone()` to + | +LL | x.to_string() + | + +error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:68:19 + | +LL | fn path_cloned(x: &PathBuf) -> PathBuf { + | ^^^^^^^^ + | +help: change this to + | +LL | fn path_cloned(x: &Path) -> PathBuf { + | ^^^^^ +help: change `x.clone()` to + | +LL | let a = x.to_path_buf(); + | ^^^^^^^^^^^^^^^ +help: change `x.clone()` to + | +LL | let b = x.to_path_buf(); + | ^^^^^^^^^^^^^^^ +help: change `x.clone()` to + | +LL | x.to_path_buf() + | + +error: writing `&String` instead of `&str` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:76:44 + | +LL | fn false_positive_capacity(x: &Vec, y: &String) { + | ^^^^^^^ + | +help: change this to + | +LL | fn false_positive_capacity(x: &Vec, y: &str) { + | ^^^^ +help: change `y.clone()` to + | +LL | let b = y.to_string(); + | ^^^^^^^^^^^^^ +help: change `y.as_str()` to + | +LL | let c = y; + | ^ + +error: using a reference to `Cow` is not recommended + --> $DIR/ptr_arg.rs:90:25 + | +LL | fn test_cow_with_ref(c: &Cow<[i32]>) {} + | ^^^^^^^^^^^ help: change this to: `&[i32]` + +error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices + --> $DIR/ptr_arg.rs:143:21 + | +LL | fn foo_vec(vec: &Vec) { + | ^^^^^^^^ + | +help: change this to + | +LL | fn foo_vec(vec: &[u8]) { + | ^^^^^ +help: change `vec.clone()` to + | +LL | let _ = vec.to_owned().pop(); + | ^^^^^^^^^^^^^^ +help: change `vec.clone()` to + | +LL | let _ = vec.to_owned().clone(); + | ^^^^^^^^^^^^^^ + +error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:148:23 + | +LL | fn foo_path(path: &PathBuf) { + | ^^^^^^^^ + | +help: change this to + | +LL | fn foo_path(path: &Path) { + | ^^^^^ +help: change `path.clone()` to + | +LL | let _ = path.to_path_buf().pop(); + | ^^^^^^^^^^^^^^^^^^ +help: change `path.clone()` to + | +LL | let _ = path.to_path_buf().clone(); + | ^^^^^^^^^^^^^^^^^^ + +error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:153:21 + | +LL | fn foo_str(str: &PathBuf) { + | ^^^^^^^^ + | +help: change this to + | +LL | fn foo_str(str: &Path) { + | ^^^^^ +help: change `str.clone()` to + | +LL | let _ = str.to_path_buf().pop(); + | ^^^^^^^^^^^^^^^^^ +help: change `str.clone()` to + | +LL | let _ = str.to_path_buf().clone(); + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.fixed b/src/tools/clippy/tests/ui/ptr_as_ptr.fixed new file mode 100644 index 0000000000..8346a9454f --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_as_ptr.fixed @@ -0,0 +1,50 @@ +// run-rustfix + +#![warn(clippy::ptr_as_ptr)] +#![feature(custom_inner_attributes)] + +fn main() { + let ptr: *const u32 = &42_u32; + let mut_ptr: *mut u32 = &mut 42_u32; + + let _ = ptr.cast::(); + let _ = mut_ptr.cast::(); + + // Make sure the lint can handle the difference in their operator precedences. + unsafe { + let ptr_ptr: *const *const u32 = &ptr; + let _ = (*ptr_ptr).cast::(); + } + + // Changes in mutability. Do not lint this. + let _ = ptr as *mut i32; + let _ = mut_ptr as *const i32; + + // `pointer::cast` cannot perform unsized coercions unlike `as`. Do not lint this. + let ptr_of_array: *const [u32; 4] = &[1, 2, 3, 4]; + let _ = ptr_of_array as *const [u32]; + let _ = ptr_of_array as *const dyn std::fmt::Debug; + + // Ensure the lint doesn't produce unnecessary turbofish for inferred types. + let _: *const i32 = ptr.cast(); + let _: *mut i32 = mut_ptr.cast(); +} + +fn _msrv_1_37() { + #![clippy::msrv = "1.37"] + let ptr: *const u32 = &42_u32; + let mut_ptr: *mut u32 = &mut 42_u32; + + // `pointer::cast` was stabilized in 1.38. Do not lint this + let _ = ptr as *const i32; + let _ = mut_ptr as *mut i32; +} + +fn _msrv_1_38() { + #![clippy::msrv = "1.38"] + let ptr: *const u32 = &42_u32; + let mut_ptr: *mut u32 = &mut 42_u32; + + let _ = ptr.cast::(); + let _ = mut_ptr.cast::(); +} diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.rs b/src/tools/clippy/tests/ui/ptr_as_ptr.rs new file mode 100644 index 0000000000..b68d4bc0aa --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_as_ptr.rs @@ -0,0 +1,50 @@ +// run-rustfix + +#![warn(clippy::ptr_as_ptr)] +#![feature(custom_inner_attributes)] + +fn main() { + let ptr: *const u32 = &42_u32; + let mut_ptr: *mut u32 = &mut 42_u32; + + let _ = ptr as *const i32; + let _ = mut_ptr as *mut i32; + + // Make sure the lint can handle the difference in their operator precedences. + unsafe { + let ptr_ptr: *const *const u32 = &ptr; + let _ = *ptr_ptr as *const i32; + } + + // Changes in mutability. Do not lint this. + let _ = ptr as *mut i32; + let _ = mut_ptr as *const i32; + + // `pointer::cast` cannot perform unsized coercions unlike `as`. Do not lint this. + let ptr_of_array: *const [u32; 4] = &[1, 2, 3, 4]; + let _ = ptr_of_array as *const [u32]; + let _ = ptr_of_array as *const dyn std::fmt::Debug; + + // Ensure the lint doesn't produce unnecessary turbofish for inferred types. + let _: *const i32 = ptr as *const _; + let _: *mut i32 = mut_ptr as _; +} + +fn _msrv_1_37() { + #![clippy::msrv = "1.37"] + let ptr: *const u32 = &42_u32; + let mut_ptr: *mut u32 = &mut 42_u32; + + // `pointer::cast` was stabilized in 1.38. Do not lint this + let _ = ptr as *const i32; + let _ = mut_ptr as *mut i32; +} + +fn _msrv_1_38() { + #![clippy::msrv = "1.38"] + let ptr: *const u32 = &42_u32; + let mut_ptr: *mut u32 = &mut 42_u32; + + let _ = ptr as *const i32; + let _ = mut_ptr as *mut i32; +} diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.stderr b/src/tools/clippy/tests/ui/ptr_as_ptr.stderr new file mode 100644 index 0000000000..854906dc11 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_as_ptr.stderr @@ -0,0 +1,46 @@ +error: `as` casting between raw pointers without changing its mutability + --> $DIR/ptr_as_ptr.rs:10:13 + | +LL | let _ = ptr as *const i32; + | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` + | + = note: `-D clippy::ptr-as-ptr` implied by `-D warnings` + +error: `as` casting between raw pointers without changing its mutability + --> $DIR/ptr_as_ptr.rs:11:13 + | +LL | let _ = mut_ptr as *mut i32; + | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` + +error: `as` casting between raw pointers without changing its mutability + --> $DIR/ptr_as_ptr.rs:16:17 + | +LL | let _ = *ptr_ptr as *const i32; + | ^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `(*ptr_ptr).cast::()` + +error: `as` casting between raw pointers without changing its mutability + --> $DIR/ptr_as_ptr.rs:29:25 + | +LL | let _: *const i32 = ptr as *const _; + | ^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast()` + +error: `as` casting between raw pointers without changing its mutability + --> $DIR/ptr_as_ptr.rs:30:23 + | +LL | let _: *mut i32 = mut_ptr as _; + | ^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast()` + +error: `as` casting between raw pointers without changing its mutability + --> $DIR/ptr_as_ptr.rs:48:13 + | +LL | let _ = ptr as *const i32; + | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` + +error: `as` casting between raw pointers without changing its mutability + --> $DIR/ptr_as_ptr.rs:49:13 + | +LL | let _ = mut_ptr as *mut i32; + | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/ptr_eq.fixed b/src/tools/clippy/tests/ui/ptr_eq.fixed new file mode 100644 index 0000000000..209081e6e8 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_eq.fixed @@ -0,0 +1,38 @@ +// run-rustfix +#![warn(clippy::ptr_eq)] + +macro_rules! mac { + ($a:expr, $b:expr) => { + $a as *const _ as usize == $b as *const _ as usize + }; +} + +macro_rules! another_mac { + ($a:expr, $b:expr) => { + $a as *const _ == $b as *const _ + }; +} + +fn main() { + let a = &[1, 2, 3]; + let b = &[1, 2, 3]; + + let _ = std::ptr::eq(a, b); + let _ = std::ptr::eq(a, b); + let _ = a.as_ptr() == b as *const _; + let _ = a.as_ptr() == b.as_ptr(); + + // Do not lint + + let _ = mac!(a, b); + let _ = another_mac!(a, b); + + let a = &mut [1, 2, 3]; + let b = &mut [1, 2, 3]; + + let _ = a.as_mut_ptr() == b as *mut [i32] as *mut _; + let _ = a.as_mut_ptr() == b.as_mut_ptr(); + + let _ = a == b; + let _ = core::ptr::eq(a, b); +} diff --git a/src/tools/clippy/tests/ui/ptr_eq.rs b/src/tools/clippy/tests/ui/ptr_eq.rs new file mode 100644 index 0000000000..6916287080 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_eq.rs @@ -0,0 +1,38 @@ +// run-rustfix +#![warn(clippy::ptr_eq)] + +macro_rules! mac { + ($a:expr, $b:expr) => { + $a as *const _ as usize == $b as *const _ as usize + }; +} + +macro_rules! another_mac { + ($a:expr, $b:expr) => { + $a as *const _ == $b as *const _ + }; +} + +fn main() { + let a = &[1, 2, 3]; + let b = &[1, 2, 3]; + + let _ = a as *const _ as usize == b as *const _ as usize; + let _ = a as *const _ == b as *const _; + let _ = a.as_ptr() == b as *const _; + let _ = a.as_ptr() == b.as_ptr(); + + // Do not lint + + let _ = mac!(a, b); + let _ = another_mac!(a, b); + + let a = &mut [1, 2, 3]; + let b = &mut [1, 2, 3]; + + let _ = a.as_mut_ptr() == b as *mut [i32] as *mut _; + let _ = a.as_mut_ptr() == b.as_mut_ptr(); + + let _ = a == b; + let _ = core::ptr::eq(a, b); +} diff --git a/src/tools/clippy/tests/ui/ptr_eq.stderr b/src/tools/clippy/tests/ui/ptr_eq.stderr new file mode 100644 index 0000000000..45d8c60382 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_eq.stderr @@ -0,0 +1,16 @@ +error: use `std::ptr::eq` when comparing raw pointers + --> $DIR/ptr_eq.rs:20:13 + | +LL | let _ = a as *const _ as usize == b as *const _ as usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::eq(a, b)` + | + = note: `-D clippy::ptr-eq` implied by `-D warnings` + +error: use `std::ptr::eq` when comparing raw pointers + --> $DIR/ptr_eq.rs:21:13 + | +LL | let _ = a as *const _ == b as *const _; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::eq(a, b)` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/ptr_offset_with_cast.fixed b/src/tools/clippy/tests/ui/ptr_offset_with_cast.fixed new file mode 100644 index 0000000000..718e391e8b --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_offset_with_cast.fixed @@ -0,0 +1,20 @@ +// run-rustfix + +fn main() { + let vec = vec![b'a', b'b', b'c']; + let ptr = vec.as_ptr(); + + let offset_u8 = 1_u8; + let offset_usize = 1_usize; + let offset_isize = 1_isize; + + unsafe { + let _ = ptr.add(offset_usize); + let _ = ptr.offset(offset_isize as isize); + let _ = ptr.offset(offset_u8 as isize); + + let _ = ptr.wrapping_add(offset_usize); + let _ = ptr.wrapping_offset(offset_isize as isize); + let _ = ptr.wrapping_offset(offset_u8 as isize); + } +} diff --git a/src/tools/clippy/tests/ui/ptr_offset_with_cast.rs b/src/tools/clippy/tests/ui/ptr_offset_with_cast.rs new file mode 100644 index 0000000000..f613742c74 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_offset_with_cast.rs @@ -0,0 +1,20 @@ +// run-rustfix + +fn main() { + let vec = vec![b'a', b'b', b'c']; + let ptr = vec.as_ptr(); + + let offset_u8 = 1_u8; + let offset_usize = 1_usize; + let offset_isize = 1_isize; + + unsafe { + let _ = ptr.offset(offset_usize as isize); + let _ = ptr.offset(offset_isize as isize); + let _ = ptr.offset(offset_u8 as isize); + + let _ = ptr.wrapping_offset(offset_usize as isize); + let _ = ptr.wrapping_offset(offset_isize as isize); + let _ = ptr.wrapping_offset(offset_u8 as isize); + } +} diff --git a/src/tools/clippy/tests/ui/ptr_offset_with_cast.stderr b/src/tools/clippy/tests/ui/ptr_offset_with_cast.stderr new file mode 100644 index 0000000000..fd45224ca0 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_offset_with_cast.stderr @@ -0,0 +1,16 @@ +error: use of `offset` with a `usize` casted to an `isize` + --> $DIR/ptr_offset_with_cast.rs:12:17 + | +LL | let _ = ptr.offset(offset_usize as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.add(offset_usize)` + | + = note: `-D clippy::ptr-offset-with-cast` implied by `-D warnings` + +error: use of `wrapping_offset` with a `usize` casted to an `isize` + --> $DIR/ptr_offset_with_cast.rs:16:17 + | +LL | let _ = ptr.wrapping_offset(offset_usize as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.wrapping_add(offset_usize)` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/question_mark.fixed b/src/tools/clippy/tests/ui/question_mark.fixed new file mode 100644 index 0000000000..0b5746cb52 --- /dev/null +++ b/src/tools/clippy/tests/ui/question_mark.fixed @@ -0,0 +1,126 @@ +// run-rustfix +#![allow(unreachable_code)] +#![allow(clippy::unnecessary_wraps)] + +fn some_func(a: Option) -> Option { + a?; + + a +} + +fn some_other_func(a: Option) -> Option { + if a.is_none() { + return None; + } else { + return Some(0); + } + unreachable!() +} + +pub enum SeemsOption { + Some(T), + None, +} + +impl SeemsOption { + pub fn is_none(&self) -> bool { + match *self { + SeemsOption::None => true, + SeemsOption::Some(_) => false, + } + } +} + +fn returns_something_similar_to_option(a: SeemsOption) -> SeemsOption { + if a.is_none() { + return SeemsOption::None; + } + + a +} + +pub struct CopyStruct { + pub opt: Option, +} + +impl CopyStruct { + #[rustfmt::skip] + pub fn func(&self) -> Option { + (self.opt)?; + + self.opt?; + + let _ = Some(self.opt?); + + let _ = self.opt?; + + self.opt + } +} + +#[derive(Clone)] +pub struct MoveStruct { + pub opt: Option>, +} + +impl MoveStruct { + pub fn ref_func(&self) -> Option> { + self.opt.as_ref()?; + + self.opt.clone() + } + + pub fn mov_func_reuse(self) -> Option> { + self.opt.as_ref()?; + + self.opt + } + + pub fn mov_func_no_use(self) -> Option> { + self.opt.as_ref()?; + Some(Vec::new()) + } + + pub fn if_let_ref_func(self) -> Option> { + let v: &Vec<_> = self.opt.as_ref()?; + + Some(v.clone()) + } + + pub fn if_let_mov_func(self) -> Option> { + let v = self.opt?; + + Some(v) + } +} + +fn func() -> Option { + fn f() -> Option { + Some(String::new()) + } + + f()?; + + Some(0) +} + +fn main() { + some_func(Some(42)); + some_func(None); + some_other_func(Some(42)); + + let copy_struct = CopyStruct { opt: Some(54) }; + copy_struct.func(); + + let move_struct = MoveStruct { + opt: Some(vec![42, 1337]), + }; + move_struct.ref_func(); + move_struct.clone().mov_func_reuse(); + move_struct.mov_func_no_use(); + + let so = SeemsOption::Some(45); + returns_something_similar_to_option(so); + + func(); +} diff --git a/src/tools/clippy/tests/ui/question_mark.rs b/src/tools/clippy/tests/ui/question_mark.rs new file mode 100644 index 0000000000..0f0825c933 --- /dev/null +++ b/src/tools/clippy/tests/ui/question_mark.rs @@ -0,0 +1,156 @@ +// run-rustfix +#![allow(unreachable_code)] +#![allow(clippy::unnecessary_wraps)] + +fn some_func(a: Option) -> Option { + if a.is_none() { + return None; + } + + a +} + +fn some_other_func(a: Option) -> Option { + if a.is_none() { + return None; + } else { + return Some(0); + } + unreachable!() +} + +pub enum SeemsOption { + Some(T), + None, +} + +impl SeemsOption { + pub fn is_none(&self) -> bool { + match *self { + SeemsOption::None => true, + SeemsOption::Some(_) => false, + } + } +} + +fn returns_something_similar_to_option(a: SeemsOption) -> SeemsOption { + if a.is_none() { + return SeemsOption::None; + } + + a +} + +pub struct CopyStruct { + pub opt: Option, +} + +impl CopyStruct { + #[rustfmt::skip] + pub fn func(&self) -> Option { + if (self.opt).is_none() { + return None; + } + + if self.opt.is_none() { + return None + } + + let _ = if self.opt.is_none() { + return None; + } else { + self.opt + }; + + let _ = if let Some(x) = self.opt { + x + } else { + return None; + }; + + self.opt + } +} + +#[derive(Clone)] +pub struct MoveStruct { + pub opt: Option>, +} + +impl MoveStruct { + pub fn ref_func(&self) -> Option> { + if self.opt.is_none() { + return None; + } + + self.opt.clone() + } + + pub fn mov_func_reuse(self) -> Option> { + if self.opt.is_none() { + return None; + } + + self.opt + } + + pub fn mov_func_no_use(self) -> Option> { + if self.opt.is_none() { + return None; + } + Some(Vec::new()) + } + + pub fn if_let_ref_func(self) -> Option> { + let v: &Vec<_> = if let Some(ref v) = self.opt { + v + } else { + return None; + }; + + Some(v.clone()) + } + + pub fn if_let_mov_func(self) -> Option> { + let v = if let Some(v) = self.opt { + v + } else { + return None; + }; + + Some(v) + } +} + +fn func() -> Option { + fn f() -> Option { + Some(String::new()) + } + + if f().is_none() { + return None; + } + + Some(0) +} + +fn main() { + some_func(Some(42)); + some_func(None); + some_other_func(Some(42)); + + let copy_struct = CopyStruct { opt: Some(54) }; + copy_struct.func(); + + let move_struct = MoveStruct { + opt: Some(vec![42, 1337]), + }; + move_struct.ref_func(); + move_struct.clone().mov_func_reuse(); + move_struct.mov_func_no_use(); + + let so = SeemsOption::Some(45); + returns_something_similar_to_option(so); + + func(); +} diff --git a/src/tools/clippy/tests/ui/question_mark.stderr b/src/tools/clippy/tests/ui/question_mark.stderr new file mode 100644 index 0000000000..6f330cfa38 --- /dev/null +++ b/src/tools/clippy/tests/ui/question_mark.stderr @@ -0,0 +1,104 @@ +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:6:5 + | +LL | / if a.is_none() { +LL | | return None; +LL | | } + | |_____^ help: replace it with: `a?;` + | + = note: `-D clippy::question-mark` implied by `-D warnings` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:51:9 + | +LL | / if (self.opt).is_none() { +LL | | return None; +LL | | } + | |_________^ help: replace it with: `(self.opt)?;` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:55:9 + | +LL | / if self.opt.is_none() { +LL | | return None +LL | | } + | |_________^ help: replace it with: `self.opt?;` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:59:17 + | +LL | let _ = if self.opt.is_none() { + | _________________^ +LL | | return None; +LL | | } else { +LL | | self.opt +LL | | }; + | |_________^ help: replace it with: `Some(self.opt?)` + +error: this if-let-else may be rewritten with the `?` operator + --> $DIR/question_mark.rs:65:17 + | +LL | let _ = if let Some(x) = self.opt { + | _________________^ +LL | | x +LL | | } else { +LL | | return None; +LL | | }; + | |_________^ help: replace it with: `self.opt?` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:82:9 + | +LL | / if self.opt.is_none() { +LL | | return None; +LL | | } + | |_________^ help: replace it with: `self.opt.as_ref()?;` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:90:9 + | +LL | / if self.opt.is_none() { +LL | | return None; +LL | | } + | |_________^ help: replace it with: `self.opt.as_ref()?;` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:98:9 + | +LL | / if self.opt.is_none() { +LL | | return None; +LL | | } + | |_________^ help: replace it with: `self.opt.as_ref()?;` + +error: this if-let-else may be rewritten with the `?` operator + --> $DIR/question_mark.rs:105:26 + | +LL | let v: &Vec<_> = if let Some(ref v) = self.opt { + | __________________________^ +LL | | v +LL | | } else { +LL | | return None; +LL | | }; + | |_________^ help: replace it with: `self.opt.as_ref()?` + +error: this if-let-else may be rewritten with the `?` operator + --> $DIR/question_mark.rs:115:17 + | +LL | let v = if let Some(v) = self.opt { + | _________________^ +LL | | v +LL | | } else { +LL | | return None; +LL | | }; + | |_________^ help: replace it with: `self.opt?` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:130:5 + | +LL | / if f().is_none() { +LL | | return None; +LL | | } + | |_____^ help: replace it with: `f()?;` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/range.rs b/src/tools/clippy/tests/ui/range.rs new file mode 100644 index 0000000000..628282509c --- /dev/null +++ b/src/tools/clippy/tests/ui/range.rs @@ -0,0 +1,16 @@ +#[warn(clippy::range_zip_with_len)] +fn main() { + let v1 = vec![1, 2, 3]; + let v2 = vec![4, 5]; + let _x = v1.iter().zip(0..v1.len()); + let _y = v1.iter().zip(0..v2.len()); // No error +} + +#[allow(unused)] +fn no_panic_with_fake_range_types() { + struct Range { + foo: i32, + } + + let _ = Range { foo: 0 }; +} diff --git a/src/tools/clippy/tests/ui/range.stderr b/src/tools/clippy/tests/ui/range.stderr new file mode 100644 index 0000000000..dcb5061371 --- /dev/null +++ b/src/tools/clippy/tests/ui/range.stderr @@ -0,0 +1,10 @@ +error: it is more idiomatic to use `v1.iter().enumerate()` + --> $DIR/range.rs:5:14 + | +LL | let _x = v1.iter().zip(0..v1.len()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::range-zip-with-len` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/range_contains.fixed b/src/tools/clippy/tests/ui/range_contains.fixed new file mode 100644 index 0000000000..47c974e614 --- /dev/null +++ b/src/tools/clippy/tests/ui/range_contains.fixed @@ -0,0 +1,51 @@ +// run-rustfix + +#[warn(clippy::manual_range_contains)] +#[allow(unused)] +#[allow(clippy::no_effect)] +#[allow(clippy::short_circuit_statement)] +#[allow(clippy::unnecessary_operation)] +fn main() { + let x = 9_u32; + + // order shouldn't matter + (8..12).contains(&x); + (21..42).contains(&x); + (1..100).contains(&x); + + // also with inclusive ranges + (9..=99).contains(&x); + (1..=33).contains(&x); + (1..=999).contains(&x); + + // and the outside + !(8..12).contains(&x); + !(21..42).contains(&x); + !(1..100).contains(&x); + + // also with the outside of inclusive ranges + !(9..=99).contains(&x); + !(1..=33).contains(&x); + !(1..=999).contains(&x); + + // not a range.contains + x > 8 && x < 12; // lower bound not inclusive + x < 8 && x <= 12; // same direction + x >= 12 && 12 >= x; // same bounds + x < 8 && x > 12; // wrong direction + + x <= 8 || x >= 12; + x >= 8 || x >= 12; + x < 12 || 12 < x; + x >= 8 || x <= 12; + + // Fix #6315 + let y = 3.; + (0. ..1.).contains(&y); + !(0. ..=1.).contains(&y); +} + +// Fix #6373 +pub const fn in_range(a: i32) -> bool { + 3 <= a && a <= 20 +} diff --git a/src/tools/clippy/tests/ui/range_contains.rs b/src/tools/clippy/tests/ui/range_contains.rs new file mode 100644 index 0000000000..835deced5e --- /dev/null +++ b/src/tools/clippy/tests/ui/range_contains.rs @@ -0,0 +1,51 @@ +// run-rustfix + +#[warn(clippy::manual_range_contains)] +#[allow(unused)] +#[allow(clippy::no_effect)] +#[allow(clippy::short_circuit_statement)] +#[allow(clippy::unnecessary_operation)] +fn main() { + let x = 9_u32; + + // order shouldn't matter + x >= 8 && x < 12; + x < 42 && x >= 21; + 100 > x && 1 <= x; + + // also with inclusive ranges + x >= 9 && x <= 99; + x <= 33 && x >= 1; + 999 >= x && 1 <= x; + + // and the outside + x < 8 || x >= 12; + x >= 42 || x < 21; + 100 <= x || 1 > x; + + // also with the outside of inclusive ranges + x < 9 || x > 99; + x > 33 || x < 1; + 999 < x || 1 > x; + + // not a range.contains + x > 8 && x < 12; // lower bound not inclusive + x < 8 && x <= 12; // same direction + x >= 12 && 12 >= x; // same bounds + x < 8 && x > 12; // wrong direction + + x <= 8 || x >= 12; + x >= 8 || x >= 12; + x < 12 || 12 < x; + x >= 8 || x <= 12; + + // Fix #6315 + let y = 3.; + y >= 0. && y < 1.; + y < 0. || y > 1.; +} + +// Fix #6373 +pub const fn in_range(a: i32) -> bool { + 3 <= a && a <= 20 +} diff --git a/src/tools/clippy/tests/ui/range_contains.stderr b/src/tools/clippy/tests/ui/range_contains.stderr new file mode 100644 index 0000000000..bc79f1bca8 --- /dev/null +++ b/src/tools/clippy/tests/ui/range_contains.stderr @@ -0,0 +1,88 @@ +error: manual `Range::contains` implementation + --> $DIR/range_contains.rs:12:5 + | +LL | x >= 8 && x < 12; + | ^^^^^^^^^^^^^^^^ help: use: `(8..12).contains(&x)` + | + = note: `-D clippy::manual-range-contains` implied by `-D warnings` + +error: manual `Range::contains` implementation + --> $DIR/range_contains.rs:13:5 + | +LL | x < 42 && x >= 21; + | ^^^^^^^^^^^^^^^^^ help: use: `(21..42).contains(&x)` + +error: manual `Range::contains` implementation + --> $DIR/range_contains.rs:14:5 + | +LL | 100 > x && 1 <= x; + | ^^^^^^^^^^^^^^^^^ help: use: `(1..100).contains(&x)` + +error: manual `RangeInclusive::contains` implementation + --> $DIR/range_contains.rs:17:5 + | +LL | x >= 9 && x <= 99; + | ^^^^^^^^^^^^^^^^^ help: use: `(9..=99).contains(&x)` + +error: manual `RangeInclusive::contains` implementation + --> $DIR/range_contains.rs:18:5 + | +LL | x <= 33 && x >= 1; + | ^^^^^^^^^^^^^^^^^ help: use: `(1..=33).contains(&x)` + +error: manual `RangeInclusive::contains` implementation + --> $DIR/range_contains.rs:19:5 + | +LL | 999 >= x && 1 <= x; + | ^^^^^^^^^^^^^^^^^^ help: use: `(1..=999).contains(&x)` + +error: manual `!Range::contains` implementation + --> $DIR/range_contains.rs:22:5 + | +LL | x < 8 || x >= 12; + | ^^^^^^^^^^^^^^^^ help: use: `!(8..12).contains(&x)` + +error: manual `!Range::contains` implementation + --> $DIR/range_contains.rs:23:5 + | +LL | x >= 42 || x < 21; + | ^^^^^^^^^^^^^^^^^ help: use: `!(21..42).contains(&x)` + +error: manual `!Range::contains` implementation + --> $DIR/range_contains.rs:24:5 + | +LL | 100 <= x || 1 > x; + | ^^^^^^^^^^^^^^^^^ help: use: `!(1..100).contains(&x)` + +error: manual `!RangeInclusive::contains` implementation + --> $DIR/range_contains.rs:27:5 + | +LL | x < 9 || x > 99; + | ^^^^^^^^^^^^^^^ help: use: `!(9..=99).contains(&x)` + +error: manual `!RangeInclusive::contains` implementation + --> $DIR/range_contains.rs:28:5 + | +LL | x > 33 || x < 1; + | ^^^^^^^^^^^^^^^ help: use: `!(1..=33).contains(&x)` + +error: manual `!RangeInclusive::contains` implementation + --> $DIR/range_contains.rs:29:5 + | +LL | 999 < x || 1 > x; + | ^^^^^^^^^^^^^^^^ help: use: `!(1..=999).contains(&x)` + +error: manual `Range::contains` implementation + --> $DIR/range_contains.rs:44:5 + | +LL | y >= 0. && y < 1.; + | ^^^^^^^^^^^^^^^^^ help: use: `(0. ..1.).contains(&y)` + +error: manual `!RangeInclusive::contains` implementation + --> $DIR/range_contains.rs:45:5 + | +LL | y < 0. || y > 1.; + | ^^^^^^^^^^^^^^^^ help: use: `!(0. ..=1.).contains(&y)` + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.fixed b/src/tools/clippy/tests/ui/range_plus_minus_one.fixed new file mode 100644 index 0000000000..19b253b0fe --- /dev/null +++ b/src/tools/clippy/tests/ui/range_plus_minus_one.fixed @@ -0,0 +1,42 @@ +// run-rustfix + +#![allow(unused_parens)] + +fn f() -> usize { + 42 +} + +#[warn(clippy::range_plus_one)] +#[warn(clippy::range_minus_one)] +fn main() { + for _ in 0..2 {} + for _ in 0..=2 {} + + for _ in 0..=3 {} + for _ in 0..=3 + 1 {} + + for _ in 0..=5 {} + for _ in 0..=1 + 5 {} + + for _ in 1..=1 {} + for _ in 1..=1 + 1 {} + + for _ in 0..13 + 13 {} + for _ in 0..=13 - 7 {} + + for _ in 0..=f() {} + for _ in 0..=(1 + f()) {} + + let _ = ..11 - 1; + let _ = ..11; + let _ = ..11; + let _ = (1..=11); + let _ = ((f() + 1)..=f()); + + const ONE: usize = 1; + // integer consts are linted, too + for _ in 1..=ONE {} + + let mut vec: Vec<()> = std::vec::Vec::new(); + vec.drain(..); +} diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.rs b/src/tools/clippy/tests/ui/range_plus_minus_one.rs new file mode 100644 index 0000000000..7d03411754 --- /dev/null +++ b/src/tools/clippy/tests/ui/range_plus_minus_one.rs @@ -0,0 +1,42 @@ +// run-rustfix + +#![allow(unused_parens)] + +fn f() -> usize { + 42 +} + +#[warn(clippy::range_plus_one)] +#[warn(clippy::range_minus_one)] +fn main() { + for _ in 0..2 {} + for _ in 0..=2 {} + + for _ in 0..3 + 1 {} + for _ in 0..=3 + 1 {} + + for _ in 0..1 + 5 {} + for _ in 0..=1 + 5 {} + + for _ in 1..1 + 1 {} + for _ in 1..=1 + 1 {} + + for _ in 0..13 + 13 {} + for _ in 0..=13 - 7 {} + + for _ in 0..(1 + f()) {} + for _ in 0..=(1 + f()) {} + + let _ = ..11 - 1; + let _ = ..=11 - 1; + let _ = ..=(11 - 1); + let _ = (1..11 + 1); + let _ = (f() + 1)..(f() + 1); + + const ONE: usize = 1; + // integer consts are linted, too + for _ in 1..ONE + ONE {} + + let mut vec: Vec<()> = std::vec::Vec::new(); + vec.drain(..); +} diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.stderr b/src/tools/clippy/tests/ui/range_plus_minus_one.stderr new file mode 100644 index 0000000000..fb4f165859 --- /dev/null +++ b/src/tools/clippy/tests/ui/range_plus_minus_one.stderr @@ -0,0 +1,60 @@ +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:15:14 + | +LL | for _ in 0..3 + 1 {} + | ^^^^^^^^ help: use: `0..=3` + | + = note: `-D clippy::range-plus-one` implied by `-D warnings` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:18:14 + | +LL | for _ in 0..1 + 5 {} + | ^^^^^^^^ help: use: `0..=5` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:21:14 + | +LL | for _ in 1..1 + 1 {} + | ^^^^^^^^ help: use: `1..=1` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:27:14 + | +LL | for _ in 0..(1 + f()) {} + | ^^^^^^^^^^^^ help: use: `0..=f()` + +error: an exclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:31:13 + | +LL | let _ = ..=11 - 1; + | ^^^^^^^^^ help: use: `..11` + | + = note: `-D clippy::range-minus-one` implied by `-D warnings` + +error: an exclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:32:13 + | +LL | let _ = ..=(11 - 1); + | ^^^^^^^^^^^ help: use: `..11` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:33:13 + | +LL | let _ = (1..11 + 1); + | ^^^^^^^^^^^ help: use: `(1..=11)` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:34:13 + | +LL | let _ = (f() + 1)..(f() + 1); + | ^^^^^^^^^^^^^^^^^^^^ help: use: `((f() + 1)..=f())` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:38:14 + | +LL | for _ in 1..ONE + ONE {} + | ^^^^^^^^^^^^ help: use: `1..=ONE` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/rc_buffer.rs b/src/tools/clippy/tests/ui/rc_buffer.rs new file mode 100644 index 0000000000..1fa9864393 --- /dev/null +++ b/src/tools/clippy/tests/ui/rc_buffer.rs @@ -0,0 +1,26 @@ +#![warn(clippy::rc_buffer)] + +use std::cell::RefCell; +use std::ffi::OsString; +use std::path::PathBuf; +use std::rc::Rc; + +struct S { + // triggers lint + bad1: Rc, + bad2: Rc, + bad3: Rc>, + bad4: Rc, + // does not trigger lint + good1: Rc>, +} + +// triggers lint +fn func_bad1(_: Rc) {} +fn func_bad2(_: Rc) {} +fn func_bad3(_: Rc>) {} +fn func_bad4(_: Rc) {} +// does not trigger lint +fn func_good1(_: Rc>) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/rc_buffer.stderr b/src/tools/clippy/tests/ui/rc_buffer.stderr new file mode 100644 index 0000000000..e4cc169af0 --- /dev/null +++ b/src/tools/clippy/tests/ui/rc_buffer.stderr @@ -0,0 +1,52 @@ +error: usage of `Rc` when T is a buffer type + --> $DIR/rc_buffer.rs:10:11 + | +LL | bad1: Rc, + | ^^^^^^^^^^ help: try: `Rc` + | + = note: `-D clippy::rc-buffer` implied by `-D warnings` + +error: usage of `Rc` when T is a buffer type + --> $DIR/rc_buffer.rs:11:11 + | +LL | bad2: Rc, + | ^^^^^^^^^^^ help: try: `Rc` + +error: usage of `Rc` when T is a buffer type + --> $DIR/rc_buffer.rs:12:11 + | +LL | bad3: Rc>, + | ^^^^^^^^^^^ help: try: `Rc<[u8]>` + +error: usage of `Rc` when T is a buffer type + --> $DIR/rc_buffer.rs:13:11 + | +LL | bad4: Rc, + | ^^^^^^^^^^^^ help: try: `Rc` + +error: usage of `Rc` when T is a buffer type + --> $DIR/rc_buffer.rs:19:17 + | +LL | fn func_bad1(_: Rc) {} + | ^^^^^^^^^^ help: try: `Rc` + +error: usage of `Rc` when T is a buffer type + --> $DIR/rc_buffer.rs:20:17 + | +LL | fn func_bad2(_: Rc) {} + | ^^^^^^^^^^^ help: try: `Rc` + +error: usage of `Rc` when T is a buffer type + --> $DIR/rc_buffer.rs:21:17 + | +LL | fn func_bad3(_: Rc>) {} + | ^^^^^^^^^^^ help: try: `Rc<[u8]>` + +error: usage of `Rc` when T is a buffer type + --> $DIR/rc_buffer.rs:22:17 + | +LL | fn func_bad4(_: Rc) {} + | ^^^^^^^^^^^^ help: try: `Rc` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/rc_buffer_arc.rs b/src/tools/clippy/tests/ui/rc_buffer_arc.rs new file mode 100644 index 0000000000..5d58658481 --- /dev/null +++ b/src/tools/clippy/tests/ui/rc_buffer_arc.rs @@ -0,0 +1,25 @@ +#![warn(clippy::rc_buffer)] + +use std::ffi::OsString; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; + +struct S { + // triggers lint + bad1: Arc, + bad2: Arc, + bad3: Arc>, + bad4: Arc, + // does not trigger lint + good1: Arc>, +} + +// triggers lint +fn func_bad1(_: Arc) {} +fn func_bad2(_: Arc) {} +fn func_bad3(_: Arc>) {} +fn func_bad4(_: Arc) {} +// does not trigger lint +fn func_good1(_: Arc>) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/rc_buffer_arc.stderr b/src/tools/clippy/tests/ui/rc_buffer_arc.stderr new file mode 100644 index 0000000000..8252270d2a --- /dev/null +++ b/src/tools/clippy/tests/ui/rc_buffer_arc.stderr @@ -0,0 +1,52 @@ +error: usage of `Arc` when T is a buffer type + --> $DIR/rc_buffer_arc.rs:9:11 + | +LL | bad1: Arc, + | ^^^^^^^^^^^ help: try: `Arc` + | + = note: `-D clippy::rc-buffer` implied by `-D warnings` + +error: usage of `Arc` when T is a buffer type + --> $DIR/rc_buffer_arc.rs:10:11 + | +LL | bad2: Arc, + | ^^^^^^^^^^^^ help: try: `Arc` + +error: usage of `Arc` when T is a buffer type + --> $DIR/rc_buffer_arc.rs:11:11 + | +LL | bad3: Arc>, + | ^^^^^^^^^^^^ help: try: `Arc<[u8]>` + +error: usage of `Arc` when T is a buffer type + --> $DIR/rc_buffer_arc.rs:12:11 + | +LL | bad4: Arc, + | ^^^^^^^^^^^^^ help: try: `Arc` + +error: usage of `Arc` when T is a buffer type + --> $DIR/rc_buffer_arc.rs:18:17 + | +LL | fn func_bad1(_: Arc) {} + | ^^^^^^^^^^^ help: try: `Arc` + +error: usage of `Arc` when T is a buffer type + --> $DIR/rc_buffer_arc.rs:19:17 + | +LL | fn func_bad2(_: Arc) {} + | ^^^^^^^^^^^^ help: try: `Arc` + +error: usage of `Arc` when T is a buffer type + --> $DIR/rc_buffer_arc.rs:20:17 + | +LL | fn func_bad3(_: Arc>) {} + | ^^^^^^^^^^^^ help: try: `Arc<[u8]>` + +error: usage of `Arc` when T is a buffer type + --> $DIR/rc_buffer_arc.rs:21:17 + | +LL | fn func_bad4(_: Arc) {} + | ^^^^^^^^^^^^^ help: try: `Arc` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/rc_buffer_redefined_string.rs b/src/tools/clippy/tests/ui/rc_buffer_redefined_string.rs new file mode 100644 index 0000000000..5d31a848cf --- /dev/null +++ b/src/tools/clippy/tests/ui/rc_buffer_redefined_string.rs @@ -0,0 +1,12 @@ +#![warn(clippy::rc_buffer)] + +use std::rc::Rc; + +struct String; + +struct S { + // does not trigger lint + good1: Rc, +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/rc_buffer_redefined_string.stderr b/src/tools/clippy/tests/ui/rc_buffer_redefined_string.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tools/clippy/tests/ui/redundant_allocation.fixed b/src/tools/clippy/tests/ui/redundant_allocation.fixed new file mode 100644 index 0000000000..6514fd6d1a --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_allocation.fixed @@ -0,0 +1,48 @@ +// run-rustfix +#![warn(clippy::all)] +#![allow(clippy::boxed_local, clippy::needless_pass_by_value)] +#![allow(clippy::blacklisted_name, unused_variables, dead_code)] + +use std::boxed::Box; +use std::rc::Rc; + +pub struct MyStruct {} + +pub struct SubT { + foo: T, +} + +pub enum MyEnum { + One, + Two, +} + +// Rc<&T> + +pub fn test1(foo: &T) {} + +pub fn test2(foo: &MyStruct) {} + +pub fn test3(foo: &MyEnum) {} + +pub fn test4_neg(foo: Rc>) {} + +// Rc> + +pub fn test5(a: Rc) {} + +// Rc> + +pub fn test6(a: Rc) {} + +// Box<&T> + +pub fn test7(foo: &T) {} + +pub fn test8(foo: &MyStruct) {} + +pub fn test9(foo: &MyEnum) {} + +pub fn test10_neg(foo: Box>) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/redundant_allocation.rs b/src/tools/clippy/tests/ui/redundant_allocation.rs new file mode 100644 index 0000000000..677b3e56d4 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_allocation.rs @@ -0,0 +1,48 @@ +// run-rustfix +#![warn(clippy::all)] +#![allow(clippy::boxed_local, clippy::needless_pass_by_value)] +#![allow(clippy::blacklisted_name, unused_variables, dead_code)] + +use std::boxed::Box; +use std::rc::Rc; + +pub struct MyStruct {} + +pub struct SubT { + foo: T, +} + +pub enum MyEnum { + One, + Two, +} + +// Rc<&T> + +pub fn test1(foo: Rc<&T>) {} + +pub fn test2(foo: Rc<&MyStruct>) {} + +pub fn test3(foo: Rc<&MyEnum>) {} + +pub fn test4_neg(foo: Rc>) {} + +// Rc> + +pub fn test5(a: Rc>) {} + +// Rc> + +pub fn test6(a: Rc>) {} + +// Box<&T> + +pub fn test7(foo: Box<&T>) {} + +pub fn test8(foo: Box<&MyStruct>) {} + +pub fn test9(foo: Box<&MyEnum>) {} + +pub fn test10_neg(foo: Box>) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/redundant_allocation.stderr b/src/tools/clippy/tests/ui/redundant_allocation.stderr new file mode 100644 index 0000000000..92e4f67f5d --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_allocation.stderr @@ -0,0 +1,52 @@ +error: usage of `Rc<&T>` + --> $DIR/redundant_allocation.rs:22:22 + | +LL | pub fn test1(foo: Rc<&T>) {} + | ^^^^^^ help: try: `&T` + | + = note: `-D clippy::redundant-allocation` implied by `-D warnings` + +error: usage of `Rc<&T>` + --> $DIR/redundant_allocation.rs:24:19 + | +LL | pub fn test2(foo: Rc<&MyStruct>) {} + | ^^^^^^^^^^^^^ help: try: `&MyStruct` + +error: usage of `Rc<&T>` + --> $DIR/redundant_allocation.rs:26:19 + | +LL | pub fn test3(foo: Rc<&MyEnum>) {} + | ^^^^^^^^^^^ help: try: `&MyEnum` + +error: usage of `Rc>` + --> $DIR/redundant_allocation.rs:32:17 + | +LL | pub fn test5(a: Rc>) {} + | ^^^^^^^^^^^^ help: try: `Rc` + +error: usage of `Rc>` + --> $DIR/redundant_allocation.rs:36:17 + | +LL | pub fn test6(a: Rc>) {} + | ^^^^^^^^^^^^^ help: try: `Rc` + +error: usage of `Box<&T>` + --> $DIR/redundant_allocation.rs:40:22 + | +LL | pub fn test7(foo: Box<&T>) {} + | ^^^^^^^ help: try: `&T` + +error: usage of `Box<&T>` + --> $DIR/redundant_allocation.rs:42:19 + | +LL | pub fn test8(foo: Box<&MyStruct>) {} + | ^^^^^^^^^^^^^^ help: try: `&MyStruct` + +error: usage of `Box<&T>` + --> $DIR/redundant_allocation.rs:44:19 + | +LL | pub fn test9(foo: Box<&MyEnum>) {} + | ^^^^^^^^^^^^ help: try: `&MyEnum` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_clone.fixed b/src/tools/clippy/tests/ui/redundant_clone.fixed new file mode 100644 index 0000000000..ec309109ed --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_clone.fixed @@ -0,0 +1,184 @@ +// run-rustfix +// rustfix-only-machine-applicable + +#![allow(clippy::implicit_clone)] +use std::ffi::OsString; +use std::path::Path; + +fn main() { + let _s = ["lorem", "ipsum"].join(" "); + + let s = String::from("foo"); + let _s = s; + + let s = String::from("foo"); + let _s = s; + + let s = String::from("foo"); + let _s = s; + + let _s = Path::new("/a/b/").join("c"); + + let _s = Path::new("/a/b/").join("c"); + + let _s = OsString::new(); + + let _s = OsString::new(); + + // Check that lint level works + #[allow(clippy::redundant_clone)] + let _s = String::new().to_string(); + + let tup = (String::from("foo"),); + let _t = tup.0; + + let tup_ref = &(String::from("foo"),); + let _s = tup_ref.0.clone(); // this `.clone()` cannot be removed + + { + let x = String::new(); + let y = &x; + + let _x = x.clone(); // ok; `x` is borrowed by `y` + + let _ = y.len(); + } + + let x = (String::new(),); + let _ = Some(String::new()).unwrap_or_else(|| x.0.clone()); // ok; closure borrows `x` + + with_branch(Alpha, true); + cannot_double_move(Alpha); + cannot_move_from_type_with_drop(); + borrower_propagation(); + not_consumed(); + issue_5405(); + manually_drop(); +} + +#[derive(Clone)] +struct Alpha; +fn with_branch(a: Alpha, b: bool) -> (Alpha, Alpha) { + if b { (a.clone(), a) } else { (Alpha, a) } +} + +fn cannot_double_move(a: Alpha) -> (Alpha, Alpha) { + (a.clone(), a) +} + +struct TypeWithDrop { + x: String, +} + +impl Drop for TypeWithDrop { + fn drop(&mut self) {} +} + +fn cannot_move_from_type_with_drop() -> String { + let s = TypeWithDrop { x: String::new() }; + s.x.clone() // removing this `clone()` summons E0509 +} + +fn borrower_propagation() { + let s = String::new(); + let t = String::new(); + + { + fn b() -> bool { + unimplemented!() + } + let _u = if b() { &s } else { &t }; + + // ok; `s` and `t` are possibly borrowed + let _s = s.clone(); + let _t = t.clone(); + } + + { + let _u = || s.len(); + let _v = [&t; 32]; + let _s = s.clone(); // ok + let _t = t.clone(); // ok + } + + { + let _u = { + let u = Some(&s); + let _ = s.clone(); // ok + u + }; + let _s = s.clone(); // ok + } + + { + use std::convert::identity as id; + let _u = id(id(&s)); + let _s = s.clone(); // ok, `u` borrows `s` + } + + let _s = s; + let _t = t; + + #[derive(Clone)] + struct Foo { + x: usize, + } + + { + let f = Foo { x: 123 }; + let _x = Some(f.x); + let _f = f; + } + + { + let f = Foo { x: 123 }; + let _x = &f.x; + let _f = f.clone(); // ok + } +} + +fn not_consumed() { + let x = std::path::PathBuf::from("home"); + let y = x.join("matthias"); + // join() creates a new owned PathBuf, does not take a &mut to x variable, thus the .clone() is + // redundant. (It also does not consume the PathBuf) + + println!("x: {:?}, y: {:?}", x, y); + + let mut s = String::new(); + s.clone().push_str("foo"); // OK, removing this `clone()` will change the behavior. + s.push_str("bar"); + assert_eq!(s, "bar"); + + let t = Some(s); + // OK + if let Some(x) = t.clone() { + println!("{}", x); + } + if let Some(x) = t { + println!("{}", x); + } +} + +#[allow(clippy::clone_on_copy)] +fn issue_5405() { + let a: [String; 1] = [String::from("foo")]; + let _b: String = a[0].clone(); + + let c: [usize; 2] = [2, 3]; + let _d: usize = c[1].clone(); +} + +fn manually_drop() { + use std::mem::ManuallyDrop; + use std::sync::Arc; + + let a = ManuallyDrop::new(Arc::new("Hello!".to_owned())); + let _ = a.clone(); // OK + + let p: *const String = Arc::into_raw(ManuallyDrop::into_inner(a)); + unsafe { + Arc::from_raw(p); + Arc::from_raw(p); + } +} diff --git a/src/tools/clippy/tests/ui/redundant_clone.rs b/src/tools/clippy/tests/ui/redundant_clone.rs new file mode 100644 index 0000000000..b57027456e --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_clone.rs @@ -0,0 +1,184 @@ +// run-rustfix +// rustfix-only-machine-applicable + +#![allow(clippy::implicit_clone)] +use std::ffi::OsString; +use std::path::Path; + +fn main() { + let _s = ["lorem", "ipsum"].join(" ").to_string(); + + let s = String::from("foo"); + let _s = s.clone(); + + let s = String::from("foo"); + let _s = s.to_string(); + + let s = String::from("foo"); + let _s = s.to_owned(); + + let _s = Path::new("/a/b/").join("c").to_owned(); + + let _s = Path::new("/a/b/").join("c").to_path_buf(); + + let _s = OsString::new().to_owned(); + + let _s = OsString::new().to_os_string(); + + // Check that lint level works + #[allow(clippy::redundant_clone)] + let _s = String::new().to_string(); + + let tup = (String::from("foo"),); + let _t = tup.0.clone(); + + let tup_ref = &(String::from("foo"),); + let _s = tup_ref.0.clone(); // this `.clone()` cannot be removed + + { + let x = String::new(); + let y = &x; + + let _x = x.clone(); // ok; `x` is borrowed by `y` + + let _ = y.len(); + } + + let x = (String::new(),); + let _ = Some(String::new()).unwrap_or_else(|| x.0.clone()); // ok; closure borrows `x` + + with_branch(Alpha, true); + cannot_double_move(Alpha); + cannot_move_from_type_with_drop(); + borrower_propagation(); + not_consumed(); + issue_5405(); + manually_drop(); +} + +#[derive(Clone)] +struct Alpha; +fn with_branch(a: Alpha, b: bool) -> (Alpha, Alpha) { + if b { (a.clone(), a.clone()) } else { (Alpha, a) } +} + +fn cannot_double_move(a: Alpha) -> (Alpha, Alpha) { + (a.clone(), a) +} + +struct TypeWithDrop { + x: String, +} + +impl Drop for TypeWithDrop { + fn drop(&mut self) {} +} + +fn cannot_move_from_type_with_drop() -> String { + let s = TypeWithDrop { x: String::new() }; + s.x.clone() // removing this `clone()` summons E0509 +} + +fn borrower_propagation() { + let s = String::new(); + let t = String::new(); + + { + fn b() -> bool { + unimplemented!() + } + let _u = if b() { &s } else { &t }; + + // ok; `s` and `t` are possibly borrowed + let _s = s.clone(); + let _t = t.clone(); + } + + { + let _u = || s.len(); + let _v = [&t; 32]; + let _s = s.clone(); // ok + let _t = t.clone(); // ok + } + + { + let _u = { + let u = Some(&s); + let _ = s.clone(); // ok + u + }; + let _s = s.clone(); // ok + } + + { + use std::convert::identity as id; + let _u = id(id(&s)); + let _s = s.clone(); // ok, `u` borrows `s` + } + + let _s = s.clone(); + let _t = t.clone(); + + #[derive(Clone)] + struct Foo { + x: usize, + } + + { + let f = Foo { x: 123 }; + let _x = Some(f.x); + let _f = f.clone(); + } + + { + let f = Foo { x: 123 }; + let _x = &f.x; + let _f = f.clone(); // ok + } +} + +fn not_consumed() { + let x = std::path::PathBuf::from("home"); + let y = x.clone().join("matthias"); + // join() creates a new owned PathBuf, does not take a &mut to x variable, thus the .clone() is + // redundant. (It also does not consume the PathBuf) + + println!("x: {:?}, y: {:?}", x, y); + + let mut s = String::new(); + s.clone().push_str("foo"); // OK, removing this `clone()` will change the behavior. + s.push_str("bar"); + assert_eq!(s, "bar"); + + let t = Some(s); + // OK + if let Some(x) = t.clone() { + println!("{}", x); + } + if let Some(x) = t { + println!("{}", x); + } +} + +#[allow(clippy::clone_on_copy)] +fn issue_5405() { + let a: [String; 1] = [String::from("foo")]; + let _b: String = a[0].clone(); + + let c: [usize; 2] = [2, 3]; + let _d: usize = c[1].clone(); +} + +fn manually_drop() { + use std::mem::ManuallyDrop; + use std::sync::Arc; + + let a = ManuallyDrop::new(Arc::new("Hello!".to_owned())); + let _ = a.clone(); // OK + + let p: *const String = Arc::into_raw(ManuallyDrop::into_inner(a)); + unsafe { + Arc::from_raw(p); + Arc::from_raw(p); + } +} diff --git a/src/tools/clippy/tests/ui/redundant_clone.stderr b/src/tools/clippy/tests/ui/redundant_clone.stderr new file mode 100644 index 0000000000..821e7934be --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_clone.stderr @@ -0,0 +1,171 @@ +error: redundant clone + --> $DIR/redundant_clone.rs:9:42 + | +LL | let _s = ["lorem", "ipsum"].join(" ").to_string(); + | ^^^^^^^^^^^^ help: remove this + | + = note: `-D clippy::redundant-clone` implied by `-D warnings` +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:9:14 + | +LL | let _s = ["lorem", "ipsum"].join(" ").to_string(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:12:15 + | +LL | let _s = s.clone(); + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:12:14 + | +LL | let _s = s.clone(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:15:15 + | +LL | let _s = s.to_string(); + | ^^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:15:14 + | +LL | let _s = s.to_string(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:18:15 + | +LL | let _s = s.to_owned(); + | ^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:18:14 + | +LL | let _s = s.to_owned(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:20:42 + | +LL | let _s = Path::new("/a/b/").join("c").to_owned(); + | ^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:20:14 + | +LL | let _s = Path::new("/a/b/").join("c").to_owned(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:22:42 + | +LL | let _s = Path::new("/a/b/").join("c").to_path_buf(); + | ^^^^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:22:14 + | +LL | let _s = Path::new("/a/b/").join("c").to_path_buf(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:24:29 + | +LL | let _s = OsString::new().to_owned(); + | ^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:24:14 + | +LL | let _s = OsString::new().to_owned(); + | ^^^^^^^^^^^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:26:29 + | +LL | let _s = OsString::new().to_os_string(); + | ^^^^^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:26:14 + | +LL | let _s = OsString::new().to_os_string(); + | ^^^^^^^^^^^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:33:19 + | +LL | let _t = tup.0.clone(); + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:33:14 + | +LL | let _t = tup.0.clone(); + | ^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:62:25 + | +LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) } + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:62:24 + | +LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) } + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:119:15 + | +LL | let _s = s.clone(); + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:119:14 + | +LL | let _s = s.clone(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:120:15 + | +LL | let _t = t.clone(); + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:120:14 + | +LL | let _t = t.clone(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:130:19 + | +LL | let _f = f.clone(); + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:130:18 + | +LL | let _f = f.clone(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:142:14 + | +LL | let y = x.clone().join("matthias"); + | ^^^^^^^^ help: remove this + | +note: cloned value is neither consumed nor mutated + --> $DIR/redundant_clone.rs:142:13 + | +LL | let y = x.clone().join("matthias"); + | ^^^^^^^^^ + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_early.rs b/src/tools/clippy/tests/ui/redundant_closure_call_early.rs new file mode 100644 index 0000000000..3dd365620c --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call_early.rs @@ -0,0 +1,19 @@ +// non rustfixable, see redundant_closure_call_fixable.rs + +#![warn(clippy::redundant_closure_call)] + +fn main() { + let mut i = 1; + + // lint here + let mut k = (|m| m + 1)(i); + + // lint here + k = (|a, b| a * b)(1, 5); + + // don't lint these + #[allow(clippy::needless_return)] + (|| return 2)(); + (|| -> Option { None? })(); + (|| -> Result { Err(2)? })(); +} diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_early.stderr b/src/tools/clippy/tests/ui/redundant_closure_call_early.stderr new file mode 100644 index 0000000000..2735e41738 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call_early.stderr @@ -0,0 +1,16 @@ +error: try not to call a closure in the expression where it is declared + --> $DIR/redundant_closure_call_early.rs:9:17 + | +LL | let mut k = (|m| m + 1)(i); + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::redundant-closure-call` implied by `-D warnings` + +error: try not to call a closure in the expression where it is declared + --> $DIR/redundant_closure_call_early.rs:12:9 + | +LL | k = (|a, b| a * b)(1, 5); + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.fixed b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.fixed new file mode 100644 index 0000000000..0abca6fca0 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.fixed @@ -0,0 +1,8 @@ +// run-rustfix + +#![warn(clippy::redundant_closure_call)] +#![allow(unused)] + +fn main() { + let a = 42; +} diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.rs b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.rs new file mode 100644 index 0000000000..f8b9d37a5c --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.rs @@ -0,0 +1,8 @@ +// run-rustfix + +#![warn(clippy::redundant_closure_call)] +#![allow(unused)] + +fn main() { + let a = (|| 42)(); +} diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.stderr b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.stderr new file mode 100644 index 0000000000..afd704ef12 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.stderr @@ -0,0 +1,10 @@ +error: try not to call a closure in the expression where it is declared + --> $DIR/redundant_closure_call_fixable.rs:7:13 + | +LL | let a = (|| 42)(); + | ^^^^^^^^^ help: try doing something like: `42` + | + = note: `-D clippy::redundant-closure-call` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_late.rs b/src/tools/clippy/tests/ui/redundant_closure_call_late.rs new file mode 100644 index 0000000000..1f4864b728 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call_late.rs @@ -0,0 +1,39 @@ +// non rustfixable, see redundant_closure_call_fixable.rs + +#![warn(clippy::redundant_closure_call)] + +fn main() { + let mut i = 1; + + // don't lint here, the closure is used more than once + let closure = |i| i + 1; + i = closure(3); + i = closure(4); + + // lint here + let redun_closure = || 1; + i = redun_closure(); + + // shadowed closures are supported, lint here + let shadowed_closure = || 1; + i = shadowed_closure(); + let shadowed_closure = || 2; + i = shadowed_closure(); + + // don't lint here + let shadowed_closure = || 2; + i = shadowed_closure(); + i = shadowed_closure(); + + // Fix FP in #5916 + let mut x; + let create = || 2 * 2; + x = create(); + fun(move || { + x = create(); + }) +} + +fn fun(mut f: T) { + f(); +} diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_late.stderr b/src/tools/clippy/tests/ui/redundant_closure_call_late.stderr new file mode 100644 index 0000000000..7c8865f1bd --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call_late.stderr @@ -0,0 +1,22 @@ +error: closure called just once immediately after it was declared + --> $DIR/redundant_closure_call_late.rs:15:5 + | +LL | i = redun_closure(); + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::redundant-closure-call` implied by `-D warnings` + +error: closure called just once immediately after it was declared + --> $DIR/redundant_closure_call_late.rs:19:5 + | +LL | i = shadowed_closure(); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: closure called just once immediately after it was declared + --> $DIR/redundant_closure_call_late.rs:21:5 + | +LL | i = shadowed_closure(); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_else.rs b/src/tools/clippy/tests/ui/redundant_else.rs new file mode 100644 index 0000000000..737c8a9f8d --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_else.rs @@ -0,0 +1,154 @@ +#![warn(clippy::redundant_else)] +#![allow(clippy::needless_return)] + +fn main() { + loop { + // break + if foo() { + println!("Love your neighbor;"); + break; + } else { + println!("yet don't pull down your hedge."); + } + // continue + if foo() { + println!("He that lies down with Dogs,"); + continue; + } else { + println!("shall rise up with fleas."); + } + // match block + if foo() { + match foo() { + 1 => break, + _ => return, + } + } else { + println!("You may delay, but time will not."); + } + } + // else if + if foo() { + return; + } else if foo() { + return; + } else { + println!("A fat kitchen makes a lean will."); + } + // let binding outside of block + let _ = { + if foo() { + return; + } else { + 1 + } + }; + // else if with let binding outside of block + let _ = { + if foo() { + return; + } else if foo() { + return; + } else { + 2 + } + }; + // inside if let + let _ = if let Some(1) = foo() { + let _ = 1; + if foo() { + return; + } else { + 1 + } + } else { + 1 + }; + + // + // non-lint cases + // + + // sanity check + if foo() { + let _ = 1; + } else { + println!("Who is wise? He that learns from every one."); + } + // else if without else + if foo() { + return; + } else if foo() { + foo() + }; + // nested if return + if foo() { + if foo() { + return; + } + } else { + foo() + }; + // match with non-breaking branch + if foo() { + match foo() { + 1 => foo(), + _ => return, + } + } else { + println!("Three may keep a secret, if two of them are dead."); + } + // let binding + let _ = if foo() { + return; + } else { + 1 + }; + // assign + let a; + a = if foo() { + return; + } else { + 1 + }; + // assign-op + a += if foo() { + return; + } else { + 1 + }; + // if return else if else + if foo() { + return; + } else if foo() { + 1 + } else { + 2 + }; + // if else if return else + if foo() { + 1 + } else if foo() { + return; + } else { + 2 + }; + // else if with let binding + let _ = if foo() { + return; + } else if foo() { + return; + } else { + 2 + }; + // inside function call + Box::new(if foo() { + return; + } else { + 1 + }); +} + +fn foo() -> T { + unimplemented!("I'm not Santa Claus") +} diff --git a/src/tools/clippy/tests/ui/redundant_else.stderr b/src/tools/clippy/tests/ui/redundant_else.stderr new file mode 100644 index 0000000000..9000cdc814 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_else.stderr @@ -0,0 +1,80 @@ +error: redundant else block + --> $DIR/redundant_else.rs:10:16 + | +LL | } else { + | ________________^ +LL | | println!("yet don't pull down your hedge."); +LL | | } + | |_________^ + | + = note: `-D clippy::redundant-else` implied by `-D warnings` + = help: remove the `else` block and move the contents out + +error: redundant else block + --> $DIR/redundant_else.rs:17:16 + | +LL | } else { + | ________________^ +LL | | println!("shall rise up with fleas."); +LL | | } + | |_________^ + | + = help: remove the `else` block and move the contents out + +error: redundant else block + --> $DIR/redundant_else.rs:26:16 + | +LL | } else { + | ________________^ +LL | | println!("You may delay, but time will not."); +LL | | } + | |_________^ + | + = help: remove the `else` block and move the contents out + +error: redundant else block + --> $DIR/redundant_else.rs:35:12 + | +LL | } else { + | ____________^ +LL | | println!("A fat kitchen makes a lean will."); +LL | | } + | |_____^ + | + = help: remove the `else` block and move the contents out + +error: redundant else block + --> $DIR/redundant_else.rs:42:16 + | +LL | } else { + | ________________^ +LL | | 1 +LL | | } + | |_________^ + | + = help: remove the `else` block and move the contents out + +error: redundant else block + --> $DIR/redundant_else.rs:52:16 + | +LL | } else { + | ________________^ +LL | | 2 +LL | | } + | |_________^ + | + = help: remove the `else` block and move the contents out + +error: redundant else block + --> $DIR/redundant_else.rs:61:16 + | +LL | } else { + | ________________^ +LL | | 1 +LL | | } + | |_________^ + | + = help: remove the `else` block and move the contents out + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_field_names.fixed b/src/tools/clippy/tests/ui/redundant_field_names.fixed new file mode 100644 index 0000000000..5b4b8eeedd --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_field_names.fixed @@ -0,0 +1,71 @@ +// run-rustfix +#![warn(clippy::redundant_field_names)] +#![allow(clippy::no_effect, dead_code, unused_variables)] + +#[macro_use] +extern crate derive_new; + +use std::ops::{Range, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive}; + +mod foo { + pub const BAR: u8 = 0; +} + +struct Person { + gender: u8, + age: u8, + name: u8, + buzz: u64, + foo: u8, +} + +#[derive(new)] +pub struct S { + v: String, +} + +fn main() { + let gender: u8 = 42; + let age = 0; + let fizz: u64 = 0; + let name: u8 = 0; + + let me = Person { + gender, + age, + + name, //should be ok + buzz: fizz, //should be ok + foo: foo::BAR, //should be ok + }; + + // Range expressions + let (start, end) = (0, 0); + + let _ = start..; + let _ = ..end; + let _ = start..end; + + let _ = ..=end; + let _ = start..=end; + + // Issue #2799 + let _: Vec<_> = (start..end).collect(); + + // hand-written Range family structs are linted + let _ = RangeFrom { start }; + let _ = RangeTo { end }; + let _ = Range { start, end }; + let _ = RangeInclusive::new(start, end); + let _ = RangeToInclusive { end }; +} + +fn issue_3476() { + fn foo() {} + + struct S { + foo: fn(), + } + + S { foo: foo:: }; +} diff --git a/src/tools/clippy/tests/ui/redundant_field_names.rs b/src/tools/clippy/tests/ui/redundant_field_names.rs new file mode 100644 index 0000000000..3f97b80c56 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_field_names.rs @@ -0,0 +1,71 @@ +// run-rustfix +#![warn(clippy::redundant_field_names)] +#![allow(clippy::no_effect, dead_code, unused_variables)] + +#[macro_use] +extern crate derive_new; + +use std::ops::{Range, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive}; + +mod foo { + pub const BAR: u8 = 0; +} + +struct Person { + gender: u8, + age: u8, + name: u8, + buzz: u64, + foo: u8, +} + +#[derive(new)] +pub struct S { + v: String, +} + +fn main() { + let gender: u8 = 42; + let age = 0; + let fizz: u64 = 0; + let name: u8 = 0; + + let me = Person { + gender: gender, + age: age, + + name, //should be ok + buzz: fizz, //should be ok + foo: foo::BAR, //should be ok + }; + + // Range expressions + let (start, end) = (0, 0); + + let _ = start..; + let _ = ..end; + let _ = start..end; + + let _ = ..=end; + let _ = start..=end; + + // Issue #2799 + let _: Vec<_> = (start..end).collect(); + + // hand-written Range family structs are linted + let _ = RangeFrom { start: start }; + let _ = RangeTo { end: end }; + let _ = Range { start: start, end: end }; + let _ = RangeInclusive::new(start, end); + let _ = RangeToInclusive { end: end }; +} + +fn issue_3476() { + fn foo() {} + + struct S { + foo: fn(), + } + + S { foo: foo:: }; +} diff --git a/src/tools/clippy/tests/ui/redundant_field_names.stderr b/src/tools/clippy/tests/ui/redundant_field_names.stderr new file mode 100644 index 0000000000..7976292df2 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_field_names.stderr @@ -0,0 +1,46 @@ +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:34:9 + | +LL | gender: gender, + | ^^^^^^^^^^^^^^ help: replace it with: `gender` + | + = note: `-D clippy::redundant-field-names` implied by `-D warnings` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:35:9 + | +LL | age: age, + | ^^^^^^^^ help: replace it with: `age` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:56:25 + | +LL | let _ = RangeFrom { start: start }; + | ^^^^^^^^^^^^ help: replace it with: `start` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:57:23 + | +LL | let _ = RangeTo { end: end }; + | ^^^^^^^^ help: replace it with: `end` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:58:21 + | +LL | let _ = Range { start: start, end: end }; + | ^^^^^^^^^^^^ help: replace it with: `start` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:58:35 + | +LL | let _ = Range { start: start, end: end }; + | ^^^^^^^^ help: replace it with: `end` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:60:32 + | +LL | let _ = RangeToInclusive { end: end }; + | ^^^^^^^^ help: replace it with: `end` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_ipaddr.fixed b/src/tools/clippy/tests/ui/redundant_pattern_matching_ipaddr.fixed new file mode 100644 index 0000000000..acc8de5f41 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_ipaddr.fixed @@ -0,0 +1,73 @@ +// run-rustfix + +#![warn(clippy::all)] +#![warn(clippy::redundant_pattern_matching)] +#![allow(unused_must_use, clippy::needless_bool, clippy::match_like_matches_macro)] + +use std::net::{ + IpAddr::{self, V4, V6}, + Ipv4Addr, Ipv6Addr, +}; + +fn main() { + let ipaddr: IpAddr = V4(Ipv4Addr::LOCALHOST); + if ipaddr.is_ipv4() {} + + if V4(Ipv4Addr::LOCALHOST).is_ipv4() {} + + if V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + + while V4(Ipv4Addr::LOCALHOST).is_ipv4() {} + + while V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + + if V4(Ipv4Addr::LOCALHOST).is_ipv4() {} + + if V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + + if let V4(ipaddr) = V4(Ipv4Addr::LOCALHOST) { + println!("{}", ipaddr); + } + + V4(Ipv4Addr::LOCALHOST).is_ipv4(); + + V4(Ipv4Addr::LOCALHOST).is_ipv6(); + + V6(Ipv6Addr::LOCALHOST).is_ipv6(); + + V6(Ipv6Addr::LOCALHOST).is_ipv4(); + + let _ = if V4(Ipv4Addr::LOCALHOST).is_ipv4() { + true + } else { + false + }; + + ipaddr_const(); + + let _ = if gen_ipaddr().is_ipv4() { + 1 + } else if gen_ipaddr().is_ipv6() { + 2 + } else { + 3 + }; +} + +fn gen_ipaddr() -> IpAddr { + V4(Ipv4Addr::LOCALHOST) +} + +const fn ipaddr_const() { + if V4(Ipv4Addr::LOCALHOST).is_ipv4() {} + + if V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + + while V4(Ipv4Addr::LOCALHOST).is_ipv4() {} + + while V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + + V4(Ipv4Addr::LOCALHOST).is_ipv4(); + + V6(Ipv6Addr::LOCALHOST).is_ipv6(); +} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_ipaddr.rs b/src/tools/clippy/tests/ui/redundant_pattern_matching_ipaddr.rs new file mode 100644 index 0000000000..678d91ce93 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_ipaddr.rs @@ -0,0 +1,91 @@ +// run-rustfix + +#![warn(clippy::all)] +#![warn(clippy::redundant_pattern_matching)] +#![allow(unused_must_use, clippy::needless_bool, clippy::match_like_matches_macro)] + +use std::net::{ + IpAddr::{self, V4, V6}, + Ipv4Addr, Ipv6Addr, +}; + +fn main() { + let ipaddr: IpAddr = V4(Ipv4Addr::LOCALHOST); + if let V4(_) = &ipaddr {} + + if let V4(_) = V4(Ipv4Addr::LOCALHOST) {} + + if let V6(_) = V6(Ipv6Addr::LOCALHOST) {} + + while let V4(_) = V4(Ipv4Addr::LOCALHOST) {} + + while let V6(_) = V6(Ipv6Addr::LOCALHOST) {} + + if V4(Ipv4Addr::LOCALHOST).is_ipv4() {} + + if V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + + if let V4(ipaddr) = V4(Ipv4Addr::LOCALHOST) { + println!("{}", ipaddr); + } + + match V4(Ipv4Addr::LOCALHOST) { + V4(_) => true, + V6(_) => false, + }; + + match V4(Ipv4Addr::LOCALHOST) { + V4(_) => false, + V6(_) => true, + }; + + match V6(Ipv6Addr::LOCALHOST) { + V4(_) => false, + V6(_) => true, + }; + + match V6(Ipv6Addr::LOCALHOST) { + V4(_) => true, + V6(_) => false, + }; + + let _ = if let V4(_) = V4(Ipv4Addr::LOCALHOST) { + true + } else { + false + }; + + ipaddr_const(); + + let _ = if let V4(_) = gen_ipaddr() { + 1 + } else if let V6(_) = gen_ipaddr() { + 2 + } else { + 3 + }; +} + +fn gen_ipaddr() -> IpAddr { + V4(Ipv4Addr::LOCALHOST) +} + +const fn ipaddr_const() { + if let V4(_) = V4(Ipv4Addr::LOCALHOST) {} + + if let V6(_) = V6(Ipv6Addr::LOCALHOST) {} + + while let V4(_) = V4(Ipv4Addr::LOCALHOST) {} + + while let V6(_) = V6(Ipv6Addr::LOCALHOST) {} + + match V4(Ipv4Addr::LOCALHOST) { + V4(_) => true, + V6(_) => false, + }; + + match V6(Ipv6Addr::LOCALHOST) { + V4(_) => false, + V6(_) => true, + }; +} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_ipaddr.stderr b/src/tools/clippy/tests/ui/redundant_pattern_matching_ipaddr.stderr new file mode 100644 index 0000000000..caf458cd86 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_ipaddr.stderr @@ -0,0 +1,130 @@ +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:14:12 + | +LL | if let V4(_) = &ipaddr {} + | -------^^^^^---------- help: try this: `if ipaddr.is_ipv4()` + | + = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` + +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:16:12 + | +LL | if let V4(_) = V4(Ipv4Addr::LOCALHOST) {} + | -------^^^^^-------------------------- help: try this: `if V4(Ipv4Addr::LOCALHOST).is_ipv4()` + +error: redundant pattern matching, consider using `is_ipv6()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:18:12 + | +LL | if let V6(_) = V6(Ipv6Addr::LOCALHOST) {} + | -------^^^^^-------------------------- help: try this: `if V6(Ipv6Addr::LOCALHOST).is_ipv6()` + +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:20:15 + | +LL | while let V4(_) = V4(Ipv4Addr::LOCALHOST) {} + | ----------^^^^^-------------------------- help: try this: `while V4(Ipv4Addr::LOCALHOST).is_ipv4()` + +error: redundant pattern matching, consider using `is_ipv6()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:22:15 + | +LL | while let V6(_) = V6(Ipv6Addr::LOCALHOST) {} + | ----------^^^^^-------------------------- help: try this: `while V6(Ipv6Addr::LOCALHOST).is_ipv6()` + +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:32:5 + | +LL | / match V4(Ipv4Addr::LOCALHOST) { +LL | | V4(_) => true, +LL | | V6(_) => false, +LL | | }; + | |_____^ help: try this: `V4(Ipv4Addr::LOCALHOST).is_ipv4()` + +error: redundant pattern matching, consider using `is_ipv6()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:37:5 + | +LL | / match V4(Ipv4Addr::LOCALHOST) { +LL | | V4(_) => false, +LL | | V6(_) => true, +LL | | }; + | |_____^ help: try this: `V4(Ipv4Addr::LOCALHOST).is_ipv6()` + +error: redundant pattern matching, consider using `is_ipv6()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:42:5 + | +LL | / match V6(Ipv6Addr::LOCALHOST) { +LL | | V4(_) => false, +LL | | V6(_) => true, +LL | | }; + | |_____^ help: try this: `V6(Ipv6Addr::LOCALHOST).is_ipv6()` + +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:47:5 + | +LL | / match V6(Ipv6Addr::LOCALHOST) { +LL | | V4(_) => true, +LL | | V6(_) => false, +LL | | }; + | |_____^ help: try this: `V6(Ipv6Addr::LOCALHOST).is_ipv4()` + +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:52:20 + | +LL | let _ = if let V4(_) = V4(Ipv4Addr::LOCALHOST) { + | -------^^^^^-------------------------- help: try this: `if V4(Ipv4Addr::LOCALHOST).is_ipv4()` + +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:60:20 + | +LL | let _ = if let V4(_) = gen_ipaddr() { + | -------^^^^^--------------- help: try this: `if gen_ipaddr().is_ipv4()` + +error: redundant pattern matching, consider using `is_ipv6()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:62:19 + | +LL | } else if let V6(_) = gen_ipaddr() { + | -------^^^^^--------------- help: try this: `if gen_ipaddr().is_ipv6()` + +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:74:12 + | +LL | if let V4(_) = V4(Ipv4Addr::LOCALHOST) {} + | -------^^^^^-------------------------- help: try this: `if V4(Ipv4Addr::LOCALHOST).is_ipv4()` + +error: redundant pattern matching, consider using `is_ipv6()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:76:12 + | +LL | if let V6(_) = V6(Ipv6Addr::LOCALHOST) {} + | -------^^^^^-------------------------- help: try this: `if V6(Ipv6Addr::LOCALHOST).is_ipv6()` + +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:78:15 + | +LL | while let V4(_) = V4(Ipv4Addr::LOCALHOST) {} + | ----------^^^^^-------------------------- help: try this: `while V4(Ipv4Addr::LOCALHOST).is_ipv4()` + +error: redundant pattern matching, consider using `is_ipv6()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:80:15 + | +LL | while let V6(_) = V6(Ipv6Addr::LOCALHOST) {} + | ----------^^^^^-------------------------- help: try this: `while V6(Ipv6Addr::LOCALHOST).is_ipv6()` + +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:82:5 + | +LL | / match V4(Ipv4Addr::LOCALHOST) { +LL | | V4(_) => true, +LL | | V6(_) => false, +LL | | }; + | |_____^ help: try this: `V4(Ipv4Addr::LOCALHOST).is_ipv4()` + +error: redundant pattern matching, consider using `is_ipv6()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:87:5 + | +LL | / match V6(Ipv6Addr::LOCALHOST) { +LL | | V4(_) => false, +LL | | V6(_) => true, +LL | | }; + | |_____^ help: try this: `V6(Ipv6Addr::LOCALHOST).is_ipv6()` + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_option.fixed b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.fixed new file mode 100644 index 0000000000..66f580a0a6 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.fixed @@ -0,0 +1,76 @@ +// run-rustfix + +#![warn(clippy::all)] +#![warn(clippy::redundant_pattern_matching)] +#![allow(unused_must_use, clippy::needless_bool, clippy::match_like_matches_macro)] + +fn main() { + if None::<()>.is_none() {} + + if Some(42).is_some() {} + + if Some(42).is_some() { + foo(); + } else { + bar(); + } + + while Some(42).is_some() {} + + while Some(42).is_none() {} + + while None::<()>.is_none() {} + + let mut v = vec![1, 2, 3]; + while v.pop().is_some() { + foo(); + } + + if None::.is_none() {} + + if Some(42).is_some() {} + + Some(42).is_some(); + + None::<()>.is_none(); + + let _ = None::<()>.is_none(); + + let opt = Some(false); + let _ = if opt.is_some() { true } else { false }; + + issue6067(); + + let _ = if gen_opt().is_some() { + 1 + } else if gen_opt().is_none() { + 2 + } else { + 3 + }; +} + +fn gen_opt() -> Option<()> { + None +} + +fn foo() {} + +fn bar() {} + +// Methods that are unstable const should not be suggested within a const context, see issue #5697. +// However, in Rust 1.48.0 the methods `is_some` and `is_none` of `Option` were stabilized as const, +// so the following should be linted. +const fn issue6067() { + if Some(42).is_some() {} + + if None::<()>.is_none() {} + + while Some(42).is_some() {} + + while None::<()>.is_none() {} + + Some(42).is_some(); + + None::<()>.is_none(); +} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_option.rs b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.rs new file mode 100644 index 0000000000..f18b27b8b9 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.rs @@ -0,0 +1,91 @@ +// run-rustfix + +#![warn(clippy::all)] +#![warn(clippy::redundant_pattern_matching)] +#![allow(unused_must_use, clippy::needless_bool, clippy::match_like_matches_macro)] + +fn main() { + if let None = None::<()> {} + + if let Some(_) = Some(42) {} + + if let Some(_) = Some(42) { + foo(); + } else { + bar(); + } + + while let Some(_) = Some(42) {} + + while let None = Some(42) {} + + while let None = None::<()> {} + + let mut v = vec![1, 2, 3]; + while let Some(_) = v.pop() { + foo(); + } + + if None::.is_none() {} + + if Some(42).is_some() {} + + match Some(42) { + Some(_) => true, + None => false, + }; + + match None::<()> { + Some(_) => false, + None => true, + }; + + let _ = match None::<()> { + Some(_) => false, + None => true, + }; + + let opt = Some(false); + let _ = if let Some(_) = opt { true } else { false }; + + issue6067(); + + let _ = if let Some(_) = gen_opt() { + 1 + } else if let None = gen_opt() { + 2 + } else { + 3 + }; +} + +fn gen_opt() -> Option<()> { + None +} + +fn foo() {} + +fn bar() {} + +// Methods that are unstable const should not be suggested within a const context, see issue #5697. +// However, in Rust 1.48.0 the methods `is_some` and `is_none` of `Option` were stabilized as const, +// so the following should be linted. +const fn issue6067() { + if let Some(_) = Some(42) {} + + if let None = None::<()> {} + + while let Some(_) = Some(42) {} + + while let None = None::<()> {} + + match Some(42) { + Some(_) => true, + None => false, + }; + + match None::<()> { + Some(_) => false, + None => true, + }; +} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_option.stderr b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.stderr new file mode 100644 index 0000000000..58482a0ab7 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.stderr @@ -0,0 +1,134 @@ +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:8:12 + | +LL | if let None = None::<()> {} + | -------^^^^------------- help: try this: `if None::<()>.is_none()` + | + = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_option.rs:10:12 + | +LL | if let Some(_) = Some(42) {} + | -------^^^^^^^----------- help: try this: `if Some(42).is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_option.rs:12:12 + | +LL | if let Some(_) = Some(42) { + | -------^^^^^^^----------- help: try this: `if Some(42).is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_option.rs:18:15 + | +LL | while let Some(_) = Some(42) {} + | ----------^^^^^^^----------- help: try this: `while Some(42).is_some()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:20:15 + | +LL | while let None = Some(42) {} + | ----------^^^^----------- help: try this: `while Some(42).is_none()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:22:15 + | +LL | while let None = None::<()> {} + | ----------^^^^------------- help: try this: `while None::<()>.is_none()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_option.rs:25:15 + | +LL | while let Some(_) = v.pop() { + | ----------^^^^^^^---------- help: try this: `while v.pop().is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_option.rs:33:5 + | +LL | / match Some(42) { +LL | | Some(_) => true, +LL | | None => false, +LL | | }; + | |_____^ help: try this: `Some(42).is_some()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:38:5 + | +LL | / match None::<()> { +LL | | Some(_) => false, +LL | | None => true, +LL | | }; + | |_____^ help: try this: `None::<()>.is_none()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:43:13 + | +LL | let _ = match None::<()> { + | _____________^ +LL | | Some(_) => false, +LL | | None => true, +LL | | }; + | |_____^ help: try this: `None::<()>.is_none()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_option.rs:49:20 + | +LL | let _ = if let Some(_) = opt { true } else { false }; + | -------^^^^^^^------ help: try this: `if opt.is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_option.rs:53:20 + | +LL | let _ = if let Some(_) = gen_opt() { + | -------^^^^^^^------------ help: try this: `if gen_opt().is_some()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:55:19 + | +LL | } else if let None = gen_opt() { + | -------^^^^------------ help: try this: `if gen_opt().is_none()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_option.rs:74:12 + | +LL | if let Some(_) = Some(42) {} + | -------^^^^^^^----------- help: try this: `if Some(42).is_some()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:76:12 + | +LL | if let None = None::<()> {} + | -------^^^^------------- help: try this: `if None::<()>.is_none()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_option.rs:78:15 + | +LL | while let Some(_) = Some(42) {} + | ----------^^^^^^^----------- help: try this: `while Some(42).is_some()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:80:15 + | +LL | while let None = None::<()> {} + | ----------^^^^------------- help: try this: `while None::<()>.is_none()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_option.rs:82:5 + | +LL | / match Some(42) { +LL | | Some(_) => true, +LL | | None => false, +LL | | }; + | |_____^ help: try this: `Some(42).is_some()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:87:5 + | +LL | / match None::<()> { +LL | | Some(_) => false, +LL | | None => true, +LL | | }; + | |_____^ help: try this: `None::<()>.is_none()` + +error: aborting due to 19 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_poll.fixed b/src/tools/clippy/tests/ui/redundant_pattern_matching_poll.fixed new file mode 100644 index 0000000000..465aa80dac --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_poll.fixed @@ -0,0 +1,70 @@ +// run-rustfix + +#![warn(clippy::all)] +#![warn(clippy::redundant_pattern_matching)] +#![allow(unused_must_use, clippy::needless_bool, clippy::match_like_matches_macro)] + +use std::task::Poll::{self, Pending, Ready}; + +fn main() { + if Pending::<()>.is_pending() {} + + if Ready(42).is_ready() {} + + if Ready(42).is_ready() { + foo(); + } else { + bar(); + } + + while Ready(42).is_ready() {} + + while Ready(42).is_pending() {} + + while Pending::<()>.is_pending() {} + + if Pending::.is_pending() {} + + if Ready(42).is_ready() {} + + Ready(42).is_ready(); + + Pending::<()>.is_pending(); + + let _ = Pending::<()>.is_pending(); + + let poll = Ready(false); + let _ = if poll.is_ready() { true } else { false }; + + poll_const(); + + let _ = if gen_poll().is_ready() { + 1 + } else if gen_poll().is_pending() { + 2 + } else { + 3 + }; +} + +fn gen_poll() -> Poll<()> { + Pending +} + +fn foo() {} + +fn bar() {} + +const fn poll_const() { + if Ready(42).is_ready() {} + + if Pending::<()>.is_pending() {} + + while Ready(42).is_ready() {} + + while Pending::<()>.is_pending() {} + + Ready(42).is_ready(); + + Pending::<()>.is_pending(); +} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_poll.rs b/src/tools/clippy/tests/ui/redundant_pattern_matching_poll.rs new file mode 100644 index 0000000000..7891ff353b --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_poll.rs @@ -0,0 +1,85 @@ +// run-rustfix + +#![warn(clippy::all)] +#![warn(clippy::redundant_pattern_matching)] +#![allow(unused_must_use, clippy::needless_bool, clippy::match_like_matches_macro)] + +use std::task::Poll::{self, Pending, Ready}; + +fn main() { + if let Pending = Pending::<()> {} + + if let Ready(_) = Ready(42) {} + + if let Ready(_) = Ready(42) { + foo(); + } else { + bar(); + } + + while let Ready(_) = Ready(42) {} + + while let Pending = Ready(42) {} + + while let Pending = Pending::<()> {} + + if Pending::.is_pending() {} + + if Ready(42).is_ready() {} + + match Ready(42) { + Ready(_) => true, + Pending => false, + }; + + match Pending::<()> { + Ready(_) => false, + Pending => true, + }; + + let _ = match Pending::<()> { + Ready(_) => false, + Pending => true, + }; + + let poll = Ready(false); + let _ = if let Ready(_) = poll { true } else { false }; + + poll_const(); + + let _ = if let Ready(_) = gen_poll() { + 1 + } else if let Pending = gen_poll() { + 2 + } else { + 3 + }; +} + +fn gen_poll() -> Poll<()> { + Pending +} + +fn foo() {} + +fn bar() {} + +const fn poll_const() { + if let Ready(_) = Ready(42) {} + + if let Pending = Pending::<()> {} + + while let Ready(_) = Ready(42) {} + + while let Pending = Pending::<()> {} + + match Ready(42) { + Ready(_) => true, + Pending => false, + }; + + match Pending::<()> { + Ready(_) => false, + Pending => true, + }; +} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_poll.stderr b/src/tools/clippy/tests/ui/redundant_pattern_matching_poll.stderr new file mode 100644 index 0000000000..5ffc6c47c9 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_poll.stderr @@ -0,0 +1,128 @@ +error: redundant pattern matching, consider using `is_pending()` + --> $DIR/redundant_pattern_matching_poll.rs:10:12 + | +LL | if let Pending = Pending::<()> {} + | -------^^^^^^^---------------- help: try this: `if Pending::<()>.is_pending()` + | + = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` + +error: redundant pattern matching, consider using `is_ready()` + --> $DIR/redundant_pattern_matching_poll.rs:12:12 + | +LL | if let Ready(_) = Ready(42) {} + | -------^^^^^^^^------------ help: try this: `if Ready(42).is_ready()` + +error: redundant pattern matching, consider using `is_ready()` + --> $DIR/redundant_pattern_matching_poll.rs:14:12 + | +LL | if let Ready(_) = Ready(42) { + | -------^^^^^^^^------------ help: try this: `if Ready(42).is_ready()` + +error: redundant pattern matching, consider using `is_ready()` + --> $DIR/redundant_pattern_matching_poll.rs:20:15 + | +LL | while let Ready(_) = Ready(42) {} + | ----------^^^^^^^^------------ help: try this: `while Ready(42).is_ready()` + +error: redundant pattern matching, consider using `is_pending()` + --> $DIR/redundant_pattern_matching_poll.rs:22:15 + | +LL | while let Pending = Ready(42) {} + | ----------^^^^^^^------------ help: try this: `while Ready(42).is_pending()` + +error: redundant pattern matching, consider using `is_pending()` + --> $DIR/redundant_pattern_matching_poll.rs:24:15 + | +LL | while let Pending = Pending::<()> {} + | ----------^^^^^^^---------------- help: try this: `while Pending::<()>.is_pending()` + +error: redundant pattern matching, consider using `is_ready()` + --> $DIR/redundant_pattern_matching_poll.rs:30:5 + | +LL | / match Ready(42) { +LL | | Ready(_) => true, +LL | | Pending => false, +LL | | }; + | |_____^ help: try this: `Ready(42).is_ready()` + +error: redundant pattern matching, consider using `is_pending()` + --> $DIR/redundant_pattern_matching_poll.rs:35:5 + | +LL | / match Pending::<()> { +LL | | Ready(_) => false, +LL | | Pending => true, +LL | | }; + | |_____^ help: try this: `Pending::<()>.is_pending()` + +error: redundant pattern matching, consider using `is_pending()` + --> $DIR/redundant_pattern_matching_poll.rs:40:13 + | +LL | let _ = match Pending::<()> { + | _____________^ +LL | | Ready(_) => false, +LL | | Pending => true, +LL | | }; + | |_____^ help: try this: `Pending::<()>.is_pending()` + +error: redundant pattern matching, consider using `is_ready()` + --> $DIR/redundant_pattern_matching_poll.rs:46:20 + | +LL | let _ = if let Ready(_) = poll { true } else { false }; + | -------^^^^^^^^------- help: try this: `if poll.is_ready()` + +error: redundant pattern matching, consider using `is_ready()` + --> $DIR/redundant_pattern_matching_poll.rs:50:20 + | +LL | let _ = if let Ready(_) = gen_poll() { + | -------^^^^^^^^------------- help: try this: `if gen_poll().is_ready()` + +error: redundant pattern matching, consider using `is_pending()` + --> $DIR/redundant_pattern_matching_poll.rs:52:19 + | +LL | } else if let Pending = gen_poll() { + | -------^^^^^^^------------- help: try this: `if gen_poll().is_pending()` + +error: redundant pattern matching, consider using `is_ready()` + --> $DIR/redundant_pattern_matching_poll.rs:68:12 + | +LL | if let Ready(_) = Ready(42) {} + | -------^^^^^^^^------------ help: try this: `if Ready(42).is_ready()` + +error: redundant pattern matching, consider using `is_pending()` + --> $DIR/redundant_pattern_matching_poll.rs:70:12 + | +LL | if let Pending = Pending::<()> {} + | -------^^^^^^^---------------- help: try this: `if Pending::<()>.is_pending()` + +error: redundant pattern matching, consider using `is_ready()` + --> $DIR/redundant_pattern_matching_poll.rs:72:15 + | +LL | while let Ready(_) = Ready(42) {} + | ----------^^^^^^^^------------ help: try this: `while Ready(42).is_ready()` + +error: redundant pattern matching, consider using `is_pending()` + --> $DIR/redundant_pattern_matching_poll.rs:74:15 + | +LL | while let Pending = Pending::<()> {} + | ----------^^^^^^^---------------- help: try this: `while Pending::<()>.is_pending()` + +error: redundant pattern matching, consider using `is_ready()` + --> $DIR/redundant_pattern_matching_poll.rs:76:5 + | +LL | / match Ready(42) { +LL | | Ready(_) => true, +LL | | Pending => false, +LL | | }; + | |_____^ help: try this: `Ready(42).is_ready()` + +error: redundant pattern matching, consider using `is_pending()` + --> $DIR/redundant_pattern_matching_poll.rs:81:5 + | +LL | / match Pending::<()> { +LL | | Ready(_) => false, +LL | | Pending => true, +LL | | }; + | |_____^ help: try this: `Pending::<()>.is_pending()` + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_result.fixed b/src/tools/clippy/tests/ui/redundant_pattern_matching_result.fixed new file mode 100644 index 0000000000..e94c5704b4 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_result.fixed @@ -0,0 +1,109 @@ +// run-rustfix + +#![warn(clippy::all)] +#![warn(clippy::redundant_pattern_matching)] +#![allow( + unused_must_use, + clippy::needless_bool, + clippy::match_like_matches_macro, + clippy::unnecessary_wraps, + deprecated +)] + +fn main() { + let result: Result = Err(5); + if result.is_ok() {} + + if Ok::(42).is_ok() {} + + if Err::(42).is_err() {} + + while Ok::(10).is_ok() {} + + while Ok::(10).is_err() {} + + if Ok::(42).is_ok() {} + + if Err::(42).is_err() {} + + if let Ok(x) = Ok::(42) { + println!("{}", x); + } + + Ok::(42).is_ok(); + + Ok::(42).is_err(); + + Err::(42).is_err(); + + Err::(42).is_ok(); + + let _ = if Ok::(4).is_ok() { true } else { false }; + + issue5504(); + issue6067(); + issue6065(); + + let _ = if gen_res().is_ok() { + 1 + } else if gen_res().is_err() { + 2 + } else { + 3 + }; +} + +fn gen_res() -> Result<(), ()> { + Ok(()) +} + +macro_rules! m { + () => { + Some(42u32) + }; +} + +fn issue5504() { + fn result_opt() -> Result, i32> { + Err(42) + } + + fn try_result_opt() -> Result { + while r#try!(result_opt()).is_some() {} + if r#try!(result_opt()).is_some() {} + Ok(42) + } + + try_result_opt(); + + if m!().is_some() {} + while m!().is_some() {} +} + +fn issue6065() { + macro_rules! if_let_in_macro { + ($pat:pat, $x:expr) => { + if let Some($pat) = $x {} + }; + } + + // shouldn't be linted + if_let_in_macro!(_, Some(42)); +} + +// Methods that are unstable const should not be suggested within a const context, see issue #5697. +// However, in Rust 1.48.0 the methods `is_ok` and `is_err` of `Result` were stabilized as const, +// so the following should be linted. +const fn issue6067() { + if Ok::(42).is_ok() {} + + if Err::(42).is_err() {} + + while Ok::(10).is_ok() {} + + while Ok::(10).is_err() {} + + Ok::(42).is_ok(); + + Err::(42).is_err(); +} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_result.rs b/src/tools/clippy/tests/ui/redundant_pattern_matching_result.rs new file mode 100644 index 0000000000..5d17529423 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_result.rs @@ -0,0 +1,127 @@ +// run-rustfix + +#![warn(clippy::all)] +#![warn(clippy::redundant_pattern_matching)] +#![allow( + unused_must_use, + clippy::needless_bool, + clippy::match_like_matches_macro, + clippy::unnecessary_wraps, + deprecated +)] + +fn main() { + let result: Result = Err(5); + if let Ok(_) = &result {} + + if let Ok(_) = Ok::(42) {} + + if let Err(_) = Err::(42) {} + + while let Ok(_) = Ok::(10) {} + + while let Err(_) = Ok::(10) {} + + if Ok::(42).is_ok() {} + + if Err::(42).is_err() {} + + if let Ok(x) = Ok::(42) { + println!("{}", x); + } + + match Ok::(42) { + Ok(_) => true, + Err(_) => false, + }; + + match Ok::(42) { + Ok(_) => false, + Err(_) => true, + }; + + match Err::(42) { + Ok(_) => false, + Err(_) => true, + }; + + match Err::(42) { + Ok(_) => true, + Err(_) => false, + }; + + let _ = if let Ok(_) = Ok::(4) { true } else { false }; + + issue5504(); + issue6067(); + issue6065(); + + let _ = if let Ok(_) = gen_res() { + 1 + } else if let Err(_) = gen_res() { + 2 + } else { + 3 + }; +} + +fn gen_res() -> Result<(), ()> { + Ok(()) +} + +macro_rules! m { + () => { + Some(42u32) + }; +} + +fn issue5504() { + fn result_opt() -> Result, i32> { + Err(42) + } + + fn try_result_opt() -> Result { + while let Some(_) = r#try!(result_opt()) {} + if let Some(_) = r#try!(result_opt()) {} + Ok(42) + } + + try_result_opt(); + + if let Some(_) = m!() {} + while let Some(_) = m!() {} +} + +fn issue6065() { + macro_rules! if_let_in_macro { + ($pat:pat, $x:expr) => { + if let Some($pat) = $x {} + }; + } + + // shouldn't be linted + if_let_in_macro!(_, Some(42)); +} + +// Methods that are unstable const should not be suggested within a const context, see issue #5697. +// However, in Rust 1.48.0 the methods `is_ok` and `is_err` of `Result` were stabilized as const, +// so the following should be linted. +const fn issue6067() { + if let Ok(_) = Ok::(42) {} + + if let Err(_) = Err::(42) {} + + while let Ok(_) = Ok::(10) {} + + while let Err(_) = Ok::(10) {} + + match Ok::(42) { + Ok(_) => true, + Err(_) => false, + }; + + match Err::(42) { + Ok(_) => false, + Err(_) => true, + }; +} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_result.stderr b/src/tools/clippy/tests/ui/redundant_pattern_matching_result.stderr new file mode 100644 index 0000000000..d6a46babb7 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_result.stderr @@ -0,0 +1,154 @@ +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching_result.rs:15:12 + | +LL | if let Ok(_) = &result {} + | -------^^^^^---------- help: try this: `if result.is_ok()` + | + = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching_result.rs:17:12 + | +LL | if let Ok(_) = Ok::(42) {} + | -------^^^^^--------------------- help: try this: `if Ok::(42).is_ok()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching_result.rs:19:12 + | +LL | if let Err(_) = Err::(42) {} + | -------^^^^^^---------------------- help: try this: `if Err::(42).is_err()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching_result.rs:21:15 + | +LL | while let Ok(_) = Ok::(10) {} + | ----------^^^^^--------------------- help: try this: `while Ok::(10).is_ok()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching_result.rs:23:15 + | +LL | while let Err(_) = Ok::(10) {} + | ----------^^^^^^--------------------- help: try this: `while Ok::(10).is_err()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching_result.rs:33:5 + | +LL | / match Ok::(42) { +LL | | Ok(_) => true, +LL | | Err(_) => false, +LL | | }; + | |_____^ help: try this: `Ok::(42).is_ok()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching_result.rs:38:5 + | +LL | / match Ok::(42) { +LL | | Ok(_) => false, +LL | | Err(_) => true, +LL | | }; + | |_____^ help: try this: `Ok::(42).is_err()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching_result.rs:43:5 + | +LL | / match Err::(42) { +LL | | Ok(_) => false, +LL | | Err(_) => true, +LL | | }; + | |_____^ help: try this: `Err::(42).is_err()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching_result.rs:48:5 + | +LL | / match Err::(42) { +LL | | Ok(_) => true, +LL | | Err(_) => false, +LL | | }; + | |_____^ help: try this: `Err::(42).is_ok()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching_result.rs:53:20 + | +LL | let _ = if let Ok(_) = Ok::(4) { true } else { false }; + | -------^^^^^--------------------- help: try this: `if Ok::(4).is_ok()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching_result.rs:59:20 + | +LL | let _ = if let Ok(_) = gen_res() { + | -------^^^^^------------ help: try this: `if gen_res().is_ok()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching_result.rs:61:19 + | +LL | } else if let Err(_) = gen_res() { + | -------^^^^^^------------ help: try this: `if gen_res().is_err()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_result.rs:84:19 + | +LL | while let Some(_) = r#try!(result_opt()) {} + | ----------^^^^^^^----------------------- help: try this: `while r#try!(result_opt()).is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_result.rs:85:16 + | +LL | if let Some(_) = r#try!(result_opt()) {} + | -------^^^^^^^----------------------- help: try this: `if r#try!(result_opt()).is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_result.rs:91:12 + | +LL | if let Some(_) = m!() {} + | -------^^^^^^^------- help: try this: `if m!().is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching_result.rs:92:15 + | +LL | while let Some(_) = m!() {} + | ----------^^^^^^^------- help: try this: `while m!().is_some()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching_result.rs:110:12 + | +LL | if let Ok(_) = Ok::(42) {} + | -------^^^^^--------------------- help: try this: `if Ok::(42).is_ok()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching_result.rs:112:12 + | +LL | if let Err(_) = Err::(42) {} + | -------^^^^^^---------------------- help: try this: `if Err::(42).is_err()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching_result.rs:114:15 + | +LL | while let Ok(_) = Ok::(10) {} + | ----------^^^^^--------------------- help: try this: `while Ok::(10).is_ok()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching_result.rs:116:15 + | +LL | while let Err(_) = Ok::(10) {} + | ----------^^^^^^--------------------- help: try this: `while Ok::(10).is_err()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching_result.rs:118:5 + | +LL | / match Ok::(42) { +LL | | Ok(_) => true, +LL | | Err(_) => false, +LL | | }; + | |_____^ help: try this: `Ok::(42).is_ok()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching_result.rs:123:5 + | +LL | / match Err::(42) { +LL | | Ok(_) => false, +LL | | Err(_) => true, +LL | | }; + | |_____^ help: try this: `Err::(42).is_err()` + +error: aborting due to 22 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_pub_crate.fixed b/src/tools/clippy/tests/ui/redundant_pub_crate.fixed new file mode 100644 index 0000000000..25f2fd061b --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pub_crate.fixed @@ -0,0 +1,107 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::redundant_pub_crate)] + +mod m1 { + fn f() {} + pub fn g() {} // private due to m1 + pub fn h() {} + + mod m1_1 { + fn f() {} + pub fn g() {} // private due to m1_1 and m1 + pub fn h() {} + } + + pub mod m1_2 { + // ^ private due to m1 + fn f() {} + pub fn g() {} // private due to m1_2 and m1 + pub fn h() {} + } + + pub mod m1_3 { + fn f() {} + pub fn g() {} // private due to m1 + pub fn h() {} + } +} + +pub(crate) mod m2 { + fn f() {} + pub fn g() {} // already crate visible due to m2 + pub fn h() {} + + mod m2_1 { + fn f() {} + pub fn g() {} // private due to m2_1 + pub fn h() {} + } + + pub mod m2_2 { + // ^ already crate visible due to m2 + fn f() {} + pub fn g() {} // already crate visible due to m2_2 and m2 + pub fn h() {} + } + + pub mod m2_3 { + fn f() {} + pub fn g() {} // already crate visible due to m2 + pub fn h() {} + } +} + +pub mod m3 { + fn f() {} + pub(crate) fn g() {} // ok: m3 is exported + pub fn h() {} + + mod m3_1 { + fn f() {} + pub fn g() {} // private due to m3_1 + pub fn h() {} + } + + pub(crate) mod m3_2 { + // ^ ok + fn f() {} + pub fn g() {} // already crate visible due to m3_2 + pub fn h() {} + } + + pub mod m3_3 { + fn f() {} + pub(crate) fn g() {} // ok: m3 and m3_3 are exported + pub fn h() {} + } +} + +mod m4 { + fn f() {} + pub fn g() {} // private: not re-exported by `pub use m4::*` + pub fn h() {} + + mod m4_1 { + fn f() {} + pub fn g() {} // private due to m4_1 + pub fn h() {} + } + + pub mod m4_2 { + // ^ private: not re-exported by `pub use m4::*` + fn f() {} + pub fn g() {} // private due to m4_2 + pub fn h() {} + } + + pub mod m4_3 { + fn f() {} + pub(crate) fn g() {} // ok: m4_3 is re-exported by `pub use m4::*` + pub fn h() {} + } +} + +pub use m4::*; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/redundant_pub_crate.rs b/src/tools/clippy/tests/ui/redundant_pub_crate.rs new file mode 100644 index 0000000000..616286b4f3 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pub_crate.rs @@ -0,0 +1,107 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::redundant_pub_crate)] + +mod m1 { + fn f() {} + pub(crate) fn g() {} // private due to m1 + pub fn h() {} + + mod m1_1 { + fn f() {} + pub(crate) fn g() {} // private due to m1_1 and m1 + pub fn h() {} + } + + pub(crate) mod m1_2 { + // ^ private due to m1 + fn f() {} + pub(crate) fn g() {} // private due to m1_2 and m1 + pub fn h() {} + } + + pub mod m1_3 { + fn f() {} + pub(crate) fn g() {} // private due to m1 + pub fn h() {} + } +} + +pub(crate) mod m2 { + fn f() {} + pub(crate) fn g() {} // already crate visible due to m2 + pub fn h() {} + + mod m2_1 { + fn f() {} + pub(crate) fn g() {} // private due to m2_1 + pub fn h() {} + } + + pub(crate) mod m2_2 { + // ^ already crate visible due to m2 + fn f() {} + pub(crate) fn g() {} // already crate visible due to m2_2 and m2 + pub fn h() {} + } + + pub mod m2_3 { + fn f() {} + pub(crate) fn g() {} // already crate visible due to m2 + pub fn h() {} + } +} + +pub mod m3 { + fn f() {} + pub(crate) fn g() {} // ok: m3 is exported + pub fn h() {} + + mod m3_1 { + fn f() {} + pub(crate) fn g() {} // private due to m3_1 + pub fn h() {} + } + + pub(crate) mod m3_2 { + // ^ ok + fn f() {} + pub(crate) fn g() {} // already crate visible due to m3_2 + pub fn h() {} + } + + pub mod m3_3 { + fn f() {} + pub(crate) fn g() {} // ok: m3 and m3_3 are exported + pub fn h() {} + } +} + +mod m4 { + fn f() {} + pub(crate) fn g() {} // private: not re-exported by `pub use m4::*` + pub fn h() {} + + mod m4_1 { + fn f() {} + pub(crate) fn g() {} // private due to m4_1 + pub fn h() {} + } + + pub(crate) mod m4_2 { + // ^ private: not re-exported by `pub use m4::*` + fn f() {} + pub(crate) fn g() {} // private due to m4_2 + pub fn h() {} + } + + pub mod m4_3 { + fn f() {} + pub(crate) fn g() {} // ok: m4_3 is re-exported by `pub use m4::*` + pub fn h() {} + } +} + +pub use m4::*; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/redundant_pub_crate.stderr b/src/tools/clippy/tests/ui/redundant_pub_crate.stderr new file mode 100644 index 0000000000..6fccdaa4e2 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pub_crate.stderr @@ -0,0 +1,132 @@ +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:7:5 + | +LL | pub(crate) fn g() {} // private due to m1 + | ----------^^^^^ + | | + | help: consider using: `pub` + | + = note: `-D clippy::redundant-pub-crate` implied by `-D warnings` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:12:9 + | +LL | pub(crate) fn g() {} // private due to m1_1 and m1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) module inside private module + --> $DIR/redundant_pub_crate.rs:16:5 + | +LL | pub(crate) mod m1_2 { + | ----------^^^^^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:19:9 + | +LL | pub(crate) fn g() {} // private due to m1_2 and m1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:25:9 + | +LL | pub(crate) fn g() {} // private due to m1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:32:5 + | +LL | pub(crate) fn g() {} // already crate visible due to m2 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:37:9 + | +LL | pub(crate) fn g() {} // private due to m2_1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) module inside private module + --> $DIR/redundant_pub_crate.rs:41:5 + | +LL | pub(crate) mod m2_2 { + | ----------^^^^^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:44:9 + | +LL | pub(crate) fn g() {} // already crate visible due to m2_2 and m2 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:50:9 + | +LL | pub(crate) fn g() {} // already crate visible due to m2 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:62:9 + | +LL | pub(crate) fn g() {} // private due to m3_1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:69:9 + | +LL | pub(crate) fn g() {} // already crate visible due to m3_2 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:82:5 + | +LL | pub(crate) fn g() {} // private: not re-exported by `pub use m4::*` + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:87:9 + | +LL | pub(crate) fn g() {} // private due to m4_1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) module inside private module + --> $DIR/redundant_pub_crate.rs:91:5 + | +LL | pub(crate) mod m4_2 { + | ----------^^^^^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:94:9 + | +LL | pub(crate) fn g() {} // private due to m4_2 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_slicing.rs b/src/tools/clippy/tests/ui/redundant_slicing.rs new file mode 100644 index 0000000000..922b8b4ce5 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_slicing.rs @@ -0,0 +1,11 @@ +#![allow(unused)] +#![warn(clippy::redundant_slicing)] + +fn main() { + let x: &[u32] = &[0]; + let err = &x[..]; + + let v = vec![0]; + let ok = &v[..]; + let err = &(&v[..])[..]; +} diff --git a/src/tools/clippy/tests/ui/redundant_slicing.stderr b/src/tools/clippy/tests/ui/redundant_slicing.stderr new file mode 100644 index 0000000000..9efd6484ad --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_slicing.stderr @@ -0,0 +1,16 @@ +error: redundant slicing of the whole range + --> $DIR/redundant_slicing.rs:6:15 + | +LL | let err = &x[..]; + | ^^^^^^ help: use the original slice instead: `x` + | + = note: `-D clippy::redundant-slicing` implied by `-D warnings` + +error: redundant slicing of the whole range + --> $DIR/redundant_slicing.rs:10:15 + | +LL | let err = &(&v[..])[..]; + | ^^^^^^^^^^^^^ help: use the original slice instead: `(&v[..])` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_static_lifetimes.fixed b/src/tools/clippy/tests/ui/redundant_static_lifetimes.fixed new file mode 100644 index 0000000000..921249606a --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_static_lifetimes.fixed @@ -0,0 +1,56 @@ +// run-rustfix + +#![allow(unused)] + +#[derive(Debug)] +struct Foo {} + +const VAR_ONE: &str = "Test constant #1"; // ERROR Consider removing 'static. + +const VAR_TWO: &str = "Test constant #2"; // This line should not raise a warning. + +const VAR_THREE: &[&str] = &["one", "two"]; // ERROR Consider removing 'static + +const VAR_FOUR: (&str, (&str, &str), &str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + +const VAR_SIX: &u8 = &5; + +const VAR_HEIGHT: &Foo = &Foo {}; + +const VAR_SLICE: &[u8] = b"Test constant #1"; // ERROR Consider removing 'static. + +const VAR_TUPLE: &(u8, u8) = &(1, 2); // ERROR Consider removing 'static. + +const VAR_ARRAY: &[u8; 1] = b"T"; // ERROR Consider removing 'static. + +static STATIC_VAR_ONE: &str = "Test static #1"; // ERROR Consider removing 'static. + +static STATIC_VAR_TWO: &str = "Test static #2"; // This line should not raise a warning. + +static STATIC_VAR_THREE: &[&str] = &["one", "two"]; // ERROR Consider removing 'static + +static STATIC_VAR_SIX: &u8 = &5; + +static STATIC_VAR_HEIGHT: &Foo = &Foo {}; + +static STATIC_VAR_SLICE: &[u8] = b"Test static #3"; // ERROR Consider removing 'static. + +static STATIC_VAR_TUPLE: &(u8, u8) = &(1, 2); // ERROR Consider removing 'static. + +static STATIC_VAR_ARRAY: &[u8; 1] = b"T"; // ERROR Consider removing 'static. + +fn main() { + let false_positive: &'static str = "test"; +} + +trait Bar { + const TRAIT_VAR: &'static str; +} + +impl Foo { + const IMPL_VAR: &'static str = "var"; +} + +impl Bar for Foo { + const TRAIT_VAR: &'static str = "foo"; +} diff --git a/src/tools/clippy/tests/ui/redundant_static_lifetimes.rs b/src/tools/clippy/tests/ui/redundant_static_lifetimes.rs new file mode 100644 index 0000000000..4d4b249d07 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_static_lifetimes.rs @@ -0,0 +1,56 @@ +// run-rustfix + +#![allow(unused)] + +#[derive(Debug)] +struct Foo {} + +const VAR_ONE: &'static str = "Test constant #1"; // ERROR Consider removing 'static. + +const VAR_TWO: &str = "Test constant #2"; // This line should not raise a warning. + +const VAR_THREE: &[&'static str] = &["one", "two"]; // ERROR Consider removing 'static + +const VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + +const VAR_SIX: &'static u8 = &5; + +const VAR_HEIGHT: &'static Foo = &Foo {}; + +const VAR_SLICE: &'static [u8] = b"Test constant #1"; // ERROR Consider removing 'static. + +const VAR_TUPLE: &'static (u8, u8) = &(1, 2); // ERROR Consider removing 'static. + +const VAR_ARRAY: &'static [u8; 1] = b"T"; // ERROR Consider removing 'static. + +static STATIC_VAR_ONE: &'static str = "Test static #1"; // ERROR Consider removing 'static. + +static STATIC_VAR_TWO: &str = "Test static #2"; // This line should not raise a warning. + +static STATIC_VAR_THREE: &[&'static str] = &["one", "two"]; // ERROR Consider removing 'static + +static STATIC_VAR_SIX: &'static u8 = &5; + +static STATIC_VAR_HEIGHT: &'static Foo = &Foo {}; + +static STATIC_VAR_SLICE: &'static [u8] = b"Test static #3"; // ERROR Consider removing 'static. + +static STATIC_VAR_TUPLE: &'static (u8, u8) = &(1, 2); // ERROR Consider removing 'static. + +static STATIC_VAR_ARRAY: &'static [u8; 1] = b"T"; // ERROR Consider removing 'static. + +fn main() { + let false_positive: &'static str = "test"; +} + +trait Bar { + const TRAIT_VAR: &'static str; +} + +impl Foo { + const IMPL_VAR: &'static str = "var"; +} + +impl Bar for Foo { + const TRAIT_VAR: &'static str = "foo"; +} diff --git a/src/tools/clippy/tests/ui/redundant_static_lifetimes.stderr b/src/tools/clippy/tests/ui/redundant_static_lifetimes.stderr new file mode 100644 index 0000000000..649831f9c0 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_static_lifetimes.stderr @@ -0,0 +1,100 @@ +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:8:17 + | +LL | const VAR_ONE: &'static str = "Test constant #1"; // ERROR Consider removing 'static. + | -^^^^^^^---- help: consider removing `'static`: `&str` + | + = note: `-D clippy::redundant-static-lifetimes` implied by `-D warnings` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:12:21 + | +LL | const VAR_THREE: &[&'static str] = &["one", "two"]; // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:14:32 + | +LL | const VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:14:47 + | +LL | const VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:16:17 + | +LL | const VAR_SIX: &'static u8 = &5; + | -^^^^^^^--- help: consider removing `'static`: `&u8` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:18:20 + | +LL | const VAR_HEIGHT: &'static Foo = &Foo {}; + | -^^^^^^^---- help: consider removing `'static`: `&Foo` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:20:19 + | +LL | const VAR_SLICE: &'static [u8] = b"Test constant #1"; // ERROR Consider removing 'static. + | -^^^^^^^----- help: consider removing `'static`: `&[u8]` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:22:19 + | +LL | const VAR_TUPLE: &'static (u8, u8) = &(1, 2); // ERROR Consider removing 'static. + | -^^^^^^^--------- help: consider removing `'static`: `&(u8, u8)` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:24:19 + | +LL | const VAR_ARRAY: &'static [u8; 1] = b"T"; // ERROR Consider removing 'static. + | -^^^^^^^-------- help: consider removing `'static`: `&[u8; 1]` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:26:25 + | +LL | static STATIC_VAR_ONE: &'static str = "Test static #1"; // ERROR Consider removing 'static. + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:30:29 + | +LL | static STATIC_VAR_THREE: &[&'static str] = &["one", "two"]; // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:32:25 + | +LL | static STATIC_VAR_SIX: &'static u8 = &5; + | -^^^^^^^--- help: consider removing `'static`: `&u8` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:34:28 + | +LL | static STATIC_VAR_HEIGHT: &'static Foo = &Foo {}; + | -^^^^^^^---- help: consider removing `'static`: `&Foo` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:36:27 + | +LL | static STATIC_VAR_SLICE: &'static [u8] = b"Test static #3"; // ERROR Consider removing 'static. + | -^^^^^^^----- help: consider removing `'static`: `&[u8]` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:38:27 + | +LL | static STATIC_VAR_TUPLE: &'static (u8, u8) = &(1, 2); // ERROR Consider removing 'static. + | -^^^^^^^--------- help: consider removing `'static`: `&(u8, u8)` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:40:27 + | +LL | static STATIC_VAR_ARRAY: &'static [u8; 1] = b"T"; // ERROR Consider removing 'static. + | -^^^^^^^-------- help: consider removing `'static`: `&[u8; 1]` + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.rs b/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.rs new file mode 100644 index 0000000000..f57dd58e23 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.rs @@ -0,0 +1,13 @@ +// these are rustfixable, but run-rustfix tests cannot handle them + +const VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + +const VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + +static STATIC_VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + +static STATIC_VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + +static STATIC_VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.stderr b/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.stderr new file mode 100644 index 0000000000..cc7e55a757 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.stderr @@ -0,0 +1,64 @@ +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:3:18 + | +LL | const VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + | -^^^^^^^------------------ help: consider removing `'static`: `&[&[&'static str]]` + | + = note: `-D clippy::redundant-static-lifetimes` implied by `-D warnings` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:3:30 + | +LL | const VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:5:29 + | +LL | const VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + | -^^^^^^^--------------- help: consider removing `'static`: `&[&'static str]` + +error: constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:5:39 + | +LL | const VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:7:40 + | +LL | static STATIC_VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:7:55 + | +LL | static STATIC_VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:9:26 + | +LL | static STATIC_VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + | -^^^^^^^------------------ help: consider removing `'static`: `&[&[&'static str]]` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:9:38 + | +LL | static STATIC_VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:11:37 + | +LL | static STATIC_VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + | -^^^^^^^--------------- help: consider removing `'static`: `&[&'static str]` + +error: statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:11:47 + | +LL | static STATIC_VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/ref_option_ref.rs b/src/tools/clippy/tests/ui/ref_option_ref.rs new file mode 100644 index 0000000000..b2c275d68a --- /dev/null +++ b/src/tools/clippy/tests/ui/ref_option_ref.rs @@ -0,0 +1,47 @@ +#![allow(unused)] +#![warn(clippy::ref_option_ref)] + +// This lint is not tagged as run-rustfix because automatically +// changing the type of a variable would also means changing +// all usages of this variable to match and This is not handled +// by this lint. + +static THRESHOLD: i32 = 10; +static REF_THRESHOLD: &Option<&i32> = &Some(&THRESHOLD); +const CONST_THRESHOLD: &i32 = &10; +const REF_CONST: &Option<&i32> = &Some(&CONST_THRESHOLD); + +type RefOptRefU32<'a> = &'a Option<&'a u32>; +type RefOptRef<'a, T> = &'a Option<&'a T>; + +fn foo(data: &Option<&u32>) {} + +fn bar(data: &u32) -> &Option<&u32> { + &None +} + +struct StructRef<'a> { + data: &'a Option<&'a u32>, +} + +struct StructTupleRef<'a>(u32, &'a Option<&'a u32>); + +enum EnumRef<'a> { + Variant1(u32), + Variant2(&'a Option<&'a u32>), +} + +trait RefOptTrait { + type A; + fn foo(&self, _: Self::A); +} + +impl RefOptTrait for u32 { + type A = &'static Option<&'static Self>; + + fn foo(&self, _: Self::A) {} +} + +fn main() { + let x: &Option<&u32> = &None; +} diff --git a/src/tools/clippy/tests/ui/ref_option_ref.stderr b/src/tools/clippy/tests/ui/ref_option_ref.stderr new file mode 100644 index 0000000000..4e7fc80006 --- /dev/null +++ b/src/tools/clippy/tests/ui/ref_option_ref.stderr @@ -0,0 +1,70 @@ +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:10:23 + | +LL | static REF_THRESHOLD: &Option<&i32> = &Some(&THRESHOLD); + | ^^^^^^^^^^^^^ help: try: `Option<&i32>` + | + = note: `-D clippy::ref-option-ref` implied by `-D warnings` + +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:12:18 + | +LL | const REF_CONST: &Option<&i32> = &Some(&CONST_THRESHOLD); + | ^^^^^^^^^^^^^ help: try: `Option<&i32>` + +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:14:25 + | +LL | type RefOptRefU32<'a> = &'a Option<&'a u32>; + | ^^^^^^^^^^^^^^^^^^^ help: try: `Option<&'a u32>` + +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:15:25 + | +LL | type RefOptRef<'a, T> = &'a Option<&'a T>; + | ^^^^^^^^^^^^^^^^^ help: try: `Option<&'a T>` + +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:17:14 + | +LL | fn foo(data: &Option<&u32>) {} + | ^^^^^^^^^^^^^ help: try: `Option<&u32>` + +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:19:23 + | +LL | fn bar(data: &u32) -> &Option<&u32> { + | ^^^^^^^^^^^^^ help: try: `Option<&u32>` + +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:24:11 + | +LL | data: &'a Option<&'a u32>, + | ^^^^^^^^^^^^^^^^^^^ help: try: `Option<&'a u32>` + +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:27:32 + | +LL | struct StructTupleRef<'a>(u32, &'a Option<&'a u32>); + | ^^^^^^^^^^^^^^^^^^^ help: try: `Option<&'a u32>` + +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:31:14 + | +LL | Variant2(&'a Option<&'a u32>), + | ^^^^^^^^^^^^^^^^^^^ help: try: `Option<&'a u32>` + +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:40:14 + | +LL | type A = &'static Option<&'static Self>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Option<&'static Self>` + +error: since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>` + --> $DIR/ref_option_ref.rs:46:12 + | +LL | let x: &Option<&u32> = &None; + | ^^^^^^^^^^^^^ help: try: `Option<&u32>` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/regex.rs b/src/tools/clippy/tests/ui/regex.rs new file mode 100644 index 0000000000..f7f3b195cc --- /dev/null +++ b/src/tools/clippy/tests/ui/regex.rs @@ -0,0 +1,82 @@ +#![allow(unused)] +#![warn(clippy::invalid_regex, clippy::trivial_regex)] + +extern crate regex; + +use regex::bytes::{Regex as BRegex, RegexBuilder as BRegexBuilder, RegexSet as BRegexSet}; +use regex::{Regex, RegexBuilder, RegexSet}; + +const OPENING_PAREN: &str = "("; +const NOT_A_REAL_REGEX: &str = "foobar"; + +fn syntax_error() { + let pipe_in_wrong_position = Regex::new("|"); + let pipe_in_wrong_position_builder = RegexBuilder::new("|"); + let wrong_char_ranice = Regex::new("[z-a]"); + let some_unicode = Regex::new("[é-è]"); + + let some_regex = Regex::new(OPENING_PAREN); + + let binary_pipe_in_wrong_position = BRegex::new("|"); + let some_binary_regex = BRegex::new(OPENING_PAREN); + let some_binary_regex_builder = BRegexBuilder::new(OPENING_PAREN); + + let closing_paren = ")"; + let not_linted = Regex::new(closing_paren); + + let set = RegexSet::new(&[r"[a-z]+@[a-z]+\.(com|org|net)", r"[a-z]+\.(com|org|net)"]); + let bset = BRegexSet::new(&[ + r"[a-z]+@[a-z]+\.(com|org|net)", + r"[a-z]+\.(com|org|net)", + r".", // regression test + ]); + + let set_error = RegexSet::new(&[OPENING_PAREN, r"[a-z]+\.(com|org|net)"]); + let bset_error = BRegexSet::new(&[OPENING_PAREN, r"[a-z]+\.(com|org|net)"]); + + let raw_string_error = Regex::new(r"[...\/...]"); + let raw_string_error = Regex::new(r#"[...\/...]"#); +} + +fn trivial_regex() { + let trivial_eq = Regex::new("^foobar$"); + + let trivial_eq_builder = RegexBuilder::new("^foobar$"); + + let trivial_starts_with = Regex::new("^foobar"); + + let trivial_ends_with = Regex::new("foobar$"); + + let trivial_contains = Regex::new("foobar"); + + let trivial_contains = Regex::new(NOT_A_REAL_REGEX); + + let trivial_backslash = Regex::new("a\\.b"); + + // unlikely corner cases + let trivial_empty = Regex::new(""); + + let trivial_empty = Regex::new("^"); + + let trivial_empty = Regex::new("^$"); + + let binary_trivial_empty = BRegex::new("^$"); + + // non-trivial regexes + let non_trivial_dot = Regex::new("a.b"); + let non_trivial_dot_builder = RegexBuilder::new("a.b"); + let non_trivial_eq = Regex::new("^foo|bar$"); + let non_trivial_starts_with = Regex::new("^foo|bar"); + let non_trivial_ends_with = Regex::new("^foo|bar"); + let non_trivial_ends_with = Regex::new("foo|bar"); + let non_trivial_binary = BRegex::new("foo|bar"); + let non_trivial_binary_builder = BRegexBuilder::new("foo|bar"); + + // #6005: unicode classes in bytes::Regex + let a_byte_of_unicode = BRegex::new(r"\p{C}"); +} + +fn main() { + syntax_error(); + trivial_regex(); +} diff --git a/src/tools/clippy/tests/ui/regex.stderr b/src/tools/clippy/tests/ui/regex.stderr new file mode 100644 index 0000000000..1394a9b63b --- /dev/null +++ b/src/tools/clippy/tests/ui/regex.stderr @@ -0,0 +1,171 @@ +error: trivial regex + --> $DIR/regex.rs:13:45 + | +LL | let pipe_in_wrong_position = Regex::new("|"); + | ^^^ + | + = note: `-D clippy::trivial-regex` implied by `-D warnings` + = help: the regex is unlikely to be useful as it is + +error: trivial regex + --> $DIR/regex.rs:14:60 + | +LL | let pipe_in_wrong_position_builder = RegexBuilder::new("|"); + | ^^^ + | + = help: the regex is unlikely to be useful as it is + +error: regex syntax error: invalid character class range, the start must be <= the end + --> $DIR/regex.rs:15:42 + | +LL | let wrong_char_ranice = Regex::new("[z-a]"); + | ^^^ + | + = note: `-D clippy::invalid-regex` implied by `-D warnings` + +error: regex syntax error: invalid character class range, the start must be <= the end + --> $DIR/regex.rs:16:37 + | +LL | let some_unicode = Regex::new("[é-è]"); + | ^^^ + +error: regex syntax error on position 0: unclosed group + --> $DIR/regex.rs:18:33 + | +LL | let some_regex = Regex::new(OPENING_PAREN); + | ^^^^^^^^^^^^^ + +error: trivial regex + --> $DIR/regex.rs:20:53 + | +LL | let binary_pipe_in_wrong_position = BRegex::new("|"); + | ^^^ + | + = help: the regex is unlikely to be useful as it is + +error: regex syntax error on position 0: unclosed group + --> $DIR/regex.rs:21:41 + | +LL | let some_binary_regex = BRegex::new(OPENING_PAREN); + | ^^^^^^^^^^^^^ + +error: regex syntax error on position 0: unclosed group + --> $DIR/regex.rs:22:56 + | +LL | let some_binary_regex_builder = BRegexBuilder::new(OPENING_PAREN); + | ^^^^^^^^^^^^^ + +error: regex syntax error on position 0: unclosed group + --> $DIR/regex.rs:34:37 + | +LL | let set_error = RegexSet::new(&[OPENING_PAREN, r"[a-z]+/.(com|org|net)"]); + | ^^^^^^^^^^^^^ + +error: regex syntax error on position 0: unclosed group + --> $DIR/regex.rs:35:39 + | +LL | let bset_error = BRegexSet::new(&[OPENING_PAREN, r"[a-z]+/.(com|org|net)"]); + | ^^^^^^^^^^^^^ + +error: regex syntax error: unrecognized escape sequence + --> $DIR/regex.rs:37:45 + | +LL | let raw_string_error = Regex::new(r"[...//...]"); + | ^^ + +error: regex syntax error: unrecognized escape sequence + --> $DIR/regex.rs:38:46 + | +LL | let raw_string_error = Regex::new(r#"[...//...]"#); + | ^^ + +error: trivial regex + --> $DIR/regex.rs:42:33 + | +LL | let trivial_eq = Regex::new("^foobar$"); + | ^^^^^^^^^^ + | + = help: consider using `==` on `str`s + +error: trivial regex + --> $DIR/regex.rs:44:48 + | +LL | let trivial_eq_builder = RegexBuilder::new("^foobar$"); + | ^^^^^^^^^^ + | + = help: consider using `==` on `str`s + +error: trivial regex + --> $DIR/regex.rs:46:42 + | +LL | let trivial_starts_with = Regex::new("^foobar"); + | ^^^^^^^^^ + | + = help: consider using `str::starts_with` + +error: trivial regex + --> $DIR/regex.rs:48:40 + | +LL | let trivial_ends_with = Regex::new("foobar$"); + | ^^^^^^^^^ + | + = help: consider using `str::ends_with` + +error: trivial regex + --> $DIR/regex.rs:50:39 + | +LL | let trivial_contains = Regex::new("foobar"); + | ^^^^^^^^ + | + = help: consider using `str::contains` + +error: trivial regex + --> $DIR/regex.rs:52:39 + | +LL | let trivial_contains = Regex::new(NOT_A_REAL_REGEX); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using `str::contains` + +error: trivial regex + --> $DIR/regex.rs:54:40 + | +LL | let trivial_backslash = Regex::new("a/.b"); + | ^^^^^^^ + | + = help: consider using `str::contains` + +error: trivial regex + --> $DIR/regex.rs:57:36 + | +LL | let trivial_empty = Regex::new(""); + | ^^ + | + = help: the regex is unlikely to be useful as it is + +error: trivial regex + --> $DIR/regex.rs:59:36 + | +LL | let trivial_empty = Regex::new("^"); + | ^^^ + | + = help: the regex is unlikely to be useful as it is + +error: trivial regex + --> $DIR/regex.rs:61:36 + | +LL | let trivial_empty = Regex::new("^$"); + | ^^^^ + | + = help: consider using `str::is_empty` + +error: trivial regex + --> $DIR/regex.rs:63:44 + | +LL | let binary_trivial_empty = BRegex::new("^$"); + | ^^^^ + | + = help: consider using `str::is_empty` + +error: aborting due to 23 previous errors + diff --git a/src/tools/clippy/tests/ui/rename.fixed b/src/tools/clippy/tests/ui/rename.fixed new file mode 100644 index 0000000000..13fbb6e2a6 --- /dev/null +++ b/src/tools/clippy/tests/ui/rename.fixed @@ -0,0 +1,19 @@ +//! Test for Clippy lint renames. +// run-rustfix + +#![allow(dead_code)] +// allow the new lint name here, to test if the new name works +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_static_lifetimes)] +// warn for the old lint name here, to test if the renaming worked +#![warn(clippy::cognitive_complexity)] + +#[warn(clippy::module_name_repetitions)] +fn main() {} + +#[warn(clippy::new_without_default)] +struct Foo; + +#[warn(clippy::redundant_static_lifetimes)] +fn foo() {} diff --git a/src/tools/clippy/tests/ui/rename.rs b/src/tools/clippy/tests/ui/rename.rs new file mode 100644 index 0000000000..cbd3b1e916 --- /dev/null +++ b/src/tools/clippy/tests/ui/rename.rs @@ -0,0 +1,19 @@ +//! Test for Clippy lint renames. +// run-rustfix + +#![allow(dead_code)] +// allow the new lint name here, to test if the new name works +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_static_lifetimes)] +// warn for the old lint name here, to test if the renaming worked +#![warn(clippy::cyclomatic_complexity)] + +#[warn(clippy::stutter)] +fn main() {} + +#[warn(clippy::new_without_default_derive)] +struct Foo; + +#[warn(clippy::const_static_lifetime)] +fn foo() {} diff --git a/src/tools/clippy/tests/ui/rename.stderr b/src/tools/clippy/tests/ui/rename.stderr new file mode 100644 index 0000000000..a9e8039460 --- /dev/null +++ b/src/tools/clippy/tests/ui/rename.stderr @@ -0,0 +1,34 @@ +error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` + --> $DIR/rename.rs:10:9 + | +LL | #![warn(clippy::cyclomatic_complexity)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` + | + = note: `-D renamed-and-removed-lints` implied by `-D warnings` + +error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` + --> $DIR/rename.rs:12:8 + | +LL | #[warn(clippy::stutter)] + | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` + +error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` + --> $DIR/rename.rs:15:8 + | +LL | #[warn(clippy::new_without_default_derive)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` + +error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` + --> $DIR/rename.rs:18:8 + | +LL | #[warn(clippy::const_static_lifetime)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` + +error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` + --> $DIR/rename.rs:10:9 + | +LL | #![warn(clippy::cyclomatic_complexity)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/renamed_builtin_attr.fixed b/src/tools/clippy/tests/ui/renamed_builtin_attr.fixed new file mode 100644 index 0000000000..cb91b841d2 --- /dev/null +++ b/src/tools/clippy/tests/ui/renamed_builtin_attr.fixed @@ -0,0 +1,4 @@ +// run-rustfix + +#[clippy::cognitive_complexity = "1"] +fn main() {} diff --git a/src/tools/clippy/tests/ui/renamed_builtin_attr.rs b/src/tools/clippy/tests/ui/renamed_builtin_attr.rs new file mode 100644 index 0000000000..b3ce275806 --- /dev/null +++ b/src/tools/clippy/tests/ui/renamed_builtin_attr.rs @@ -0,0 +1,4 @@ +// run-rustfix + +#[clippy::cyclomatic_complexity = "1"] +fn main() {} diff --git a/src/tools/clippy/tests/ui/renamed_builtin_attr.stderr b/src/tools/clippy/tests/ui/renamed_builtin_attr.stderr new file mode 100644 index 0000000000..8804676248 --- /dev/null +++ b/src/tools/clippy/tests/ui/renamed_builtin_attr.stderr @@ -0,0 +1,8 @@ +error: usage of deprecated attribute + --> $DIR/renamed_builtin_attr.rs:3:11 + | +LL | #[clippy::cyclomatic_complexity = "1"] + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `cognitive_complexity` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/repeat_once.fixed b/src/tools/clippy/tests/ui/repeat_once.fixed new file mode 100644 index 0000000000..a637c22fbc --- /dev/null +++ b/src/tools/clippy/tests/ui/repeat_once.fixed @@ -0,0 +1,16 @@ +// run-rustfix +#![warn(clippy::repeat_once)] +#[allow(unused, clippy::many_single_char_names, clippy::redundant_clone)] +fn main() { + const N: usize = 1; + let s = "str"; + let string = "String".to_string(); + let slice = [1; 5]; + + let a = [1; 5].to_vec(); + let b = slice.to_vec(); + let c = "hello".to_string(); + let d = "hi".to_string(); + let e = s.to_string(); + let f = string.clone(); +} diff --git a/src/tools/clippy/tests/ui/repeat_once.rs b/src/tools/clippy/tests/ui/repeat_once.rs new file mode 100644 index 0000000000..d99ca1b5b5 --- /dev/null +++ b/src/tools/clippy/tests/ui/repeat_once.rs @@ -0,0 +1,16 @@ +// run-rustfix +#![warn(clippy::repeat_once)] +#[allow(unused, clippy::many_single_char_names, clippy::redundant_clone)] +fn main() { + const N: usize = 1; + let s = "str"; + let string = "String".to_string(); + let slice = [1; 5]; + + let a = [1; 5].repeat(1); + let b = slice.repeat(1); + let c = "hello".repeat(N); + let d = "hi".repeat(1); + let e = s.repeat(1); + let f = string.repeat(1); +} diff --git a/src/tools/clippy/tests/ui/repeat_once.stderr b/src/tools/clippy/tests/ui/repeat_once.stderr new file mode 100644 index 0000000000..915eea3bfc --- /dev/null +++ b/src/tools/clippy/tests/ui/repeat_once.stderr @@ -0,0 +1,40 @@ +error: calling `repeat(1)` on slice + --> $DIR/repeat_once.rs:10:13 + | +LL | let a = [1; 5].repeat(1); + | ^^^^^^^^^^^^^^^^ help: consider using `.to_vec()` instead: `[1; 5].to_vec()` + | + = note: `-D clippy::repeat-once` implied by `-D warnings` + +error: calling `repeat(1)` on slice + --> $DIR/repeat_once.rs:11:13 + | +LL | let b = slice.repeat(1); + | ^^^^^^^^^^^^^^^ help: consider using `.to_vec()` instead: `slice.to_vec()` + +error: calling `repeat(1)` on str + --> $DIR/repeat_once.rs:12:13 + | +LL | let c = "hello".repeat(N); + | ^^^^^^^^^^^^^^^^^ help: consider using `.to_string()` instead: `"hello".to_string()` + +error: calling `repeat(1)` on str + --> $DIR/repeat_once.rs:13:13 + | +LL | let d = "hi".repeat(1); + | ^^^^^^^^^^^^^^ help: consider using `.to_string()` instead: `"hi".to_string()` + +error: calling `repeat(1)` on str + --> $DIR/repeat_once.rs:14:13 + | +LL | let e = s.repeat(1); + | ^^^^^^^^^^^ help: consider using `.to_string()` instead: `s.to_string()` + +error: calling `repeat(1)` on a string literal + --> $DIR/repeat_once.rs:15:13 + | +LL | let f = string.repeat(1); + | ^^^^^^^^^^^^^^^^ help: consider using `.clone()` instead: `string.clone()` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/repl_uninit.rs b/src/tools/clippy/tests/ui/repl_uninit.rs new file mode 100644 index 0000000000..ad5b8e4857 --- /dev/null +++ b/src/tools/clippy/tests/ui/repl_uninit.rs @@ -0,0 +1,41 @@ +#![allow(deprecated, invalid_value)] +#![warn(clippy::all)] + +use std::mem; + +fn might_panic(x: X) -> X { + // in practice this would be a possibly-panicky operation + x +} + +fn main() { + let mut v = vec![0i32; 4]; + // the following is UB if `might_panic` panics + unsafe { + let taken_v = mem::replace(&mut v, mem::uninitialized()); + let new_v = might_panic(taken_v); + std::mem::forget(mem::replace(&mut v, new_v)); + } + + unsafe { + let taken_v = mem::replace(&mut v, mem::MaybeUninit::uninit().assume_init()); + let new_v = might_panic(taken_v); + std::mem::forget(mem::replace(&mut v, new_v)); + } + + unsafe { + let taken_v = mem::replace(&mut v, mem::zeroed()); + let new_v = might_panic(taken_v); + std::mem::forget(mem::replace(&mut v, new_v)); + } + + // this is silly but OK, because usize is a primitive type + let mut u: usize = 42; + let uref = &mut u; + let taken_u = unsafe { mem::replace(uref, mem::zeroed()) }; + *uref = taken_u + 1; + + // this is still not OK, because uninit + let taken_u = unsafe { mem::replace(uref, mem::uninitialized()) }; + *uref = taken_u + 1; +} diff --git a/src/tools/clippy/tests/ui/repl_uninit.stderr b/src/tools/clippy/tests/ui/repl_uninit.stderr new file mode 100644 index 0000000000..09468eeaea --- /dev/null +++ b/src/tools/clippy/tests/ui/repl_uninit.stderr @@ -0,0 +1,30 @@ +error: replacing with `mem::uninitialized()` + --> $DIR/repl_uninit.rs:15:23 + | +LL | let taken_v = mem::replace(&mut v, mem::uninitialized()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::ptr::read(&mut v)` + | + = note: `-D clippy::mem-replace-with-uninit` implied by `-D warnings` + +error: replacing with `mem::MaybeUninit::uninit().assume_init()` + --> $DIR/repl_uninit.rs:21:23 + | +LL | let taken_v = mem::replace(&mut v, mem::MaybeUninit::uninit().assume_init()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::ptr::read(&mut v)` + +error: replacing with `mem::zeroed()` + --> $DIR/repl_uninit.rs:27:23 + | +LL | let taken_v = mem::replace(&mut v, mem::zeroed()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a default value or the `take_mut` crate instead + +error: replacing with `mem::uninitialized()` + --> $DIR/repl_uninit.rs:39:28 + | +LL | let taken_u = unsafe { mem::replace(uref, mem::uninitialized()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::ptr::read(uref)` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.rs b/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.rs new file mode 100644 index 0000000000..38fc996980 --- /dev/null +++ b/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.rs @@ -0,0 +1,42 @@ +#![warn(clippy::rest_pat_in_fully_bound_structs)] + +struct A { + a: i32, + b: i64, + c: &'static str, +} + +macro_rules! foo { + ($param:expr) => { + match $param { + A { a: 0, b: 0, c: "", .. } => {}, + _ => {}, + } + }; +} + +fn main() { + let a_struct = A { a: 5, b: 42, c: "A" }; + + match a_struct { + A { a: 5, b: 42, c: "", .. } => {}, // Lint + A { a: 0, b: 0, c: "", .. } => {}, // Lint + _ => {}, + } + + match a_struct { + A { a: 5, b: 42, .. } => {}, + A { a: 0, b: 0, c: "", .. } => {}, // Lint + _ => {}, + } + + // No lint + match a_struct { + A { a: 5, .. } => {}, + A { a: 0, b: 0, .. } => {}, + _ => {}, + } + + // No lint + foo!(a_struct); +} diff --git a/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.stderr b/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.stderr new file mode 100644 index 0000000000..57ebd47f8c --- /dev/null +++ b/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.stderr @@ -0,0 +1,27 @@ +error: unnecessary use of `..` pattern in struct binding. All fields were already bound + --> $DIR/rest_pat_in_fully_bound_structs.rs:22:9 + | +LL | A { a: 5, b: 42, c: "", .. } => {}, // Lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::rest-pat-in-fully-bound-structs` implied by `-D warnings` + = help: consider removing `..` from this binding + +error: unnecessary use of `..` pattern in struct binding. All fields were already bound + --> $DIR/rest_pat_in_fully_bound_structs.rs:23:9 + | +LL | A { a: 0, b: 0, c: "", .. } => {}, // Lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `..` from this binding + +error: unnecessary use of `..` pattern in struct binding. All fields were already bound + --> $DIR/rest_pat_in_fully_bound_structs.rs:29:9 + | +LL | A { a: 0, b: 0, c: "", .. } => {}, // Lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `..` from this binding + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/result_map_or_into_option.fixed b/src/tools/clippy/tests/ui/result_map_or_into_option.fixed new file mode 100644 index 0000000000..331531b516 --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_or_into_option.fixed @@ -0,0 +1,19 @@ +// run-rustfix + +#![warn(clippy::result_map_or_into_option)] + +fn main() { + let opt: Result = Ok(1); + let _ = opt.ok(); + + let rewrap = |s: u32| -> Option { Some(s) }; + + // A non-Some `f` arg should not emit the lint + let opt: Result = Ok(1); + let _ = opt.map_or(None, rewrap); + + // A non-Some `f` closure where the argument is not used as the + // return should not emit the lint + let opt: Result = Ok(1); + opt.map_or(None, |_x| Some(1)); +} diff --git a/src/tools/clippy/tests/ui/result_map_or_into_option.rs b/src/tools/clippy/tests/ui/result_map_or_into_option.rs new file mode 100644 index 0000000000..3058480e2a --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_or_into_option.rs @@ -0,0 +1,19 @@ +// run-rustfix + +#![warn(clippy::result_map_or_into_option)] + +fn main() { + let opt: Result = Ok(1); + let _ = opt.map_or(None, Some); + + let rewrap = |s: u32| -> Option { Some(s) }; + + // A non-Some `f` arg should not emit the lint + let opt: Result = Ok(1); + let _ = opt.map_or(None, rewrap); + + // A non-Some `f` closure where the argument is not used as the + // return should not emit the lint + let opt: Result = Ok(1); + opt.map_or(None, |_x| Some(1)); +} diff --git a/src/tools/clippy/tests/ui/result_map_or_into_option.stderr b/src/tools/clippy/tests/ui/result_map_or_into_option.stderr new file mode 100644 index 0000000000..febf32147d --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_or_into_option.stderr @@ -0,0 +1,10 @@ +error: called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling `ok()` instead + --> $DIR/result_map_or_into_option.rs:7:13 + | +LL | let _ = opt.map_or(None, Some); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try using `ok` instead: `opt.ok()` + | + = note: `-D clippy::result-map-or-into-option` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.fixed b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.fixed new file mode 100644 index 0000000000..631042c616 --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.fixed @@ -0,0 +1,80 @@ +// run-rustfix + +#![warn(clippy::result_map_unit_fn)] +#![allow(unused)] + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +struct HasResult { + field: Result, +} + +impl HasResult { + fn do_result_nothing(&self, value: usize) {} + + fn do_result_plus_one(&self, value: usize) -> usize { + value + 1 + } +} + +#[rustfmt::skip] +fn result_map_unit_fn() { + let x = HasResult { field: Ok(10) }; + + x.field.map(plus_one); + let _: Result<(), usize> = x.field.map(do_nothing); + + if let Ok(x_field) = x.field { do_nothing(x_field) } + + if let Ok(x_field) = x.field { do_nothing(x_field) } + + if let Ok(x_field) = x.field { diverge(x_field) } + + let captured = 10; + if let Ok(value) = x.field { do_nothing(value + captured) }; + let _: Result<(), usize> = x.field.map(|value| do_nothing(value + captured)); + + if let Ok(value) = x.field { x.do_result_nothing(value + captured) } + + if let Ok(value) = x.field { x.do_result_plus_one(value + captured); } + + + if let Ok(value) = x.field { do_nothing(value + captured) } + + if let Ok(value) = x.field { do_nothing(value + captured) } + + if let Ok(value) = x.field { do_nothing(value + captured); } + + if let Ok(value) = x.field { do_nothing(value + captured); } + + + if let Ok(value) = x.field { diverge(value + captured) } + + if let Ok(value) = x.field { diverge(value + captured) } + + if let Ok(value) = x.field { diverge(value + captured); } + + if let Ok(value) = x.field { diverge(value + captured); } + + + x.field.map(|value| plus_one(value + captured)); + x.field.map(|value| { plus_one(value + captured) }); + if let Ok(value) = x.field { let y = plus_one(value + captured); } + + if let Ok(value) = x.field { plus_one(value + captured); } + + if let Ok(value) = x.field { plus_one(value + captured); } + + + if let Ok(ref value) = x.field { do_nothing(value + captured) } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.rs b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.rs new file mode 100644 index 0000000000..679eb23262 --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.rs @@ -0,0 +1,80 @@ +// run-rustfix + +#![warn(clippy::result_map_unit_fn)] +#![allow(unused)] + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +struct HasResult { + field: Result, +} + +impl HasResult { + fn do_result_nothing(&self, value: usize) {} + + fn do_result_plus_one(&self, value: usize) -> usize { + value + 1 + } +} + +#[rustfmt::skip] +fn result_map_unit_fn() { + let x = HasResult { field: Ok(10) }; + + x.field.map(plus_one); + let _: Result<(), usize> = x.field.map(do_nothing); + + x.field.map(do_nothing); + + x.field.map(do_nothing); + + x.field.map(diverge); + + let captured = 10; + if let Ok(value) = x.field { do_nothing(value + captured) }; + let _: Result<(), usize> = x.field.map(|value| do_nothing(value + captured)); + + x.field.map(|value| x.do_result_nothing(value + captured)); + + x.field.map(|value| { x.do_result_plus_one(value + captured); }); + + + x.field.map(|value| do_nothing(value + captured)); + + x.field.map(|value| { do_nothing(value + captured) }); + + x.field.map(|value| { do_nothing(value + captured); }); + + x.field.map(|value| { { do_nothing(value + captured); } }); + + + x.field.map(|value| diverge(value + captured)); + + x.field.map(|value| { diverge(value + captured) }); + + x.field.map(|value| { diverge(value + captured); }); + + x.field.map(|value| { { diverge(value + captured); } }); + + + x.field.map(|value| plus_one(value + captured)); + x.field.map(|value| { plus_one(value + captured) }); + x.field.map(|value| { let y = plus_one(value + captured); }); + + x.field.map(|value| { plus_one(value + captured); }); + + x.field.map(|value| { { plus_one(value + captured); } }); + + + x.field.map(|ref value| { do_nothing(value + captured) }); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.stderr b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.stderr new file mode 100644 index 0000000000..4f3a8c6b79 --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.stderr @@ -0,0 +1,140 @@ +error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:35:5 + | +LL | x.field.map(do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(x_field) = x.field { do_nothing(x_field) }` + | + = note: `-D clippy::result-map-unit-fn` implied by `-D warnings` + +error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:37:5 + | +LL | x.field.map(do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(x_field) = x.field { do_nothing(x_field) }` + +error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:39:5 + | +LL | x.field.map(diverge); + | ^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(x_field) = x.field { diverge(x_field) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:45:5 + | +LL | x.field.map(|value| x.do_result_nothing(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { x.do_result_nothing(value + captured) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:47:5 + | +LL | x.field.map(|value| { x.do_result_plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { x.do_result_plus_one(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:50:5 + | +LL | x.field.map(|value| do_nothing(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { do_nothing(value + captured) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:52:5 + | +LL | x.field.map(|value| { do_nothing(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { do_nothing(value + captured) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:54:5 + | +LL | x.field.map(|value| { do_nothing(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { do_nothing(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:56:5 + | +LL | x.field.map(|value| { { do_nothing(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { do_nothing(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:59:5 + | +LL | x.field.map(|value| diverge(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { diverge(value + captured) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:61:5 + | +LL | x.field.map(|value| { diverge(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { diverge(value + captured) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:63:5 + | +LL | x.field.map(|value| { diverge(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { diverge(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:65:5 + | +LL | x.field.map(|value| { { diverge(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { diverge(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:70:5 + | +LL | x.field.map(|value| { let y = plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { let y = plus_one(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:72:5 + | +LL | x.field.map(|value| { plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { plus_one(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:74:5 + | +LL | x.field.map(|value| { { plus_one(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { plus_one(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_fixable.rs:77:5 + | +LL | x.field.map(|ref value| { do_nothing(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(ref value) = x.field { do_nothing(value + captured) }` + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.rs b/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.rs new file mode 100644 index 0000000000..b197c609d7 --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.rs @@ -0,0 +1,46 @@ +#![warn(clippy::result_map_unit_fn)] +#![feature(never_type)] +#![allow(unused)] + +struct HasResult { + field: Result, +} + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +#[rustfmt::skip] +fn result_map_unit_fn() { + let x = HasResult { field: Ok(10) }; + + x.field.map(|value| { do_nothing(value); do_nothing(value) }); + + x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); + + // Suggestion for the let block should be `{ ... }` as it's too difficult to build a + // proper suggestion for these cases + x.field.map(|value| { + do_nothing(value); + do_nothing(value) + }); + x.field.map(|value| { do_nothing(value); do_nothing(value); }); + + // The following should suggest `if let Ok(_X) ...` as it's difficult to generate a proper let variable name for them + let res: Result = Ok(42).map(diverge); + "12".parse::().map(diverge); + + let res: Result<(), usize> = Ok(plus_one(1)).map(do_nothing); + + // Should suggest `if let Ok(_y) ...` to not override the existing foo variable + let y: Result = Ok(42); + y.map(do_nothing); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.stderr b/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.stderr new file mode 100644 index 0000000000..88e4efdb0f --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.stderr @@ -0,0 +1,58 @@ +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_unfixable.rs:23:5 + | +LL | x.field.map(|value| { do_nothing(value); do_nothing(value) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { ... }` + | + = note: `-D clippy::result-map-unit-fn` implied by `-D warnings` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_unfixable.rs:25:5 + | +LL | x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { ... }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_unfixable.rs:29:5 + | +LL | x.field.map(|value| { + | _____^ + | |_____| + | || +LL | || do_nothing(value); +LL | || do_nothing(value) +LL | || }); + | ||______^- help: try this: `if let Ok(value) = x.field { ... }` + | |_______| + | + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` + --> $DIR/result_map_unit_fn_unfixable.rs:33:5 + | +LL | x.field.map(|value| { do_nothing(value); do_nothing(value); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { ... }` + +error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` + --> $DIR/result_map_unit_fn_unfixable.rs:37:5 + | +LL | "12".parse::().map(diverge); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(a) = "12".parse::() { diverge(a) }` + +error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` + --> $DIR/result_map_unit_fn_unfixable.rs:43:5 + | +LL | y.map(do_nothing); + | ^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(_y) = y { do_nothing(_y) }` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/result_unit_error.rs b/src/tools/clippy/tests/ui/result_unit_error.rs new file mode 100644 index 0000000000..a4ec803024 --- /dev/null +++ b/src/tools/clippy/tests/ui/result_unit_error.rs @@ -0,0 +1,56 @@ +#![warn(clippy::result_unit_err)] + +pub fn returns_unit_error() -> Result { + Err(()) +} + +fn private_unit_errors() -> Result { + Err(()) +} + +pub trait HasUnitError { + fn get_that_error(&self) -> Result; + + fn get_this_one_too(&self) -> Result { + Err(()) + } +} + +impl HasUnitError for () { + fn get_that_error(&self) -> Result { + Ok(true) + } +} + +trait PrivateUnitError { + fn no_problem(&self) -> Result; +} + +pub struct UnitErrorHolder; + +impl UnitErrorHolder { + pub fn unit_error(&self) -> Result { + Ok(0) + } +} + +// https://github.com/rust-lang/rust-clippy/issues/6546 +pub mod issue_6546 { + type ResInv = Result; + + pub fn should_lint() -> ResInv<(), usize> { + Ok(0) + } + + pub fn should_not_lint() -> ResInv { + Ok(()) + } + + type MyRes = Result<(A, B), Box>; + + pub fn should_not_lint2(x: i32) -> MyRes { + Ok((x, ())) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/result_unit_error.stderr b/src/tools/clippy/tests/ui/result_unit_error.stderr new file mode 100644 index 0000000000..41d8b0a7cb --- /dev/null +++ b/src/tools/clippy/tests/ui/result_unit_error.stderr @@ -0,0 +1,43 @@ +error: this returns a `Result<_, ()> + --> $DIR/result_unit_error.rs:3:1 + | +LL | pub fn returns_unit_error() -> Result { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::result-unit-err` implied by `-D warnings` + = help: use a custom Error type instead + +error: this returns a `Result<_, ()> + --> $DIR/result_unit_error.rs:12:5 + | +LL | fn get_that_error(&self) -> Result; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a custom Error type instead + +error: this returns a `Result<_, ()> + --> $DIR/result_unit_error.rs:14:5 + | +LL | fn get_this_one_too(&self) -> Result { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a custom Error type instead + +error: this returns a `Result<_, ()> + --> $DIR/result_unit_error.rs:32:5 + | +LL | pub fn unit_error(&self) -> Result { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a custom Error type instead + +error: this returns a `Result<_, ()> + --> $DIR/result_unit_error.rs:41:5 + | +LL | pub fn should_lint() -> ResInv<(), usize> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a custom Error type instead + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/reversed_empty_ranges_fixable.fixed b/src/tools/clippy/tests/ui/reversed_empty_ranges_fixable.fixed new file mode 100644 index 0000000000..79e482eec3 --- /dev/null +++ b/src/tools/clippy/tests/ui/reversed_empty_ranges_fixable.fixed @@ -0,0 +1,29 @@ +// run-rustfix +#![warn(clippy::reversed_empty_ranges)] + +const ANSWER: i32 = 42; + +fn main() { + // These should be linted: + + (21..=42).rev().for_each(|x| println!("{}", x)); + let _ = (21..ANSWER).rev().filter(|x| x % 2 == 0).take(10).collect::>(); + + for _ in (-42..=-21).rev() {} + for _ in (21u32..42u32).rev() {} + + // These should be ignored as they are not empty ranges: + + (21..=42).for_each(|x| println!("{}", x)); + (21..42).for_each(|x| println!("{}", x)); + + let arr = [1, 2, 3, 4, 5]; + let _ = &arr[1..=3]; + let _ = &arr[1..3]; + + for _ in 21..=42 {} + for _ in 21..42 {} + + // This range is empty but should be ignored, see issue #5689 + let _ = &arr[0..0]; +} diff --git a/src/tools/clippy/tests/ui/reversed_empty_ranges_fixable.rs b/src/tools/clippy/tests/ui/reversed_empty_ranges_fixable.rs new file mode 100644 index 0000000000..b2e8bf3377 --- /dev/null +++ b/src/tools/clippy/tests/ui/reversed_empty_ranges_fixable.rs @@ -0,0 +1,29 @@ +// run-rustfix +#![warn(clippy::reversed_empty_ranges)] + +const ANSWER: i32 = 42; + +fn main() { + // These should be linted: + + (42..=21).for_each(|x| println!("{}", x)); + let _ = (ANSWER..21).filter(|x| x % 2 == 0).take(10).collect::>(); + + for _ in -21..=-42 {} + for _ in 42u32..21u32 {} + + // These should be ignored as they are not empty ranges: + + (21..=42).for_each(|x| println!("{}", x)); + (21..42).for_each(|x| println!("{}", x)); + + let arr = [1, 2, 3, 4, 5]; + let _ = &arr[1..=3]; + let _ = &arr[1..3]; + + for _ in 21..=42 {} + for _ in 21..42 {} + + // This range is empty but should be ignored, see issue #5689 + let _ = &arr[0..0]; +} diff --git a/src/tools/clippy/tests/ui/reversed_empty_ranges_fixable.stderr b/src/tools/clippy/tests/ui/reversed_empty_ranges_fixable.stderr new file mode 100644 index 0000000000..de83c4f3d6 --- /dev/null +++ b/src/tools/clippy/tests/ui/reversed_empty_ranges_fixable.stderr @@ -0,0 +1,47 @@ +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_fixable.rs:9:5 + | +LL | (42..=21).for_each(|x| println!("{}", x)); + | ^^^^^^^^^ + | + = note: `-D clippy::reversed-empty-ranges` implied by `-D warnings` +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | (21..=42).rev().for_each(|x| println!("{}", x)); + | ^^^^^^^^^^^^^^^ + +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_fixable.rs:10:13 + | +LL | let _ = (ANSWER..21).filter(|x| x % 2 == 0).take(10).collect::>(); + | ^^^^^^^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | let _ = (21..ANSWER).rev().filter(|x| x % 2 == 0).take(10).collect::>(); + | ^^^^^^^^^^^^^^^^^^ + +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_fixable.rs:12:14 + | +LL | for _ in -21..=-42 {} + | ^^^^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for _ in (-42..=-21).rev() {} + | ^^^^^^^^^^^^^^^^^ + +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_fixable.rs:13:14 + | +LL | for _ in 42u32..21u32 {} + | ^^^^^^^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for _ in (21u32..42u32).rev() {} + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_fixable.fixed b/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_fixable.fixed new file mode 100644 index 0000000000..f1503ed6d1 --- /dev/null +++ b/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_fixable.fixed @@ -0,0 +1,57 @@ +// run-rustfix +#![warn(clippy::reversed_empty_ranges)] + +fn main() { + const MAX_LEN: usize = 42; + + for i in (0..10).rev() { + println!("{}", i); + } + + for i in (0..=10).rev() { + println!("{}", i); + } + + for i in (0..MAX_LEN).rev() { + println!("{}", i); + } + + for i in 5..=5 { + // not an error, this is the range with only one element “5” + println!("{}", i); + } + + for i in 0..10 { + // not an error, the start index is less than the end index + println!("{}", i); + } + + for i in -10..0 { + // not an error + println!("{}", i); + } + + for i in (0..10).rev().map(|x| x * 2) { + println!("{}", i); + } + + // testing that the empty range lint folds constants + for i in (5 + 4..10).rev() { + println!("{}", i); + } + + for i in ((3 - 1)..(5 + 2)).rev() { + println!("{}", i); + } + + for i in (2 * 2)..(2 * 3) { + // no error, 4..6 is fine + println!("{}", i); + } + + let x = 42; + for i in x..10 { + // no error, not constant-foldable + println!("{}", i); + } +} diff --git a/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_fixable.rs b/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_fixable.rs new file mode 100644 index 0000000000..a733788dc2 --- /dev/null +++ b/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_fixable.rs @@ -0,0 +1,57 @@ +// run-rustfix +#![warn(clippy::reversed_empty_ranges)] + +fn main() { + const MAX_LEN: usize = 42; + + for i in 10..0 { + println!("{}", i); + } + + for i in 10..=0 { + println!("{}", i); + } + + for i in MAX_LEN..0 { + println!("{}", i); + } + + for i in 5..=5 { + // not an error, this is the range with only one element “5” + println!("{}", i); + } + + for i in 0..10 { + // not an error, the start index is less than the end index + println!("{}", i); + } + + for i in -10..0 { + // not an error + println!("{}", i); + } + + for i in (10..0).map(|x| x * 2) { + println!("{}", i); + } + + // testing that the empty range lint folds constants + for i in 10..5 + 4 { + println!("{}", i); + } + + for i in (5 + 2)..(3 - 1) { + println!("{}", i); + } + + for i in (2 * 2)..(2 * 3) { + // no error, 4..6 is fine + println!("{}", i); + } + + let x = 42; + for i in x..10 { + // no error, not constant-foldable + println!("{}", i); + } +} diff --git a/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_fixable.stderr b/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_fixable.stderr new file mode 100644 index 0000000000..e89e040a0f --- /dev/null +++ b/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_fixable.stderr @@ -0,0 +1,69 @@ +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_loops_fixable.rs:7:14 + | +LL | for i in 10..0 { + | ^^^^^ + | + = note: `-D clippy::reversed-empty-ranges` implied by `-D warnings` +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in (0..10).rev() { + | ^^^^^^^^^^^^^ + +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_loops_fixable.rs:11:14 + | +LL | for i in 10..=0 { + | ^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in (0..=10).rev() { + | ^^^^^^^^^^^^^^ + +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_loops_fixable.rs:15:14 + | +LL | for i in MAX_LEN..0 { + | ^^^^^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in (0..MAX_LEN).rev() { + | ^^^^^^^^^^^^^^^^^^ + +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_loops_fixable.rs:34:14 + | +LL | for i in (10..0).map(|x| x * 2) { + | ^^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in (0..10).rev().map(|x| x * 2) { + | ^^^^^^^^^^^^^ + +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_loops_fixable.rs:39:14 + | +LL | for i in 10..5 + 4 { + | ^^^^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in (5 + 4..10).rev() { + | ^^^^^^^^^^^^^^^^^ + +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_loops_fixable.rs:43:14 + | +LL | for i in (5 + 2)..(3 - 1) { + | ^^^^^^^^^^^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in ((3 - 1)..(5 + 2)).rev() { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_unfixable.rs b/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_unfixable.rs new file mode 100644 index 0000000000..c4c5722441 --- /dev/null +++ b/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_unfixable.rs @@ -0,0 +1,11 @@ +#![warn(clippy::reversed_empty_ranges)] + +fn main() { + for i in 5..5 { + println!("{}", i); + } + + for i in (5 + 2)..(8 - 1) { + println!("{}", i); + } +} diff --git a/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_unfixable.stderr b/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_unfixable.stderr new file mode 100644 index 0000000000..30095d20cf --- /dev/null +++ b/src/tools/clippy/tests/ui/reversed_empty_ranges_loops_unfixable.stderr @@ -0,0 +1,16 @@ +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_loops_unfixable.rs:4:14 + | +LL | for i in 5..5 { + | ^^^^ + | + = note: `-D clippy::reversed-empty-ranges` implied by `-D warnings` + +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_loops_unfixable.rs:8:14 + | +LL | for i in (5 + 2)..(8 - 1) { + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/reversed_empty_ranges_unfixable.rs b/src/tools/clippy/tests/ui/reversed_empty_ranges_unfixable.rs new file mode 100644 index 0000000000..264d3d1e95 --- /dev/null +++ b/src/tools/clippy/tests/ui/reversed_empty_ranges_unfixable.rs @@ -0,0 +1,15 @@ +#![warn(clippy::reversed_empty_ranges)] + +const ANSWER: i32 = 42; +const SOME_NUM: usize = 3; + +fn main() { + let arr = [1, 2, 3, 4, 5]; + let _ = &arr[3usize..=1usize]; + let _ = &arr[SOME_NUM..1]; + + for _ in ANSWER..ANSWER {} + + // Should not be linted, see issue #5689 + let _ = (42 + 10..42 + 10).map(|x| x / 2).find(|&x| x == 21); +} diff --git a/src/tools/clippy/tests/ui/reversed_empty_ranges_unfixable.stderr b/src/tools/clippy/tests/ui/reversed_empty_ranges_unfixable.stderr new file mode 100644 index 0000000000..f23d4eb0f9 --- /dev/null +++ b/src/tools/clippy/tests/ui/reversed_empty_ranges_unfixable.stderr @@ -0,0 +1,22 @@ +error: this range is reversed and using it to index a slice will panic at run-time + --> $DIR/reversed_empty_ranges_unfixable.rs:8:18 + | +LL | let _ = &arr[3usize..=1usize]; + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::reversed-empty-ranges` implied by `-D warnings` + +error: this range is reversed and using it to index a slice will panic at run-time + --> $DIR/reversed_empty_ranges_unfixable.rs:9:18 + | +LL | let _ = &arr[SOME_NUM..1]; + | ^^^^^^^^^^^ + +error: this range is empty so it will yield no values + --> $DIR/reversed_empty_ranges_unfixable.rs:11:14 + | +LL | for _ in ANSWER..ANSWER {} + | ^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/same_functions_in_if_condition.rs b/src/tools/clippy/tests/ui/same_functions_in_if_condition.rs new file mode 100644 index 0000000000..7f28f02579 --- /dev/null +++ b/src/tools/clippy/tests/ui/same_functions_in_if_condition.rs @@ -0,0 +1,90 @@ +#![warn(clippy::same_functions_in_if_condition)] +#![allow(clippy::ifs_same_cond)] // This warning is different from `ifs_same_cond`. +#![allow(clippy::if_same_then_else, clippy::comparison_chain)] // all empty blocks + +fn function() -> bool { + true +} + +fn fn_arg(_arg: u8) -> bool { + true +} + +struct Struct; + +impl Struct { + fn method(&self) -> bool { + true + } + fn method_arg(&self, _arg: u8) -> bool { + true + } +} + +fn ifs_same_cond_fn() { + let a = 0; + let obj = Struct; + + if function() { + } else if function() { + //~ ERROR ifs same condition + } + + if fn_arg(a) { + } else if fn_arg(a) { + //~ ERROR ifs same condition + } + + if obj.method() { + } else if obj.method() { + //~ ERROR ifs same condition + } + + if obj.method_arg(a) { + } else if obj.method_arg(a) { + //~ ERROR ifs same condition + } + + let mut v = vec![1]; + if v.pop() == None { + //~ ERROR ifs same condition + } else if v.pop() == None { + } + + if v.len() == 42 { + //~ ERROR ifs same condition + } else if v.len() == 42 { + } + + if v.len() == 1 { + // ok, different conditions + } else if v.len() == 2 { + } + + if fn_arg(0) { + // ok, different arguments. + } else if fn_arg(1) { + } + + if obj.method_arg(0) { + // ok, different arguments. + } else if obj.method_arg(1) { + } + + if a == 1 { + // ok, warning is on `ifs_same_cond` behalf. + } else if a == 1 { + } +} + +fn main() { + // macro as condition (see #6168) + let os = if cfg!(target_os = "macos") { + "macos" + } else if cfg!(target_os = "windows") { + "windows" + } else { + "linux" + }; + println!("{}", os); +} diff --git a/src/tools/clippy/tests/ui/same_functions_in_if_condition.stderr b/src/tools/clippy/tests/ui/same_functions_in_if_condition.stderr new file mode 100644 index 0000000000..363a03846d --- /dev/null +++ b/src/tools/clippy/tests/ui/same_functions_in_if_condition.stderr @@ -0,0 +1,75 @@ +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:29:15 + | +LL | } else if function() { + | ^^^^^^^^^^ + | + = note: `-D clippy::same-functions-in-if-condition` implied by `-D warnings` +note: same as this + --> $DIR/same_functions_in_if_condition.rs:28:8 + | +LL | if function() { + | ^^^^^^^^^^ + +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:34:15 + | +LL | } else if fn_arg(a) { + | ^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:33:8 + | +LL | if fn_arg(a) { + | ^^^^^^^^^ + +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:39:15 + | +LL | } else if obj.method() { + | ^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:38:8 + | +LL | if obj.method() { + | ^^^^^^^^^^^^ + +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:44:15 + | +LL | } else if obj.method_arg(a) { + | ^^^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:43:8 + | +LL | if obj.method_arg(a) { + | ^^^^^^^^^^^^^^^^^ + +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:51:15 + | +LL | } else if v.pop() == None { + | ^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:49:8 + | +LL | if v.pop() == None { + | ^^^^^^^^^^^^^^^ + +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:56:15 + | +LL | } else if v.len() == 42 { + | ^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:54:8 + | +LL | if v.len() == 42 { + | ^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/same_item_push.rs b/src/tools/clippy/tests/ui/same_item_push.rs new file mode 100644 index 0000000000..a37c8782ec --- /dev/null +++ b/src/tools/clippy/tests/ui/same_item_push.rs @@ -0,0 +1,151 @@ +#![warn(clippy::same_item_push)] + +const VALUE: u8 = 7; + +fn mutate_increment(x: &mut u8) -> u8 { + *x += 1; + *x +} + +fn increment(x: u8) -> u8 { + x + 1 +} + +fn fun() -> usize { + 42 +} + +fn main() { + // ** linted cases ** + let mut vec: Vec = Vec::new(); + let item = 2; + for _ in 5..=20 { + vec.push(item); + } + + let mut vec: Vec = Vec::new(); + for _ in 0..15 { + let item = 2; + vec.push(item); + } + + let mut vec: Vec = Vec::new(); + for _ in 0..15 { + vec.push(13); + } + + let mut vec = Vec::new(); + for _ in 0..20 { + vec.push(VALUE); + } + + let mut vec = Vec::new(); + let item = VALUE; + for _ in 0..20 { + vec.push(item); + } + + // ** non-linted cases ** + let mut spaces = Vec::with_capacity(10); + for _ in 0..10 { + spaces.push(vec![b' ']); + } + + // Suggestion should not be given as pushed variable can mutate + let mut vec: Vec = Vec::new(); + let mut item: u8 = 2; + for _ in 0..30 { + vec.push(mutate_increment(&mut item)); + } + + let mut vec: Vec = Vec::new(); + let mut item: u8 = 2; + let mut item2 = &mut mutate_increment(&mut item); + for _ in 0..30 { + vec.push(mutate_increment(item2)); + } + + let mut vec: Vec = Vec::new(); + for (a, b) in [0, 1, 4, 9, 16].iter().enumerate() { + vec.push(a); + } + + let mut vec: Vec = Vec::new(); + for i in 0..30 { + vec.push(increment(i)); + } + + let mut vec: Vec = Vec::new(); + for i in 0..30 { + vec.push(i + i * i); + } + + // Suggestion should not be given as there are multiple pushes that are not the same + let mut vec: Vec = Vec::new(); + let item: u8 = 2; + for _ in 0..30 { + vec.push(item); + vec.push(item * 2); + } + + // Suggestion should not be given as Vec is not involved + for _ in 0..5 { + println!("Same Item Push"); + } + + struct A { + kind: u32, + } + let mut vec_a: Vec = Vec::new(); + for i in 0..30 { + vec_a.push(A { kind: i }); + } + let mut vec: Vec = Vec::new(); + for a in vec_a { + vec.push(2u8.pow(a.kind)); + } + + // Fix #5902 + let mut vec: Vec = Vec::new(); + let mut item = 0; + for _ in 0..10 { + vec.push(item); + item += 10; + } + + // Fix #5979 + let mut vec: Vec = Vec::new(); + for _ in 0..10 { + vec.push(std::fs::File::open("foobar").unwrap()); + } + // Fix #5979 + #[derive(Clone)] + struct S {} + + trait T {} + impl T for S {} + + let mut vec: Vec> = Vec::new(); + for _ in 0..10 { + vec.push(Box::new(S {})); + } + + // Fix #5985 + let mut vec = Vec::new(); + let item = 42; + let item = fun(); + for _ in 0..20 { + vec.push(item); + } + + // Fix #5985 + let mut vec = Vec::new(); + let key = 1; + for _ in 0..20 { + let item = match key { + 1 => 10, + _ => 0, + }; + vec.push(item); + } +} diff --git a/src/tools/clippy/tests/ui/same_item_push.stderr b/src/tools/clippy/tests/ui/same_item_push.stderr new file mode 100644 index 0000000000..d9ffa15780 --- /dev/null +++ b/src/tools/clippy/tests/ui/same_item_push.stderr @@ -0,0 +1,43 @@ +error: it looks like the same item is being pushed into this Vec + --> $DIR/same_item_push.rs:23:9 + | +LL | vec.push(item); + | ^^^ + | + = note: `-D clippy::same-item-push` implied by `-D warnings` + = help: try using vec![item;SIZE] or vec.resize(NEW_SIZE, item) + +error: it looks like the same item is being pushed into this Vec + --> $DIR/same_item_push.rs:29:9 + | +LL | vec.push(item); + | ^^^ + | + = help: try using vec![item;SIZE] or vec.resize(NEW_SIZE, item) + +error: it looks like the same item is being pushed into this Vec + --> $DIR/same_item_push.rs:34:9 + | +LL | vec.push(13); + | ^^^ + | + = help: try using vec![13;SIZE] or vec.resize(NEW_SIZE, 13) + +error: it looks like the same item is being pushed into this Vec + --> $DIR/same_item_push.rs:39:9 + | +LL | vec.push(VALUE); + | ^^^ + | + = help: try using vec![VALUE;SIZE] or vec.resize(NEW_SIZE, VALUE) + +error: it looks like the same item is being pushed into this Vec + --> $DIR/same_item_push.rs:45:9 + | +LL | vec.push(item); + | ^^^ + | + = help: try using vec![item;SIZE] or vec.resize(NEW_SIZE, item) + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/search_is_some.rs b/src/tools/clippy/tests/ui/search_is_some.rs new file mode 100644 index 0000000000..f0dc3b3d06 --- /dev/null +++ b/src/tools/clippy/tests/ui/search_is_some.rs @@ -0,0 +1,38 @@ +// aux-build:option_helpers.rs +extern crate option_helpers; +use option_helpers::IteratorFalsePositives; + +#[warn(clippy::search_is_some)] +#[rustfmt::skip] +fn main() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + let y = &&42; + + + // Check `find().is_some()`, multi-line case. + let _ = v.iter().find(|&x| { + *x < 0 + } + ).is_some(); + + // Check `position().is_some()`, multi-line case. + let _ = v.iter().position(|&x| { + x < 0 + } + ).is_some(); + + // Check `rposition().is_some()`, multi-line case. + let _ = v.iter().rposition(|&x| { + x < 0 + } + ).is_some(); + + // Check that we don't lint if the caller is not an `Iterator` or string + let falsepos = IteratorFalsePositives { foo: 0 }; + let _ = falsepos.find().is_some(); + let _ = falsepos.position().is_some(); + let _ = falsepos.rposition().is_some(); + // check that we don't lint if `find()` is called with + // `Pattern` that is not a string + let _ = "hello world".find(|c: char| c == 'o' || c == 'l').is_some(); +} diff --git a/src/tools/clippy/tests/ui/search_is_some.stderr b/src/tools/clippy/tests/ui/search_is_some.stderr new file mode 100644 index 0000000000..c601f568c6 --- /dev/null +++ b/src/tools/clippy/tests/ui/search_is_some.stderr @@ -0,0 +1,39 @@ +error: called `is_some()` after searching an `Iterator` with `find` + --> $DIR/search_is_some.rs:13:13 + | +LL | let _ = v.iter().find(|&x| { + | _____________^ +LL | | *x < 0 +LL | | } +LL | | ).is_some(); + | |______________________________^ + | + = note: `-D clippy::search-is-some` implied by `-D warnings` + = help: this is more succinctly expressed by calling `any()` + +error: called `is_some()` after searching an `Iterator` with `position` + --> $DIR/search_is_some.rs:19:13 + | +LL | let _ = v.iter().position(|&x| { + | _____________^ +LL | | x < 0 +LL | | } +LL | | ).is_some(); + | |______________________________^ + | + = help: this is more succinctly expressed by calling `any()` + +error: called `is_some()` after searching an `Iterator` with `rposition` + --> $DIR/search_is_some.rs:25:13 + | +LL | let _ = v.iter().rposition(|&x| { + | _____________^ +LL | | x < 0 +LL | | } +LL | | ).is_some(); + | |______________________________^ + | + = help: this is more succinctly expressed by calling `any()` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/search_is_some_fixable.fixed b/src/tools/clippy/tests/ui/search_is_some_fixable.fixed new file mode 100644 index 0000000000..dc3f290e56 --- /dev/null +++ b/src/tools/clippy/tests/ui/search_is_some_fixable.fixed @@ -0,0 +1,35 @@ +// run-rustfix + +#![warn(clippy::search_is_some)] + +fn main() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + let y = &&42; + + // Check `find().is_some()`, single-line case. + let _ = v.iter().any(|x| *x < 0); + let _ = (0..1).any(|x| **y == x); // one dereference less + let _ = (0..1).any(|x| x == 0); + let _ = v.iter().any(|x| *x == 0); + + // Check `position().is_some()`, single-line case. + let _ = v.iter().any(|&x| x < 0); + + // Check `rposition().is_some()`, single-line case. + let _ = v.iter().any(|&x| x < 0); + + let s1 = String::from("hello world"); + let s2 = String::from("world"); + // caller of `find()` is a `&`static str` + let _ = "hello world".contains("world"); + let _ = "hello world".contains(&s2); + let _ = "hello world".contains(&s2[2..]); + // caller of `find()` is a `String` + let _ = s1.contains("world"); + let _ = s1.contains(&s2); + let _ = s1.contains(&s2[2..]); + // caller of `find()` is slice of `String` + let _ = s1[2..].contains("world"); + let _ = s1[2..].contains(&s2); + let _ = s1[2..].contains(&s2[2..]); +} diff --git a/src/tools/clippy/tests/ui/search_is_some_fixable.rs b/src/tools/clippy/tests/ui/search_is_some_fixable.rs new file mode 100644 index 0000000000..146cf5adf1 --- /dev/null +++ b/src/tools/clippy/tests/ui/search_is_some_fixable.rs @@ -0,0 +1,35 @@ +// run-rustfix + +#![warn(clippy::search_is_some)] + +fn main() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + let y = &&42; + + // Check `find().is_some()`, single-line case. + let _ = v.iter().find(|&x| *x < 0).is_some(); + let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less + let _ = (0..1).find(|x| *x == 0).is_some(); + let _ = v.iter().find(|x| **x == 0).is_some(); + + // Check `position().is_some()`, single-line case. + let _ = v.iter().position(|&x| x < 0).is_some(); + + // Check `rposition().is_some()`, single-line case. + let _ = v.iter().rposition(|&x| x < 0).is_some(); + + let s1 = String::from("hello world"); + let s2 = String::from("world"); + // caller of `find()` is a `&`static str` + let _ = "hello world".find("world").is_some(); + let _ = "hello world".find(&s2).is_some(); + let _ = "hello world".find(&s2[2..]).is_some(); + // caller of `find()` is a `String` + let _ = s1.find("world").is_some(); + let _ = s1.find(&s2).is_some(); + let _ = s1.find(&s2[2..]).is_some(); + // caller of `find()` is slice of `String` + let _ = s1[2..].find("world").is_some(); + let _ = s1[2..].find(&s2).is_some(); + let _ = s1[2..].find(&s2[2..]).is_some(); +} diff --git a/src/tools/clippy/tests/ui/search_is_some_fixable.stderr b/src/tools/clippy/tests/ui/search_is_some_fixable.stderr new file mode 100644 index 0000000000..23c1d9a901 --- /dev/null +++ b/src/tools/clippy/tests/ui/search_is_some_fixable.stderr @@ -0,0 +1,94 @@ +error: called `is_some()` after searching an `Iterator` with `find` + --> $DIR/search_is_some_fixable.rs:10:22 + | +LL | let _ = v.iter().find(|&x| *x < 0).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| *x < 0)` + | + = note: `-D clippy::search-is-some` implied by `-D warnings` + +error: called `is_some()` after searching an `Iterator` with `find` + --> $DIR/search_is_some_fixable.rs:11:20 + | +LL | let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| **y == x)` + +error: called `is_some()` after searching an `Iterator` with `find` + --> $DIR/search_is_some_fixable.rs:12:20 + | +LL | let _ = (0..1).find(|x| *x == 0).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| x == 0)` + +error: called `is_some()` after searching an `Iterator` with `find` + --> $DIR/search_is_some_fixable.rs:13:22 + | +LL | let _ = v.iter().find(|x| **x == 0).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| *x == 0)` + +error: called `is_some()` after searching an `Iterator` with `position` + --> $DIR/search_is_some_fixable.rs:16:22 + | +LL | let _ = v.iter().position(|&x| x < 0).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|&x| x < 0)` + +error: called `is_some()` after searching an `Iterator` with `rposition` + --> $DIR/search_is_some_fixable.rs:19:22 + | +LL | let _ = v.iter().rposition(|&x| x < 0).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|&x| x < 0)` + +error: called `is_some()` after calling `find()` on a string + --> $DIR/search_is_some_fixable.rs:24:27 + | +LL | let _ = "hello world".find("world").is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains("world")` + +error: called `is_some()` after calling `find()` on a string + --> $DIR/search_is_some_fixable.rs:25:27 + | +LL | let _ = "hello world".find(&s2).is_some(); + | ^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2)` + +error: called `is_some()` after calling `find()` on a string + --> $DIR/search_is_some_fixable.rs:26:27 + | +LL | let _ = "hello world".find(&s2[2..]).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2[2..])` + +error: called `is_some()` after calling `find()` on a string + --> $DIR/search_is_some_fixable.rs:28:16 + | +LL | let _ = s1.find("world").is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains("world")` + +error: called `is_some()` after calling `find()` on a string + --> $DIR/search_is_some_fixable.rs:29:16 + | +LL | let _ = s1.find(&s2).is_some(); + | ^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2)` + +error: called `is_some()` after calling `find()` on a string + --> $DIR/search_is_some_fixable.rs:30:16 + | +LL | let _ = s1.find(&s2[2..]).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2[2..])` + +error: called `is_some()` after calling `find()` on a string + --> $DIR/search_is_some_fixable.rs:32:21 + | +LL | let _ = s1[2..].find("world").is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains("world")` + +error: called `is_some()` after calling `find()` on a string + --> $DIR/search_is_some_fixable.rs:33:21 + | +LL | let _ = s1[2..].find(&s2).is_some(); + | ^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2)` + +error: called `is_some()` after calling `find()` on a string + --> $DIR/search_is_some_fixable.rs:34:21 + | +LL | let _ = s1[2..].find(&s2[2..]).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2[2..])` + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/self_assignment.rs b/src/tools/clippy/tests/ui/self_assignment.rs new file mode 100644 index 0000000000..a7cbb9cd78 --- /dev/null +++ b/src/tools/clippy/tests/ui/self_assignment.rs @@ -0,0 +1,67 @@ +#![warn(clippy::self_assignment)] + +pub struct S<'a> { + a: i32, + b: [i32; 10], + c: Vec>, + e: &'a mut i32, + f: &'a mut i32, +} + +pub fn positives(mut a: usize, b: &mut u32, mut s: S) { + a = a; + *b = *b; + s = s; + s.a = s.a; + s.b[10] = s.b[5 + 5]; + s.c[0][1] = s.c[0][1]; + s.b[a] = s.b[a]; + *s.e = *s.e; + s.b[a + 10] = s.b[10 + a]; + + let mut t = (0, 1); + t.1 = t.1; + t.0 = (t.0); +} + +pub fn negatives_not_equal(mut a: usize, b: &mut usize, mut s: S) { + dbg!(&a); + a = *b; + dbg!(&a); + s.b[1] += s.b[1]; + s.b[1] = s.b[2]; + s.c[1][0] = s.c[0][1]; + s.b[a] = s.b[*b]; + s.b[a + 10] = s.b[a + 11]; + *s.e = *s.f; + + let mut t = (0, 1); + t.0 = t.1; +} + +#[allow(clippy::eval_order_dependence)] +pub fn negatives_side_effects() { + let mut v = vec![1, 2, 3, 4, 5]; + let mut i = 0; + v[{ + i += 1; + i + }] = v[{ + i += 1; + i + }]; + + fn next(n: &mut usize) -> usize { + let v = *n; + *n += 1; + v + } + + let mut w = vec![1, 2, 3, 4, 5]; + let mut i = 0; + let i = &mut i; + w[next(i)] = w[next(i)]; + w[next(i)] = w[next(i)]; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/self_assignment.stderr b/src/tools/clippy/tests/ui/self_assignment.stderr new file mode 100644 index 0000000000..826e0d0ba8 --- /dev/null +++ b/src/tools/clippy/tests/ui/self_assignment.stderr @@ -0,0 +1,70 @@ +error: self-assignment of `a` to `a` + --> $DIR/self_assignment.rs:12:5 + | +LL | a = a; + | ^^^^^ + | + = note: `-D clippy::self-assignment` implied by `-D warnings` + +error: self-assignment of `*b` to `*b` + --> $DIR/self_assignment.rs:13:5 + | +LL | *b = *b; + | ^^^^^^^ + +error: self-assignment of `s` to `s` + --> $DIR/self_assignment.rs:14:5 + | +LL | s = s; + | ^^^^^ + +error: self-assignment of `s.a` to `s.a` + --> $DIR/self_assignment.rs:15:5 + | +LL | s.a = s.a; + | ^^^^^^^^^ + +error: self-assignment of `s.b[5 + 5]` to `s.b[10]` + --> $DIR/self_assignment.rs:16:5 + | +LL | s.b[10] = s.b[5 + 5]; + | ^^^^^^^^^^^^^^^^^^^^ + +error: self-assignment of `s.c[0][1]` to `s.c[0][1]` + --> $DIR/self_assignment.rs:17:5 + | +LL | s.c[0][1] = s.c[0][1]; + | ^^^^^^^^^^^^^^^^^^^^^ + +error: self-assignment of `s.b[a]` to `s.b[a]` + --> $DIR/self_assignment.rs:18:5 + | +LL | s.b[a] = s.b[a]; + | ^^^^^^^^^^^^^^^ + +error: self-assignment of `*s.e` to `*s.e` + --> $DIR/self_assignment.rs:19:5 + | +LL | *s.e = *s.e; + | ^^^^^^^^^^^ + +error: self-assignment of `s.b[10 + a]` to `s.b[a + 10]` + --> $DIR/self_assignment.rs:20:5 + | +LL | s.b[a + 10] = s.b[10 + a]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: self-assignment of `t.1` to `t.1` + --> $DIR/self_assignment.rs:23:5 + | +LL | t.1 = t.1; + | ^^^^^^^^^ + +error: self-assignment of `(t.0)` to `t.0` + --> $DIR/self_assignment.rs:24:5 + | +LL | t.0 = (t.0); + | ^^^^^^^^^^^ + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.rs b/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.rs new file mode 100644 index 0000000000..0abe2cca26 --- /dev/null +++ b/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.rs @@ -0,0 +1,55 @@ +#![warn(clippy::semicolon_if_nothing_returned)] +#![feature(label_break_value)] + +fn get_unit() {} + +// the functions below trigger the lint +fn main() { + println!("Hello") +} + +fn hello() { + get_unit() +} + +fn basic101(x: i32) { + let y: i32; + y = x + 1 +} + +// this is fine +fn print_sum(a: i32, b: i32) { + println!("{}", a + b); + assert_eq!(true, false); +} + +fn foo(x: i32) { + let y: i32; + if x < 1 { + y = 4; + } else { + y = 5; + } +} + +fn bar(x: i32) { + let y: i32; + match x { + 1 => y = 4, + _ => y = 32, + } +} + +fn foobar(x: i32) { + let y: i32; + 'label: { + y = x + 1; + } +} + +fn loop_test(x: i32) { + let y: i32; + for &ext in &["stdout", "stderr", "fixed"] { + println!("{}", ext); + } +} diff --git a/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.stderr b/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.stderr new file mode 100644 index 0000000000..b73f896753 --- /dev/null +++ b/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.stderr @@ -0,0 +1,22 @@ +error: consider adding a `;` to the last statement for consistent formatting + --> $DIR/semicolon_if_nothing_returned.rs:8:5 + | +LL | println!("Hello") + | ^^^^^^^^^^^^^^^^^ help: add a `;` here: `println!("Hello");` + | + = note: `-D clippy::semicolon-if-nothing-returned` implied by `-D warnings` + +error: consider adding a `;` to the last statement for consistent formatting + --> $DIR/semicolon_if_nothing_returned.rs:12:5 + | +LL | get_unit() + | ^^^^^^^^^^ help: add a `;` here: `get_unit();` + +error: consider adding a `;` to the last statement for consistent formatting + --> $DIR/semicolon_if_nothing_returned.rs:17:5 + | +LL | y = x + 1 + | ^^^^^^^^^ help: add a `;` here: `y = x + 1;` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/serde.rs b/src/tools/clippy/tests/ui/serde.rs new file mode 100644 index 0000000000..5843344eba --- /dev/null +++ b/src/tools/clippy/tests/ui/serde.rs @@ -0,0 +1,47 @@ +#![warn(clippy::serde_api_misuse)] +#![allow(dead_code)] + +extern crate serde; + +struct A; + +impl<'de> serde::de::Visitor<'de> for A { + type Value = (); + + fn expecting(&self, _: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + unimplemented!() + } + + fn visit_str(self, _v: &str) -> Result + where + E: serde::de::Error, + { + unimplemented!() + } + + fn visit_string(self, _v: String) -> Result + where + E: serde::de::Error, + { + unimplemented!() + } +} + +struct B; + +impl<'de> serde::de::Visitor<'de> for B { + type Value = (); + + fn expecting(&self, _: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + unimplemented!() + } + + fn visit_string(self, _v: String) -> Result + where + E: serde::de::Error, + { + unimplemented!() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/serde.stderr b/src/tools/clippy/tests/ui/serde.stderr new file mode 100644 index 0000000000..760c9c9908 --- /dev/null +++ b/src/tools/clippy/tests/ui/serde.stderr @@ -0,0 +1,15 @@ +error: you should not implement `visit_string` without also implementing `visit_str` + --> $DIR/serde.rs:39:5 + | +LL | / fn visit_string(self, _v: String) -> Result +LL | | where +LL | | E: serde::de::Error, +LL | | { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = note: `-D clippy::serde-api-misuse` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/shadow.rs b/src/tools/clippy/tests/ui/shadow.rs new file mode 100644 index 0000000000..e366c75335 --- /dev/null +++ b/src/tools/clippy/tests/ui/shadow.rs @@ -0,0 +1,54 @@ +#![warn( + clippy::all, + clippy::pedantic, + clippy::shadow_same, + clippy::shadow_reuse, + clippy::shadow_unrelated +)] +#![allow( + unused_parens, + unused_variables, + clippy::manual_unwrap_or, + clippy::missing_docs_in_private_items, + clippy::single_match +)] + +fn id(x: T) -> T { + x +} + +#[must_use] +fn first(x: (isize, isize)) -> isize { + x.0 +} + +fn main() { + let mut x = 1; + let x = &mut x; + let x = { x }; + let x = (&*x); + let x = { *x + 1 }; + let x = id(x); + let x = (1, x); + let x = first(x); + let y = 1; + let x = y; + + let x; + x = 42; + + let o = Some(1_u8); + + if let Some(p) = o { + assert_eq!(1, p); + } + match o { + Some(p) => p, // no error, because the p above is in its own scope + None => 0, + }; + + match (x, o) { + (1, Some(a)) | (a, Some(1)) => (), // no error though `a` appears twice + _ => (), + } +} diff --git a/src/tools/clippy/tests/ui/shadow.stderr b/src/tools/clippy/tests/ui/shadow.stderr new file mode 100644 index 0000000000..7c1ad2949e --- /dev/null +++ b/src/tools/clippy/tests/ui/shadow.stderr @@ -0,0 +1,138 @@ +error: `x` is shadowed by itself in `&mut x` + --> $DIR/shadow.rs:27:5 + | +LL | let x = &mut x; + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::shadow-same` implied by `-D warnings` +note: previous binding is here + --> $DIR/shadow.rs:26:13 + | +LL | let mut x = 1; + | ^ + +error: `x` is shadowed by itself in `{ x }` + --> $DIR/shadow.rs:28:5 + | +LL | let x = { x }; + | ^^^^^^^^^^^^^^ + | +note: previous binding is here + --> $DIR/shadow.rs:27:9 + | +LL | let x = &mut x; + | ^ + +error: `x` is shadowed by itself in `(&*x)` + --> $DIR/shadow.rs:29:5 + | +LL | let x = (&*x); + | ^^^^^^^^^^^^^^ + | +note: previous binding is here + --> $DIR/shadow.rs:28:9 + | +LL | let x = { x }; + | ^ + +error: `x` is shadowed by `{ *x + 1 }` which reuses the original value + --> $DIR/shadow.rs:30:9 + | +LL | let x = { *x + 1 }; + | ^ + | + = note: `-D clippy::shadow-reuse` implied by `-D warnings` +note: initialization happens here + --> $DIR/shadow.rs:30:13 + | +LL | let x = { *x + 1 }; + | ^^^^^^^^^^ +note: previous binding is here + --> $DIR/shadow.rs:29:9 + | +LL | let x = (&*x); + | ^ + +error: `x` is shadowed by `id(x)` which reuses the original value + --> $DIR/shadow.rs:31:9 + | +LL | let x = id(x); + | ^ + | +note: initialization happens here + --> $DIR/shadow.rs:31:13 + | +LL | let x = id(x); + | ^^^^^ +note: previous binding is here + --> $DIR/shadow.rs:30:9 + | +LL | let x = { *x + 1 }; + | ^ + +error: `x` is shadowed by `(1, x)` which reuses the original value + --> $DIR/shadow.rs:32:9 + | +LL | let x = (1, x); + | ^ + | +note: initialization happens here + --> $DIR/shadow.rs:32:13 + | +LL | let x = (1, x); + | ^^^^^^ +note: previous binding is here + --> $DIR/shadow.rs:31:9 + | +LL | let x = id(x); + | ^ + +error: `x` is shadowed by `first(x)` which reuses the original value + --> $DIR/shadow.rs:33:9 + | +LL | let x = first(x); + | ^ + | +note: initialization happens here + --> $DIR/shadow.rs:33:13 + | +LL | let x = first(x); + | ^^^^^^^^ +note: previous binding is here + --> $DIR/shadow.rs:32:9 + | +LL | let x = (1, x); + | ^ + +error: `x` is being shadowed + --> $DIR/shadow.rs:35:9 + | +LL | let x = y; + | ^ + | + = note: `-D clippy::shadow-unrelated` implied by `-D warnings` +note: initialization happens here + --> $DIR/shadow.rs:35:13 + | +LL | let x = y; + | ^ +note: previous binding is here + --> $DIR/shadow.rs:33:9 + | +LL | let x = first(x); + | ^ + +error: `x` shadows a previous declaration + --> $DIR/shadow.rs:37:5 + | +LL | let x; + | ^^^^^^ + | +note: previous binding is here + --> $DIR/shadow.rs:35:9 + | +LL | let x = y; + | ^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/short_circuit_statement.fixed b/src/tools/clippy/tests/ui/short_circuit_statement.fixed new file mode 100644 index 0000000000..af0a397bd1 --- /dev/null +++ b/src/tools/clippy/tests/ui/short_circuit_statement.fixed @@ -0,0 +1,18 @@ +// run-rustfix + +#![warn(clippy::short_circuit_statement)] +#![allow(clippy::nonminimal_bool)] + +fn main() { + if f() { g(); } + if !f() { g(); } + if !(1 == 2) { g(); } +} + +fn f() -> bool { + true +} + +fn g() -> bool { + false +} diff --git a/src/tools/clippy/tests/ui/short_circuit_statement.rs b/src/tools/clippy/tests/ui/short_circuit_statement.rs new file mode 100644 index 0000000000..73a55bf1f5 --- /dev/null +++ b/src/tools/clippy/tests/ui/short_circuit_statement.rs @@ -0,0 +1,18 @@ +// run-rustfix + +#![warn(clippy::short_circuit_statement)] +#![allow(clippy::nonminimal_bool)] + +fn main() { + f() && g(); + f() || g(); + 1 == 2 || g(); +} + +fn f() -> bool { + true +} + +fn g() -> bool { + false +} diff --git a/src/tools/clippy/tests/ui/short_circuit_statement.stderr b/src/tools/clippy/tests/ui/short_circuit_statement.stderr new file mode 100644 index 0000000000..0a3f60c3d1 --- /dev/null +++ b/src/tools/clippy/tests/ui/short_circuit_statement.stderr @@ -0,0 +1,22 @@ +error: boolean short circuit operator in statement may be clearer using an explicit test + --> $DIR/short_circuit_statement.rs:7:5 + | +LL | f() && g(); + | ^^^^^^^^^^^ help: replace it with: `if f() { g(); }` + | + = note: `-D clippy::short-circuit-statement` implied by `-D warnings` + +error: boolean short circuit operator in statement may be clearer using an explicit test + --> $DIR/short_circuit_statement.rs:8:5 + | +LL | f() || g(); + | ^^^^^^^^^^^ help: replace it with: `if !f() { g(); }` + +error: boolean short circuit operator in statement may be clearer using an explicit test + --> $DIR/short_circuit_statement.rs:9:5 + | +LL | 1 == 2 || g(); + | ^^^^^^^^^^^^^^ help: replace it with: `if !(1 == 2) { g(); }` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/should_impl_trait/corner_cases.rs b/src/tools/clippy/tests/ui/should_impl_trait/corner_cases.rs new file mode 100644 index 0000000000..a7f8f54f2b --- /dev/null +++ b/src/tools/clippy/tests/ui/should_impl_trait/corner_cases.rs @@ -0,0 +1,84 @@ +// edition:2018 + +#![warn(clippy::all, clippy::pedantic)] +#![allow( + clippy::missing_errors_doc, + clippy::needless_pass_by_value, + clippy::must_use_candidate, + clippy::unused_self, + clippy::needless_lifetimes, + clippy::missing_safety_doc, + clippy::wrong_self_convention, + clippy::missing_panics_doc +)] + +use std::ops::Mul; +use std::rc::{self, Rc}; +use std::sync::{self, Arc}; + +fn main() {} + +pub struct T1; +impl T1 { + // corner cases: should not lint + + // no error, not public interface + pub(crate) fn drop(&mut self) {} + + // no error, private function + fn neg(self) -> Self { + self + } + + // no error, private function + fn eq(&self, other: Self) -> bool { + true + } + + // No error; self is a ref. + fn sub(&self, other: Self) -> &Self { + self + } + + // No error; different number of arguments. + fn div(self) -> Self { + self + } + + // No error; wrong return type. + fn rem(self, other: Self) {} + + // Fine + fn into_u32(self) -> u32 { + 0 + } + + fn into_u16(&self) -> u16 { + 0 + } + + fn to_something(self) -> u32 { + 0 + } + + fn new(self) -> Self { + unimplemented!(); + } + + pub fn next<'b>(&'b mut self) -> Option<&'b mut T1> { + unimplemented!(); + } +} + +pub struct T2; +impl T2 { + // Shouldn't trigger lint as it is unsafe. + pub unsafe fn add(self, rhs: Self) -> Self { + self + } + + // Should not trigger lint since this is an async function. + pub async fn next(&mut self) -> Option { + None + } +} diff --git a/src/tools/clippy/tests/ui/should_impl_trait/method_list_1.rs b/src/tools/clippy/tests/ui/should_impl_trait/method_list_1.rs new file mode 100644 index 0000000000..69a3390b03 --- /dev/null +++ b/src/tools/clippy/tests/ui/should_impl_trait/method_list_1.rs @@ -0,0 +1,88 @@ +// edition:2018 + +#![warn(clippy::all, clippy::pedantic)] +#![allow( + clippy::missing_errors_doc, + clippy::needless_pass_by_value, + clippy::must_use_candidate, + clippy::unused_self, + clippy::needless_lifetimes, + clippy::missing_safety_doc, + clippy::wrong_self_convention, + clippy::missing_panics_doc +)] + +use std::ops::Mul; +use std::rc::{self, Rc}; +use std::sync::{self, Arc}; + +fn main() {} +pub struct T; + +impl T { + // ***************************************** + // trait method list part 1, should lint all + // ***************************************** + pub fn add(self, other: T) -> T { + unimplemented!() + } + + pub fn as_mut(&mut self) -> &mut T { + unimplemented!() + } + + pub fn as_ref(&self) -> &T { + unimplemented!() + } + + pub fn bitand(self, rhs: T) -> T { + unimplemented!() + } + + pub fn bitor(self, rhs: Self) -> Self { + unimplemented!() + } + + pub fn bitxor(self, rhs: Self) -> Self { + unimplemented!() + } + + pub fn borrow(&self) -> &str { + unimplemented!() + } + + pub fn borrow_mut(&mut self) -> &mut str { + unimplemented!() + } + + pub fn clone(&self) -> Self { + unimplemented!() + } + + pub fn cmp(&self, other: &Self) -> Self { + unimplemented!() + } + + pub fn default() -> Self { + unimplemented!() + } + + pub fn deref(&self) -> &Self { + unimplemented!() + } + + pub fn deref_mut(&mut self) -> &mut Self { + unimplemented!() + } + + pub fn div(self, rhs: Self) -> Self { + unimplemented!() + } + + pub fn drop(&mut self) { + unimplemented!() + } + // ********** + // part 1 end + // ********** +} diff --git a/src/tools/clippy/tests/ui/should_impl_trait/method_list_1.stderr b/src/tools/clippy/tests/ui/should_impl_trait/method_list_1.stderr new file mode 100644 index 0000000000..86c6394651 --- /dev/null +++ b/src/tools/clippy/tests/ui/should_impl_trait/method_list_1.stderr @@ -0,0 +1,143 @@ +error: method `add` can be confused for the standard trait method `std::ops::Add::add` + --> $DIR/method_list_1.rs:26:5 + | +LL | / pub fn add(self, other: T) -> T { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = note: `-D clippy::should-implement-trait` implied by `-D warnings` + = help: consider implementing the trait `std::ops::Add` or choosing a less ambiguous method name + +error: method `as_mut` can be confused for the standard trait method `std::convert::AsMut::as_mut` + --> $DIR/method_list_1.rs:30:5 + | +LL | / pub fn as_mut(&mut self) -> &mut T { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::convert::AsMut` or choosing a less ambiguous method name + +error: method `as_ref` can be confused for the standard trait method `std::convert::AsRef::as_ref` + --> $DIR/method_list_1.rs:34:5 + | +LL | / pub fn as_ref(&self) -> &T { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::convert::AsRef` or choosing a less ambiguous method name + +error: method `bitand` can be confused for the standard trait method `std::ops::BitAnd::bitand` + --> $DIR/method_list_1.rs:38:5 + | +LL | / pub fn bitand(self, rhs: T) -> T { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::BitAnd` or choosing a less ambiguous method name + +error: method `bitor` can be confused for the standard trait method `std::ops::BitOr::bitor` + --> $DIR/method_list_1.rs:42:5 + | +LL | / pub fn bitor(self, rhs: Self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::BitOr` or choosing a less ambiguous method name + +error: method `bitxor` can be confused for the standard trait method `std::ops::BitXor::bitxor` + --> $DIR/method_list_1.rs:46:5 + | +LL | / pub fn bitxor(self, rhs: Self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::BitXor` or choosing a less ambiguous method name + +error: method `borrow` can be confused for the standard trait method `std::borrow::Borrow::borrow` + --> $DIR/method_list_1.rs:50:5 + | +LL | / pub fn borrow(&self) -> &str { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::borrow::Borrow` or choosing a less ambiguous method name + +error: method `borrow_mut` can be confused for the standard trait method `std::borrow::BorrowMut::borrow_mut` + --> $DIR/method_list_1.rs:54:5 + | +LL | / pub fn borrow_mut(&mut self) -> &mut str { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::borrow::BorrowMut` or choosing a less ambiguous method name + +error: method `clone` can be confused for the standard trait method `std::clone::Clone::clone` + --> $DIR/method_list_1.rs:58:5 + | +LL | / pub fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::clone::Clone` or choosing a less ambiguous method name + +error: method `cmp` can be confused for the standard trait method `std::cmp::Ord::cmp` + --> $DIR/method_list_1.rs:62:5 + | +LL | / pub fn cmp(&self, other: &Self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::cmp::Ord` or choosing a less ambiguous method name + +error: method `deref` can be confused for the standard trait method `std::ops::Deref::deref` + --> $DIR/method_list_1.rs:70:5 + | +LL | / pub fn deref(&self) -> &Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Deref` or choosing a less ambiguous method name + +error: method `deref_mut` can be confused for the standard trait method `std::ops::DerefMut::deref_mut` + --> $DIR/method_list_1.rs:74:5 + | +LL | / pub fn deref_mut(&mut self) -> &mut Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::DerefMut` or choosing a less ambiguous method name + +error: method `div` can be confused for the standard trait method `std::ops::Div::div` + --> $DIR/method_list_1.rs:78:5 + | +LL | / pub fn div(self, rhs: Self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Div` or choosing a less ambiguous method name + +error: method `drop` can be confused for the standard trait method `std::ops::Drop::drop` + --> $DIR/method_list_1.rs:82:5 + | +LL | / pub fn drop(&mut self) { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Drop` or choosing a less ambiguous method name + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/should_impl_trait/method_list_2.rs b/src/tools/clippy/tests/ui/should_impl_trait/method_list_2.rs new file mode 100644 index 0000000000..2cdc1a06fe --- /dev/null +++ b/src/tools/clippy/tests/ui/should_impl_trait/method_list_2.rs @@ -0,0 +1,89 @@ +// edition:2018 + +#![warn(clippy::all, clippy::pedantic)] +#![allow( + clippy::missing_errors_doc, + clippy::needless_pass_by_value, + clippy::must_use_candidate, + clippy::unused_self, + clippy::needless_lifetimes, + clippy::missing_safety_doc, + clippy::wrong_self_convention, + clippy::missing_panics_doc +)] + +use std::ops::Mul; +use std::rc::{self, Rc}; +use std::sync::{self, Arc}; + +fn main() {} +pub struct T; + +impl T { + // ***************************************** + // trait method list part 2, should lint all + // ***************************************** + + pub fn eq(&self, other: &Self) -> bool { + unimplemented!() + } + + pub fn from_iter(iter: T) -> Self { + unimplemented!() + } + + pub fn from_str(s: &str) -> Result { + unimplemented!() + } + + pub fn hash(&self, state: &mut T) { + unimplemented!() + } + + pub fn index(&self, index: usize) -> &Self { + unimplemented!() + } + + pub fn index_mut(&mut self, index: usize) -> &mut Self { + unimplemented!() + } + + pub fn into_iter(self) -> Self { + unimplemented!() + } + + pub fn mul(self, rhs: Self) -> Self { + unimplemented!() + } + + pub fn neg(self) -> Self { + unimplemented!() + } + + pub fn next(&mut self) -> Option { + unimplemented!() + } + + pub fn not(self) -> Self { + unimplemented!() + } + + pub fn rem(self, rhs: Self) -> Self { + unimplemented!() + } + + pub fn shl(self, rhs: Self) -> Self { + unimplemented!() + } + + pub fn shr(self, rhs: Self) -> Self { + unimplemented!() + } + + pub fn sub(self, rhs: Self) -> Self { + unimplemented!() + } + // ********** + // part 2 end + // ********** +} diff --git a/src/tools/clippy/tests/ui/should_impl_trait/method_list_2.stderr b/src/tools/clippy/tests/ui/should_impl_trait/method_list_2.stderr new file mode 100644 index 0000000000..0142e29910 --- /dev/null +++ b/src/tools/clippy/tests/ui/should_impl_trait/method_list_2.stderr @@ -0,0 +1,153 @@ +error: method `eq` can be confused for the standard trait method `std::cmp::PartialEq::eq` + --> $DIR/method_list_2.rs:27:5 + | +LL | / pub fn eq(&self, other: &Self) -> bool { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = note: `-D clippy::should-implement-trait` implied by `-D warnings` + = help: consider implementing the trait `std::cmp::PartialEq` or choosing a less ambiguous method name + +error: method `from_iter` can be confused for the standard trait method `std::iter::FromIterator::from_iter` + --> $DIR/method_list_2.rs:31:5 + | +LL | / pub fn from_iter(iter: T) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::FromIterator` or choosing a less ambiguous method name + +error: method `from_str` can be confused for the standard trait method `std::str::FromStr::from_str` + --> $DIR/method_list_2.rs:35:5 + | +LL | / pub fn from_str(s: &str) -> Result { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::str::FromStr` or choosing a less ambiguous method name + +error: method `hash` can be confused for the standard trait method `std::hash::Hash::hash` + --> $DIR/method_list_2.rs:39:5 + | +LL | / pub fn hash(&self, state: &mut T) { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::hash::Hash` or choosing a less ambiguous method name + +error: method `index` can be confused for the standard trait method `std::ops::Index::index` + --> $DIR/method_list_2.rs:43:5 + | +LL | / pub fn index(&self, index: usize) -> &Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Index` or choosing a less ambiguous method name + +error: method `index_mut` can be confused for the standard trait method `std::ops::IndexMut::index_mut` + --> $DIR/method_list_2.rs:47:5 + | +LL | / pub fn index_mut(&mut self, index: usize) -> &mut Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::IndexMut` or choosing a less ambiguous method name + +error: method `into_iter` can be confused for the standard trait method `std::iter::IntoIterator::into_iter` + --> $DIR/method_list_2.rs:51:5 + | +LL | / pub fn into_iter(self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::IntoIterator` or choosing a less ambiguous method name + +error: method `mul` can be confused for the standard trait method `std::ops::Mul::mul` + --> $DIR/method_list_2.rs:55:5 + | +LL | / pub fn mul(self, rhs: Self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Mul` or choosing a less ambiguous method name + +error: method `neg` can be confused for the standard trait method `std::ops::Neg::neg` + --> $DIR/method_list_2.rs:59:5 + | +LL | / pub fn neg(self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Neg` or choosing a less ambiguous method name + +error: method `next` can be confused for the standard trait method `std::iter::Iterator::next` + --> $DIR/method_list_2.rs:63:5 + | +LL | / pub fn next(&mut self) -> Option { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::Iterator` or choosing a less ambiguous method name + +error: method `not` can be confused for the standard trait method `std::ops::Not::not` + --> $DIR/method_list_2.rs:67:5 + | +LL | / pub fn not(self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Not` or choosing a less ambiguous method name + +error: method `rem` can be confused for the standard trait method `std::ops::Rem::rem` + --> $DIR/method_list_2.rs:71:5 + | +LL | / pub fn rem(self, rhs: Self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Rem` or choosing a less ambiguous method name + +error: method `shl` can be confused for the standard trait method `std::ops::Shl::shl` + --> $DIR/method_list_2.rs:75:5 + | +LL | / pub fn shl(self, rhs: Self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Shl` or choosing a less ambiguous method name + +error: method `shr` can be confused for the standard trait method `std::ops::Shr::shr` + --> $DIR/method_list_2.rs:79:5 + | +LL | / pub fn shr(self, rhs: Self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Shr` or choosing a less ambiguous method name + +error: method `sub` can be confused for the standard trait method `std::ops::Sub::sub` + --> $DIR/method_list_2.rs:83:5 + | +LL | / pub fn sub(self, rhs: Self) -> Self { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Sub` or choosing a less ambiguous method name + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/similar_names.rs b/src/tools/clippy/tests/ui/similar_names.rs new file mode 100644 index 0000000000..5981980988 --- /dev/null +++ b/src/tools/clippy/tests/ui/similar_names.rs @@ -0,0 +1,108 @@ +#![warn(clippy::similar_names)] +#![allow(unused, clippy::println_empty_string)] + +struct Foo { + apple: i32, + bpple: i32, +} + +fn main() { + let specter: i32; + let spectre: i32; + + let apple: i32; + + let bpple: i32; + + let cpple: i32; + + let a_bar: i32; + let b_bar: i32; + let c_bar: i32; + + let items = [5]; + for item in &items { + loop {} + } + + let foo_x: i32; + let foo_y: i32; + + let rhs: i32; + let lhs: i32; + + let bla_rhs: i32; + let bla_lhs: i32; + + let blubrhs: i32; + let blublhs: i32; + + let blubx: i32; + let bluby: i32; + + let cake: i32; + let cakes: i32; + let coke: i32; + + match 5 { + cheese @ 1 => {}, + rabbit => panic!(), + } + let cheese: i32; + match (42, 43) { + (cheese1, 1) => {}, + (cheese2, 2) => panic!(), + _ => println!(""), + } + let ipv4: i32; + let ipv6: i32; + let abcd1: i32; + let abdc2: i32; + let xyz1abc: i32; + let xyz2abc: i32; + let xyzeabc: i32; + + let parser: i32; + let parsed: i32; + let parsee: i32; + + let setter: i32; + let getter: i32; + let tx1: i32; + let rx1: i32; + let tx_cake: i32; + let rx_cake: i32; +} + +fn foo() { + let Foo { apple, bpple } = unimplemented!(); + let Foo { + apple: spring, + bpple: sprang, + } = unimplemented!(); +} + +// false positive similar_names (#3057, #2651) +// clippy claimed total_reg_src_size and total_size and +// numb_reg_src_checkouts and total_bin_size were similar +#[derive(Debug, Clone)] +pub(crate) struct DirSizes { + pub(crate) total_size: u64, + pub(crate) numb_bins: u64, + pub(crate) total_bin_size: u64, + pub(crate) total_reg_size: u64, + pub(crate) total_git_db_size: u64, + pub(crate) total_git_repos_bare_size: u64, + pub(crate) numb_git_repos_bare_repos: u64, + pub(crate) numb_git_checkouts: u64, + pub(crate) total_git_chk_size: u64, + pub(crate) total_reg_cache_size: u64, + pub(crate) total_reg_src_size: u64, + pub(crate) numb_reg_cache_entries: u64, + pub(crate) numb_reg_src_checkouts: u64, +} + +fn ignore_underscore_prefix() { + let hello: (); + let _hello: (); +} diff --git a/src/tools/clippy/tests/ui/similar_names.stderr b/src/tools/clippy/tests/ui/similar_names.stderr new file mode 100644 index 0000000000..0256f126a9 --- /dev/null +++ b/src/tools/clippy/tests/ui/similar_names.stderr @@ -0,0 +1,107 @@ +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:15:9 + | +LL | let bpple: i32; + | ^^^^^ + | + = note: `-D clippy::similar-names` implied by `-D warnings` +note: existing binding defined here + --> $DIR/similar_names.rs:13:9 + | +LL | let apple: i32; + | ^^^^^ +help: separate the discriminating character by an underscore like: `b_pple` + --> $DIR/similar_names.rs:15:9 + | +LL | let bpple: i32; + | ^^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:17:9 + | +LL | let cpple: i32; + | ^^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:13:9 + | +LL | let apple: i32; + | ^^^^^ +help: separate the discriminating character by an underscore like: `c_pple` + --> $DIR/similar_names.rs:17:9 + | +LL | let cpple: i32; + | ^^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:41:9 + | +LL | let bluby: i32; + | ^^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:40:9 + | +LL | let blubx: i32; + | ^^^^^ +help: separate the discriminating character by an underscore like: `blub_y` + --> $DIR/similar_names.rs:41:9 + | +LL | let bluby: i32; + | ^^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:45:9 + | +LL | let coke: i32; + | ^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:43:9 + | +LL | let cake: i32; + | ^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:63:9 + | +LL | let xyzeabc: i32; + | ^^^^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:61:9 + | +LL | let xyz1abc: i32; + | ^^^^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:67:9 + | +LL | let parsee: i32; + | ^^^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:65:9 + | +LL | let parser: i32; + | ^^^^^^ +help: separate the discriminating character by an underscore like: `parse_e` + --> $DIR/similar_names.rs:67:9 + | +LL | let parsee: i32; + | ^^^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:81:16 + | +LL | bpple: sprang, + | ^^^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:80:16 + | +LL | apple: spring, + | ^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/single_char_add_str.fixed b/src/tools/clippy/tests/ui/single_char_add_str.fixed new file mode 100644 index 0000000000..63a6d37a9c --- /dev/null +++ b/src/tools/clippy/tests/ui/single_char_add_str.fixed @@ -0,0 +1,45 @@ +// run-rustfix +#![warn(clippy::single_char_add_str)] + +macro_rules! get_string { + () => { + String::from("Hello world!") + }; +} + +fn main() { + // `push_str` tests + + let mut string = String::new(); + string.push('R'); + string.push('\''); + + string.push('u'); + string.push_str("st"); + string.push_str(""); + string.push('\x52'); + string.push('\u{0052}'); + string.push('a'); + + get_string!().push('ö'); + + // `insert_str` tests + + let mut string = String::new(); + string.insert(0, 'R'); + string.insert(1, '\''); + + string.insert(0, 'u'); + string.insert_str(2, "st"); + string.insert_str(0, ""); + string.insert(0, '\x52'); + string.insert(0, '\u{0052}'); + let x: usize = 2; + string.insert(x, 'a'); + const Y: usize = 1; + string.insert(Y, 'a'); + string.insert(Y, '"'); + string.insert(Y, '\''); + + get_string!().insert(1, '?'); +} diff --git a/src/tools/clippy/tests/ui/single_char_add_str.rs b/src/tools/clippy/tests/ui/single_char_add_str.rs new file mode 100644 index 0000000000..a799ea7d88 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_char_add_str.rs @@ -0,0 +1,45 @@ +// run-rustfix +#![warn(clippy::single_char_add_str)] + +macro_rules! get_string { + () => { + String::from("Hello world!") + }; +} + +fn main() { + // `push_str` tests + + let mut string = String::new(); + string.push_str("R"); + string.push_str("'"); + + string.push('u'); + string.push_str("st"); + string.push_str(""); + string.push_str("\x52"); + string.push_str("\u{0052}"); + string.push_str(r##"a"##); + + get_string!().push_str("ö"); + + // `insert_str` tests + + let mut string = String::new(); + string.insert_str(0, "R"); + string.insert_str(1, "'"); + + string.insert(0, 'u'); + string.insert_str(2, "st"); + string.insert_str(0, ""); + string.insert_str(0, "\x52"); + string.insert_str(0, "\u{0052}"); + let x: usize = 2; + string.insert_str(x, r##"a"##); + const Y: usize = 1; + string.insert_str(Y, r##"a"##); + string.insert_str(Y, r##"""##); + string.insert_str(Y, r##"'"##); + + get_string!().insert_str(1, "?"); +} diff --git a/src/tools/clippy/tests/ui/single_char_add_str.stderr b/src/tools/clippy/tests/ui/single_char_add_str.stderr new file mode 100644 index 0000000000..55d91583ad --- /dev/null +++ b/src/tools/clippy/tests/ui/single_char_add_str.stderr @@ -0,0 +1,94 @@ +error: calling `push_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:14:5 + | +LL | string.push_str("R"); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('R')` + | + = note: `-D clippy::single-char-add-str` implied by `-D warnings` + +error: calling `push_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:15:5 + | +LL | string.push_str("'"); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/'')` + +error: calling `push_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:20:5 + | +LL | string.push_str("/x52"); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/x52')` + +error: calling `push_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:21:5 + | +LL | string.push_str("/u{0052}"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/u{0052}')` + +error: calling `push_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:22:5 + | +LL | string.push_str(r##"a"##); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('a')` + +error: calling `push_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:24:5 + | +LL | get_string!().push_str("ö"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `get_string!().push('ö')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:29:5 + | +LL | string.insert_str(0, "R"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, 'R')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:30:5 + | +LL | string.insert_str(1, "'"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(1, '/'')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:35:5 + | +LL | string.insert_str(0, "/x52"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, '/x52')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:36:5 + | +LL | string.insert_str(0, "/u{0052}"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, '/u{0052}')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:38:5 + | +LL | string.insert_str(x, r##"a"##); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(x, 'a')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:40:5 + | +LL | string.insert_str(Y, r##"a"##); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(Y, 'a')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:41:5 + | +LL | string.insert_str(Y, r##"""##); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(Y, '"')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:42:5 + | +LL | string.insert_str(Y, r##"'"##); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(Y, '/'')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_add_str.rs:44:5 + | +LL | get_string!().insert_str(1, "?"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `get_string!().insert(1, '?')` + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/single_char_pattern.fixed b/src/tools/clippy/tests/ui/single_char_pattern.fixed new file mode 100644 index 0000000000..d8b5f19e14 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_char_pattern.fixed @@ -0,0 +1,57 @@ +// run-rustfix + +#![allow(unused_must_use)] + +use std::collections::HashSet; + +fn main() { + let x = "foo"; + x.split('x'); + x.split("xx"); + x.split('x'); + + let y = "x"; + x.split(y); + x.split('ß'); + x.split('ℝ'); + x.split('💣'); + // Can't use this lint for unicode code points which don't fit in a char + x.split("❤️"); + x.contains('x'); + x.starts_with('x'); + x.ends_with('x'); + x.find('x'); + x.rfind('x'); + x.rsplit('x'); + x.split_terminator('x'); + x.rsplit_terminator('x'); + x.splitn(0, 'x'); + x.rsplitn(0, 'x'); + x.matches('x'); + x.rmatches('x'); + x.match_indices('x'); + x.rmatch_indices('x'); + x.trim_start_matches('x'); + x.trim_end_matches('x'); + // Make sure we escape characters correctly. + x.split('\n'); + x.split('\''); + x.split('\''); + + let h = HashSet::::new(); + h.contains("X"); // should not warn + + x.replace(";", ",").split(','); // issue #2978 + x.starts_with('\x03'); // issue #2996 + + // Issue #3204 + const S: &str = "#"; + x.find(S); + + // Raw string + x.split('a'); + x.split('a'); + x.split('a'); + x.split('\''); + x.split('#'); +} diff --git a/src/tools/clippy/tests/ui/single_char_pattern.rs b/src/tools/clippy/tests/ui/single_char_pattern.rs new file mode 100644 index 0000000000..a7bc73e375 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_char_pattern.rs @@ -0,0 +1,57 @@ +// run-rustfix + +#![allow(unused_must_use)] + +use std::collections::HashSet; + +fn main() { + let x = "foo"; + x.split("x"); + x.split("xx"); + x.split('x'); + + let y = "x"; + x.split(y); + x.split("ß"); + x.split("ℝ"); + x.split("💣"); + // Can't use this lint for unicode code points which don't fit in a char + x.split("❤️"); + x.contains("x"); + x.starts_with("x"); + x.ends_with("x"); + x.find("x"); + x.rfind("x"); + x.rsplit("x"); + x.split_terminator("x"); + x.rsplit_terminator("x"); + x.splitn(0, "x"); + x.rsplitn(0, "x"); + x.matches("x"); + x.rmatches("x"); + x.match_indices("x"); + x.rmatch_indices("x"); + x.trim_start_matches("x"); + x.trim_end_matches("x"); + // Make sure we escape characters correctly. + x.split("\n"); + x.split("'"); + x.split("\'"); + + let h = HashSet::::new(); + h.contains("X"); // should not warn + + x.replace(";", ",").split(","); // issue #2978 + x.starts_with("\x03"); // issue #2996 + + // Issue #3204 + const S: &str = "#"; + x.find(S); + + // Raw string + x.split(r"a"); + x.split(r#"a"#); + x.split(r###"a"###); + x.split(r###"'"###); + x.split(r###"#"###); +} diff --git a/src/tools/clippy/tests/ui/single_char_pattern.stderr b/src/tools/clippy/tests/ui/single_char_pattern.stderr new file mode 100644 index 0000000000..ee4e7e50ef --- /dev/null +++ b/src/tools/clippy/tests/ui/single_char_pattern.stderr @@ -0,0 +1,184 @@ +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:9:13 + | +LL | x.split("x"); + | ^^^ help: try using a `char` instead: `'x'` + | + = note: `-D clippy::single-char-pattern` implied by `-D warnings` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:15:13 + | +LL | x.split("ß"); + | ^^^ help: try using a `char` instead: `'ß'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:16:13 + | +LL | x.split("ℝ"); + | ^^^ help: try using a `char` instead: `'ℝ'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:17:13 + | +LL | x.split("💣"); + | ^^^^ help: try using a `char` instead: `'💣'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:20:16 + | +LL | x.contains("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:21:19 + | +LL | x.starts_with("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:22:17 + | +LL | x.ends_with("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:23:12 + | +LL | x.find("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:24:13 + | +LL | x.rfind("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:25:14 + | +LL | x.rsplit("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:26:24 + | +LL | x.split_terminator("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:27:25 + | +LL | x.rsplit_terminator("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:28:17 + | +LL | x.splitn(0, "x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:29:18 + | +LL | x.rsplitn(0, "x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:30:15 + | +LL | x.matches("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:31:16 + | +LL | x.rmatches("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:32:21 + | +LL | x.match_indices("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:33:22 + | +LL | x.rmatch_indices("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:34:26 + | +LL | x.trim_start_matches("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:35:24 + | +LL | x.trim_end_matches("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:37:13 + | +LL | x.split("/n"); + | ^^^^ help: try using a `char` instead: `'/n'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:38:13 + | +LL | x.split("'"); + | ^^^ help: try using a `char` instead: `'/''` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:39:13 + | +LL | x.split("/'"); + | ^^^^ help: try using a `char` instead: `'/''` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:44:31 + | +LL | x.replace(";", ",").split(","); // issue #2978 + | ^^^ help: try using a `char` instead: `','` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:45:19 + | +LL | x.starts_with("/x03"); // issue #2996 + | ^^^^^^ help: try using a `char` instead: `'/x03'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:52:13 + | +LL | x.split(r"a"); + | ^^^^ help: try using a `char` instead: `'a'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:53:13 + | +LL | x.split(r#"a"#); + | ^^^^^^ help: try using a `char` instead: `'a'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:54:13 + | +LL | x.split(r###"a"###); + | ^^^^^^^^^^ help: try using a `char` instead: `'a'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:55:13 + | +LL | x.split(r###"'"###); + | ^^^^^^^^^^ help: try using a `char` instead: `'/''` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:56:13 + | +LL | x.split(r###"#"###); + | ^^^^^^^^^^ help: try using a `char` instead: `'#'` + +error: aborting due to 30 previous errors + diff --git a/src/tools/clippy/tests/ui/single_component_path_imports.fixed b/src/tools/clippy/tests/ui/single_component_path_imports.fixed new file mode 100644 index 0000000000..a7a8499b58 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_component_path_imports.fixed @@ -0,0 +1,21 @@ +// run-rustfix +// edition:2018 +#![warn(clippy::single_component_path_imports)] +#![allow(unused_imports)] + + +use serde as edres; +pub use serde; + +macro_rules! m { + () => { + use regex; + }; +} + +fn main() { + regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); + + // False positive #5154, shouldn't trigger lint. + m!(); +} diff --git a/src/tools/clippy/tests/ui/single_component_path_imports.rs b/src/tools/clippy/tests/ui/single_component_path_imports.rs new file mode 100644 index 0000000000..9a427e90ad --- /dev/null +++ b/src/tools/clippy/tests/ui/single_component_path_imports.rs @@ -0,0 +1,21 @@ +// run-rustfix +// edition:2018 +#![warn(clippy::single_component_path_imports)] +#![allow(unused_imports)] + +use regex; +use serde as edres; +pub use serde; + +macro_rules! m { + () => { + use regex; + }; +} + +fn main() { + regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); + + // False positive #5154, shouldn't trigger lint. + m!(); +} diff --git a/src/tools/clippy/tests/ui/single_component_path_imports.stderr b/src/tools/clippy/tests/ui/single_component_path_imports.stderr new file mode 100644 index 0000000000..519ada0169 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_component_path_imports.stderr @@ -0,0 +1,10 @@ +error: this import is redundant + --> $DIR/single_component_path_imports.rs:6:1 + | +LL | use regex; + | ^^^^^^^^^^ help: remove it entirely + | + = note: `-D clippy::single-component-path-imports` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/single_element_loop.fixed b/src/tools/clippy/tests/ui/single_element_loop.fixed new file mode 100644 index 0000000000..8ca068293a --- /dev/null +++ b/src/tools/clippy/tests/ui/single_element_loop.fixed @@ -0,0 +1,11 @@ +// run-rustfix +// Tests from for_loop.rs that don't have suggestions + +#[warn(clippy::single_element_loop)] +fn main() { + let item1 = 2; + { + let item = &item1; + println!("{}", item); + } +} diff --git a/src/tools/clippy/tests/ui/single_element_loop.rs b/src/tools/clippy/tests/ui/single_element_loop.rs new file mode 100644 index 0000000000..57e9336a31 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_element_loop.rs @@ -0,0 +1,10 @@ +// run-rustfix +// Tests from for_loop.rs that don't have suggestions + +#[warn(clippy::single_element_loop)] +fn main() { + let item1 = 2; + for item in &[item1] { + println!("{}", item); + } +} diff --git a/src/tools/clippy/tests/ui/single_element_loop.stderr b/src/tools/clippy/tests/ui/single_element_loop.stderr new file mode 100644 index 0000000000..90be1dc328 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_element_loop.stderr @@ -0,0 +1,19 @@ +error: for loop over a single element + --> $DIR/single_element_loop.rs:7:5 + | +LL | / for item in &[item1] { +LL | | println!("{}", item); +LL | | } + | |_____^ + | + = note: `-D clippy::single-element-loop` implied by `-D warnings` +help: try + | +LL | { +LL | let item = &item1; +LL | println!("{}", item); +LL | } + | + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/single_match.rs b/src/tools/clippy/tests/ui/single_match.rs new file mode 100644 index 0000000000..ca884b41c4 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match.rs @@ -0,0 +1,151 @@ +#![warn(clippy::single_match)] + +fn dummy() {} + +fn single_match() { + let x = Some(1u8); + + match x { + Some(y) => { + println!("{:?}", y); + }, + _ => (), + }; + + let x = Some(1u8); + match x { + // Note the missing block braces. + // We suggest `if let Some(y) = x { .. }` because the macro + // is expanded before we can do anything. + Some(y) => println!("{:?}", y), + _ => (), + } + + let z = (1u8, 1u8); + match z { + (2..=3, 7..=9) => dummy(), + _ => {}, + }; + + // Not linted (pattern guards used) + match x { + Some(y) if y == 0 => println!("{:?}", y), + _ => (), + } + + // Not linted (no block with statements in the single arm) + match z { + (2..=3, 7..=9) => println!("{:?}", z), + _ => println!("nope"), + } +} + +enum Foo { + Bar, + Baz(u8), +} +use std::borrow::Cow; +use Foo::*; + +fn single_match_know_enum() { + let x = Some(1u8); + let y: Result<_, i8> = Ok(1i8); + + match x { + Some(y) => dummy(), + None => (), + }; + + match y { + Ok(y) => dummy(), + Err(..) => (), + }; + + let c = Cow::Borrowed(""); + + match c { + Cow::Borrowed(..) => dummy(), + Cow::Owned(..) => (), + }; + + let z = Foo::Bar; + // no warning + match z { + Bar => println!("42"), + Baz(_) => (), + } + + match z { + Baz(_) => println!("42"), + Bar => (), + } +} + +// issue #173 +fn if_suggestion() { + let x = "test"; + match x { + "test" => println!(), + _ => (), + } + + #[derive(PartialEq, Eq)] + enum Foo { + A, + B, + C(u32), + } + + let x = Foo::A; + match x { + Foo::A => println!(), + _ => (), + } + + const FOO_C: Foo = Foo::C(0); + match x { + FOO_C => println!(), + _ => (), + } + + match &&x { + Foo::A => println!(), + _ => (), + } + + let x = &x; + match &x { + Foo::A => println!(), + _ => (), + } + + enum Bar { + A, + B, + } + impl PartialEq for Bar { + fn eq(&self, rhs: &Self) -> bool { + matches!((self, rhs), (Self::A, Self::A) | (Self::B, Self::B)) + } + } + impl Eq for Bar {} + + let x = Bar::A; + match x { + Bar::A => println!(), + _ => (), + } +} + +macro_rules! single_match { + ($num:literal) => { + match $num { + 15 => println!("15"), + _ => (), + } + }; +} + +fn main() { + single_match!(5); +} diff --git a/src/tools/clippy/tests/ui/single_match.stderr b/src/tools/clippy/tests/ui/single_match.stderr new file mode 100644 index 0000000000..7ea6955b74 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match.stderr @@ -0,0 +1,123 @@ +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:8:5 + | +LL | / match x { +LL | | Some(y) => { +LL | | println!("{:?}", y); +LL | | }, +LL | | _ => (), +LL | | }; + | |_____^ + | + = note: `-D clippy::single-match` implied by `-D warnings` +help: try this + | +LL | if let Some(y) = x { +LL | println!("{:?}", y); +LL | }; + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:16:5 + | +LL | / match x { +LL | | // Note the missing block braces. +LL | | // We suggest `if let Some(y) = x { .. }` because the macro +LL | | // is expanded before we can do anything. +LL | | Some(y) => println!("{:?}", y), +LL | | _ => (), +LL | | } + | |_____^ help: try this: `if let Some(y) = x { println!("{:?}", y) }` + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:25:5 + | +LL | / match z { +LL | | (2..=3, 7..=9) => dummy(), +LL | | _ => {}, +LL | | }; + | |_____^ help: try this: `if let (2..=3, 7..=9) = z { dummy() }` + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:54:5 + | +LL | / match x { +LL | | Some(y) => dummy(), +LL | | None => (), +LL | | }; + | |_____^ help: try this: `if let Some(y) = x { dummy() }` + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:59:5 + | +LL | / match y { +LL | | Ok(y) => dummy(), +LL | | Err(..) => (), +LL | | }; + | |_____^ help: try this: `if let Ok(y) = y { dummy() }` + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:66:5 + | +LL | / match c { +LL | | Cow::Borrowed(..) => dummy(), +LL | | Cow::Owned(..) => (), +LL | | }; + | |_____^ help: try this: `if let Cow::Borrowed(..) = c { dummy() }` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> $DIR/single_match.rs:87:5 + | +LL | / match x { +LL | | "test" => println!(), +LL | | _ => (), +LL | | } + | |_____^ help: try this: `if x == "test" { println!() }` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> $DIR/single_match.rs:100:5 + | +LL | / match x { +LL | | Foo::A => println!(), +LL | | _ => (), +LL | | } + | |_____^ help: try this: `if x == Foo::A { println!() }` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> $DIR/single_match.rs:106:5 + | +LL | / match x { +LL | | FOO_C => println!(), +LL | | _ => (), +LL | | } + | |_____^ help: try this: `if x == FOO_C { println!() }` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> $DIR/single_match.rs:111:5 + | +LL | / match &&x { +LL | | Foo::A => println!(), +LL | | _ => (), +LL | | } + | |_____^ help: try this: `if x == Foo::A { println!() }` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> $DIR/single_match.rs:117:5 + | +LL | / match &x { +LL | | Foo::A => println!(), +LL | | _ => (), +LL | | } + | |_____^ help: try this: `if x == &Foo::A { println!() }` + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:134:5 + | +LL | / match x { +LL | | Bar::A => println!(), +LL | | _ => (), +LL | | } + | |_____^ help: try this: `if let Bar::A = x { println!() }` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/single_match_else.rs b/src/tools/clippy/tests/ui/single_match_else.rs new file mode 100644 index 0000000000..b624a41a29 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match_else.rs @@ -0,0 +1,86 @@ +#![warn(clippy::single_match_else)] +#![allow(clippy::needless_return)] +#![allow(clippy::no_effect)] + +enum ExprNode { + ExprAddrOf, + Butterflies, + Unicorns, +} + +static NODE: ExprNode = ExprNode::Unicorns; + +fn unwrap_addr() -> Option<&'static ExprNode> { + match ExprNode::Butterflies { + ExprNode::ExprAddrOf => Some(&NODE), + _ => { + let x = 5; + None + }, + } +} + +macro_rules! unwrap_addr { + ($expression:expr) => { + match $expression { + ExprNode::ExprAddrOf => Some(&NODE), + _ => { + let x = 5; + None + }, + } + }; +} + +#[rustfmt::skip] +fn main() { + unwrap_addr!(ExprNode::Unicorns); + + // + // don't lint single exprs/statements + // + + // don't lint here + match Some(1) { + Some(a) => println!("${:?}", a), + None => return, + } + + // don't lint here + match Some(1) { + Some(a) => println!("${:?}", a), + None => { + return + }, + } + + // don't lint here + match Some(1) { + Some(a) => println!("${:?}", a), + None => { + return; + }, + } + + // + // lint multiple exprs/statements "else" blocks + // + + // lint here + match Some(1) { + Some(a) => println!("${:?}", a), + None => { + println!("else block"); + return + }, + } + + // lint here + match Some(1) { + Some(a) => println!("${:?}", a), + None => { + println!("else block"); + return; + }, + } +} diff --git a/src/tools/clippy/tests/ui/single_match_else.stderr b/src/tools/clippy/tests/ui/single_match_else.stderr new file mode 100644 index 0000000000..20be4fa226 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match_else.stderr @@ -0,0 +1,63 @@ +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:14:5 + | +LL | / match ExprNode::Butterflies { +LL | | ExprNode::ExprAddrOf => Some(&NODE), +LL | | _ => { +LL | | let x = 5; +LL | | None +LL | | }, +LL | | } + | |_____^ + | + = note: `-D clippy::single-match-else` implied by `-D warnings` +help: try this + | +LL | if let ExprNode::ExprAddrOf = ExprNode::Butterflies { Some(&NODE) } else { +LL | let x = 5; +LL | None +LL | } + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:70:5 + | +LL | / match Some(1) { +LL | | Some(a) => println!("${:?}", a), +LL | | None => { +LL | | println!("else block"); +LL | | return +LL | | }, +LL | | } + | |_____^ + | +help: try this + | +LL | if let Some(a) = Some(1) { println!("${:?}", a) } else { +LL | println!("else block"); +LL | return +LL | } + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:79:5 + | +LL | / match Some(1) { +LL | | Some(a) => println!("${:?}", a), +LL | | None => { +LL | | println!("else block"); +LL | | return; +LL | | }, +LL | | } + | |_____^ + | +help: try this + | +LL | if let Some(a) = Some(1) { println!("${:?}", a) } else { +LL | println!("else block"); +LL | return; +LL | } + | + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.rs b/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.rs new file mode 100644 index 0000000000..2594e8fa6a --- /dev/null +++ b/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.rs @@ -0,0 +1,37 @@ +#![warn(clippy::size_of_in_element_count)] +#![allow(clippy::ptr_offset_with_cast)] + +use std::mem::{size_of, size_of_val}; +use std::ptr::{copy, copy_nonoverlapping, write_bytes}; + +fn main() { + const SIZE: usize = 128; + const HALF_SIZE: usize = SIZE / 2; + const DOUBLE_SIZE: usize = SIZE * 2; + let mut x = [2u8; SIZE]; + let mut y = [2u8; SIZE]; + + // Count expression involving multiplication of size_of (Should trigger the lint) + unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::() * SIZE) }; + + // Count expression involving nested multiplications of size_of (Should trigger the lint) + unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), HALF_SIZE * size_of_val(&x[0]) * 2) }; + + // Count expression involving divisions of size_of (Should trigger the lint) + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::() / 2) }; + + // Count expression involving divisions by size_of (Should not trigger the lint) + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / size_of::()) }; + + // Count expression involving divisions by multiple size_of (Should not trigger the lint) + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 * size_of::())) }; + + // Count expression involving recursive divisions by size_of (Should trigger the lint) + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 / size_of::())) }; + + // No size_of calls (Should not trigger the lint) + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), SIZE) }; + + // Different types for pointee and size_of (Should not trigger the lint) + unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::() / 2 * SIZE) }; +} diff --git a/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.stderr b/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.stderr new file mode 100644 index 0000000000..0f0dff57f5 --- /dev/null +++ b/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.stderr @@ -0,0 +1,35 @@ +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/expressions.rs:15:62 + | +LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::size-of-in-element-count` implied by `-D warnings` + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/expressions.rs:18:62 + | +LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), HALF_SIZE * size_of_val(&x[0]) * 2) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/expressions.rs:21:47 + | +LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::() / 2) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/expressions.rs:30:47 + | +LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 / size_of::())) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/size_of_in_element_count/functions.rs b/src/tools/clippy/tests/ui/size_of_in_element_count/functions.rs new file mode 100644 index 0000000000..09d08ac37d --- /dev/null +++ b/src/tools/clippy/tests/ui/size_of_in_element_count/functions.rs @@ -0,0 +1,46 @@ +#![warn(clippy::size_of_in_element_count)] +#![allow(clippy::ptr_offset_with_cast)] + +use std::mem::{size_of, size_of_val}; +use std::ptr::{ + copy, copy_nonoverlapping, slice_from_raw_parts, slice_from_raw_parts_mut, swap_nonoverlapping, write_bytes, +}; +use std::slice::{from_raw_parts, from_raw_parts_mut}; + +fn main() { + const SIZE: usize = 128; + const HALF_SIZE: usize = SIZE / 2; + const DOUBLE_SIZE: usize = SIZE * 2; + let mut x = [2u8; SIZE]; + let mut y = [2u8; SIZE]; + + // Count is size_of (Should trigger the lint) + unsafe { copy_nonoverlapping::(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; + unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) }; + + unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::()) }; + unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::()) }; + unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::()) }; + unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::()) }; + + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) }; + + unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::() * SIZE) }; + unsafe { write_bytes(y.as_mut_ptr(), 0u8, size_of::() * SIZE) }; + + unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::() * SIZE) }; + + slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE); + slice_from_raw_parts(y.as_ptr(), size_of::() * SIZE); + + unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE) }; + unsafe { from_raw_parts(y.as_ptr(), size_of::() * SIZE) }; + + unsafe { y.as_mut_ptr().sub(size_of::()) }; + y.as_ptr().wrapping_sub(size_of::()); + unsafe { y.as_ptr().add(size_of::()) }; + y.as_mut_ptr().wrapping_add(size_of::()); + unsafe { y.as_ptr().offset(size_of::() as isize) }; + y.as_mut_ptr().wrapping_offset(size_of::() as isize); +} diff --git a/src/tools/clippy/tests/ui/size_of_in_element_count/functions.stderr b/src/tools/clippy/tests/ui/size_of_in_element_count/functions.stderr new file mode 100644 index 0000000000..c1e824167b --- /dev/null +++ b/src/tools/clippy/tests/ui/size_of_in_element_count/functions.stderr @@ -0,0 +1,171 @@ +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:18:68 + | +LL | unsafe { copy_nonoverlapping::(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::size-of-in-element-count` implied by `-D warnings` + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:19:62 + | +LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) }; + | ^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:21:49 + | +LL | unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:22:64 + | +LL | unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:23:51 + | +LL | unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:24:66 + | +LL | unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:26:47 + | +LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:27:47 + | +LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) }; + | ^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:29:46 + | +LL | unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:30:47 + | +LL | unsafe { write_bytes(y.as_mut_ptr(), 0u8, size_of::() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:32:66 + | +LL | unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:34:46 + | +LL | slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:35:38 + | +LL | slice_from_raw_parts(y.as_ptr(), size_of::() * SIZE); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:37:49 + | +LL | unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:38:41 + | +LL | unsafe { from_raw_parts(y.as_ptr(), size_of::() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:40:33 + | +LL | unsafe { y.as_mut_ptr().sub(size_of::()) }; + | ^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:41:29 + | +LL | y.as_ptr().wrapping_sub(size_of::()); + | ^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:42:29 + | +LL | unsafe { y.as_ptr().add(size_of::()) }; + | ^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:43:33 + | +LL | y.as_mut_ptr().wrapping_add(size_of::()); + | ^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:44:32 + | +LL | unsafe { y.as_ptr().offset(size_of::() as isize) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: found a count of bytes instead of a count of elements of `T` + --> $DIR/functions.rs:45:36 + | +LL | y.as_mut_ptr().wrapping_offset(size_of::() as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type + +error: aborting due to 21 previous errors + diff --git a/src/tools/clippy/tests/ui/skip_while_next.rs b/src/tools/clippy/tests/ui/skip_while_next.rs new file mode 100644 index 0000000000..a522c0f08b --- /dev/null +++ b/src/tools/clippy/tests/ui/skip_while_next.rs @@ -0,0 +1,29 @@ +// aux-build:option_helpers.rs + +#![warn(clippy::skip_while_next)] +#![allow(clippy::blacklisted_name)] + +extern crate option_helpers; +use option_helpers::IteratorFalsePositives; + +#[rustfmt::skip] +fn skip_while_next() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + + // Single-line case. + let _ = v.iter().skip_while(|&x| *x < 0).next(); + + // Multi-line case. + let _ = v.iter().skip_while(|&x| { + *x < 0 + } + ).next(); + + // Check that hat we don't lint if the caller is not an `Iterator`. + let foo = IteratorFalsePositives { foo: 0 }; + let _ = foo.skip_while().next(); +} + +fn main() { + skip_while_next(); +} diff --git a/src/tools/clippy/tests/ui/skip_while_next.stderr b/src/tools/clippy/tests/ui/skip_while_next.stderr new file mode 100644 index 0000000000..269cc13468 --- /dev/null +++ b/src/tools/clippy/tests/ui/skip_while_next.stderr @@ -0,0 +1,23 @@ +error: called `skip_while(

).next()` on an `Iterator` + --> $DIR/skip_while_next.rs:14:13 + | +LL | let _ = v.iter().skip_while(|&x| *x < 0).next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::skip-while-next` implied by `-D warnings` + = help: this is more succinctly expressed by calling `.find(!

)` instead + +error: called `skip_while(

).next()` on an `Iterator` + --> $DIR/skip_while_next.rs:17:13 + | +LL | let _ = v.iter().skip_while(|&x| { + | _____________^ +LL | | *x < 0 +LL | | } +LL | | ).next(); + | |___________________________^ + | + = help: this is more succinctly expressed by calling `.find(!

)` instead + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/slow_vector_initialization.rs b/src/tools/clippy/tests/ui/slow_vector_initialization.rs new file mode 100644 index 0000000000..c5ae3ff769 --- /dev/null +++ b/src/tools/clippy/tests/ui/slow_vector_initialization.rs @@ -0,0 +1,63 @@ +use std::iter::repeat; + +fn main() { + resize_vector(); + extend_vector(); + mixed_extend_resize_vector(); +} + +fn extend_vector() { + // Extend with constant expression + let len = 300; + let mut vec1 = Vec::with_capacity(len); + vec1.extend(repeat(0).take(len)); + + // Extend with len expression + let mut vec2 = Vec::with_capacity(len - 10); + vec2.extend(repeat(0).take(len - 10)); + + // Extend with mismatching expression should not be warned + let mut vec3 = Vec::with_capacity(24322); + vec3.extend(repeat(0).take(2)); +} + +fn mixed_extend_resize_vector() { + // Mismatching len + let mut mismatching_len = Vec::with_capacity(30); + mismatching_len.extend(repeat(0).take(40)); + + // Slow initialization + let mut resized_vec = Vec::with_capacity(30); + resized_vec.resize(30, 0); + + let mut extend_vec = Vec::with_capacity(30); + extend_vec.extend(repeat(0).take(30)); +} + +fn resize_vector() { + // Resize with constant expression + let len = 300; + let mut vec1 = Vec::with_capacity(len); + vec1.resize(len, 0); + + // Resize mismatch len + let mut vec2 = Vec::with_capacity(200); + vec2.resize(10, 0); + + // Resize with len expression + let mut vec3 = Vec::with_capacity(len - 10); + vec3.resize(len - 10, 0); + + // Reinitialization should be warned + vec1 = Vec::with_capacity(10); + vec1.resize(10, 0); +} + +fn do_stuff(vec: &mut Vec) {} + +fn extend_vector_with_manipulations_between() { + let len = 300; + let mut vec1: Vec = Vec::with_capacity(len); + do_stuff(&mut vec1); + vec1.extend(repeat(0).take(len)); +} diff --git a/src/tools/clippy/tests/ui/slow_vector_initialization.stderr b/src/tools/clippy/tests/ui/slow_vector_initialization.stderr new file mode 100644 index 0000000000..5d2788ec26 --- /dev/null +++ b/src/tools/clippy/tests/ui/slow_vector_initialization.stderr @@ -0,0 +1,60 @@ +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:13:5 + | +LL | let mut vec1 = Vec::with_capacity(len); + | ----------------------- help: consider replace allocation with: `vec![0; len]` +LL | vec1.extend(repeat(0).take(len)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::slow-vector-initialization` implied by `-D warnings` + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:17:5 + | +LL | let mut vec2 = Vec::with_capacity(len - 10); + | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` +LL | vec2.extend(repeat(0).take(len - 10)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:31:5 + | +LL | let mut resized_vec = Vec::with_capacity(30); + | ---------------------- help: consider replace allocation with: `vec![0; 30]` +LL | resized_vec.resize(30, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:34:5 + | +LL | let mut extend_vec = Vec::with_capacity(30); + | ---------------------- help: consider replace allocation with: `vec![0; 30]` +LL | extend_vec.extend(repeat(0).take(30)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:41:5 + | +LL | let mut vec1 = Vec::with_capacity(len); + | ----------------------- help: consider replace allocation with: `vec![0; len]` +LL | vec1.resize(len, 0); + | ^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:49:5 + | +LL | let mut vec3 = Vec::with_capacity(len - 10); + | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` +LL | vec3.resize(len - 10, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:53:5 + | +LL | vec1 = Vec::with_capacity(10); + | ---------------------- help: consider replace allocation with: `vec![0; 10]` +LL | vec1.resize(10, 0); + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/stable_sort_primitive.fixed b/src/tools/clippy/tests/ui/stable_sort_primitive.fixed new file mode 100644 index 0000000000..8f8f566593 --- /dev/null +++ b/src/tools/clippy/tests/ui/stable_sort_primitive.fixed @@ -0,0 +1,32 @@ +// run-rustfix +#![warn(clippy::stable_sort_primitive)] + +fn main() { + // positive examples + let mut vec = vec![1, 3, 2]; + vec.sort_unstable(); + let mut vec = vec![false, false, true]; + vec.sort_unstable(); + let mut vec = vec!['a', 'A', 'c']; + vec.sort_unstable(); + let mut vec = vec!["ab", "cd", "ab", "bc"]; + vec.sort_unstable(); + let mut vec = vec![(2, 1), (1, 2), (2, 5)]; + vec.sort_unstable(); + let mut vec = vec![[2, 1], [1, 2], [2, 5]]; + vec.sort_unstable(); + let mut arr = [1, 3, 2]; + arr.sort_unstable(); + // Negative examples: behavior changes if made unstable + let mut vec = vec![1, 3, 2]; + vec.sort_by_key(|i| i / 2); + vec.sort_by(|a, b| (a + b).cmp(&b)); + // negative examples - Not of a primitive type + let mut vec_of_complex = vec![String::from("hello"), String::from("world!")]; + vec_of_complex.sort(); + vec_of_complex.sort_by_key(String::len); + let mut vec = vec![(String::from("hello"), String::from("world"))]; + vec.sort(); + let mut vec = vec![[String::from("hello"), String::from("world")]]; + vec.sort(); +} diff --git a/src/tools/clippy/tests/ui/stable_sort_primitive.rs b/src/tools/clippy/tests/ui/stable_sort_primitive.rs new file mode 100644 index 0000000000..f9bd977906 --- /dev/null +++ b/src/tools/clippy/tests/ui/stable_sort_primitive.rs @@ -0,0 +1,32 @@ +// run-rustfix +#![warn(clippy::stable_sort_primitive)] + +fn main() { + // positive examples + let mut vec = vec![1, 3, 2]; + vec.sort(); + let mut vec = vec![false, false, true]; + vec.sort(); + let mut vec = vec!['a', 'A', 'c']; + vec.sort(); + let mut vec = vec!["ab", "cd", "ab", "bc"]; + vec.sort(); + let mut vec = vec![(2, 1), (1, 2), (2, 5)]; + vec.sort(); + let mut vec = vec![[2, 1], [1, 2], [2, 5]]; + vec.sort(); + let mut arr = [1, 3, 2]; + arr.sort(); + // Negative examples: behavior changes if made unstable + let mut vec = vec![1, 3, 2]; + vec.sort_by_key(|i| i / 2); + vec.sort_by(|a, b| (a + b).cmp(&b)); + // negative examples - Not of a primitive type + let mut vec_of_complex = vec![String::from("hello"), String::from("world!")]; + vec_of_complex.sort(); + vec_of_complex.sort_by_key(String::len); + let mut vec = vec![(String::from("hello"), String::from("world"))]; + vec.sort(); + let mut vec = vec![[String::from("hello"), String::from("world")]]; + vec.sort(); +} diff --git a/src/tools/clippy/tests/ui/stable_sort_primitive.stderr b/src/tools/clippy/tests/ui/stable_sort_primitive.stderr new file mode 100644 index 0000000000..b8d22ed250 --- /dev/null +++ b/src/tools/clippy/tests/ui/stable_sort_primitive.stderr @@ -0,0 +1,59 @@ +error: used `sort` on primitive type `i32` + --> $DIR/stable_sort_primitive.rs:7:5 + | +LL | vec.sort(); + | ^^^^^^^^^^ help: try: `vec.sort_unstable()` + | + = note: `-D clippy::stable-sort-primitive` implied by `-D warnings` + = note: an unstable sort would perform faster without any observable difference for this data type + +error: used `sort` on primitive type `bool` + --> $DIR/stable_sort_primitive.rs:9:5 + | +LL | vec.sort(); + | ^^^^^^^^^^ help: try: `vec.sort_unstable()` + | + = note: an unstable sort would perform faster without any observable difference for this data type + +error: used `sort` on primitive type `char` + --> $DIR/stable_sort_primitive.rs:11:5 + | +LL | vec.sort(); + | ^^^^^^^^^^ help: try: `vec.sort_unstable()` + | + = note: an unstable sort would perform faster without any observable difference for this data type + +error: used `sort` on primitive type `str` + --> $DIR/stable_sort_primitive.rs:13:5 + | +LL | vec.sort(); + | ^^^^^^^^^^ help: try: `vec.sort_unstable()` + | + = note: an unstable sort would perform faster without any observable difference for this data type + +error: used `sort` on primitive type `tuple` + --> $DIR/stable_sort_primitive.rs:15:5 + | +LL | vec.sort(); + | ^^^^^^^^^^ help: try: `vec.sort_unstable()` + | + = note: an unstable sort would perform faster without any observable difference for this data type + +error: used `sort` on primitive type `array` + --> $DIR/stable_sort_primitive.rs:17:5 + | +LL | vec.sort(); + | ^^^^^^^^^^ help: try: `vec.sort_unstable()` + | + = note: an unstable sort would perform faster without any observable difference for this data type + +error: used `sort` on primitive type `i32` + --> $DIR/stable_sort_primitive.rs:19:5 + | +LL | arr.sort(); + | ^^^^^^^^^^ help: try: `arr.sort_unstable()` + | + = note: an unstable sort would perform faster without any observable difference for this data type + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/starts_ends_with.fixed b/src/tools/clippy/tests/ui/starts_ends_with.fixed new file mode 100644 index 0000000000..7dfcf9c91e --- /dev/null +++ b/src/tools/clippy/tests/ui/starts_ends_with.fixed @@ -0,0 +1,46 @@ +// run-rustfix +#![allow(dead_code, unused_must_use)] + +fn main() {} + +#[allow(clippy::unnecessary_operation)] +fn starts_with() { + "".starts_with(' '); + !"".starts_with(' '); +} + +fn chars_cmp_with_unwrap() { + let s = String::from("foo"); + if s.starts_with('f') { + // s.starts_with('f') + // Nothing here + } + if s.ends_with('o') { + // s.ends_with('o') + // Nothing here + } + if s.ends_with('o') { + // s.ends_with('o') + // Nothing here + } + if !s.starts_with('f') { + // !s.starts_with('f') + // Nothing here + } + if !s.ends_with('o') { + // !s.ends_with('o') + // Nothing here + } + if !s.ends_with('o') { + // !s.ends_with('o') + // Nothing here + } +} + +#[allow(clippy::unnecessary_operation)] +fn ends_with() { + "".ends_with(' '); + !"".ends_with(' '); + "".ends_with(' '); + !"".ends_with(' '); +} diff --git a/src/tools/clippy/tests/ui/starts_ends_with.rs b/src/tools/clippy/tests/ui/starts_ends_with.rs new file mode 100644 index 0000000000..e48a424635 --- /dev/null +++ b/src/tools/clippy/tests/ui/starts_ends_with.rs @@ -0,0 +1,46 @@ +// run-rustfix +#![allow(dead_code, unused_must_use)] + +fn main() {} + +#[allow(clippy::unnecessary_operation)] +fn starts_with() { + "".chars().next() == Some(' '); + Some(' ') != "".chars().next(); +} + +fn chars_cmp_with_unwrap() { + let s = String::from("foo"); + if s.chars().next().unwrap() == 'f' { + // s.starts_with('f') + // Nothing here + } + if s.chars().next_back().unwrap() == 'o' { + // s.ends_with('o') + // Nothing here + } + if s.chars().last().unwrap() == 'o' { + // s.ends_with('o') + // Nothing here + } + if s.chars().next().unwrap() != 'f' { + // !s.starts_with('f') + // Nothing here + } + if s.chars().next_back().unwrap() != 'o' { + // !s.ends_with('o') + // Nothing here + } + if s.chars().last().unwrap() != 'o' { + // !s.ends_with('o') + // Nothing here + } +} + +#[allow(clippy::unnecessary_operation)] +fn ends_with() { + "".chars().last() == Some(' '); + Some(' ') != "".chars().last(); + "".chars().next_back() == Some(' '); + Some(' ') != "".chars().next_back(); +} diff --git a/src/tools/clippy/tests/ui/starts_ends_with.stderr b/src/tools/clippy/tests/ui/starts_ends_with.stderr new file mode 100644 index 0000000000..7c726d0e01 --- /dev/null +++ b/src/tools/clippy/tests/ui/starts_ends_with.stderr @@ -0,0 +1,78 @@ +error: you should use the `starts_with` method + --> $DIR/starts_ends_with.rs:8:5 + | +LL | "".chars().next() == Some(' '); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `"".starts_with(' ')` + | + = note: `-D clippy::chars-next-cmp` implied by `-D warnings` + +error: you should use the `starts_with` method + --> $DIR/starts_ends_with.rs:9:5 + | +LL | Some(' ') != "".chars().next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".starts_with(' ')` + +error: you should use the `starts_with` method + --> $DIR/starts_ends_with.rs:14:8 + | +LL | if s.chars().next().unwrap() == 'f' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `s.starts_with('f')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:18:8 + | +LL | if s.chars().next_back().unwrap() == 'o' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `s.ends_with('o')` + | + = note: `-D clippy::chars-last-cmp` implied by `-D warnings` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:22:8 + | +LL | if s.chars().last().unwrap() == 'o' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `s.ends_with('o')` + +error: you should use the `starts_with` method + --> $DIR/starts_ends_with.rs:26:8 + | +LL | if s.chars().next().unwrap() != 'f' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!s.starts_with('f')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:30:8 + | +LL | if s.chars().next_back().unwrap() != 'o' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!s.ends_with('o')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:34:8 + | +LL | if s.chars().last().unwrap() != 'o' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!s.ends_with('o')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:42:5 + | +LL | "".chars().last() == Some(' '); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `"".ends_with(' ')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:43:5 + | +LL | Some(' ') != "".chars().last(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".ends_with(' ')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:44:5 + | +LL | "".chars().next_back() == Some(' '); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `"".ends_with(' ')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:45:5 + | +LL | Some(' ') != "".chars().next_back(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".ends_with(' ')` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/str_to_string.rs b/src/tools/clippy/tests/ui/str_to_string.rs new file mode 100644 index 0000000000..08f7340251 --- /dev/null +++ b/src/tools/clippy/tests/ui/str_to_string.rs @@ -0,0 +1,7 @@ +#![warn(clippy::str_to_string)] + +fn main() { + let hello = "hello world".to_string(); + let msg = &hello[..]; + msg.to_string(); +} diff --git a/src/tools/clippy/tests/ui/str_to_string.stderr b/src/tools/clippy/tests/ui/str_to_string.stderr new file mode 100644 index 0000000000..b1f73eda5d --- /dev/null +++ b/src/tools/clippy/tests/ui/str_to_string.stderr @@ -0,0 +1,19 @@ +error: `to_string()` called on a `&str` + --> $DIR/str_to_string.rs:4:17 + | +LL | let hello = "hello world".to_string(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::str-to-string` implied by `-D warnings` + = help: consider using `.to_owned()` + +error: `to_string()` called on a `&str` + --> $DIR/str_to_string.rs:6:5 + | +LL | msg.to_string(); + | ^^^^^^^^^^^^^^^ + | + = help: consider using `.to_owned()` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/string_add.rs b/src/tools/clippy/tests/ui/string_add.rs new file mode 100644 index 0000000000..30fd17c59e --- /dev/null +++ b/src/tools/clippy/tests/ui/string_add.rs @@ -0,0 +1,26 @@ +// aux-build:macro_rules.rs + +#[macro_use] +extern crate macro_rules; + +#[warn(clippy::string_add)] +#[allow(clippy::string_add_assign, unused)] +fn main() { + // ignores assignment distinction + let mut x = "".to_owned(); + + for _ in 1..3 { + x = x + "."; + } + + let y = "".to_owned(); + let z = y + "..."; + + assert_eq!(&x, &z); + + let mut x = 1; + x = x + 1; + assert_eq!(2, x); + + string_add!(); +} diff --git a/src/tools/clippy/tests/ui/string_add.stderr b/src/tools/clippy/tests/ui/string_add.stderr new file mode 100644 index 0000000000..3987641c75 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_add.stderr @@ -0,0 +1,30 @@ +error: manual implementation of an assign operation + --> $DIR/string_add.rs:13:9 + | +LL | x = x + "."; + | ^^^^^^^^^^^ help: replace it with: `x += "."` + | + = note: `-D clippy::assign-op-pattern` implied by `-D warnings` + +error: you added something to a string. Consider using `String::push_str()` instead + --> $DIR/string_add.rs:13:13 + | +LL | x = x + "."; + | ^^^^^^^ + | + = note: `-D clippy::string-add` implied by `-D warnings` + +error: you added something to a string. Consider using `String::push_str()` instead + --> $DIR/string_add.rs:17:13 + | +LL | let z = y + "..."; + | ^^^^^^^^^ + +error: manual implementation of an assign operation + --> $DIR/string_add.rs:22:5 + | +LL | x = x + 1; + | ^^^^^^^^^ help: replace it with: `x += 1` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/string_add_assign.fixed b/src/tools/clippy/tests/ui/string_add_assign.fixed new file mode 100644 index 0000000000..db71bab1e5 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_add_assign.fixed @@ -0,0 +1,21 @@ +// run-rustfix + +#[allow(clippy::string_add, unused)] +#[warn(clippy::string_add_assign)] +fn main() { + // ignores assignment distinction + let mut x = "".to_owned(); + + for _ in 1..3 { + x += "."; + } + + let y = "".to_owned(); + let z = y + "..."; + + assert_eq!(&x, &z); + + let mut x = 1; + x += 1; + assert_eq!(2, x); +} diff --git a/src/tools/clippy/tests/ui/string_add_assign.rs b/src/tools/clippy/tests/ui/string_add_assign.rs new file mode 100644 index 0000000000..644991945c --- /dev/null +++ b/src/tools/clippy/tests/ui/string_add_assign.rs @@ -0,0 +1,21 @@ +// run-rustfix + +#[allow(clippy::string_add, unused)] +#[warn(clippy::string_add_assign)] +fn main() { + // ignores assignment distinction + let mut x = "".to_owned(); + + for _ in 1..3 { + x = x + "."; + } + + let y = "".to_owned(); + let z = y + "..."; + + assert_eq!(&x, &z); + + let mut x = 1; + x = x + 1; + assert_eq!(2, x); +} diff --git a/src/tools/clippy/tests/ui/string_add_assign.stderr b/src/tools/clippy/tests/ui/string_add_assign.stderr new file mode 100644 index 0000000000..7676175c1b --- /dev/null +++ b/src/tools/clippy/tests/ui/string_add_assign.stderr @@ -0,0 +1,24 @@ +error: you assigned the result of adding something to this string. Consider using `String::push_str()` instead + --> $DIR/string_add_assign.rs:10:9 + | +LL | x = x + "."; + | ^^^^^^^^^^^ + | + = note: `-D clippy::string-add-assign` implied by `-D warnings` + +error: manual implementation of an assign operation + --> $DIR/string_add_assign.rs:10:9 + | +LL | x = x + "."; + | ^^^^^^^^^^^ help: replace it with: `x += "."` + | + = note: `-D clippy::assign-op-pattern` implied by `-D warnings` + +error: manual implementation of an assign operation + --> $DIR/string_add_assign.rs:19:5 + | +LL | x = x + 1; + | ^^^^^^^^^ help: replace it with: `x += 1` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/string_extend.fixed b/src/tools/clippy/tests/ui/string_extend.fixed new file mode 100644 index 0000000000..1883a9f832 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_extend.fixed @@ -0,0 +1,32 @@ +// run-rustfix + +#[derive(Copy, Clone)] +struct HasChars; + +impl HasChars { + fn chars(self) -> std::str::Chars<'static> { + "HasChars".chars() + } +} + +fn main() { + let abc = "abc"; + let def = String::from("def"); + let mut s = String::new(); + + s.push_str(abc); + s.push_str(abc); + + s.push_str("abc"); + s.push_str("abc"); + + s.push_str(&def); + s.push_str(&def); + + s.extend(abc.chars().skip(1)); + s.extend("abc".chars().skip(1)); + s.extend(['a', 'b', 'c'].iter()); + + let f = HasChars; + s.extend(f.chars()); +} diff --git a/src/tools/clippy/tests/ui/string_extend.rs b/src/tools/clippy/tests/ui/string_extend.rs new file mode 100644 index 0000000000..07d0baa1be --- /dev/null +++ b/src/tools/clippy/tests/ui/string_extend.rs @@ -0,0 +1,32 @@ +// run-rustfix + +#[derive(Copy, Clone)] +struct HasChars; + +impl HasChars { + fn chars(self) -> std::str::Chars<'static> { + "HasChars".chars() + } +} + +fn main() { + let abc = "abc"; + let def = String::from("def"); + let mut s = String::new(); + + s.push_str(abc); + s.extend(abc.chars()); + + s.push_str("abc"); + s.extend("abc".chars()); + + s.push_str(&def); + s.extend(def.chars()); + + s.extend(abc.chars().skip(1)); + s.extend("abc".chars().skip(1)); + s.extend(['a', 'b', 'c'].iter()); + + let f = HasChars; + s.extend(f.chars()); +} diff --git a/src/tools/clippy/tests/ui/string_extend.stderr b/src/tools/clippy/tests/ui/string_extend.stderr new file mode 100644 index 0000000000..6af8c9e166 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_extend.stderr @@ -0,0 +1,22 @@ +error: calling `.extend(_.chars())` + --> $DIR/string_extend.rs:18:5 + | +LL | s.extend(abc.chars()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.push_str(abc)` + | + = note: `-D clippy::string-extend-chars` implied by `-D warnings` + +error: calling `.extend(_.chars())` + --> $DIR/string_extend.rs:21:5 + | +LL | s.extend("abc".chars()); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.push_str("abc")` + +error: calling `.extend(_.chars())` + --> $DIR/string_extend.rs:24:5 + | +LL | s.extend(def.chars()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.push_str(&def)` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/string_from_utf8_as_bytes.fixed b/src/tools/clippy/tests/ui/string_from_utf8_as_bytes.fixed new file mode 100644 index 0000000000..6e665cdd56 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_from_utf8_as_bytes.fixed @@ -0,0 +1,6 @@ +// run-rustfix +#![warn(clippy::string_from_utf8_as_bytes)] + +fn main() { + let _ = Some(&"Hello World!"[6..11]); +} diff --git a/src/tools/clippy/tests/ui/string_from_utf8_as_bytes.rs b/src/tools/clippy/tests/ui/string_from_utf8_as_bytes.rs new file mode 100644 index 0000000000..670d206d36 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_from_utf8_as_bytes.rs @@ -0,0 +1,6 @@ +// run-rustfix +#![warn(clippy::string_from_utf8_as_bytes)] + +fn main() { + let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]); +} diff --git a/src/tools/clippy/tests/ui/string_from_utf8_as_bytes.stderr b/src/tools/clippy/tests/ui/string_from_utf8_as_bytes.stderr new file mode 100644 index 0000000000..bf5e5d33e8 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_from_utf8_as_bytes.stderr @@ -0,0 +1,10 @@ +error: calling a slice of `as_bytes()` with `from_utf8` should be not necessary + --> $DIR/string_from_utf8_as_bytes.rs:5:13 + | +LL | let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(&"Hello World!"[6..11])` + | + = note: `-D clippy::string-from-utf8-as-bytes` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/string_lit_as_bytes.fixed b/src/tools/clippy/tests/ui/string_lit_as_bytes.fixed new file mode 100644 index 0000000000..ccf8f61c4a --- /dev/null +++ b/src/tools/clippy/tests/ui/string_lit_as_bytes.fixed @@ -0,0 +1,24 @@ +// run-rustfix + +#![allow(dead_code, unused_variables)] +#![warn(clippy::string_lit_as_bytes)] + +fn str_lit_as_bytes() { + let bs = b"hello there"; + + let bs = br###"raw string with 3# plus " ""###; + + // no warning, because these cannot be written as byte string literals: + let ubs = "☃".as_bytes(); + let ubs = "hello there! this is a very long string".as_bytes(); + + let strify = stringify!(foobar).as_bytes(); + + let current_version = env!("CARGO_PKG_VERSION").as_bytes(); + + let includestr = include_bytes!("entry_unfixable.rs"); + + let _ = b"string with newline\t\n"; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/string_lit_as_bytes.rs b/src/tools/clippy/tests/ui/string_lit_as_bytes.rs new file mode 100644 index 0000000000..178df08e24 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_lit_as_bytes.rs @@ -0,0 +1,24 @@ +// run-rustfix + +#![allow(dead_code, unused_variables)] +#![warn(clippy::string_lit_as_bytes)] + +fn str_lit_as_bytes() { + let bs = "hello there".as_bytes(); + + let bs = r###"raw string with 3# plus " ""###.as_bytes(); + + // no warning, because these cannot be written as byte string literals: + let ubs = "☃".as_bytes(); + let ubs = "hello there! this is a very long string".as_bytes(); + + let strify = stringify!(foobar).as_bytes(); + + let current_version = env!("CARGO_PKG_VERSION").as_bytes(); + + let includestr = include_str!("entry_unfixable.rs").as_bytes(); + + let _ = "string with newline\t\n".as_bytes(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/string_lit_as_bytes.stderr b/src/tools/clippy/tests/ui/string_lit_as_bytes.stderr new file mode 100644 index 0000000000..99c512354d --- /dev/null +++ b/src/tools/clippy/tests/ui/string_lit_as_bytes.stderr @@ -0,0 +1,28 @@ +error: calling `as_bytes()` on a string literal + --> $DIR/string_lit_as_bytes.rs:7:14 + | +LL | let bs = "hello there".as_bytes(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using a byte string literal instead: `b"hello there"` + | + = note: `-D clippy::string-lit-as-bytes` implied by `-D warnings` + +error: calling `as_bytes()` on a string literal + --> $DIR/string_lit_as_bytes.rs:9:14 + | +LL | let bs = r###"raw string with 3# plus " ""###.as_bytes(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using a byte string literal instead: `br###"raw string with 3# plus " ""###` + +error: calling `as_bytes()` on `include_str!(..)` + --> $DIR/string_lit_as_bytes.rs:19:22 + | +LL | let includestr = include_str!("entry_unfixable.rs").as_bytes(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `include_bytes!(..)` instead: `include_bytes!("entry_unfixable.rs")` + +error: calling `as_bytes()` on a string literal + --> $DIR/string_lit_as_bytes.rs:21:13 + | +LL | let _ = "string with newline/t/n".as_bytes(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using a byte string literal instead: `b"string with newline/t/n"` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/string_to_string.rs b/src/tools/clippy/tests/ui/string_to_string.rs new file mode 100644 index 0000000000..4c66855f70 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_to_string.rs @@ -0,0 +1,7 @@ +#![warn(clippy::string_to_string)] +#![allow(clippy::redundant_clone)] + +fn main() { + let mut message = String::from("Hello"); + let mut v = message.to_string(); +} diff --git a/src/tools/clippy/tests/ui/string_to_string.stderr b/src/tools/clippy/tests/ui/string_to_string.stderr new file mode 100644 index 0000000000..1ebd17999b --- /dev/null +++ b/src/tools/clippy/tests/ui/string_to_string.stderr @@ -0,0 +1,11 @@ +error: `to_string()` called on a `String` + --> $DIR/string_to_string.rs:6:17 + | +LL | let mut v = message.to_string(); + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::string-to-string` implied by `-D warnings` + = help: consider using `.clone()` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/struct_excessive_bools.rs b/src/tools/clippy/tests/ui/struct_excessive_bools.rs new file mode 100644 index 0000000000..ce4fe830a0 --- /dev/null +++ b/src/tools/clippy/tests/ui/struct_excessive_bools.rs @@ -0,0 +1,44 @@ +#![warn(clippy::struct_excessive_bools)] + +macro_rules! foo { + () => { + struct MacroFoo { + a: bool, + b: bool, + c: bool, + d: bool, + } + }; +} + +foo!(); + +struct Foo { + a: bool, + b: bool, + c: bool, +} + +struct BadFoo { + a: bool, + b: bool, + c: bool, + d: bool, +} + +#[repr(C)] +struct Bar { + a: bool, + b: bool, + c: bool, + d: bool, +} + +fn main() { + struct FooFoo { + a: bool, + b: bool, + c: bool, + d: bool, + } +} diff --git a/src/tools/clippy/tests/ui/struct_excessive_bools.stderr b/src/tools/clippy/tests/ui/struct_excessive_bools.stderr new file mode 100644 index 0000000000..2941bf2983 --- /dev/null +++ b/src/tools/clippy/tests/ui/struct_excessive_bools.stderr @@ -0,0 +1,29 @@ +error: more than 3 bools in a struct + --> $DIR/struct_excessive_bools.rs:22:1 + | +LL | / struct BadFoo { +LL | | a: bool, +LL | | b: bool, +LL | | c: bool, +LL | | d: bool, +LL | | } + | |_^ + | + = note: `-D clippy::struct-excessive-bools` implied by `-D warnings` + = help: consider using a state machine or refactoring bools into two-variant enums + +error: more than 3 bools in a struct + --> $DIR/struct_excessive_bools.rs:38:5 + | +LL | / struct FooFoo { +LL | | a: bool, +LL | | b: bool, +LL | | c: bool, +LL | | d: bool, +LL | | } + | |_____^ + | + = help: consider using a state machine or refactoring bools into two-variant enums + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.rs b/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.rs new file mode 100644 index 0000000000..ae253a0487 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.rs @@ -0,0 +1,170 @@ +#![warn(clippy::suspicious_arithmetic_impl)] +use std::ops::{ + Add, AddAssign, BitAnd, BitOr, BitOrAssign, BitXor, Div, DivAssign, Mul, MulAssign, Rem, Shl, Shr, Sub, +}; + +#[derive(Copy, Clone)] +struct Foo(u32); + +impl Add for Foo { + type Output = Foo; + + fn add(self, other: Self) -> Self { + Foo(self.0 - other.0) + } +} + +impl AddAssign for Foo { + fn add_assign(&mut self, other: Foo) { + *self = *self - other; + } +} + +impl BitOrAssign for Foo { + fn bitor_assign(&mut self, other: Foo) { + let idx = other.0; + self.0 |= 1 << idx; // OK: BinOpKind::Shl part of AssignOp as child node + } +} + +impl MulAssign for Foo { + fn mul_assign(&mut self, other: Foo) { + self.0 /= other.0; + } +} + +impl DivAssign for Foo { + fn div_assign(&mut self, other: Foo) { + self.0 /= other.0; // OK: BinOpKind::Div == DivAssign + } +} + +impl Mul for Foo { + type Output = Foo; + + fn mul(self, other: Foo) -> Foo { + Foo(self.0 * other.0 % 42) // OK: BinOpKind::Rem part of BiExpr as parent node + } +} + +impl Sub for Foo { + type Output = Foo; + + fn sub(self, other: Self) -> Self { + Foo(self.0 * other.0 - 42) // OK: BinOpKind::Mul part of BiExpr as child node + } +} + +impl Div for Foo { + type Output = Foo; + + fn div(self, other: Self) -> Self { + Foo(do_nothing(self.0 + other.0) / 42) // OK: BinOpKind::Add part of BiExpr as child node + } +} + +impl Rem for Foo { + type Output = Foo; + + fn rem(self, other: Self) -> Self { + Foo(self.0 / other.0) + } +} + +impl BitAnd for Foo { + type Output = Foo; + + fn bitand(self, other: Self) -> Self { + Foo(self.0 | other.0) + } +} + +impl BitOr for Foo { + type Output = Foo; + + fn bitor(self, other: Self) -> Self { + Foo(self.0 ^ other.0) + } +} + +impl BitXor for Foo { + type Output = Foo; + + fn bitxor(self, other: Self) -> Self { + Foo(self.0 & other.0) + } +} + +impl Shl for Foo { + type Output = Foo; + + fn shl(self, other: Self) -> Self { + Foo(self.0 >> other.0) + } +} + +impl Shr for Foo { + type Output = Foo; + + fn shr(self, other: Self) -> Self { + Foo(self.0 << other.0) + } +} + +struct Bar(i32); + +impl Add for Bar { + type Output = Bar; + + fn add(self, other: Self) -> Self { + Bar(self.0 & !other.0) // OK: Not part of BiExpr as child node + } +} + +impl Sub for Bar { + type Output = Bar; + + fn sub(self, other: Self) -> Self { + if self.0 <= other.0 { + Bar(-(self.0 & other.0)) // OK: Neg part of BiExpr as parent node + } else { + Bar(0) + } + } +} + +fn main() {} + +fn do_nothing(x: u32) -> u32 { + x +} + +struct MultipleBinops(u32); + +impl Add for MultipleBinops { + type Output = MultipleBinops; + + // OK: multiple Binops in `add` impl + fn add(self, other: Self) -> Self::Output { + let mut result = self.0 + other.0; + if result >= u32::max_value() { + result -= u32::max_value(); + } + MultipleBinops(result) + } +} + +impl Mul for MultipleBinops { + type Output = MultipleBinops; + + // OK: multiple Binops in `mul` impl + fn mul(self, other: Self) -> Self::Output { + let mut result: u32 = 0; + let size = std::cmp::max(self.0, other.0) as usize; + let mut v = vec![0; size + 1]; + for i in 0..size + 1 { + result *= i as u32; + } + MultipleBinops(result) + } +} diff --git a/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.stderr b/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.stderr new file mode 100644 index 0000000000..388fc74008 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.stderr @@ -0,0 +1,60 @@ +error: suspicious use of binary operator in `Add` impl + --> $DIR/suspicious_arithmetic_impl.rs:13:20 + | +LL | Foo(self.0 - other.0) + | ^ + | + = note: `-D clippy::suspicious-arithmetic-impl` implied by `-D warnings` + +error: suspicious use of binary operator in `AddAssign` impl + --> $DIR/suspicious_arithmetic_impl.rs:19:23 + | +LL | *self = *self - other; + | ^ + | + = note: `#[deny(clippy::suspicious_op_assign_impl)]` on by default + +error: suspicious use of binary operator in `MulAssign` impl + --> $DIR/suspicious_arithmetic_impl.rs:32:16 + | +LL | self.0 /= other.0; + | ^^ + +error: suspicious use of binary operator in `Rem` impl + --> $DIR/suspicious_arithmetic_impl.rs:70:20 + | +LL | Foo(self.0 / other.0) + | ^ + +error: suspicious use of binary operator in `BitAnd` impl + --> $DIR/suspicious_arithmetic_impl.rs:78:20 + | +LL | Foo(self.0 | other.0) + | ^ + +error: suspicious use of binary operator in `BitOr` impl + --> $DIR/suspicious_arithmetic_impl.rs:86:20 + | +LL | Foo(self.0 ^ other.0) + | ^ + +error: suspicious use of binary operator in `BitXor` impl + --> $DIR/suspicious_arithmetic_impl.rs:94:20 + | +LL | Foo(self.0 & other.0) + | ^ + +error: suspicious use of binary operator in `Shl` impl + --> $DIR/suspicious_arithmetic_impl.rs:102:20 + | +LL | Foo(self.0 >> other.0) + | ^^ + +error: suspicious use of binary operator in `Shr` impl + --> $DIR/suspicious_arithmetic_impl.rs:110:20 + | +LL | Foo(self.0 << other.0) + | ^^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/suspicious_else_formatting.rs b/src/tools/clippy/tests/ui/suspicious_else_formatting.rs new file mode 100644 index 0000000000..226010ec6d --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_else_formatting.rs @@ -0,0 +1,79 @@ +#![warn(clippy::suspicious_else_formatting)] + +fn foo() -> bool { + true +} + +#[rustfmt::skip] +fn main() { + // weird `else` formatting: + if foo() { + } { + } + + if foo() { + } if foo() { + } + + let _ = { // if as the last expression + let _ = 0; + + if foo() { + } if foo() { + } + else { + } + }; + + let _ = { // if in the middle of a block + if foo() { + } if foo() { + } + else { + } + + let _ = 0; + }; + + if foo() { + } else + { + } + + if foo() { + } + else + { + } + + if foo() { + } else + if foo() { // the span of the above error should continue here + } + + if foo() { + } + else + if foo() { // the span of the above error should continue here + } + + // those are ok: + if foo() { + } + { + } + + if foo() { + } else { + } + + if foo() { + } + else { + } + + if foo() { + } + if foo() { + } +} diff --git a/src/tools/clippy/tests/ui/suspicious_else_formatting.stderr b/src/tools/clippy/tests/ui/suspicious_else_formatting.stderr new file mode 100644 index 0000000000..bbc036d376 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_else_formatting.stderr @@ -0,0 +1,77 @@ +error: this looks like an `else {..}` but the `else` is missing + --> $DIR/suspicious_else_formatting.rs:11:6 + | +LL | } { + | ^ + | + = note: `-D clippy::suspicious-else-formatting` implied by `-D warnings` + = note: to remove this lint, add the missing `else` or add a new line before the next block + +error: this looks like an `else if` but the `else` is missing + --> $DIR/suspicious_else_formatting.rs:15:6 + | +LL | } if foo() { + | ^ + | + = note: to remove this lint, add the missing `else` or add a new line before the second `if` + +error: this looks like an `else if` but the `else` is missing + --> $DIR/suspicious_else_formatting.rs:22:10 + | +LL | } if foo() { + | ^ + | + = note: to remove this lint, add the missing `else` or add a new line before the second `if` + +error: this looks like an `else if` but the `else` is missing + --> $DIR/suspicious_else_formatting.rs:30:10 + | +LL | } if foo() { + | ^ + | + = note: to remove this lint, add the missing `else` or add a new line before the second `if` + +error: this is an `else {..}` but the formatting might hide it + --> $DIR/suspicious_else_formatting.rs:39:6 + | +LL | } else + | ______^ +LL | | { + | |____^ + | + = note: to remove this lint, remove the `else` or remove the new line between `else` and `{..}` + +error: this is an `else {..}` but the formatting might hide it + --> $DIR/suspicious_else_formatting.rs:44:6 + | +LL | } + | ______^ +LL | | else +LL | | { + | |____^ + | + = note: to remove this lint, remove the `else` or remove the new line between `else` and `{..}` + +error: this is an `else if` but the formatting might hide it + --> $DIR/suspicious_else_formatting.rs:50:6 + | +LL | } else + | ______^ +LL | | if foo() { // the span of the above error should continue here + | |____^ + | + = note: to remove this lint, remove the `else` or remove the new line between `else` and `if` + +error: this is an `else if` but the formatting might hide it + --> $DIR/suspicious_else_formatting.rs:55:6 + | +LL | } + | ______^ +LL | | else +LL | | if foo() { // the span of the above error should continue here + | |____^ + | + = note: to remove this lint, remove the `else` or remove the new line between `else` and `if` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/suspicious_map.rs b/src/tools/clippy/tests/ui/suspicious_map.rs new file mode 100644 index 0000000000..d838d8fde2 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_map.rs @@ -0,0 +1,5 @@ +#![warn(clippy::suspicious_map)] + +fn main() { + let _ = (0..3).map(|x| x + 2).count(); +} diff --git a/src/tools/clippy/tests/ui/suspicious_map.stderr b/src/tools/clippy/tests/ui/suspicious_map.stderr new file mode 100644 index 0000000000..e1b4ba4037 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_map.stderr @@ -0,0 +1,11 @@ +error: this call to `map()` won't have an effect on the call to `count()` + --> $DIR/suspicious_map.rs:4:13 + | +LL | let _ = (0..3).map(|x| x + 2).count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::suspicious-map` implied by `-D warnings` + = help: make sure you did not confuse `map` with `filter` or `for_each` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/suspicious_operation_groupings.rs b/src/tools/clippy/tests/ui/suspicious_operation_groupings.rs new file mode 100644 index 0000000000..2f8c7cec50 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_operation_groupings.rs @@ -0,0 +1,207 @@ +#![warn(clippy::suspicious_operation_groupings)] + +struct Vec3 { + x: f64, + y: f64, + z: f64, +} + +impl Eq for Vec3 {} + +impl PartialEq for Vec3 { + fn eq(&self, other: &Self) -> bool { + // This should trigger the lint because `self.x` is compared to `other.y` + self.x == other.y && self.y == other.y && self.z == other.z + } +} + +struct S { + a: i32, + b: i32, + c: i32, + d: i32, +} + +fn buggy_ab_cmp(s1: &S, s2: &S) -> bool { + // There's no `s1.b` + s1.a < s2.a && s1.a < s2.b +} + +struct SaOnly { + a: i32, +} + +impl S { + fn a(&self) -> i32 { + 0 + } +} + +fn do_not_give_bad_suggestions_for_this_unusual_expr(s1: &S, s2: &SaOnly) -> bool { + // This is superficially similar to `buggy_ab_cmp`, but we should not suggest + // `s2.b` since that is invalid. + s1.a < s2.a && s1.a() < s1.b +} + +fn do_not_give_bad_suggestions_for_this_macro_expr(s1: &S, s2: &SaOnly) -> bool { + macro_rules! s1 { + () => { + S { + a: 1, + b: 1, + c: 1, + d: 1, + } + }; + } + + // This is superficially similar to `buggy_ab_cmp`, but we should not suggest + // `s2.b` since that is invalid. + s1.a < s2.a && s1!().a < s1.b +} + +fn do_not_give_bad_suggestions_for_this_incorrect_expr(s1: &S, s2: &SaOnly) -> bool { + // There's two `s1.b`, but we should not suggest `s2.b` since that is invalid + s1.a < s2.a && s1.b < s1.b +} + +fn permissable(s1: &S, s2: &S) -> bool { + // Something like this seems like it might actually be what is desired. + s1.a == s2.b +} + +fn non_boolean_operators(s1: &S, s2: &S) -> i32 { + // There's no `s2.c` + s1.a * s2.a + s1.b * s2.b + s1.c * s2.b + s1.d * s2.d +} + +fn odd_number_of_pairs(s1: &S, s2: &S) -> i32 { + // There's no `s2.b` + s1.a * s2.a + s1.b * s2.c + s1.c * s2.c +} + +fn not_caught_by_eq_op_middle_change_left(s1: &S, s2: &S) -> i32 { + // There's no `s1.b` + s1.a * s2.a + s2.b * s2.b + s1.c * s2.c +} + +fn not_caught_by_eq_op_middle_change_right(s1: &S, s2: &S) -> i32 { + // There's no `s2.b` + s1.a * s2.a + s1.b * s1.b + s1.c * s2.c +} + +fn not_caught_by_eq_op_start(s1: &S, s2: &S) -> i32 { + // There's no `s2.a` + s1.a * s1.a + s1.b * s2.b + s1.c * s2.c +} + +fn not_caught_by_eq_op_end(s1: &S, s2: &S) -> i32 { + // There's no `s2.c` + s1.a * s2.a + s1.b * s2.b + s1.c * s1.c +} + +fn the_cross_product_should_not_lint(s1: &S, s2: &S) -> (i32, i32, i32) { + ( + s1.b * s2.c - s1.c * s2.b, + s1.c * s2.a - s1.a * s2.c, + s1.a * s2.b - s1.b * s2.a, + ) +} + +fn outer_parens_simple(s1: &S, s2: &S) -> i32 { + // There's no `s2.b` + (s1.a * s2.a + s1.b * s1.b) +} + +fn outer_parens(s1: &S, s2: &S) -> i32 { + // There's no `s2.c` + (s1.a * s2.a + s1.b * s2.b + s1.c * s2.b + s1.d * s2.d) +} + +fn inner_parens(s1: &S, s2: &S) -> i32 { + // There's no `s2.c` + (s1.a * s2.a) + (s1.b * s2.b) + (s1.c * s2.b) + (s1.d * s2.d) +} + +fn outer_and_some_inner_parens(s1: &S, s2: &S) -> i32 { + // There's no `s2.c` + ((s1.a * s2.a) + (s1.b * s2.b) + (s1.c * s2.b) + (s1.d * s2.d)) +} + +fn all_parens_balanced_tree(s1: &S, s2: &S) -> i32 { + // There's no `s2.c` + (((s1.a * s2.a) + (s1.b * s2.b)) + ((s1.c * s2.b) + (s1.d * s2.d))) +} + +fn all_parens_left_tree(s1: &S, s2: &S) -> i32 { + // There's no `s2.c` + (((s1.a * s2.a) + (s1.b * s2.b) + (s1.c * s2.b)) + (s1.d * s2.d)) +} + +fn all_parens_right_tree(s1: &S, s2: &S) -> i32 { + // There's no `s2.c` + ((s1.a * s2.a) + ((s1.b * s2.b) + (s1.c * s2.b) + (s1.d * s2.d))) +} + +fn inside_other_binop_expression(s1: &S, s2: &S) -> i32 { + // There's no `s1.b` + (s1.a * s2.a + s2.b * s2.b) / 2 +} + +fn inside_function_call(s1: &S, s2: &S) -> i32 { + // There's no `s1.b` + i32::swap_bytes(s1.a * s2.a + s2.b * s2.b) +} + +fn inside_larger_boolean_expression(s1: &S, s2: &S) -> bool { + // There's no `s1.c` + s1.a > 0 && s1.b > 0 && s1.d == s2.c && s1.d == s2.d +} + +fn inside_larger_boolean_expression_with_unsorted_ops(s1: &S, s2: &S) -> bool { + // There's no `s1.c` + s1.a > 0 && s1.d == s2.c && s1.b > 0 && s1.d == s2.d +} + +struct Nested { + inner: ((i32,), (i32,), (i32,)), +} + +fn changed_middle_ident(n1: &Nested, n2: &Nested) -> bool { + // There's no `n2.inner.2.0` + (n1.inner.0).0 == (n2.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.1).0 +} + +// `eq_op` should catch this one. +fn changed_initial_ident(n1: &Nested, n2: &Nested) -> bool { + // There's no `n2.inner.0.0` + (n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0 +} + +fn inside_fn_with_similar_expression(s1: &S, s2: &S, strict: bool) -> bool { + if strict { + s1.a < s2.a && s1.b < s2.b + } else { + // There's no `s1.b` in this subexpression + s1.a <= s2.a && s1.a <= s2.b + } +} + +fn inside_an_if_statement(s1: &S, s2: &S) { + // There's no `s1.b` + if s1.a < s2.a && s1.a < s2.b { + s1.c = s2.c; + } +} + +fn maximum_unary_minus_right_tree(s1: &S, s2: &S) -> i32 { + // There's no `s2.c` + -(-(-s1.a * -s2.a) + (-(-s1.b * -s2.b) + -(-s1.c * -s2.b) + -(-s1.d * -s2.d))) +} + +fn unary_minus_and_an_if_expression(s1: &S, s2: &S) -> i32 { + // There's no `s1.b` + -(if -s1.a < -s2.a && -s1.a < -s2.b { s1.c } else { s2.a }) +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/suspicious_operation_groupings.stderr b/src/tools/clippy/tests/ui/suspicious_operation_groupings.stderr new file mode 100644 index 0000000000..96065699d3 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_operation_groupings.stderr @@ -0,0 +1,166 @@ +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:14:9 + | +LL | self.x == other.y && self.y == other.y && self.z == other.z + | ^^^^^^^^^^^^^^^^^ help: did you mean: `self.x == other.x` + | + = note: `-D clippy::suspicious-operation-groupings` implied by `-D warnings` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:14:9 + | +LL | self.x == other.y && self.y == other.y && self.z == other.z + | ^^^^^^^^^^^^^^^^^ help: did you mean: `self.x == other.x` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:27:20 + | +LL | s1.a < s2.a && s1.a < s2.b + | ^^^^^^^^^^^ help: did you mean: `s1.b < s2.b` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:75:33 + | +LL | s1.a * s2.a + s1.b * s2.b + s1.c * s2.b + s1.d * s2.d + | ^^^^^^^^^^^ help: did you mean: `s1.c * s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:80:19 + | +LL | s1.a * s2.a + s1.b * s2.c + s1.c * s2.c + | ^^^^^^^^^^^ help: did you mean: `s1.b * s2.b` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:80:19 + | +LL | s1.a * s2.a + s1.b * s2.c + s1.c * s2.c + | ^^^^^^^^^^^ help: did you mean: `s1.b * s2.b` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:85:19 + | +LL | s1.a * s2.a + s2.b * s2.b + s1.c * s2.c + | ^^^^^^^^^^^ help: did you mean: `s1.b * s2.b` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:90:19 + | +LL | s1.a * s2.a + s1.b * s1.b + s1.c * s2.c + | ^^^^^^^^^^^ help: did you mean: `s1.b * s2.b` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:95:5 + | +LL | s1.a * s1.a + s1.b * s2.b + s1.c * s2.c + | ^^^^^^^^^^^ help: did you mean: `s1.a * s2.a` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:100:33 + | +LL | s1.a * s2.a + s1.b * s2.b + s1.c * s1.c + | ^^^^^^^^^^^ help: did you mean: `s1.c * s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:113:20 + | +LL | (s1.a * s2.a + s1.b * s1.b) + | ^^^^^^^^^^^ help: did you mean: `s1.b * s2.b` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:118:34 + | +LL | (s1.a * s2.a + s1.b * s2.b + s1.c * s2.b + s1.d * s2.d) + | ^^^^^^^^^^^ help: did you mean: `s1.c * s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:123:38 + | +LL | (s1.a * s2.a) + (s1.b * s2.b) + (s1.c * s2.b) + (s1.d * s2.d) + | ^^^^^^^^^^^ help: did you mean: `s1.c * s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:128:39 + | +LL | ((s1.a * s2.a) + (s1.b * s2.b) + (s1.c * s2.b) + (s1.d * s2.d)) + | ^^^^^^^^^^^ help: did you mean: `s1.c * s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:133:42 + | +LL | (((s1.a * s2.a) + (s1.b * s2.b)) + ((s1.c * s2.b) + (s1.d * s2.d))) + | ^^^^^^^^^^^ help: did you mean: `s1.c * s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:133:42 + | +LL | (((s1.a * s2.a) + (s1.b * s2.b)) + ((s1.c * s2.b) + (s1.d * s2.d))) + | ^^^^^^^^^^^ help: did you mean: `s1.c * s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:138:40 + | +LL | (((s1.a * s2.a) + (s1.b * s2.b) + (s1.c * s2.b)) + (s1.d * s2.d)) + | ^^^^^^^^^^^ help: did you mean: `s1.c * s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:143:40 + | +LL | ((s1.a * s2.a) + ((s1.b * s2.b) + (s1.c * s2.b) + (s1.d * s2.d))) + | ^^^^^^^^^^^ help: did you mean: `s1.c * s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:148:20 + | +LL | (s1.a * s2.a + s2.b * s2.b) / 2 + | ^^^^^^^^^^^ help: did you mean: `s1.b * s2.b` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:153:35 + | +LL | i32::swap_bytes(s1.a * s2.a + s2.b * s2.b) + | ^^^^^^^^^^^ help: did you mean: `s1.b * s2.b` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:158:29 + | +LL | s1.a > 0 && s1.b > 0 && s1.d == s2.c && s1.d == s2.d + | ^^^^^^^^^^^^ help: did you mean: `s1.c == s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:163:17 + | +LL | s1.a > 0 && s1.d == s2.c && s1.b > 0 && s1.d == s2.d + | ^^^^^^^^^^^^ help: did you mean: `s1.c == s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:172:77 + | +LL | (n1.inner.0).0 == (n2.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.1).0 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `(n1.inner.2).0 == (n2.inner.2).0` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:186:25 + | +LL | s1.a <= s2.a && s1.a <= s2.b + | ^^^^^^^^^^^^ help: did you mean: `s1.b <= s2.b` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:192:23 + | +LL | if s1.a < s2.a && s1.a < s2.b { + | ^^^^^^^^^^^ help: did you mean: `s1.b < s2.b` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:199:48 + | +LL | -(-(-s1.a * -s2.a) + (-(-s1.b * -s2.b) + -(-s1.c * -s2.b) + -(-s1.d * -s2.d))) + | ^^^^^^^^^^^^^ help: did you mean: `-s1.c * -s2.c` + +error: this sequence of operators looks suspiciously like a bug + --> $DIR/suspicious_operation_groupings.rs:204:27 + | +LL | -(if -s1.a < -s2.a && -s1.a < -s2.b { s1.c } else { s2.a }) + | ^^^^^^^^^^^^^ help: did you mean: `-s1.b < -s2.b` + +error: aborting due to 27 previous errors + diff --git a/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.rs b/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.rs new file mode 100644 index 0000000000..9564e373c2 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.rs @@ -0,0 +1,23 @@ +#![warn(clippy::suspicious_unary_op_formatting)] + +#[rustfmt::skip] +fn main() { + // weird binary operator formatting: + let a = 42; + + if a >- 30 {} + if a >=- 30 {} + + let b = true; + let c = false; + + if b &&! c {} + + if a >- 30 {} + + // those are ok: + if a >-30 {} + if a < -30 {} + if b && !c {} + if a > - 30 {} +} diff --git a/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.stderr b/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.stderr new file mode 100644 index 0000000000..581527dcff --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.stderr @@ -0,0 +1,35 @@ +error: by not having a space between `>` and `-` it looks like `>-` is a single operator + --> $DIR/suspicious_unary_op_formatting.rs:8:9 + | +LL | if a >- 30 {} + | ^^^^ + | + = note: `-D clippy::suspicious-unary-op-formatting` implied by `-D warnings` + = help: put a space between `>` and `-` and remove the space after `-` + +error: by not having a space between `>=` and `-` it looks like `>=-` is a single operator + --> $DIR/suspicious_unary_op_formatting.rs:9:9 + | +LL | if a >=- 30 {} + | ^^^^^ + | + = help: put a space between `>=` and `-` and remove the space after `-` + +error: by not having a space between `&&` and `!` it looks like `&&!` is a single operator + --> $DIR/suspicious_unary_op_formatting.rs:14:9 + | +LL | if b &&! c {} + | ^^^^^ + | + = help: put a space between `&&` and `!` and remove the space after `!` + +error: by not having a space between `>` and `-` it looks like `>-` is a single operator + --> $DIR/suspicious_unary_op_formatting.rs:16:9 + | +LL | if a >- 30 {} + | ^^^^^^ + | + = help: put a space between `>` and `-` and remove the space after `-` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/swap.fixed b/src/tools/clippy/tests/ui/swap.fixed new file mode 100644 index 0000000000..0f8f839a0d --- /dev/null +++ b/src/tools/clippy/tests/ui/swap.fixed @@ -0,0 +1,83 @@ +// run-rustfix + +#![warn(clippy::all)] +#![allow( + clippy::blacklisted_name, + clippy::no_effect, + clippy::redundant_clone, + redundant_semicolons, + unused_assignments +)] + +struct Foo(u32); + +#[derive(Clone)] +struct Bar { + a: u32, + b: u32, +} + +fn field() { + let mut bar = Bar { a: 1, b: 2 }; + + let temp = bar.a; + bar.a = bar.b; + bar.b = temp; + + let mut baz = vec![bar.clone(), bar.clone()]; + let temp = baz[0].a; + baz[0].a = baz[1].a; + baz[1].a = temp; +} + +fn array() { + let mut foo = [1, 2]; + foo.swap(0, 1); + + foo.swap(0, 1); +} + +fn slice() { + let foo = &mut [1, 2]; + foo.swap(0, 1); + + foo.swap(0, 1); +} + +fn unswappable_slice() { + let foo = &mut [vec![1, 2], vec![3, 4]]; + let temp = foo[0][1]; + foo[0][1] = foo[1][0]; + foo[1][0] = temp; + + // swap(foo[0][1], foo[1][0]) would fail +} + +fn vec() { + let mut foo = vec![1, 2]; + foo.swap(0, 1); + + foo.swap(0, 1); +} + +#[rustfmt::skip] +fn main() { + field(); + array(); + slice(); + unswappable_slice(); + vec(); + + let mut a = 42; + let mut b = 1337; + + std::mem::swap(&mut a, &mut b); + + ; std::mem::swap(&mut a, &mut b); + + let mut c = Foo(42); + + std::mem::swap(&mut c.0, &mut a); + + ; std::mem::swap(&mut c.0, &mut a); +} diff --git a/src/tools/clippy/tests/ui/swap.rs b/src/tools/clippy/tests/ui/swap.rs new file mode 100644 index 0000000000..5763d9e82d --- /dev/null +++ b/src/tools/clippy/tests/ui/swap.rs @@ -0,0 +1,95 @@ +// run-rustfix + +#![warn(clippy::all)] +#![allow( + clippy::blacklisted_name, + clippy::no_effect, + clippy::redundant_clone, + redundant_semicolons, + unused_assignments +)] + +struct Foo(u32); + +#[derive(Clone)] +struct Bar { + a: u32, + b: u32, +} + +fn field() { + let mut bar = Bar { a: 1, b: 2 }; + + let temp = bar.a; + bar.a = bar.b; + bar.b = temp; + + let mut baz = vec![bar.clone(), bar.clone()]; + let temp = baz[0].a; + baz[0].a = baz[1].a; + baz[1].a = temp; +} + +fn array() { + let mut foo = [1, 2]; + let temp = foo[0]; + foo[0] = foo[1]; + foo[1] = temp; + + foo.swap(0, 1); +} + +fn slice() { + let foo = &mut [1, 2]; + let temp = foo[0]; + foo[0] = foo[1]; + foo[1] = temp; + + foo.swap(0, 1); +} + +fn unswappable_slice() { + let foo = &mut [vec![1, 2], vec![3, 4]]; + let temp = foo[0][1]; + foo[0][1] = foo[1][0]; + foo[1][0] = temp; + + // swap(foo[0][1], foo[1][0]) would fail +} + +fn vec() { + let mut foo = vec![1, 2]; + let temp = foo[0]; + foo[0] = foo[1]; + foo[1] = temp; + + foo.swap(0, 1); +} + +#[rustfmt::skip] +fn main() { + field(); + array(); + slice(); + unswappable_slice(); + vec(); + + let mut a = 42; + let mut b = 1337; + + a = b; + b = a; + + ; let t = a; + a = b; + b = t; + + let mut c = Foo(42); + + c.0 = a; + a = c.0; + + ; let t = c.0; + c.0 = a; + a = t; +} diff --git a/src/tools/clippy/tests/ui/swap.stderr b/src/tools/clippy/tests/ui/swap.stderr new file mode 100644 index 0000000000..f49bcfedf3 --- /dev/null +++ b/src/tools/clippy/tests/ui/swap.stderr @@ -0,0 +1,69 @@ +error: this looks like you are swapping elements of `foo` manually + --> $DIR/swap.rs:35:5 + | +LL | / let temp = foo[0]; +LL | | foo[0] = foo[1]; +LL | | foo[1] = temp; + | |_________________^ help: try: `foo.swap(0, 1)` + | + = note: `-D clippy::manual-swap` implied by `-D warnings` + +error: this looks like you are swapping elements of `foo` manually + --> $DIR/swap.rs:44:5 + | +LL | / let temp = foo[0]; +LL | | foo[0] = foo[1]; +LL | | foo[1] = temp; + | |_________________^ help: try: `foo.swap(0, 1)` + +error: this looks like you are swapping elements of `foo` manually + --> $DIR/swap.rs:62:5 + | +LL | / let temp = foo[0]; +LL | | foo[0] = foo[1]; +LL | | foo[1] = temp; + | |_________________^ help: try: `foo.swap(0, 1)` + +error: this looks like you are swapping `a` and `b` manually + --> $DIR/swap.rs:83:7 + | +LL | ; let t = a; + | _______^ +LL | | a = b; +LL | | b = t; + | |_________^ help: try: `std::mem::swap(&mut a, &mut b)` + | + = note: or maybe you should use `std::mem::replace`? + +error: this looks like you are swapping `c.0` and `a` manually + --> $DIR/swap.rs:92:7 + | +LL | ; let t = c.0; + | _______^ +LL | | c.0 = a; +LL | | a = t; + | |_________^ help: try: `std::mem::swap(&mut c.0, &mut a)` + | + = note: or maybe you should use `std::mem::replace`? + +error: this looks like you are trying to swap `a` and `b` + --> $DIR/swap.rs:80:5 + | +LL | / a = b; +LL | | b = a; + | |_________^ help: try: `std::mem::swap(&mut a, &mut b)` + | + = note: `-D clippy::almost-swapped` implied by `-D warnings` + = note: or maybe you should use `std::mem::replace`? + +error: this looks like you are trying to swap `c.0` and `a` + --> $DIR/swap.rs:89:5 + | +LL | / c.0 = a; +LL | | a = c.0; + | |___________^ help: try: `std::mem::swap(&mut c.0, &mut a)` + | + = note: or maybe you should use `std::mem::replace`? + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/tabs_in_doc_comments.fixed b/src/tools/clippy/tests/ui/tabs_in_doc_comments.fixed new file mode 100644 index 0000000000..4bc4bc86c7 --- /dev/null +++ b/src/tools/clippy/tests/ui/tabs_in_doc_comments.fixed @@ -0,0 +1,22 @@ +// run-rustfix + +#![warn(clippy::tabs_in_doc_comments)] +#[allow(dead_code)] + +/// +/// Struct to hold two strings: +/// - first one +/// - second one +pub struct DoubleString { + /// + /// - First String: + /// - needs to be inside here + first_string: String, + /// + /// - Second String: + /// - needs to be inside here + second_string: String, +} + +/// This is main +fn main() {} diff --git a/src/tools/clippy/tests/ui/tabs_in_doc_comments.rs b/src/tools/clippy/tests/ui/tabs_in_doc_comments.rs new file mode 100644 index 0000000000..9db3416e65 --- /dev/null +++ b/src/tools/clippy/tests/ui/tabs_in_doc_comments.rs @@ -0,0 +1,22 @@ +// run-rustfix + +#![warn(clippy::tabs_in_doc_comments)] +#[allow(dead_code)] + +/// +/// Struct to hold two strings: +/// - first one +/// - second one +pub struct DoubleString { + /// + /// - First String: + /// - needs to be inside here + first_string: String, + /// + /// - Second String: + /// - needs to be inside here + second_string: String, +} + +/// This is main +fn main() {} diff --git a/src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr b/src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr new file mode 100644 index 0000000000..355f2e8057 --- /dev/null +++ b/src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr @@ -0,0 +1,52 @@ +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:12:9 + | +LL | /// - First String: + | ^^^^ help: consider using four spaces per tab + | + = note: `-D clippy::tabs-in-doc-comments` implied by `-D warnings` + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:13:9 + | +LL | /// - needs to be inside here + | ^^^^^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:16:9 + | +LL | /// - Second String: + | ^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:17:9 + | +LL | /// - needs to be inside here + | ^^^^^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:8:5 + | +LL | /// - first one + | ^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:8:13 + | +LL | /// - first one + | ^^^^^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:9:5 + | +LL | /// - second one + | ^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:9:14 + | +LL | /// - second one + | ^^^^ help: consider using four spaces per tab + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/temporary_assignment.rs b/src/tools/clippy/tests/ui/temporary_assignment.rs new file mode 100644 index 0000000000..b4a931043b --- /dev/null +++ b/src/tools/clippy/tests/ui/temporary_assignment.rs @@ -0,0 +1,71 @@ +#![warn(clippy::temporary_assignment)] +#![allow(const_item_mutation)] + +use std::ops::{Deref, DerefMut}; + +struct TupleStruct(i32); + +struct Struct { + field: i32, +} + +struct MultiStruct { + structure: Struct, +} + +struct Wrapper<'a> { + inner: &'a mut Struct, +} + +impl<'a> Deref for Wrapper<'a> { + type Target = Struct; + fn deref(&self) -> &Struct { + self.inner + } +} + +impl<'a> DerefMut for Wrapper<'a> { + fn deref_mut(&mut self) -> &mut Struct { + self.inner + } +} + +struct ArrayStruct { + array: [i32; 1], +} + +const A: TupleStruct = TupleStruct(1); +const B: Struct = Struct { field: 1 }; +const C: MultiStruct = MultiStruct { + structure: Struct { field: 1 }, +}; +const D: ArrayStruct = ArrayStruct { array: [1] }; + +fn main() { + let mut s = Struct { field: 0 }; + let mut t = (0, 0); + + Struct { field: 0 }.field = 1; + MultiStruct { + structure: Struct { field: 0 }, + } + .structure + .field = 1; + ArrayStruct { array: [0] }.array[0] = 1; + (0, 0).0 = 1; + + // no error + s.field = 1; + t.0 = 1; + Wrapper { inner: &mut s }.field = 1; + let mut a_mut = TupleStruct(1); + a_mut.0 = 2; + let mut b_mut = Struct { field: 1 }; + b_mut.field = 2; + let mut c_mut = MultiStruct { + structure: Struct { field: 1 }, + }; + c_mut.structure.field = 2; + let mut d_mut = ArrayStruct { array: [1] }; + d_mut.array[0] = 2; +} diff --git a/src/tools/clippy/tests/ui/temporary_assignment.stderr b/src/tools/clippy/tests/ui/temporary_assignment.stderr new file mode 100644 index 0000000000..4cc32c79f0 --- /dev/null +++ b/src/tools/clippy/tests/ui/temporary_assignment.stderr @@ -0,0 +1,32 @@ +error: assignment to temporary + --> $DIR/temporary_assignment.rs:48:5 + | +LL | Struct { field: 0 }.field = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::temporary-assignment` implied by `-D warnings` + +error: assignment to temporary + --> $DIR/temporary_assignment.rs:49:5 + | +LL | / MultiStruct { +LL | | structure: Struct { field: 0 }, +LL | | } +LL | | .structure +LL | | .field = 1; + | |______________^ + +error: assignment to temporary + --> $DIR/temporary_assignment.rs:54:5 + | +LL | ArrayStruct { array: [0] }.array[0] = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: assignment to temporary + --> $DIR/temporary_assignment.rs:55:5 + | +LL | (0, 0).0 = 1; + | ^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/to_digit_is_some.fixed b/src/tools/clippy/tests/ui/to_digit_is_some.fixed new file mode 100644 index 0000000000..19184df0be --- /dev/null +++ b/src/tools/clippy/tests/ui/to_digit_is_some.fixed @@ -0,0 +1,11 @@ +//run-rustfix + +#![warn(clippy::to_digit_is_some)] + +fn main() { + let c = 'x'; + let d = &c; + + let _ = d.is_digit(10); + let _ = char::is_digit(c, 10); +} diff --git a/src/tools/clippy/tests/ui/to_digit_is_some.rs b/src/tools/clippy/tests/ui/to_digit_is_some.rs new file mode 100644 index 0000000000..45a6728ebf --- /dev/null +++ b/src/tools/clippy/tests/ui/to_digit_is_some.rs @@ -0,0 +1,11 @@ +//run-rustfix + +#![warn(clippy::to_digit_is_some)] + +fn main() { + let c = 'x'; + let d = &c; + + let _ = d.to_digit(10).is_some(); + let _ = char::to_digit(c, 10).is_some(); +} diff --git a/src/tools/clippy/tests/ui/to_digit_is_some.stderr b/src/tools/clippy/tests/ui/to_digit_is_some.stderr new file mode 100644 index 0000000000..177d3ccd3e --- /dev/null +++ b/src/tools/clippy/tests/ui/to_digit_is_some.stderr @@ -0,0 +1,16 @@ +error: use of `.to_digit(..).is_some()` + --> $DIR/to_digit_is_some.rs:9:13 + | +LL | let _ = d.to_digit(10).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `d.is_digit(10)` + | + = note: `-D clippy::to-digit-is-some` implied by `-D warnings` + +error: use of `.to_digit(..).is_some()` + --> $DIR/to_digit_is_some.rs:10:13 + | +LL | let _ = char::to_digit(c, 10).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `char::is_digit(c, 10)` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/to_string_in_display.rs b/src/tools/clippy/tests/ui/to_string_in_display.rs new file mode 100644 index 0000000000..eb8105c6b6 --- /dev/null +++ b/src/tools/clippy/tests/ui/to_string_in_display.rs @@ -0,0 +1,69 @@ +#![warn(clippy::to_string_in_display)] +#![allow(clippy::inherent_to_string_shadow_display)] + +use std::fmt; + +struct A; +impl A { + fn fmt(&self) { + self.to_string(); + } +} + +trait B { + fn fmt(&self) {} +} + +impl B for A { + fn fmt(&self) { + self.to_string(); + } +} + +impl fmt::Display for A { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} + +fn fmt(a: A) { + a.to_string(); +} + +struct C; + +impl C { + fn to_string(&self) -> String { + String::from("I am C") + } +} + +impl fmt::Display for C { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} + +enum D { + E(String), + F, +} + +impl std::fmt::Display for D { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + Self::E(string) => write!(f, "E {}", string.to_string()), + Self::F => write!(f, "F"), + } + } +} + +fn main() { + let a = A; + a.to_string(); + a.fmt(); + fmt(a); + + let c = C; + c.to_string(); +} diff --git a/src/tools/clippy/tests/ui/to_string_in_display.stderr b/src/tools/clippy/tests/ui/to_string_in_display.stderr new file mode 100644 index 0000000000..5f26ef413e --- /dev/null +++ b/src/tools/clippy/tests/ui/to_string_in_display.stderr @@ -0,0 +1,10 @@ +error: using `to_string` in `fmt::Display` implementation might lead to infinite recursion + --> $DIR/to_string_in_display.rs:25:25 + | +LL | write!(f, "{}", self.to_string()) + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::to-string-in-display` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg.fixed b/src/tools/clippy/tests/ui/toplevel_ref_arg.fixed new file mode 100644 index 0000000000..b129d95c56 --- /dev/null +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg.fixed @@ -0,0 +1,50 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::toplevel_ref_arg)] + +#[macro_use] +extern crate macro_rules; + +macro_rules! gen_binding { + () => { + let _y = &42; + }; +} + +fn main() { + // Closures should not warn + let y = |ref x| println!("{:?}", x); + y(1u8); + + let _x = &1; + + let _y: &(&_, u8) = &(&1, 2); + + let _z = &(1 + 2); + + let _z = &mut (1 + 2); + + let (ref x, _) = (1, 2); // ok, not top level + println!("The answer is {}.", x); + + let _x = &vec![1, 2, 3]; + + // Make sure that allowing the lint works + #[allow(clippy::toplevel_ref_arg)] + let ref mut _x = 1_234_543; + + // ok + for ref _x in 0..10 {} + + // lint in macro + #[allow(unused)] + { + gen_binding!(); + } + + // do not lint in external macro + { + ref_arg_binding!(); + } +} diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg.rs b/src/tools/clippy/tests/ui/toplevel_ref_arg.rs new file mode 100644 index 0000000000..73eb4ff730 --- /dev/null +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg.rs @@ -0,0 +1,50 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::toplevel_ref_arg)] + +#[macro_use] +extern crate macro_rules; + +macro_rules! gen_binding { + () => { + let ref _y = 42; + }; +} + +fn main() { + // Closures should not warn + let y = |ref x| println!("{:?}", x); + y(1u8); + + let ref _x = 1; + + let ref _y: (&_, u8) = (&1, 2); + + let ref _z = 1 + 2; + + let ref mut _z = 1 + 2; + + let (ref x, _) = (1, 2); // ok, not top level + println!("The answer is {}.", x); + + let ref _x = vec![1, 2, 3]; + + // Make sure that allowing the lint works + #[allow(clippy::toplevel_ref_arg)] + let ref mut _x = 1_234_543; + + // ok + for ref _x in 0..10 {} + + // lint in macro + #[allow(unused)] + { + gen_binding!(); + } + + // do not lint in external macro + { + ref_arg_binding!(); + } +} diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg.stderr b/src/tools/clippy/tests/ui/toplevel_ref_arg.stderr new file mode 100644 index 0000000000..15cb933fed --- /dev/null +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg.stderr @@ -0,0 +1,45 @@ +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:20:9 + | +LL | let ref _x = 1; + | ----^^^^^^----- help: try: `let _x = &1;` + | + = note: `-D clippy::toplevel-ref-arg` implied by `-D warnings` + +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:22:9 + | +LL | let ref _y: (&_, u8) = (&1, 2); + | ----^^^^^^--------------------- help: try: `let _y: &(&_, u8) = &(&1, 2);` + +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:24:9 + | +LL | let ref _z = 1 + 2; + | ----^^^^^^--------- help: try: `let _z = &(1 + 2);` + +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:26:9 + | +LL | let ref mut _z = 1 + 2; + | ----^^^^^^^^^^--------- help: try: `let _z = &mut (1 + 2);` + +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:31:9 + | +LL | let ref _x = vec![1, 2, 3]; + | ----^^^^^^----------------- help: try: `let _x = &vec![1, 2, 3];` + +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:11:13 + | +LL | let ref _y = 42; + | ----^^^^^^------ help: try: `let _y = &42;` +... +LL | gen_binding!(); + | --------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.rs b/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.rs new file mode 100644 index 0000000000..1a493fbce0 --- /dev/null +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.rs @@ -0,0 +1,33 @@ +// aux-build:macro_rules.rs + +#![warn(clippy::toplevel_ref_arg)] +#![allow(unused)] + +#[macro_use] +extern crate macro_rules; + +fn the_answer(ref mut x: u8) { + *x = 42; +} + +macro_rules! gen_function { + () => { + fn fun_example(ref _x: usize) {} + }; +} + +fn main() { + let mut x = 0; + the_answer(x); + + // lint in macro + #[allow(unused)] + { + gen_function!(); + } + + // do not lint in external macro + { + ref_arg_function!(); + } +} diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.stderr b/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.stderr new file mode 100644 index 0000000000..b8cfd98739 --- /dev/null +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.stderr @@ -0,0 +1,21 @@ +error: `ref` directly on a function argument is ignored. Consider using a reference type instead + --> $DIR/toplevel_ref_arg_non_rustfix.rs:9:15 + | +LL | fn the_answer(ref mut x: u8) { + | ^^^^^^^^^ + | + = note: `-D clippy::toplevel-ref-arg` implied by `-D warnings` + +error: `ref` directly on a function argument is ignored. Consider using a reference type instead + --> $DIR/toplevel_ref_arg_non_rustfix.rs:15:24 + | +LL | fn fun_example(ref _x: usize) {} + | ^^^^^^ +... +LL | gen_function!(); + | ---------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/trailing_zeros.rs b/src/tools/clippy/tests/ui/trailing_zeros.rs new file mode 100644 index 0000000000..fbdc977b76 --- /dev/null +++ b/src/tools/clippy/tests/ui/trailing_zeros.rs @@ -0,0 +1,10 @@ +#![allow(unused_parens)] +#![warn(clippy::verbose_bit_mask)] + +fn main() { + let x: i32 = 42; + let _ = (x & 0b1111 == 0); // suggest trailing_zeros + let _ = x & 0b1_1111 == 0; // suggest trailing_zeros + let _ = x & 0b1_1010 == 0; // do not lint + let _ = x & 1 == 0; // do not lint +} diff --git a/src/tools/clippy/tests/ui/trailing_zeros.stderr b/src/tools/clippy/tests/ui/trailing_zeros.stderr new file mode 100644 index 0000000000..7985511183 --- /dev/null +++ b/src/tools/clippy/tests/ui/trailing_zeros.stderr @@ -0,0 +1,16 @@ +error: bit mask could be simplified with a call to `trailing_zeros` + --> $DIR/trailing_zeros.rs:6:13 + | +LL | let _ = (x & 0b1111 == 0); // suggest trailing_zeros + | ^^^^^^^^^^^^^^^^^ help: try: `x.trailing_zeros() >= 4` + | + = note: `-D clippy::verbose-bit-mask` implied by `-D warnings` + +error: bit mask could be simplified with a call to `trailing_zeros` + --> $DIR/trailing_zeros.rs:7:13 + | +LL | let _ = x & 0b1_1111 == 0; // suggest trailing_zeros + | ^^^^^^^^^^^^^^^^^ help: try: `x.trailing_zeros() >= 5` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/trait_duplication_in_bounds.rs b/src/tools/clippy/tests/ui/trait_duplication_in_bounds.rs new file mode 100644 index 0000000000..cb2b0054e3 --- /dev/null +++ b/src/tools/clippy/tests/ui/trait_duplication_in_bounds.rs @@ -0,0 +1,31 @@ +#![deny(clippy::trait_duplication_in_bounds)] + +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; + +fn bad_foo(arg0: T, arg1: Z) +where + T: Clone, + T: Default, +{ + unimplemented!(); +} + +fn good_bar(arg: T) { + unimplemented!(); +} + +fn good_foo(arg: T) +where + T: Clone + Default, +{ + unimplemented!(); +} + +fn good_foobar(arg: T) +where + T: Clone, +{ + unimplemented!(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/trait_duplication_in_bounds.stderr b/src/tools/clippy/tests/ui/trait_duplication_in_bounds.stderr new file mode 100644 index 0000000000..027e1c7520 --- /dev/null +++ b/src/tools/clippy/tests/ui/trait_duplication_in_bounds.stderr @@ -0,0 +1,23 @@ +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds.rs:5:15 + | +LL | fn bad_foo(arg0: T, arg1: Z) + | ^^^^^ + | +note: the lint level is defined here + --> $DIR/trait_duplication_in_bounds.rs:1:9 + | +LL | #![deny(clippy::trait_duplication_in_bounds)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider removing this trait bound + +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds.rs:5:23 + | +LL | fn bad_foo(arg0: T, arg1: Z) + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute.rs b/src/tools/clippy/tests/ui/transmute.rs new file mode 100644 index 0000000000..9f1948359e --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute.rs @@ -0,0 +1,112 @@ +#![feature(const_fn_transmute)] +#![allow(dead_code)] + +extern crate core; + +use std::mem::transmute as my_transmute; +use std::vec::Vec as MyVec; + +fn my_int() -> Usize { + Usize(42) +} + +fn my_vec() -> MyVec { + vec![] +} + +#[allow(clippy::needless_lifetimes, clippy::transmute_ptr_to_ptr)] +#[warn(clippy::useless_transmute)] +unsafe fn _generic<'a, T, U: 'a>(t: &'a T) { + let _: &'a T = core::intrinsics::transmute(t); + + let _: &'a U = core::intrinsics::transmute(t); + + let _: *const T = core::intrinsics::transmute(t); + + let _: *mut T = core::intrinsics::transmute(t); + + let _: *const U = core::intrinsics::transmute(t); +} + +#[warn(clippy::useless_transmute)] +fn useless() { + unsafe { + let _: Vec = core::intrinsics::transmute(my_vec()); + + let _: Vec = core::mem::transmute(my_vec()); + + let _: Vec = std::intrinsics::transmute(my_vec()); + + let _: Vec = std::mem::transmute(my_vec()); + + let _: Vec = my_transmute(my_vec()); + + let _: *const usize = std::mem::transmute(5_isize); + + let _ = 5_isize as *const usize; + + let _: *const usize = std::mem::transmute(1 + 1usize); + + let _ = (1 + 1_usize) as *const usize; + } +} + +struct Usize(usize); + +#[warn(clippy::crosspointer_transmute)] +fn crosspointer() { + let mut int: Usize = Usize(0); + let int_const_ptr: *const Usize = &int as *const Usize; + let int_mut_ptr: *mut Usize = &mut int as *mut Usize; + + unsafe { + let _: Usize = core::intrinsics::transmute(int_const_ptr); + + let _: Usize = core::intrinsics::transmute(int_mut_ptr); + + let _: *const Usize = core::intrinsics::transmute(my_int()); + + let _: *mut Usize = core::intrinsics::transmute(my_int()); + } +} + +#[warn(clippy::transmute_int_to_char)] +fn int_to_char() { + let _: char = unsafe { std::mem::transmute(0_u32) }; + let _: char = unsafe { std::mem::transmute(0_i32) }; +} + +#[warn(clippy::transmute_int_to_bool)] +fn int_to_bool() { + let _: bool = unsafe { std::mem::transmute(0_u8) }; +} + +#[warn(clippy::transmute_int_to_float)] +mod int_to_float { + fn test() { + let _: f32 = unsafe { std::mem::transmute(0_u32) }; + let _: f32 = unsafe { std::mem::transmute(0_i32) }; + let _: f64 = unsafe { std::mem::transmute(0_u64) }; + let _: f64 = unsafe { std::mem::transmute(0_i64) }; + } + + mod issue_5747 { + const VALUE32: f32 = unsafe { std::mem::transmute(0_u32) }; + const VALUE64: f64 = unsafe { std::mem::transmute(0_i64) }; + + const fn from_bits_32(v: i32) -> f32 { + unsafe { std::mem::transmute(v) } + } + + const fn from_bits_64(v: u64) -> f64 { + unsafe { std::mem::transmute(v) } + } + } +} + +fn bytes_to_str(b: &[u8], mb: &mut [u8]) { + let _: &str = unsafe { std::mem::transmute(b) }; + let _: &mut str = unsafe { std::mem::transmute(mb) }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/transmute.stderr b/src/tools/clippy/tests/ui/transmute.stderr new file mode 100644 index 0000000000..ad9953d12b --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute.stderr @@ -0,0 +1,158 @@ +error: transmute from a type (`&T`) to itself + --> $DIR/transmute.rs:20:20 + | +LL | let _: &'a T = core::intrinsics::transmute(t); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::useless-transmute` implied by `-D warnings` + +error: transmute from a reference to a pointer + --> $DIR/transmute.rs:24:23 + | +LL | let _: *const T = core::intrinsics::transmute(t); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T` + +error: transmute from a reference to a pointer + --> $DIR/transmute.rs:26:21 + | +LL | let _: *mut T = core::intrinsics::transmute(t); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *mut T` + +error: transmute from a reference to a pointer + --> $DIR/transmute.rs:28:23 + | +LL | let _: *const U = core::intrinsics::transmute(t); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *const U` + +error: transmute from a type (`std::vec::Vec`) to itself + --> $DIR/transmute.rs:34:27 + | +LL | let _: Vec = core::intrinsics::transmute(my_vec()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`std::vec::Vec`) to itself + --> $DIR/transmute.rs:36:27 + | +LL | let _: Vec = core::mem::transmute(my_vec()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`std::vec::Vec`) to itself + --> $DIR/transmute.rs:38:27 + | +LL | let _: Vec = std::intrinsics::transmute(my_vec()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`std::vec::Vec`) to itself + --> $DIR/transmute.rs:40:27 + | +LL | let _: Vec = std::mem::transmute(my_vec()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`std::vec::Vec`) to itself + --> $DIR/transmute.rs:42:27 + | +LL | let _: Vec = my_transmute(my_vec()); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from an integer to a pointer + --> $DIR/transmute.rs:44:31 + | +LL | let _: *const usize = std::mem::transmute(5_isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `5_isize as *const usize` + +error: transmute from an integer to a pointer + --> $DIR/transmute.rs:48:31 + | +LL | let _: *const usize = std::mem::transmute(1 + 1usize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(1 + 1usize) as *const usize` + +error: transmute from a type (`*const Usize`) to the type that it points to (`Usize`) + --> $DIR/transmute.rs:63:24 + | +LL | let _: Usize = core::intrinsics::transmute(int_const_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::crosspointer-transmute` implied by `-D warnings` + +error: transmute from a type (`*mut Usize`) to the type that it points to (`Usize`) + --> $DIR/transmute.rs:65:24 + | +LL | let _: Usize = core::intrinsics::transmute(int_mut_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`Usize`) to a pointer to that type (`*const Usize`) + --> $DIR/transmute.rs:67:31 + | +LL | let _: *const Usize = core::intrinsics::transmute(my_int()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`Usize`) to a pointer to that type (`*mut Usize`) + --> $DIR/transmute.rs:69:29 + | +LL | let _: *mut Usize = core::intrinsics::transmute(my_int()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a `u32` to a `char` + --> $DIR/transmute.rs:75:28 + | +LL | let _: char = unsafe { std::mem::transmute(0_u32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::char::from_u32(0_u32).unwrap()` + | + = note: `-D clippy::transmute-int-to-char` implied by `-D warnings` + +error: transmute from a `i32` to a `char` + --> $DIR/transmute.rs:76:28 + | +LL | let _: char = unsafe { std::mem::transmute(0_i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::char::from_u32(0_i32 as u32).unwrap()` + +error: transmute from a `u8` to a `bool` + --> $DIR/transmute.rs:81:28 + | +LL | let _: bool = unsafe { std::mem::transmute(0_u8) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `0_u8 != 0` + | + = note: `-D clippy::transmute-int-to-bool` implied by `-D warnings` + +error: transmute from a `u32` to a `f32` + --> $DIR/transmute.rs:87:31 + | +LL | let _: f32 = unsafe { std::mem::transmute(0_u32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f32::from_bits(0_u32)` + | + = note: `-D clippy::transmute-int-to-float` implied by `-D warnings` + +error: transmute from a `i32` to a `f32` + --> $DIR/transmute.rs:88:31 + | +LL | let _: f32 = unsafe { std::mem::transmute(0_i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f32::from_bits(0_i32 as u32)` + +error: transmute from a `u64` to a `f64` + --> $DIR/transmute.rs:89:31 + | +LL | let _: f64 = unsafe { std::mem::transmute(0_u64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f64::from_bits(0_u64)` + +error: transmute from a `i64` to a `f64` + --> $DIR/transmute.rs:90:31 + | +LL | let _: f64 = unsafe { std::mem::transmute(0_i64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f64::from_bits(0_i64 as u64)` + +error: transmute from a `&[u8]` to a `&str` + --> $DIR/transmute.rs:108:28 + | +LL | let _: &str = unsafe { std::mem::transmute(b) }; + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8(b).unwrap()` + | + = note: `-D clippy::transmute-bytes-to-str` implied by `-D warnings` + +error: transmute from a `&mut [u8]` to a `&mut str` + --> $DIR/transmute.rs:109:32 + | +LL | let _: &mut str = unsafe { std::mem::transmute(mb) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_mut(mb).unwrap()` + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_32bit.rs b/src/tools/clippy/tests/ui/transmute_32bit.rs new file mode 100644 index 0000000000..ffe22b12f5 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_32bit.rs @@ -0,0 +1,14 @@ +// ignore-64bit + +#[warn(clippy::wrong_transmute)] +fn main() { + unsafe { + let _: *const usize = std::mem::transmute(6.0f32); + + let _: *mut usize = std::mem::transmute(6.0f32); + + let _: *const usize = std::mem::transmute('x'); + + let _: *mut usize = std::mem::transmute('x'); + } +} diff --git a/src/tools/clippy/tests/ui/transmute_32bit.stderr b/src/tools/clippy/tests/ui/transmute_32bit.stderr new file mode 100644 index 0000000000..040519564b --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_32bit.stderr @@ -0,0 +1,28 @@ +error: transmute from a `f32` to a pointer + --> $DIR/transmute_32bit.rs:6:31 + | +LL | let _: *const usize = std::mem::transmute(6.0f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::wrong-transmute` implied by `-D warnings` + +error: transmute from a `f32` to a pointer + --> $DIR/transmute_32bit.rs:8:29 + | +LL | let _: *mut usize = std::mem::transmute(6.0f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a `char` to a pointer + --> $DIR/transmute_32bit.rs:10:31 + | +LL | let _: *const usize = std::mem::transmute('x'); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a `char` to a pointer + --> $DIR/transmute_32bit.rs:12:29 + | +LL | let _: *mut usize = std::mem::transmute('x'); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_64bit.rs b/src/tools/clippy/tests/ui/transmute_64bit.rs new file mode 100644 index 0000000000..00dc0b2c36 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_64bit.rs @@ -0,0 +1,10 @@ +// ignore-32bit + +#[warn(clippy::wrong_transmute)] +fn main() { + unsafe { + let _: *const usize = std::mem::transmute(6.0f64); + + let _: *mut usize = std::mem::transmute(6.0f64); + } +} diff --git a/src/tools/clippy/tests/ui/transmute_64bit.stderr b/src/tools/clippy/tests/ui/transmute_64bit.stderr new file mode 100644 index 0000000000..d1854c009e --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_64bit.stderr @@ -0,0 +1,16 @@ +error: transmute from a `f64` to a pointer + --> $DIR/transmute_64bit.rs:6:31 + | +LL | let _: *const usize = std::mem::transmute(6.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::wrong-transmute` implied by `-D warnings` + +error: transmute from a `f64` to a pointer + --> $DIR/transmute_64bit.rs:8:29 + | +LL | let _: *mut usize = std::mem::transmute(6.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_collection.rs b/src/tools/clippy/tests/ui/transmute_collection.rs new file mode 100644 index 0000000000..cd5a712779 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_collection.rs @@ -0,0 +1,47 @@ +#![warn(clippy::unsound_collection_transmute)] + +use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque}; +use std::mem::transmute; + +fn main() { + unsafe { + // wrong size + let _ = transmute::<_, Vec>(vec![0u8]); + // wrong layout + let _ = transmute::<_, Vec<[u8; 4]>>(vec![1234u32]); + + // wrong size + let _ = transmute::<_, VecDeque>(VecDeque::::new()); + // wrong layout + let _ = transmute::<_, VecDeque>(VecDeque::<[u8; 4]>::new()); + + // wrong size + let _ = transmute::<_, BinaryHeap>(BinaryHeap::::new()); + // wrong layout + let _ = transmute::<_, BinaryHeap>(BinaryHeap::<[u8; 4]>::new()); + + // wrong size + let _ = transmute::<_, BTreeSet>(BTreeSet::::new()); + // wrong layout + let _ = transmute::<_, BTreeSet>(BTreeSet::<[u8; 4]>::new()); + + // wrong size + let _ = transmute::<_, HashSet>(HashSet::::new()); + // wrong layout + let _ = transmute::<_, HashSet>(HashSet::<[u8; 4]>::new()); + + // wrong size + let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + // wrong layout + let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + let _ = transmute::<_, BTreeMap>(BTreeMap::<[u8; 4], u32>::new()); + + // wrong size + let _ = transmute::<_, HashMap>(HashMap::::new()); + let _ = transmute::<_, HashMap>(HashMap::::new()); + // wrong layout + let _ = transmute::<_, HashMap>(HashMap::::new()); + let _ = transmute::<_, HashMap>(HashMap::<[u8; 4], u32>::new()); + } +} diff --git a/src/tools/clippy/tests/ui/transmute_collection.stderr b/src/tools/clippy/tests/ui/transmute_collection.stderr new file mode 100644 index 0000000000..ebc05c402a --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_collection.stderr @@ -0,0 +1,112 @@ +error: transmute from `std::vec::Vec` to `std::vec::Vec` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:9:17 + | +LL | let _ = transmute::<_, Vec>(vec![0u8]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unsound-collection-transmute` implied by `-D warnings` + +error: transmute from `std::vec::Vec` to `std::vec::Vec<[u8; 4]>` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:11:17 + | +LL | let _ = transmute::<_, Vec<[u8; 4]>>(vec![1234u32]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::VecDeque` to `std::collections::VecDeque` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:14:17 + | +LL | let _ = transmute::<_, VecDeque>(VecDeque::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::VecDeque<[u8; 4]>` to `std::collections::VecDeque` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:16:17 + | +LL | let _ = transmute::<_, VecDeque>(VecDeque::<[u8; 4]>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BinaryHeap` to `std::collections::BinaryHeap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:19:17 + | +LL | let _ = transmute::<_, BinaryHeap>(BinaryHeap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BinaryHeap<[u8; 4]>` to `std::collections::BinaryHeap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:21:17 + | +LL | let _ = transmute::<_, BinaryHeap>(BinaryHeap::<[u8; 4]>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeSet` to `std::collections::BTreeSet` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:24:17 + | +LL | let _ = transmute::<_, BTreeSet>(BTreeSet::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeSet<[u8; 4]>` to `std::collections::BTreeSet` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:26:17 + | +LL | let _ = transmute::<_, BTreeSet>(BTreeSet::<[u8; 4]>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashSet` to `std::collections::HashSet` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:29:17 + | +LL | let _ = transmute::<_, HashSet>(HashSet::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashSet<[u8; 4]>` to `std::collections::HashSet` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:31:17 + | +LL | let _ = transmute::<_, HashSet>(HashSet::<[u8; 4]>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeMap` to `std::collections::BTreeMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:34:17 + | +LL | let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeMap` to `std::collections::BTreeMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:35:17 + | +LL | let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeMap` to `std::collections::BTreeMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:37:17 + | +LL | let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeMap<[u8; 4], u32>` to `std::collections::BTreeMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:38:17 + | +LL | let _ = transmute::<_, BTreeMap>(BTreeMap::<[u8; 4], u32>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashMap` to `std::collections::HashMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:41:17 + | +LL | let _ = transmute::<_, HashMap>(HashMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashMap` to `std::collections::HashMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:42:17 + | +LL | let _ = transmute::<_, HashMap>(HashMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashMap` to `std::collections::HashMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:44:17 + | +LL | let _ = transmute::<_, HashMap>(HashMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashMap<[u8; 4], u32>` to `std::collections::HashMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:45:17 + | +LL | let _ = transmute::<_, HashMap>(HashMap::<[u8; 4], u32>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_float_to_int.rs b/src/tools/clippy/tests/ui/transmute_float_to_int.rs new file mode 100644 index 0000000000..1040fee4b3 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_float_to_int.rs @@ -0,0 +1,26 @@ +#![feature(const_fn_transmute)] +#![warn(clippy::transmute_float_to_int)] + +fn float_to_int() { + let _: u32 = unsafe { std::mem::transmute(1f32) }; + let _: i32 = unsafe { std::mem::transmute(1f32) }; + let _: u64 = unsafe { std::mem::transmute(1f64) }; + let _: i64 = unsafe { std::mem::transmute(1f64) }; + let _: u64 = unsafe { std::mem::transmute(1.0) }; + let _: u64 = unsafe { std::mem::transmute(-1.0) }; +} + +mod issue_5747 { + const VALUE32: i32 = unsafe { std::mem::transmute(1f32) }; + const VALUE64: u64 = unsafe { std::mem::transmute(1f64) }; + + const fn to_bits_32(v: f32) -> u32 { + unsafe { std::mem::transmute(v) } + } + + const fn to_bits_64(v: f64) -> i64 { + unsafe { std::mem::transmute(v) } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/transmute_float_to_int.stderr b/src/tools/clippy/tests/ui/transmute_float_to_int.stderr new file mode 100644 index 0000000000..5a40cf381d --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_float_to_int.stderr @@ -0,0 +1,40 @@ +error: transmute from a `f32` to a `u32` + --> $DIR/transmute_float_to_int.rs:5:27 + | +LL | let _: u32 = unsafe { std::mem::transmute(1f32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f32.to_bits()` + | + = note: `-D clippy::transmute-float-to-int` implied by `-D warnings` + +error: transmute from a `f32` to a `i32` + --> $DIR/transmute_float_to_int.rs:6:27 + | +LL | let _: i32 = unsafe { std::mem::transmute(1f32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f32.to_bits() as i32` + +error: transmute from a `f64` to a `u64` + --> $DIR/transmute_float_to_int.rs:7:27 + | +LL | let _: u64 = unsafe { std::mem::transmute(1f64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f64.to_bits()` + +error: transmute from a `f64` to a `i64` + --> $DIR/transmute_float_to_int.rs:8:27 + | +LL | let _: i64 = unsafe { std::mem::transmute(1f64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f64.to_bits() as i64` + +error: transmute from a `f64` to a `u64` + --> $DIR/transmute_float_to_int.rs:9:27 + | +LL | let _: u64 = unsafe { std::mem::transmute(1.0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1.0f64.to_bits()` + +error: transmute from a `f64` to a `u64` + --> $DIR/transmute_float_to_int.rs:10:27 + | +LL | let _: u64 = unsafe { std::mem::transmute(-1.0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-1.0f64).to_bits()` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.rs b/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.rs new file mode 100644 index 0000000000..9e213aab68 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.rs @@ -0,0 +1,62 @@ +#![warn(clippy::transmute_ptr_to_ptr)] + +// Make sure we can modify lifetimes, which is one of the recommended uses +// of transmute + +// Make sure we can do static lifetime transmutes +unsafe fn transmute_lifetime_to_static<'a, T>(t: &'a T) -> &'static T { + std::mem::transmute::<&'a T, &'static T>(t) +} + +// Make sure we can do non-static lifetime transmutes +unsafe fn transmute_lifetime<'a, 'b, T>(t: &'a T, u: &'b T) -> &'b T { + std::mem::transmute::<&'a T, &'b T>(t) +} + +struct LifetimeParam<'a> { + s: &'a str, +} + +struct GenericParam { + t: T, +} + +fn transmute_ptr_to_ptr() { + let ptr = &1u32 as *const u32; + let mut_ptr = &mut 1u32 as *mut u32; + unsafe { + // pointer-to-pointer transmutes; bad + let _: *const f32 = std::mem::transmute(ptr); + let _: *mut f32 = std::mem::transmute(mut_ptr); + // ref-ref transmutes; bad + let _: &f32 = std::mem::transmute(&1u32); + let _: &f64 = std::mem::transmute(&1f32); + // ^ this test is here because both f32 and f64 are the same TypeVariant, but they are not + // the same type + let _: &mut f32 = std::mem::transmute(&mut 1u32); + let _: &GenericParam = std::mem::transmute(&GenericParam { t: 1u32 }); + } + + // these are recommendations for solving the above; if these lint we need to update + // those suggestions + let _ = ptr as *const f32; + let _ = mut_ptr as *mut f32; + let _ = unsafe { &*(&1u32 as *const u32 as *const f32) }; + let _ = unsafe { &mut *(&mut 1u32 as *mut u32 as *mut f32) }; + + // transmute internal lifetimes, should not lint + let s = "hello world".to_owned(); + let lp = LifetimeParam { s: &s }; + let _: &LifetimeParam<'static> = unsafe { std::mem::transmute(&lp) }; + let _: &GenericParam<&LifetimeParam<'static>> = unsafe { std::mem::transmute(&GenericParam { t: &lp }) }; +} + +// dereferencing raw pointers in const contexts, should not lint as it's unstable (issue 5959) +const _: &() = { + struct Zst; + let zst = &Zst; + + unsafe { std::mem::transmute::<&'static Zst, &'static ()>(zst) } +}; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.stderr b/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.stderr new file mode 100644 index 0000000000..4d1b8fcc19 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.stderr @@ -0,0 +1,40 @@ +error: transmute from a pointer to a pointer + --> $DIR/transmute_ptr_to_ptr.rs:29:29 + | +LL | let _: *const f32 = std::mem::transmute(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr as *const f32` + | + = note: `-D clippy::transmute-ptr-to-ptr` implied by `-D warnings` + +error: transmute from a pointer to a pointer + --> $DIR/transmute_ptr_to_ptr.rs:30:27 + | +LL | let _: *mut f32 = std::mem::transmute(mut_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `mut_ptr as *mut f32` + +error: transmute from a reference to a reference + --> $DIR/transmute_ptr_to_ptr.rs:32:23 + | +LL | let _: &f32 = std::mem::transmute(&1u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(&1u32 as *const u32 as *const f32)` + +error: transmute from a reference to a reference + --> $DIR/transmute_ptr_to_ptr.rs:33:23 + | +LL | let _: &f64 = std::mem::transmute(&1f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(&1f32 as *const f32 as *const f64)` + +error: transmute from a reference to a reference + --> $DIR/transmute_ptr_to_ptr.rs:36:27 + | +LL | let _: &mut f32 = std::mem::transmute(&mut 1u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(&mut 1u32 as *mut u32 as *mut f32)` + +error: transmute from a reference to a reference + --> $DIR/transmute_ptr_to_ptr.rs:37:37 + | +LL | let _: &GenericParam = std::mem::transmute(&GenericParam { t: 1u32 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(&GenericParam { t: 1u32 } as *const GenericParam as *const GenericParam)` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ref.rs b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.rs new file mode 100644 index 0000000000..ba35c6adc4 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.rs @@ -0,0 +1,41 @@ +#![warn(clippy::transmute_ptr_to_ref)] + +unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { + let _: &T = std::mem::transmute(p); + let _: &T = &*p; + + let _: &mut T = std::mem::transmute(m); + let _: &mut T = &mut *m; + + let _: &T = std::mem::transmute(m); + let _: &T = &*m; + + let _: &mut T = std::mem::transmute(p as *mut T); + let _ = &mut *(p as *mut T); + + let _: &T = std::mem::transmute(o); + let _: &T = &*(o as *const T); + + let _: &mut T = std::mem::transmute(om); + let _: &mut T = &mut *(om as *mut T); + + let _: &T = std::mem::transmute(om); + let _: &T = &*(om as *const T); +} + +fn issue1231() { + struct Foo<'a, T> { + bar: &'a T, + } + + let raw = 42 as *const i32; + let _: &Foo = unsafe { std::mem::transmute::<_, &Foo<_>>(raw) }; + + let _: &Foo<&u8> = unsafe { std::mem::transmute::<_, &Foo<&_>>(raw) }; + + type Bar<'a> = &'a u8; + let raw = 42 as *const i32; + unsafe { std::mem::transmute::<_, Bar>(raw) }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ref.stderr b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.stderr new file mode 100644 index 0000000000..df0598a58c --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.stderr @@ -0,0 +1,64 @@ +error: transmute from a pointer type (`*const T`) to a reference type (`&T`) + --> $DIR/transmute_ptr_to_ref.rs:4:17 + | +LL | let _: &T = std::mem::transmute(p); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*p` + | + = note: `-D clippy::transmute-ptr-to-ref` implied by `-D warnings` + +error: transmute from a pointer type (`*mut T`) to a reference type (`&mut T`) + --> $DIR/transmute_ptr_to_ref.rs:7:21 + | +LL | let _: &mut T = std::mem::transmute(m); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *m` + +error: transmute from a pointer type (`*mut T`) to a reference type (`&T`) + --> $DIR/transmute_ptr_to_ref.rs:10:17 + | +LL | let _: &T = std::mem::transmute(m); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*m` + +error: transmute from a pointer type (`*mut T`) to a reference type (`&mut T`) + --> $DIR/transmute_ptr_to_ref.rs:13:21 + | +LL | let _: &mut T = std::mem::transmute(p as *mut T); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(p as *mut T)` + +error: transmute from a pointer type (`*const U`) to a reference type (`&T`) + --> $DIR/transmute_ptr_to_ref.rs:16:17 + | +LL | let _: &T = std::mem::transmute(o); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(o as *const T)` + +error: transmute from a pointer type (`*mut U`) to a reference type (`&mut T`) + --> $DIR/transmute_ptr_to_ref.rs:19:21 + | +LL | let _: &mut T = std::mem::transmute(om); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(om as *mut T)` + +error: transmute from a pointer type (`*mut U`) to a reference type (`&T`) + --> $DIR/transmute_ptr_to_ref.rs:22:17 + | +LL | let _: &T = std::mem::transmute(om); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(om as *const T)` + +error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo`) + --> $DIR/transmute_ptr_to_ref.rs:32:32 + | +LL | let _: &Foo = unsafe { std::mem::transmute::<_, &Foo<_>>(raw) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const Foo<_>)` + +error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo<&u8>`) + --> $DIR/transmute_ptr_to_ref.rs:34:33 + | +LL | let _: &Foo<&u8> = unsafe { std::mem::transmute::<_, &Foo<&_>>(raw) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const Foo<&_>)` + +error: transmute from a pointer type (`*const i32`) to a reference type (`&u8`) + --> $DIR/transmute_ptr_to_ref.rs:38:14 + | +LL | unsafe { std::mem::transmute::<_, Bar>(raw) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const u8)` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed new file mode 100644 index 0000000000..b6f1e83181 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed @@ -0,0 +1,78 @@ +// run-rustfix +#![warn(clippy::transmutes_expressible_as_ptr_casts)] +// These two warnings currrently cover the cases transmutes_expressible_as_ptr_casts +// would otherwise be responsible for +#![warn(clippy::useless_transmute)] +#![warn(clippy::transmute_ptr_to_ptr)] +#![allow(unused_unsafe)] +#![allow(dead_code)] + +use std::mem::{size_of, transmute}; + +// rustc_typeck::check::cast contains documentation about when a cast `e as U` is +// valid, which we quote from below. +fn main() { + // We should see an error message for each transmute, and no error messages for + // the casts, since the casts are the recommended fixes. + + // e is an integer and U is *U_0, while U_0: Sized; addr-ptr-cast + let _ptr_i32_transmute = unsafe { usize::MAX as *const i32 }; + let ptr_i32 = usize::MAX as *const i32; + + // e has type *T, U is *U_0, and either U_0: Sized ... + let _ptr_i8_transmute = unsafe { ptr_i32 as *const i8 }; + let _ptr_i8 = ptr_i32 as *const i8; + + let slice_ptr = &[0, 1, 2, 3] as *const [i32]; + + // ... or pointer_kind(T) = pointer_kind(U_0); ptr-ptr-cast + let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u16] }; + let _ptr_to_unsized = slice_ptr as *const [u16]; + // TODO: We could try testing vtable casts here too, but maybe + // we should wait until std::raw::TraitObject is stabilized? + + // e has type *T and U is a numeric type, while T: Sized; ptr-addr-cast + let _usize_from_int_ptr_transmute = unsafe { ptr_i32 as usize }; + let _usize_from_int_ptr = ptr_i32 as usize; + + let array_ref: &[i32; 4] = &[1, 2, 3, 4]; + + // e has type &[T; n] and U is *const T; array-ptr-cast + let _array_ptr_transmute = unsafe { array_ref as *const [i32; 4] }; + let _array_ptr = array_ref as *const [i32; 4]; + + fn foo(_: usize) -> u8 { + 42 + } + + // e is a function pointer type and U has type *T, while T: Sized; fptr-ptr-cast + let _usize_ptr_transmute = unsafe { foo as *const usize }; + let _usize_ptr_transmute = foo as *const usize; + + // e is a function pointer type and U is an integer; fptr-addr-cast + let _usize_from_fn_ptr_transmute = unsafe { foo as usize }; + let _usize_from_fn_ptr = foo as *const usize; +} + +// If a ref-to-ptr cast of this form where the pointer type points to a type other +// than the referenced type, calling `CastCheck::do_check` has been observed to +// cause an ICE error message. `do_check` is currently called inside the +// `transmutes_expressible_as_ptr_casts` check, but other, more specific lints +// currently prevent it from being called in these cases. This test is meant to +// fail if the ordering of the checks ever changes enough to cause these cases to +// fall through into `do_check`. +fn trigger_do_check_to_emit_error(in_param: &[i32; 1]) -> *const u8 { + unsafe { in_param as *const [i32; 1] as *const u8 } +} + +#[repr(C)] +struct Single(u64); + +#[repr(C)] +struct Pair(u32, u32); + +fn cannot_be_expressed_as_pointer_cast(in_param: Single) -> Pair { + assert_eq!(size_of::(), size_of::()); + + unsafe { transmute::(in_param) } +} diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs new file mode 100644 index 0000000000..0205d1ece6 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs @@ -0,0 +1,78 @@ +// run-rustfix +#![warn(clippy::transmutes_expressible_as_ptr_casts)] +// These two warnings currrently cover the cases transmutes_expressible_as_ptr_casts +// would otherwise be responsible for +#![warn(clippy::useless_transmute)] +#![warn(clippy::transmute_ptr_to_ptr)] +#![allow(unused_unsafe)] +#![allow(dead_code)] + +use std::mem::{size_of, transmute}; + +// rustc_typeck::check::cast contains documentation about when a cast `e as U` is +// valid, which we quote from below. +fn main() { + // We should see an error message for each transmute, and no error messages for + // the casts, since the casts are the recommended fixes. + + // e is an integer and U is *U_0, while U_0: Sized; addr-ptr-cast + let _ptr_i32_transmute = unsafe { transmute::(usize::MAX) }; + let ptr_i32 = usize::MAX as *const i32; + + // e has type *T, U is *U_0, and either U_0: Sized ... + let _ptr_i8_transmute = unsafe { transmute::<*const i32, *const i8>(ptr_i32) }; + let _ptr_i8 = ptr_i32 as *const i8; + + let slice_ptr = &[0, 1, 2, 3] as *const [i32]; + + // ... or pointer_kind(T) = pointer_kind(U_0); ptr-ptr-cast + let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u16]>(slice_ptr) }; + let _ptr_to_unsized = slice_ptr as *const [u16]; + // TODO: We could try testing vtable casts here too, but maybe + // we should wait until std::raw::TraitObject is stabilized? + + // e has type *T and U is a numeric type, while T: Sized; ptr-addr-cast + let _usize_from_int_ptr_transmute = unsafe { transmute::<*const i32, usize>(ptr_i32) }; + let _usize_from_int_ptr = ptr_i32 as usize; + + let array_ref: &[i32; 4] = &[1, 2, 3, 4]; + + // e has type &[T; n] and U is *const T; array-ptr-cast + let _array_ptr_transmute = unsafe { transmute::<&[i32; 4], *const [i32; 4]>(array_ref) }; + let _array_ptr = array_ref as *const [i32; 4]; + + fn foo(_: usize) -> u8 { + 42 + } + + // e is a function pointer type and U has type *T, while T: Sized; fptr-ptr-cast + let _usize_ptr_transmute = unsafe { transmute:: u8, *const usize>(foo) }; + let _usize_ptr_transmute = foo as *const usize; + + // e is a function pointer type and U is an integer; fptr-addr-cast + let _usize_from_fn_ptr_transmute = unsafe { transmute:: u8, usize>(foo) }; + let _usize_from_fn_ptr = foo as *const usize; +} + +// If a ref-to-ptr cast of this form where the pointer type points to a type other +// than the referenced type, calling `CastCheck::do_check` has been observed to +// cause an ICE error message. `do_check` is currently called inside the +// `transmutes_expressible_as_ptr_casts` check, but other, more specific lints +// currently prevent it from being called in these cases. This test is meant to +// fail if the ordering of the checks ever changes enough to cause these cases to +// fall through into `do_check`. +fn trigger_do_check_to_emit_error(in_param: &[i32; 1]) -> *const u8 { + unsafe { transmute::<&[i32; 1], *const u8>(in_param) } +} + +#[repr(C)] +struct Single(u64); + +#[repr(C)] +struct Pair(u32, u32); + +fn cannot_be_expressed_as_pointer_cast(in_param: Single) -> Pair { + assert_eq!(size_of::(), size_of::()); + + unsafe { transmute::(in_param) } +} diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr new file mode 100644 index 0000000000..1157b17931 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr @@ -0,0 +1,56 @@ +error: transmute from an integer to a pointer + --> $DIR/transmutes_expressible_as_ptr_casts.rs:19:39 + | +LL | let _ptr_i32_transmute = unsafe { transmute::(usize::MAX) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `usize::MAX as *const i32` + | + = note: `-D clippy::useless-transmute` implied by `-D warnings` + +error: transmute from a pointer to a pointer + --> $DIR/transmutes_expressible_as_ptr_casts.rs:23:38 + | +LL | let _ptr_i8_transmute = unsafe { transmute::<*const i32, *const i8>(ptr_i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr_i32 as *const i8` + | + = note: `-D clippy::transmute-ptr-to-ptr` implied by `-D warnings` + +error: transmute from a pointer to a pointer + --> $DIR/transmutes_expressible_as_ptr_casts.rs:29:46 + | +LL | let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u16]>(slice_ptr) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `slice_ptr as *const [u16]` + +error: transmute from `*const i32` to `usize` which could be expressed as a pointer cast instead + --> $DIR/transmutes_expressible_as_ptr_casts.rs:35:50 + | +LL | let _usize_from_int_ptr_transmute = unsafe { transmute::<*const i32, usize>(ptr_i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr_i32 as usize` + | + = note: `-D clippy::transmutes-expressible-as-ptr-casts` implied by `-D warnings` + +error: transmute from a reference to a pointer + --> $DIR/transmutes_expressible_as_ptr_casts.rs:41:41 + | +LL | let _array_ptr_transmute = unsafe { transmute::<&[i32; 4], *const [i32; 4]>(array_ref) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `array_ref as *const [i32; 4]` + +error: transmute from `fn(usize) -> u8 {main::foo}` to `*const usize` which could be expressed as a pointer cast instead + --> $DIR/transmutes_expressible_as_ptr_casts.rs:49:41 + | +LL | let _usize_ptr_transmute = unsafe { transmute:: u8, *const usize>(foo) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as *const usize` + +error: transmute from `fn(usize) -> u8 {main::foo}` to `usize` which could be expressed as a pointer cast instead + --> $DIR/transmutes_expressible_as_ptr_casts.rs:53:49 + | +LL | let _usize_from_fn_ptr_transmute = unsafe { transmute:: u8, usize>(foo) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as usize` + +error: transmute from a reference to a pointer + --> $DIR/transmutes_expressible_as_ptr_casts.rs:65:14 + | +LL | unsafe { transmute::<&[i32; 1], *const u8>(in_param) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `in_param as *const [i32; 1] as *const u8` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/transmuting_null.rs b/src/tools/clippy/tests/ui/transmuting_null.rs new file mode 100644 index 0000000000..ea3ee8edc8 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmuting_null.rs @@ -0,0 +1,30 @@ +#![allow(dead_code)] +#![warn(clippy::transmuting_null)] +#![allow(clippy::zero_ptr)] +#![allow(clippy::transmute_ptr_to_ref)] +#![allow(clippy::eq_op)] + +// Easy to lint because these only span one line. +fn one_liners() { + unsafe { + let _: &u64 = std::mem::transmute(0 as *const u64); + let _: &u64 = std::mem::transmute(std::ptr::null::()); + } +} + +pub const ZPTR: *const usize = 0 as *const _; +pub const NOT_ZPTR: *const usize = 1 as *const _; + +fn transmute_const() { + unsafe { + // Should raise a lint. + let _: &u64 = std::mem::transmute(ZPTR); + // Should NOT raise a lint. + let _: &u64 = std::mem::transmute(NOT_ZPTR); + } +} + +fn main() { + one_liners(); + transmute_const(); +} diff --git a/src/tools/clippy/tests/ui/transmuting_null.stderr b/src/tools/clippy/tests/ui/transmuting_null.stderr new file mode 100644 index 0000000000..1848fc2490 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmuting_null.stderr @@ -0,0 +1,22 @@ +error: transmuting a known null pointer into a reference + --> $DIR/transmuting_null.rs:10:23 + | +LL | let _: &u64 = std::mem::transmute(0 as *const u64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::transmuting-null` implied by `-D warnings` + +error: transmuting a known null pointer into a reference + --> $DIR/transmuting_null.rs:11:23 + | +LL | let _: &u64 = std::mem::transmute(std::ptr::null::()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmuting a known null pointer into a reference + --> $DIR/transmuting_null.rs:21:23 + | +LL | let _: &u64 = std::mem::transmute(ZPTR); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.rs b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.rs new file mode 100644 index 0000000000..e7e0a31feb --- /dev/null +++ b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.rs @@ -0,0 +1,132 @@ +// normalize-stderr-test "\(\d+ byte\)" -> "(N byte)" +// normalize-stderr-test "\(limit: \d+ byte\)" -> "(limit: N byte)" + +#![deny(clippy::trivially_copy_pass_by_ref)] +#![allow( + clippy::many_single_char_names, + clippy::blacklisted_name, + clippy::redundant_field_names +)] + +#[derive(Copy, Clone)] +struct Foo(u32); + +#[derive(Copy, Clone)] +struct Bar([u8; 24]); + +#[derive(Copy, Clone)] +pub struct Color { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +struct FooRef<'a> { + foo: &'a Foo, +} + +type Baz = u32; + +fn good(a: &mut u32, b: u32, c: &Bar) {} + +fn good_return_implicit_lt_ref(foo: &Foo) -> &u32 { + &foo.0 +} + +#[allow(clippy::needless_lifetimes)] +fn good_return_explicit_lt_ref<'a>(foo: &'a Foo) -> &'a u32 { + &foo.0 +} + +fn good_return_implicit_lt_struct(foo: &Foo) -> FooRef { + FooRef { foo } +} + +#[allow(clippy::needless_lifetimes)] +fn good_return_explicit_lt_struct<'a>(foo: &'a Foo) -> FooRef<'a> { + FooRef { foo } +} + +fn bad(x: &u32, y: &Foo, z: &Baz) {} + +impl Foo { + fn good(self, a: &mut u32, b: u32, c: &Bar) {} + + fn good2(&mut self) {} + + fn bad(&self, x: &u32, y: &Foo, z: &Baz) {} + + fn bad2(x: &u32, y: &Foo, z: &Baz) {} +} + +impl AsRef for Foo { + fn as_ref(&self) -> &u32 { + &self.0 + } +} + +impl Bar { + fn good(&self, a: &mut u32, b: u32, c: &Bar) {} + + fn bad2(x: &u32, y: &Foo, z: &Baz) {} +} + +trait MyTrait { + fn trait_method(&self, _foo: &Foo); +} + +pub trait MyTrait2 { + fn trait_method2(&self, _color: &Color); +} + +impl MyTrait for Foo { + fn trait_method(&self, _foo: &Foo) { + unimplemented!() + } +} + +#[allow(unused_variables)] +mod issue3992 { + pub trait A { + #[allow(clippy::trivially_copy_pass_by_ref)] + fn a(b: &u16) {} + } + + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn c(d: &u16) {} +} + +mod issue5876 { + // Don't lint here as it is always inlined + #[inline(always)] + fn foo_always(x: &i32) { + println!("{}", x); + } + + #[inline(never)] + fn foo_never(x: &i32) { + println!("{}", x); + } + + #[inline] + fn foo(x: &i32) { + println!("{}", x); + } +} + +fn main() { + let (mut foo, bar) = (Foo(0), Bar([0; 24])); + let (mut a, b, c, x, y, z) = (0, 0, Bar([0; 24]), 0, Foo(0), 0); + good(&mut a, b, &c); + good_return_implicit_lt_ref(&y); + good_return_explicit_lt_ref(&y); + bad(&x, &y, &z); + foo.good(&mut a, b, &c); + foo.good2(); + foo.bad(&x, &y, &z); + Foo::bad2(&x, &y, &z); + bar.good(&mut a, b, &c); + Bar::bad2(&x, &y, &z); + foo.as_ref(); +} diff --git a/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.stderr b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.stderr new file mode 100644 index 0000000000..ccc3cdb2b7 --- /dev/null +++ b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.stderr @@ -0,0 +1,110 @@ +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:51:11 + | +LL | fn bad(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `u32` + | +note: the lint level is defined here + --> $DIR/trivially_copy_pass_by_ref.rs:4:9 + | +LL | #![deny(clippy::trivially_copy_pass_by_ref)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:51:20 + | +LL | fn bad(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Foo` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:51:29 + | +LL | fn bad(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Baz` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:58:12 + | +LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {} + | ^^^^^ help: consider passing by value instead: `self` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:58:22 + | +LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `u32` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:58:31 + | +LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Foo` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:58:40 + | +LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Baz` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:60:16 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `u32` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:60:25 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Foo` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:60:34 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Baz` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:72:16 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `u32` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:72:25 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Foo` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:72:34 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Baz` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:76:34 + | +LL | fn trait_method(&self, _foo: &Foo); + | ^^^^ help: consider passing by value instead: `Foo` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:80:37 + | +LL | fn trait_method2(&self, _color: &Color); + | ^^^^^^ help: consider passing by value instead: `Color` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:108:21 + | +LL | fn foo_never(x: &i32) { + | ^^^^ help: consider passing by value instead: `i32` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:113:15 + | +LL | fn foo(x: &i32) { + | ^^^^ help: consider passing by value instead: `i32` + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/try_err.fixed b/src/tools/clippy/tests/ui/try_err.fixed new file mode 100644 index 0000000000..5b96bb59c5 --- /dev/null +++ b/src/tools/clippy/tests/ui/try_err.fixed @@ -0,0 +1,162 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![deny(clippy::try_err)] +#![allow(clippy::unnecessary_wraps, clippy::needless_question_mark)] + +#[macro_use] +extern crate macro_rules; + +use std::io; +use std::task::Poll; + +// Tests that a simple case works +// Should flag `Err(err)?` +pub fn basic_test() -> Result { + let err: i32 = 1; + // To avoid warnings during rustfix + if true { + return Err(err); + } + Ok(0) +} + +// Tests that `.into()` is added when appropriate +pub fn into_test() -> Result { + let err: u8 = 1; + // To avoid warnings during rustfix + if true { + return Err(err.into()); + } + Ok(0) +} + +// Tests that tries in general don't trigger the error +pub fn negative_test() -> Result { + Ok(nested_error()? + 1) +} + +// Tests that `.into()` isn't added when the error type +// matches the surrounding closure's return type, even +// when it doesn't match the surrounding function's. +pub fn closure_matches_test() -> Result { + let res: Result = Some(1) + .into_iter() + .map(|i| { + let err: i8 = 1; + // To avoid warnings during rustfix + if true { + return Err(err); + } + Ok(i) + }) + .next() + .unwrap(); + + Ok(res?) +} + +// Tests that `.into()` isn't added when the error type +// doesn't match the surrounding closure's return type. +pub fn closure_into_test() -> Result { + let res: Result = Some(1) + .into_iter() + .map(|i| { + let err: i8 = 1; + // To avoid warnings during rustfix + if true { + return Err(err.into()); + } + Ok(i) + }) + .next() + .unwrap(); + + Ok(res?) +} + +fn nested_error() -> Result { + Ok(1) +} + +// Bad suggestion when in macro (see #6242) +macro_rules! try_validation { + ($e: expr) => {{ + match $e { + Ok(_) => 0, + Err(_) => return Err(1), + } + }}; +} + +macro_rules! ret_one { + () => { + 1 + }; +} + +macro_rules! try_validation_in_macro { + ($e: expr) => {{ + match $e { + Ok(_) => 0, + Err(_) => return Err(ret_one!()), + } + }}; +} + +fn calling_macro() -> Result { + // macro + try_validation!(Ok::<_, i32>(5)); + // `Err` arg is another macro + try_validation_in_macro!(Ok::<_, i32>(5)); + Ok(5) +} + +fn main() { + basic_test().unwrap(); + into_test().unwrap(); + negative_test().unwrap(); + closure_matches_test().unwrap(); + closure_into_test().unwrap(); + calling_macro().unwrap(); + + // We don't want to lint in external macros + try_err!(); +} + +macro_rules! bar { + () => { + String::from("aasdfasdfasdfa") + }; +} + +macro_rules! foo { + () => { + bar!() + }; +} + +pub fn macro_inside(fail: bool) -> Result { + if fail { + return Err(foo!()); + } + Ok(0) +} + +pub fn poll_write(n: usize) -> Poll> { + if n == 0 { + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())) + } else if n == 1 { + return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidInput, "error"))) + }; + + Poll::Ready(Ok(n)) +} + +pub fn poll_next(ready: bool) -> Poll>> { + if !ready { + return Poll::Ready(Some(Err(io::ErrorKind::NotFound.into()))) + } + + Poll::Ready(None) +} diff --git a/src/tools/clippy/tests/ui/try_err.rs b/src/tools/clippy/tests/ui/try_err.rs new file mode 100644 index 0000000000..f220d697d2 --- /dev/null +++ b/src/tools/clippy/tests/ui/try_err.rs @@ -0,0 +1,162 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![deny(clippy::try_err)] +#![allow(clippy::unnecessary_wraps, clippy::needless_question_mark)] + +#[macro_use] +extern crate macro_rules; + +use std::io; +use std::task::Poll; + +// Tests that a simple case works +// Should flag `Err(err)?` +pub fn basic_test() -> Result { + let err: i32 = 1; + // To avoid warnings during rustfix + if true { + Err(err)?; + } + Ok(0) +} + +// Tests that `.into()` is added when appropriate +pub fn into_test() -> Result { + let err: u8 = 1; + // To avoid warnings during rustfix + if true { + Err(err)?; + } + Ok(0) +} + +// Tests that tries in general don't trigger the error +pub fn negative_test() -> Result { + Ok(nested_error()? + 1) +} + +// Tests that `.into()` isn't added when the error type +// matches the surrounding closure's return type, even +// when it doesn't match the surrounding function's. +pub fn closure_matches_test() -> Result { + let res: Result = Some(1) + .into_iter() + .map(|i| { + let err: i8 = 1; + // To avoid warnings during rustfix + if true { + Err(err)?; + } + Ok(i) + }) + .next() + .unwrap(); + + Ok(res?) +} + +// Tests that `.into()` isn't added when the error type +// doesn't match the surrounding closure's return type. +pub fn closure_into_test() -> Result { + let res: Result = Some(1) + .into_iter() + .map(|i| { + let err: i8 = 1; + // To avoid warnings during rustfix + if true { + Err(err)?; + } + Ok(i) + }) + .next() + .unwrap(); + + Ok(res?) +} + +fn nested_error() -> Result { + Ok(1) +} + +// Bad suggestion when in macro (see #6242) +macro_rules! try_validation { + ($e: expr) => {{ + match $e { + Ok(_) => 0, + Err(_) => Err(1)?, + } + }}; +} + +macro_rules! ret_one { + () => { + 1 + }; +} + +macro_rules! try_validation_in_macro { + ($e: expr) => {{ + match $e { + Ok(_) => 0, + Err(_) => Err(ret_one!())?, + } + }}; +} + +fn calling_macro() -> Result { + // macro + try_validation!(Ok::<_, i32>(5)); + // `Err` arg is another macro + try_validation_in_macro!(Ok::<_, i32>(5)); + Ok(5) +} + +fn main() { + basic_test().unwrap(); + into_test().unwrap(); + negative_test().unwrap(); + closure_matches_test().unwrap(); + closure_into_test().unwrap(); + calling_macro().unwrap(); + + // We don't want to lint in external macros + try_err!(); +} + +macro_rules! bar { + () => { + String::from("aasdfasdfasdfa") + }; +} + +macro_rules! foo { + () => { + bar!() + }; +} + +pub fn macro_inside(fail: bool) -> Result { + if fail { + Err(foo!())?; + } + Ok(0) +} + +pub fn poll_write(n: usize) -> Poll> { + if n == 0 { + Err(io::ErrorKind::WriteZero)? + } else if n == 1 { + Err(io::Error::new(io::ErrorKind::InvalidInput, "error"))? + }; + + Poll::Ready(Ok(n)) +} + +pub fn poll_next(ready: bool) -> Poll>> { + if !ready { + Err(io::ErrorKind::NotFound)? + } + + Poll::Ready(None) +} diff --git a/src/tools/clippy/tests/ui/try_err.stderr b/src/tools/clippy/tests/ui/try_err.stderr new file mode 100644 index 0000000000..2c01d37192 --- /dev/null +++ b/src/tools/clippy/tests/ui/try_err.stderr @@ -0,0 +1,78 @@ +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:19:9 + | +LL | Err(err)?; + | ^^^^^^^^^ help: try this: `return Err(err)` + | +note: the lint level is defined here + --> $DIR/try_err.rs:4:9 + | +LL | #![deny(clippy::try_err)] + | ^^^^^^^^^^^^^^^ + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:29:9 + | +LL | Err(err)?; + | ^^^^^^^^^ help: try this: `return Err(err.into())` + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:49:17 + | +LL | Err(err)?; + | ^^^^^^^^^ help: try this: `return Err(err)` + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:68:17 + | +LL | Err(err)?; + | ^^^^^^^^^ help: try this: `return Err(err.into())` + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:87:23 + | +LL | Err(_) => Err(1)?, + | ^^^^^^^ help: try this: `return Err(1)` +... +LL | try_validation!(Ok::<_, i32>(5)); + | --------------------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:102:23 + | +LL | Err(_) => Err(ret_one!())?, + | ^^^^^^^^^^^^^^^^ help: try this: `return Err(ret_one!())` +... +LL | try_validation_in_macro!(Ok::<_, i32>(5)); + | ------------------------------------------ in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:141:9 + | +LL | Err(foo!())?; + | ^^^^^^^^^^^^ help: try this: `return Err(foo!())` + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:148:9 + | +LL | Err(io::ErrorKind::WriteZero)? + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `return Poll::Ready(Err(io::ErrorKind::WriteZero.into()))` + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:150:9 + | +LL | Err(io::Error::new(io::ErrorKind::InvalidInput, "error"))? + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidInput, "error")))` + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:158:9 + | +LL | Err(io::ErrorKind::NotFound)? + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `return Poll::Ready(Some(Err(io::ErrorKind::NotFound.into())))` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/ty_fn_sig.rs b/src/tools/clippy/tests/ui/ty_fn_sig.rs new file mode 100644 index 0000000000..9e2753dcb1 --- /dev/null +++ b/src/tools/clippy/tests/ui/ty_fn_sig.rs @@ -0,0 +1,14 @@ +// Regression test + +pub fn retry(f: F) { + for _i in 0.. { + f(); + } +} + +fn main() { + for y in 0..4 { + let func = || (); + func(); + } +} diff --git a/src/tools/clippy/tests/ui/type_repetition_in_bounds.rs b/src/tools/clippy/tests/ui/type_repetition_in_bounds.rs new file mode 100644 index 0000000000..766190f209 --- /dev/null +++ b/src/tools/clippy/tests/ui/type_repetition_in_bounds.rs @@ -0,0 +1,72 @@ +#![deny(clippy::type_repetition_in_bounds)] + +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; + +pub fn foo(_t: T) +where + T: Copy, + T: Clone, +{ + unimplemented!(); +} + +pub fn bar(_t: T, _u: U) +where + T: Copy, + U: Clone, +{ + unimplemented!(); +} + +// Threshold test (see #4380) +trait LintBounds +where + Self: Clone, + Self: Copy + Default + Ord, + Self: Add + AddAssign + Sub + SubAssign, + Self: Mul + MulAssign + Div + DivAssign, +{ +} + +trait LotsOfBounds +where + Self: Clone + Copy + Default + Ord, + Self: Add + AddAssign + Sub + SubAssign, + Self: Mul + MulAssign + Div + DivAssign, +{ +} + +// Generic distinction (see #4323) +mod issue4323 { + pub struct Foo(A); + pub struct Bar { + a: Foo, + b: Foo, + } + + impl Unpin for Bar + where + Foo: Unpin, + Foo: Unpin, + { + } +} + +// Extern macros shouldn't lint (see #4326) +extern crate serde; +mod issue4326 { + use serde::{Deserialize, Serialize}; + + trait Foo {} + impl Foo for String {} + + #[derive(Debug, Serialize, Deserialize)] + struct Bar + where + S: Foo, + { + foo: S, + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/type_repetition_in_bounds.stderr b/src/tools/clippy/tests/ui/type_repetition_in_bounds.stderr new file mode 100644 index 0000000000..148c19c7d0 --- /dev/null +++ b/src/tools/clippy/tests/ui/type_repetition_in_bounds.stderr @@ -0,0 +1,23 @@ +error: this type has already been used as a bound predicate + --> $DIR/type_repetition_in_bounds.rs:8:5 + | +LL | T: Clone, + | ^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/type_repetition_in_bounds.rs:1:9 + | +LL | #![deny(clippy::type_repetition_in_bounds)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider combining the bounds: `T: Copy + Clone` + +error: this type has already been used as a bound predicate + --> $DIR/type_repetition_in_bounds.rs:25:5 + | +LL | Self: Copy + Default + Ord, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider combining the bounds: `Self: Clone + Copy + Default + Ord` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/types.fixed b/src/tools/clippy/tests/ui/types.fixed new file mode 100644 index 0000000000..417da42edf --- /dev/null +++ b/src/tools/clippy/tests/ui/types.fixed @@ -0,0 +1,15 @@ +// run-rustfix + +#![allow(dead_code, unused_variables)] +#![warn(clippy::cast_lossless)] + +// should not warn on lossy casting in constant types +// because not supported yet +const C: i32 = 42; +const C_I64: i64 = C as i64; + +fn main() { + // should suggest i64::from(c) + let c: i32 = 42; + let c_i64: i64 = i64::from(c); +} diff --git a/src/tools/clippy/tests/ui/types.rs b/src/tools/clippy/tests/ui/types.rs new file mode 100644 index 0000000000..b16e9e538b --- /dev/null +++ b/src/tools/clippy/tests/ui/types.rs @@ -0,0 +1,15 @@ +// run-rustfix + +#![allow(dead_code, unused_variables)] +#![warn(clippy::cast_lossless)] + +// should not warn on lossy casting in constant types +// because not supported yet +const C: i32 = 42; +const C_I64: i64 = C as i64; + +fn main() { + // should suggest i64::from(c) + let c: i32 = 42; + let c_i64: i64 = c as i64; +} diff --git a/src/tools/clippy/tests/ui/types.stderr b/src/tools/clippy/tests/ui/types.stderr new file mode 100644 index 0000000000..59c3e05a1a --- /dev/null +++ b/src/tools/clippy/tests/ui/types.stderr @@ -0,0 +1,10 @@ +error: casting `i32` to `i64` may become silently lossy if you later change the type + --> $DIR/types.rs:14:22 + | +LL | let c_i64: i64 = c as i64; + | ^^^^^^^^ help: try: `i64::from(c)` + | + = note: `-D clippy::cast-lossless` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/undropped_manually_drops.rs b/src/tools/clippy/tests/ui/undropped_manually_drops.rs new file mode 100644 index 0000000000..f4cfc92e1c --- /dev/null +++ b/src/tools/clippy/tests/ui/undropped_manually_drops.rs @@ -0,0 +1,26 @@ +#![warn(clippy::undropped_manually_drops)] + +struct S; + +fn main() { + let f = std::mem::drop; + let g = std::mem::ManuallyDrop::drop; + let mut manual1 = std::mem::ManuallyDrop::new(S); + let mut manual2 = std::mem::ManuallyDrop::new(S); + let mut manual3 = std::mem::ManuallyDrop::new(S); + let mut manual4 = std::mem::ManuallyDrop::new(S); + + // These lines will not drop `S` and should be linted + drop(std::mem::ManuallyDrop::new(S)); + drop(manual1); + + // FIXME: this line is not linted, though it should be + f(manual2); + + // These lines will drop `S` and should be okay. + unsafe { + std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S)); + std::mem::ManuallyDrop::drop(&mut manual3); + g(&mut manual4); + } +} diff --git a/src/tools/clippy/tests/ui/undropped_manually_drops.stderr b/src/tools/clippy/tests/ui/undropped_manually_drops.stderr new file mode 100644 index 0000000000..2ac0fe9869 --- /dev/null +++ b/src/tools/clippy/tests/ui/undropped_manually_drops.stderr @@ -0,0 +1,19 @@ +error: the inner value of this ManuallyDrop will not be dropped + --> $DIR/undropped_manually_drops.rs:14:5 + | +LL | drop(std::mem::ManuallyDrop::new(S)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::undropped-manually-drops` implied by `-D warnings` + = help: to drop a `ManuallyDrop`, use std::mem::ManuallyDrop::drop + +error: the inner value of this ManuallyDrop will not be dropped + --> $DIR/undropped_manually_drops.rs:15:5 + | +LL | drop(manual1); + | ^^^^^^^^^^^^^ + | + = help: to drop a `ManuallyDrop`, use std::mem::ManuallyDrop::drop + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/unicode.rs b/src/tools/clippy/tests/ui/unicode.rs new file mode 100644 index 0000000000..1f596c312f --- /dev/null +++ b/src/tools/clippy/tests/ui/unicode.rs @@ -0,0 +1,27 @@ +#[warn(clippy::invisible_characters)] +fn zero() { + print!("Here >​< is a ZWS, and ​another"); + print!("This\u{200B}is\u{200B}fine"); + print!("Here >­< is a SHY, and ­another"); + print!("This\u{ad}is\u{ad}fine"); + print!("Here >⁠< is a WJ, and ⁠another"); + print!("This\u{2060}is\u{2060}fine"); +} + +#[warn(clippy::unicode_not_nfc)] +fn canon() { + print!("̀àh?"); + print!("a\u{0300}h?"); // also ok +} + +#[warn(clippy::non_ascii_literal)] +fn uni() { + print!("Üben!"); + print!("\u{DC}ben!"); // this is ok +} + +fn main() { + zero(); + uni(); + canon(); +} diff --git a/src/tools/clippy/tests/ui/unicode.stderr b/src/tools/clippy/tests/ui/unicode.stderr new file mode 100644 index 0000000000..3fca463c62 --- /dev/null +++ b/src/tools/clippy/tests/ui/unicode.stderr @@ -0,0 +1,38 @@ +error: invisible character detected + --> $DIR/unicode.rs:3:12 + | +LL | print!("Here >​< is a ZWS, and ​another"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{200B}< is a ZWS, and /u{200B}another"` + | + = note: `-D clippy::invisible-characters` implied by `-D warnings` + +error: invisible character detected + --> $DIR/unicode.rs:5:12 + | +LL | print!("Here >­< is a SHY, and ­another"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{AD}< is a SHY, and /u{AD}another"` + +error: invisible character detected + --> $DIR/unicode.rs:7:12 + | +LL | print!("Here >⁠< is a WJ, and ⁠another"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{2060}< is a WJ, and /u{2060}another"` + +error: non-NFC Unicode sequence detected + --> $DIR/unicode.rs:13:12 + | +LL | print!("̀àh?"); + | ^^^^^ help: consider replacing the string with: `"̀àh?"` + | + = note: `-D clippy::unicode-not-nfc` implied by `-D warnings` + +error: literal non-ASCII character detected + --> $DIR/unicode.rs:19:12 + | +LL | print!("Üben!"); + | ^^^^^^^ help: consider replacing the string with: `"/u{dc}ben!"` + | + = note: `-D clippy::non-ascii-literal` implied by `-D warnings` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/uninit.rs b/src/tools/clippy/tests/ui/uninit.rs new file mode 100644 index 0000000000..f42b884e0f --- /dev/null +++ b/src/tools/clippy/tests/ui/uninit.rs @@ -0,0 +1,22 @@ +#![feature(stmt_expr_attributes)] + +use std::mem::MaybeUninit; + +fn main() { + let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; + + // edge case: For now we lint on empty arrays + let _: [u8; 0] = unsafe { MaybeUninit::uninit().assume_init() }; + + // edge case: For now we accept unit tuples + let _: () = unsafe { MaybeUninit::uninit().assume_init() }; + + // This is OK, because `MaybeUninit` allows uninitialized data. + let _: MaybeUninit = unsafe { MaybeUninit::uninit().assume_init() }; + + // This is OK, because all constitutent types are uninit-compatible. + let _: (MaybeUninit, MaybeUninit) = unsafe { MaybeUninit::uninit().assume_init() }; + + // This is OK, because all constitutent types are uninit-compatible. + let _: (MaybeUninit, [MaybeUninit; 2]) = unsafe { MaybeUninit::uninit().assume_init() }; +} diff --git a/src/tools/clippy/tests/ui/uninit.stderr b/src/tools/clippy/tests/ui/uninit.stderr new file mode 100644 index 0000000000..a37233ecdd --- /dev/null +++ b/src/tools/clippy/tests/ui/uninit.stderr @@ -0,0 +1,16 @@ +error: this call for this type may be undefined behavior + --> $DIR/uninit.rs:6:29 + | +LL | let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::uninit_assumed_init)]` on by default + +error: this call for this type may be undefined behavior + --> $DIR/uninit.rs:9:31 + | +LL | let _: [u8; 0] = unsafe { MaybeUninit::uninit().assume_init() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/unit_arg.rs b/src/tools/clippy/tests/ui/unit_arg.rs new file mode 100644 index 0000000000..938cc3c785 --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_arg.rs @@ -0,0 +1,131 @@ +#![warn(clippy::unit_arg)] +#![allow( + clippy::no_effect, + unused_must_use, + unused_variables, + clippy::unused_unit, + clippy::unnecessary_wraps, + clippy::or_fun_call, + clippy::needless_question_mark +)] + +use std::fmt::Debug; + +fn foo(t: T) { + println!("{:?}", t); +} + +fn foo3(t1: T1, t2: T2, t3: T3) { + println!("{:?}, {:?}, {:?}", t1, t2, t3); +} + +struct Bar; + +impl Bar { + fn bar(&self, t: T) { + println!("{:?}", t); + } +} + +fn baz(t: T) { + foo(t); +} + +trait Tr { + type Args; + fn do_it(args: Self::Args); +} + +struct A; +impl Tr for A { + type Args = (); + fn do_it(_: Self::Args) {} +} + +struct B; +impl Tr for B { + type Args = ::Args; + + fn do_it(args: Self::Args) { + A::do_it(args) + } +} + +fn bad() { + foo({ + 1; + }); + foo(foo(1)); + foo({ + foo(1); + foo(2); + }); + let b = Bar; + b.bar({ + 1; + }); + taking_multiple_units(foo(0), foo(1)); + taking_multiple_units(foo(0), { + foo(1); + foo(2); + }); + taking_multiple_units( + { + foo(0); + foo(1); + }, + { + foo(2); + foo(3); + }, + ); + // here Some(foo(2)) isn't the top level statement expression, wrap the suggestion in a block + None.or(Some(foo(2))); + // in this case, the suggestion can be inlined, no need for a surrounding block + // foo(()); foo(()) instead of { foo(()); foo(()) } + foo(foo(())); +} + +fn ok() { + foo(()); + foo(1); + foo({ 1 }); + foo3("a", 3, vec![3]); + let b = Bar; + b.bar({ 1 }); + b.bar(()); + question_mark(); + let named_unit_arg = (); + foo(named_unit_arg); + baz(()); + B::do_it(()); +} + +fn question_mark() -> Result<(), ()> { + Ok(Ok(())?)?; + Ok(Ok(()))??; + Ok(()) +} + +#[allow(dead_code)] +mod issue_2945 { + fn unit_fn() -> Result<(), i32> { + Ok(()) + } + + fn fallible() -> Result<(), i32> { + Ok(unit_fn()?) + } +} + +#[allow(dead_code)] +fn returning_expr() -> Option<()> { + Some(foo(1)) +} + +fn taking_multiple_units(a: (), b: ()) {} + +fn main() { + bad(); + ok(); +} diff --git a/src/tools/clippy/tests/ui/unit_arg.stderr b/src/tools/clippy/tests/ui/unit_arg.stderr new file mode 100644 index 0000000000..354fd51cd6 --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_arg.stderr @@ -0,0 +1,181 @@ +error: passing a unit value to a function + --> $DIR/unit_arg.rs:55:5 + | +LL | / foo({ +LL | | 1; +LL | | }); + | |______^ + | + = note: `-D clippy::unit-arg` implied by `-D warnings` +help: remove the semicolon from the last statement in the block + | +LL | 1 + | +help: or move the expression in front of the call and replace it with the unit literal `()` + | +LL | { +LL | 1; +LL | }; +LL | foo(()); + | + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:58:5 + | +LL | foo(foo(1)); + | ^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL | foo(1); +LL | foo(()); + | + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:59:5 + | +LL | / foo({ +LL | | foo(1); +LL | | foo(2); +LL | | }); + | |______^ + | +help: remove the semicolon from the last statement in the block + | +LL | foo(2) + | +help: or move the expression in front of the call and replace it with the unit literal `()` + | +LL | { +LL | foo(1); +LL | foo(2); +LL | }; +LL | foo(()); + | + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:64:5 + | +LL | / b.bar({ +LL | | 1; +LL | | }); + | |______^ + | +help: remove the semicolon from the last statement in the block + | +LL | 1 + | +help: or move the expression in front of the call and replace it with the unit literal `()` + | +LL | { +LL | 1; +LL | }; +LL | b.bar(()); + | + +error: passing unit values to a function + --> $DIR/unit_arg.rs:67:5 + | +LL | taking_multiple_units(foo(0), foo(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expressions in front of the call and replace them with the unit literal `()` + | +LL | foo(0); +LL | foo(1); +LL | taking_multiple_units((), ()); + | + +error: passing unit values to a function + --> $DIR/unit_arg.rs:68:5 + | +LL | / taking_multiple_units(foo(0), { +LL | | foo(1); +LL | | foo(2); +LL | | }); + | |______^ + | +help: remove the semicolon from the last statement in the block + | +LL | foo(2) + | +help: or move the expressions in front of the call and replace them with the unit literal `()` + | +LL | foo(0); +LL | { +LL | foo(1); +LL | foo(2); +LL | }; +LL | taking_multiple_units((), ()); + | + +error: passing unit values to a function + --> $DIR/unit_arg.rs:72:5 + | +LL | / taking_multiple_units( +LL | | { +LL | | foo(0); +LL | | foo(1); +... | +LL | | }, +LL | | ); + | |_____^ + | +help: remove the semicolon from the last statement in the block + | +LL | foo(1) + | +help: remove the semicolon from the last statement in the block + | +LL | foo(3) + | +help: or move the expressions in front of the call and replace them with the unit literal `()` + | +LL | { +LL | foo(0); +LL | foo(1); +LL | }; +LL | { +LL | foo(2); + ... + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:83:13 + | +LL | None.or(Some(foo(2))); + | ^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL | None.or({ +LL | foo(2); +LL | Some(()) +LL | }); + | + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:86:5 + | +LL | foo(foo(())); + | ^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL | foo(()); +LL | foo(()); + | + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:123:5 + | +LL | Some(foo(1)) + | ^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL | foo(1); +LL | Some(()) + | + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/unit_arg_empty_blocks.rs b/src/tools/clippy/tests/ui/unit_arg_empty_blocks.rs new file mode 100644 index 0000000000..18a31eb3de --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_arg_empty_blocks.rs @@ -0,0 +1,26 @@ +#![warn(clippy::unit_arg)] +#![allow(clippy::no_effect, unused_must_use, unused_variables)] + +use std::fmt::Debug; + +fn foo(t: T) { + println!("{:?}", t); +} + +fn foo3(t1: T1, t2: T2, t3: T3) { + println!("{:?}, {:?}, {:?}", t1, t2, t3); +} + +fn bad() { + foo({}); + foo3({}, 2, 2); + taking_two_units({}, foo(0)); + taking_three_units({}, foo(0), foo(1)); +} + +fn taking_two_units(a: (), b: ()) {} +fn taking_three_units(a: (), b: (), c: ()) {} + +fn main() { + bad(); +} diff --git a/src/tools/clippy/tests/ui/unit_arg_empty_blocks.stderr b/src/tools/clippy/tests/ui/unit_arg_empty_blocks.stderr new file mode 100644 index 0000000000..456b12a2c6 --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_arg_empty_blocks.stderr @@ -0,0 +1,45 @@ +error: passing a unit value to a function + --> $DIR/unit_arg_empty_blocks.rs:15:5 + | +LL | foo({}); + | ^^^^--^ + | | + | help: use a unit literal instead: `()` + | + = note: `-D clippy::unit-arg` implied by `-D warnings` + +error: passing a unit value to a function + --> $DIR/unit_arg_empty_blocks.rs:16:5 + | +LL | foo3({}, 2, 2); + | ^^^^^--^^^^^^^ + | | + | help: use a unit literal instead: `()` + +error: passing unit values to a function + --> $DIR/unit_arg_empty_blocks.rs:17:5 + | +LL | taking_two_units({}, foo(0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL | foo(0); +LL | taking_two_units((), ()); + | + +error: passing unit values to a function + --> $DIR/unit_arg_empty_blocks.rs:18:5 + | +LL | taking_three_units({}, foo(0), foo(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expressions in front of the call and replace them with the unit literal `()` + | +LL | foo(0); +LL | foo(1); +LL | taking_three_units((), (), ()); + | + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/unit_cmp.rs b/src/tools/clippy/tests/ui/unit_cmp.rs new file mode 100644 index 0000000000..8d3a4eed82 --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_cmp.rs @@ -0,0 +1,57 @@ +#![warn(clippy::unit_cmp)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +#[derive(PartialEq)] +pub struct ContainsUnit(()); // should be fine + +fn main() { + // this is fine + if true == false {} + + // this warns + if { + true; + } == { + false; + } {} + + if { + true; + } > { + false; + } {} + + assert_eq!( + { + true; + }, + { + false; + } + ); + debug_assert_eq!( + { + true; + }, + { + false; + } + ); + + assert_ne!( + { + true; + }, + { + false; + } + ); + debug_assert_ne!( + { + true; + }, + { + false; + } + ); +} diff --git a/src/tools/clippy/tests/ui/unit_cmp.stderr b/src/tools/clippy/tests/ui/unit_cmp.stderr new file mode 100644 index 0000000000..c8c0a85dfc --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_cmp.stderr @@ -0,0 +1,82 @@ +error: ==-comparison of unit values detected. This will always be true + --> $DIR/unit_cmp.rs:12:8 + | +LL | if { + | ________^ +LL | | true; +LL | | } == { +LL | | false; +LL | | } {} + | |_____^ + | + = note: `-D clippy::unit-cmp` implied by `-D warnings` + +error: >-comparison of unit values detected. This will always be false + --> $DIR/unit_cmp.rs:18:8 + | +LL | if { + | ________^ +LL | | true; +LL | | } > { +LL | | false; +LL | | } {} + | |_____^ + +error: `assert_eq` of unit values detected. This will always succeed + --> $DIR/unit_cmp.rs:24:5 + | +LL | / assert_eq!( +LL | | { +LL | | true; +LL | | }, +... | +LL | | } +LL | | ); + | |______^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `debug_assert_eq` of unit values detected. This will always succeed + --> $DIR/unit_cmp.rs:32:5 + | +LL | / debug_assert_eq!( +LL | | { +LL | | true; +LL | | }, +... | +LL | | } +LL | | ); + | |______^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert_ne` of unit values detected. This will always fail + --> $DIR/unit_cmp.rs:41:5 + | +LL | / assert_ne!( +LL | | { +LL | | true; +LL | | }, +... | +LL | | } +LL | | ); + | |______^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `debug_assert_ne` of unit values detected. This will always fail + --> $DIR/unit_cmp.rs:49:5 + | +LL | / debug_assert_ne!( +LL | | { +LL | | true; +LL | | }, +... | +LL | | } +LL | | ); + | |______^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/unit_return_expecting_ord.rs b/src/tools/clippy/tests/ui/unit_return_expecting_ord.rs new file mode 100644 index 0000000000..bdb4710cc6 --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_return_expecting_ord.rs @@ -0,0 +1,36 @@ +#![warn(clippy::unit_return_expecting_ord)] +#![allow(clippy::needless_return)] +#![allow(clippy::unused_unit)] +#![feature(is_sorted)] + +struct Struct { + field: isize, +} + +fn double(i: isize) -> isize { + i * 2 +} + +fn unit(_i: isize) {} + +fn main() { + let mut structs = vec![Struct { field: 2 }, Struct { field: 1 }]; + structs.sort_by_key(|s| { + double(s.field); + }); + structs.sort_by_key(|s| double(s.field)); + structs.is_sorted_by_key(|s| { + double(s.field); + }); + structs.is_sorted_by_key(|s| { + if s.field > 0 { + () + } else { + return (); + } + }); + structs.sort_by_key(|s| { + return double(s.field); + }); + structs.sort_by_key(|s| unit(s.field)); +} diff --git a/src/tools/clippy/tests/ui/unit_return_expecting_ord.stderr b/src/tools/clippy/tests/ui/unit_return_expecting_ord.stderr new file mode 100644 index 0000000000..e63d587460 --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_return_expecting_ord.stderr @@ -0,0 +1,39 @@ +error: this closure returns the unit type which also implements Ord + --> $DIR/unit_return_expecting_ord.rs:18:25 + | +LL | structs.sort_by_key(|s| { + | ^^^ + | + = note: `-D clippy::unit-return-expecting-ord` implied by `-D warnings` +help: probably caused by this trailing semicolon + --> $DIR/unit_return_expecting_ord.rs:19:24 + | +LL | double(s.field); + | ^ + +error: this closure returns the unit type which also implements PartialOrd + --> $DIR/unit_return_expecting_ord.rs:22:30 + | +LL | structs.is_sorted_by_key(|s| { + | ^^^ + | +help: probably caused by this trailing semicolon + --> $DIR/unit_return_expecting_ord.rs:23:24 + | +LL | double(s.field); + | ^ + +error: this closure returns the unit type which also implements PartialOrd + --> $DIR/unit_return_expecting_ord.rs:25:30 + | +LL | structs.is_sorted_by_key(|s| { + | ^^^ + +error: this closure returns the unit type which also implements Ord + --> $DIR/unit_return_expecting_ord.rs:35:25 + | +LL | structs.sort_by_key(|s| unit(s.field)); + | ^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/unknown_attribute.rs b/src/tools/clippy/tests/ui/unknown_attribute.rs new file mode 100644 index 0000000000..e993e63f8e --- /dev/null +++ b/src/tools/clippy/tests/ui/unknown_attribute.rs @@ -0,0 +1,3 @@ +#[clippy::unknown] +#[clippy::cognitive_complexity = "1"] +fn main() {} diff --git a/src/tools/clippy/tests/ui/unknown_attribute.stderr b/src/tools/clippy/tests/ui/unknown_attribute.stderr new file mode 100644 index 0000000000..618c5980d6 --- /dev/null +++ b/src/tools/clippy/tests/ui/unknown_attribute.stderr @@ -0,0 +1,8 @@ +error: usage of unknown attribute + --> $DIR/unknown_attribute.rs:1:11 + | +LL | #[clippy::unknown] + | ^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.fixed b/src/tools/clippy/tests/ui/unknown_clippy_lints.fixed new file mode 100644 index 0000000000..4249ff8a95 --- /dev/null +++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.fixed @@ -0,0 +1,18 @@ +// run-rustfix + +#![warn(clippy::pedantic)] +// Should suggest lowercase +#![allow(clippy::all)] +#![warn(clippy::cmp_nan)] + +// Should suggest similar clippy lint name +#[warn(clippy::if_not_else)] +#[warn(clippy::unnecessary_cast)] +#[warn(clippy::useless_transmute)] +// Shouldn't suggest rustc lint name(`dead_code`) +#[warn(clippy::drop_copy)] +// Shouldn't suggest removed/deprecated clippy lint name(`unused_collect`) +#[warn(clippy::unused_self)] +// Shouldn't suggest renamed clippy lint name(`const_static_lifetime`) +#[warn(clippy::redundant_static_lifetimes)] +fn main() {} diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.rs b/src/tools/clippy/tests/ui/unknown_clippy_lints.rs new file mode 100644 index 0000000000..5db345f544 --- /dev/null +++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.rs @@ -0,0 +1,18 @@ +// run-rustfix + +#![warn(clippy::pedantic)] +// Should suggest lowercase +#![allow(clippy::All)] +#![warn(clippy::CMP_NAN)] + +// Should suggest similar clippy lint name +#[warn(clippy::if_not_els)] +#[warn(clippy::UNNecsaRy_cAst)] +#[warn(clippy::useles_transute)] +// Shouldn't suggest rustc lint name(`dead_code`) +#[warn(clippy::dead_cod)] +// Shouldn't suggest removed/deprecated clippy lint name(`unused_collect`) +#[warn(clippy::unused_colle)] +// Shouldn't suggest renamed clippy lint name(`const_static_lifetime`) +#[warn(clippy::const_static_lifetim)] +fn main() {} diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr b/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr new file mode 100644 index 0000000000..94a667e589 --- /dev/null +++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr @@ -0,0 +1,58 @@ +error: unknown lint: `clippy::All` + --> $DIR/unknown_clippy_lints.rs:5:10 + | +LL | #![allow(clippy::All)] + | ^^^^^^^^^^^ help: did you mean: `clippy::all` + | + = note: `-D unknown-lints` implied by `-D warnings` + +error: unknown lint: `clippy::CMP_NAN` + --> $DIR/unknown_clippy_lints.rs:6:9 + | +LL | #![warn(clippy::CMP_NAN)] + | ^^^^^^^^^^^^^^^ help: did you mean: `clippy::cmp_nan` + +error: unknown lint: `clippy::if_not_els` + --> $DIR/unknown_clippy_lints.rs:9:8 + | +LL | #[warn(clippy::if_not_els)] + | ^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::if_not_else` + +error: unknown lint: `clippy::UNNecsaRy_cAst` + --> $DIR/unknown_clippy_lints.rs:10:8 + | +LL | #[warn(clippy::UNNecsaRy_cAst)] + | ^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::unnecessary_cast` + +error: unknown lint: `clippy::useles_transute` + --> $DIR/unknown_clippy_lints.rs:11:8 + | +LL | #[warn(clippy::useles_transute)] + | ^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::useless_transmute` + +error: unknown lint: `clippy::dead_cod` + --> $DIR/unknown_clippy_lints.rs:13:8 + | +LL | #[warn(clippy::dead_cod)] + | ^^^^^^^^^^^^^^^^ help: did you mean: `clippy::drop_copy` + +error: unknown lint: `clippy::unused_colle` + --> $DIR/unknown_clippy_lints.rs:15:8 + | +LL | #[warn(clippy::unused_colle)] + | ^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::unused_self` + +error: unknown lint: `clippy::const_static_lifetim` + --> $DIR/unknown_clippy_lints.rs:17:8 + | +LL | #[warn(clippy::const_static_lifetim)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::redundant_static_lifetimes` + +error: unknown lint: `clippy::All` + --> $DIR/unknown_clippy_lints.rs:5:10 + | +LL | #![allow(clippy::All)] + | ^^^^^^^^^^^ help: did you mean: `clippy::all` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_cast.rs b/src/tools/clippy/tests/ui/unnecessary_cast.rs new file mode 100644 index 0000000000..e8f2fb4666 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_cast.rs @@ -0,0 +1,26 @@ +#![warn(clippy::unnecessary_cast)] +#![allow(clippy::no_effect)] + +fn main() { + // Test cast_unnecessary + 1i32 as i32; + 1f32 as f32; + false as bool; + &1i32 as &i32; + + // macro version + macro_rules! foo { + ($a:ident, $b:ident) => { + #[allow(unused)] + pub fn $a() -> $b { + 1 as $b + } + }; + } + foo!(a, i32); + foo!(b, f32); + foo!(c, f64); + + // do not lint cast to cfg-dependant type + 1 as std::os::raw::c_char; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_cast.stderr b/src/tools/clippy/tests/ui/unnecessary_cast.stderr new file mode 100644 index 0000000000..8981d13e8e --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_cast.stderr @@ -0,0 +1,22 @@ +error: casting to the same type is unnecessary (`i32` -> `i32`) + --> $DIR/unnecessary_cast.rs:6:5 + | +LL | 1i32 as i32; + | ^^^^^^^^^^^ + | + = note: `-D clippy::unnecessary-cast` implied by `-D warnings` + +error: casting to the same type is unnecessary (`f32` -> `f32`) + --> $DIR/unnecessary_cast.rs:7:5 + | +LL | 1f32 as f32; + | ^^^^^^^^^^^ + +error: casting to the same type is unnecessary (`bool` -> `bool`) + --> $DIR/unnecessary_cast.rs:8:5 + | +LL | false as bool; + | ^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_cast_fixable.fixed b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.fixed new file mode 100644 index 0000000000..7fbce58a82 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.fixed @@ -0,0 +1,40 @@ +// run-rustfix + +#![warn(clippy::unnecessary_cast)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +fn main() { + // casting integer literal to float is unnecessary + 100_f32; + 100_f64; + 100_f64; + let _ = -100_f32; + let _ = -100_f64; + let _ = -100_f64; + 100_f32; + 100_f64; + // Should not trigger + #[rustfmt::skip] + let v = vec!(1); + &v as &[i32]; + 0x10 as f32; + 0o10 as f32; + 0b10 as f32; + 0x11 as f64; + 0o11 as f64; + 0b11 as f64; + + 1_u32; + 0x10_i32; + 0b10_usize; + 0o73_u16; + 1_000_000_000_u32; + + 1.0_f64; + 0.5_f32; + + 1.0 as u16; + + let _ = -1_i32; + let _ = -1.0_f32; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_cast_fixable.rs b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.rs new file mode 100644 index 0000000000..a71363ea4d --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.rs @@ -0,0 +1,40 @@ +// run-rustfix + +#![warn(clippy::unnecessary_cast)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +fn main() { + // casting integer literal to float is unnecessary + 100 as f32; + 100 as f64; + 100_i32 as f64; + let _ = -100 as f32; + let _ = -100 as f64; + let _ = -100_i32 as f64; + 100. as f32; + 100. as f64; + // Should not trigger + #[rustfmt::skip] + let v = vec!(1); + &v as &[i32]; + 0x10 as f32; + 0o10 as f32; + 0b10 as f32; + 0x11 as f64; + 0o11 as f64; + 0b11 as f64; + + 1 as u32; + 0x10 as i32; + 0b10 as usize; + 0o73 as u16; + 1_000_000_000 as u32; + + 1.0 as f64; + 0.5 as f32; + + 1.0 as u16; + + let _ = -1 as i32; + let _ = -1.0 as f32; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_cast_fixable.stderr b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.stderr new file mode 100644 index 0000000000..3695a8f819 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.stderr @@ -0,0 +1,106 @@ +error: casting integer literal to `f32` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:8:5 + | +LL | 100 as f32; + | ^^^^^^^^^^ help: try: `100_f32` + | + = note: `-D clippy::unnecessary-cast` implied by `-D warnings` + +error: casting integer literal to `f64` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:9:5 + | +LL | 100 as f64; + | ^^^^^^^^^^ help: try: `100_f64` + +error: casting integer literal to `f64` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:10:5 + | +LL | 100_i32 as f64; + | ^^^^^^^^^^^^^^ help: try: `100_f64` + +error: casting integer literal to `f32` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:11:13 + | +LL | let _ = -100 as f32; + | ^^^^^^^^^^^ help: try: `-100_f32` + +error: casting integer literal to `f64` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:12:13 + | +LL | let _ = -100 as f64; + | ^^^^^^^^^^^ help: try: `-100_f64` + +error: casting integer literal to `f64` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:13:13 + | +LL | let _ = -100_i32 as f64; + | ^^^^^^^^^^^^^^^ help: try: `-100_f64` + +error: casting float literal to `f32` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:14:5 + | +LL | 100. as f32; + | ^^^^^^^^^^^ help: try: `100_f32` + +error: casting float literal to `f64` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:15:5 + | +LL | 100. as f64; + | ^^^^^^^^^^^ help: try: `100_f64` + +error: casting integer literal to `u32` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:27:5 + | +LL | 1 as u32; + | ^^^^^^^^ help: try: `1_u32` + +error: casting integer literal to `i32` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:28:5 + | +LL | 0x10 as i32; + | ^^^^^^^^^^^ help: try: `0x10_i32` + +error: casting integer literal to `usize` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:29:5 + | +LL | 0b10 as usize; + | ^^^^^^^^^^^^^ help: try: `0b10_usize` + +error: casting integer literal to `u16` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:30:5 + | +LL | 0o73 as u16; + | ^^^^^^^^^^^ help: try: `0o73_u16` + +error: casting integer literal to `u32` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:31:5 + | +LL | 1_000_000_000 as u32; + | ^^^^^^^^^^^^^^^^^^^^ help: try: `1_000_000_000_u32` + +error: casting float literal to `f64` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:33:5 + | +LL | 1.0 as f64; + | ^^^^^^^^^^ help: try: `1.0_f64` + +error: casting float literal to `f32` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:34:5 + | +LL | 0.5 as f32; + | ^^^^^^^^^^ help: try: `0.5_f32` + +error: casting integer literal to `i32` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:38:13 + | +LL | let _ = -1 as i32; + | ^^^^^^^^^ help: try: `-1_i32` + +error: casting float literal to `f32` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:39:13 + | +LL | let _ = -1.0 as f32; + | ^^^^^^^^^^^ help: try: `-1.0_f32` + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_clone.rs b/src/tools/clippy/tests/ui/unnecessary_clone.rs new file mode 100644 index 0000000000..6770a7fac9 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_clone.rs @@ -0,0 +1,110 @@ +// does not test any rustfixable lints + +#![warn(clippy::clone_on_ref_ptr)] +#![allow(unused, clippy::redundant_clone, clippy::unnecessary_wraps)] + +use std::cell::RefCell; +use std::rc::{self, Rc}; +use std::sync::{self, Arc}; + +trait SomeTrait {} +struct SomeImpl; +impl SomeTrait for SomeImpl {} + +fn main() {} + +fn clone_on_ref_ptr() { + let rc = Rc::new(true); + let arc = Arc::new(true); + + let rcweak = Rc::downgrade(&rc); + let arc_weak = Arc::downgrade(&arc); + + rc.clone(); + Rc::clone(&rc); + + arc.clone(); + Arc::clone(&arc); + + rcweak.clone(); + rc::Weak::clone(&rcweak); + + arc_weak.clone(); + sync::Weak::clone(&arc_weak); + + let x = Arc::new(SomeImpl); + let _: Arc = x.clone(); +} + +fn clone_on_copy_generic(t: T) { + t.clone(); + + Some(t).clone(); +} + +fn clone_on_double_ref() { + let x = vec![1]; + let y = &&x; + let z: &Vec<_> = y.clone(); + + println!("{:p} {:p}", *y, z); +} + +mod many_derefs { + struct A; + struct B; + struct C; + struct D; + #[derive(Copy, Clone)] + struct E; + + macro_rules! impl_deref { + ($src:ident, $dst:ident) => { + impl std::ops::Deref for $src { + type Target = $dst; + fn deref(&self) -> &Self::Target { + &$dst + } + } + }; + } + + impl_deref!(A, B); + impl_deref!(B, C); + impl_deref!(C, D); + impl std::ops::Deref for D { + type Target = &'static E; + fn deref(&self) -> &Self::Target { + &&E + } + } + + fn go1() { + let a = A; + let _: E = a.clone(); + let _: E = *****a; + } + + fn check(mut encoded: &[u8]) { + let _ = &mut encoded.clone(); + let _ = &encoded.clone(); + } +} + +mod issue2076 { + use std::rc::Rc; + + macro_rules! try_opt { + ($expr: expr) => { + match $expr { + Some(value) => value, + None => return None, + } + }; + } + + fn func() -> Option> { + let rc = Rc::new(42); + Some(try_opt!(Some(rc)).clone()) + } +} diff --git a/src/tools/clippy/tests/ui/unnecessary_clone.stderr b/src/tools/clippy/tests/ui/unnecessary_clone.stderr new file mode 100644 index 0000000000..9df1ae5686 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_clone.stderr @@ -0,0 +1,106 @@ +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:23:5 + | +LL | rc.clone(); + | ^^^^^^^^^^ help: try this: `Rc::::clone(&rc)` + | + = note: `-D clippy::clone-on-ref-ptr` implied by `-D warnings` + +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:26:5 + | +LL | arc.clone(); + | ^^^^^^^^^^^ help: try this: `Arc::::clone(&arc)` + +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:29:5 + | +LL | rcweak.clone(); + | ^^^^^^^^^^^^^^ help: try this: `Weak::::clone(&rcweak)` + +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:32:5 + | +LL | arc_weak.clone(); + | ^^^^^^^^^^^^^^^^ help: try this: `Weak::::clone(&arc_weak)` + +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:36:33 + | +LL | let _: Arc = x.clone(); + | ^^^^^^^^^ help: try this: `Arc::::clone(&x)` + +error: using `clone` on type `T` which implements the `Copy` trait + --> $DIR/unnecessary_clone.rs:40:5 + | +LL | t.clone(); + | ^^^^^^^^^ help: try removing the `clone` call: `t` + | + = note: `-D clippy::clone-on-copy` implied by `-D warnings` + +error: using `clone` on type `std::option::Option` which implements the `Copy` trait + --> $DIR/unnecessary_clone.rs:42:5 + | +LL | Some(t).clone(); + | ^^^^^^^^^^^^^^^ help: try removing the `clone` call: `Some(t)` + +error: using `clone` on a double-reference; this will copy the reference of type `&std::vec::Vec` instead of cloning the inner type + --> $DIR/unnecessary_clone.rs:48:22 + | +LL | let z: &Vec<_> = y.clone(); + | ^^^^^^^^^ + | + = note: `#[deny(clippy::clone_double_ref)]` on by default +help: try dereferencing it + | +LL | let z: &Vec<_> = &(*y).clone(); + | ^^^^^^^^^^^^^ +help: or try being explicit if you are sure, that you want to clone a reference + | +LL | let z: &Vec<_> = <&std::vec::Vec>::clone(y); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: using `clone` on type `many_derefs::E` which implements the `Copy` trait + --> $DIR/unnecessary_clone.rs:84:20 + | +LL | let _: E = a.clone(); + | ^^^^^^^^^ help: try dereferencing it: `*****a` + +error: using `clone` on a double-reference; this will copy the reference of type `&[u8]` instead of cloning the inner type + --> $DIR/unnecessary_clone.rs:89:22 + | +LL | let _ = &mut encoded.clone(); + | ^^^^^^^^^^^^^^^ + | +help: try dereferencing it + | +LL | let _ = &mut &(*encoded).clone(); + | ^^^^^^^^^^^^^^^^^^^ +help: or try being explicit if you are sure, that you want to clone a reference + | +LL | let _ = &mut <&[u8]>::clone(encoded); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: using `clone` on a double-reference; this will copy the reference of type `&[u8]` instead of cloning the inner type + --> $DIR/unnecessary_clone.rs:90:18 + | +LL | let _ = &encoded.clone(); + | ^^^^^^^^^^^^^^^ + | +help: try dereferencing it + | +LL | let _ = &&(*encoded).clone(); + | ^^^^^^^^^^^^^^^^^^^ +help: or try being explicit if you are sure, that you want to clone a reference + | +LL | let _ = &<&[u8]>::clone(encoded); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:108:14 + | +LL | Some(try_opt!(Some(rc)).clone()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `Rc::::clone(&try_opt!(Some(rc)))` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_filter_map.rs b/src/tools/clippy/tests/ui/unnecessary_filter_map.rs new file mode 100644 index 0000000000..af858e4abc --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_filter_map.rs @@ -0,0 +1,17 @@ +fn main() { + let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None }); + let _ = (0..4).filter_map(|x| { + if x > 1 { + return Some(x); + }; + None + }); + let _ = (0..4).filter_map(|x| match x { + 0 | 1 => None, + _ => Some(x), + }); + + let _ = (0..4).filter_map(|x| Some(x + 1)); + + let _ = (0..4).filter_map(i32::checked_abs); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr b/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr new file mode 100644 index 0000000000..041829c3c7 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr @@ -0,0 +1,38 @@ +error: this `.filter_map` can be written more simply using `.filter` + --> $DIR/unnecessary_filter_map.rs:2:13 + | +LL | let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unnecessary-filter-map` implied by `-D warnings` + +error: this `.filter_map` can be written more simply using `.filter` + --> $DIR/unnecessary_filter_map.rs:3:13 + | +LL | let _ = (0..4).filter_map(|x| { + | _____________^ +LL | | if x > 1 { +LL | | return Some(x); +LL | | }; +LL | | None +LL | | }); + | |______^ + +error: this `.filter_map` can be written more simply using `.filter` + --> $DIR/unnecessary_filter_map.rs:9:13 + | +LL | let _ = (0..4).filter_map(|x| match x { + | _____________^ +LL | | 0 | 1 => None, +LL | | _ => Some(x), +LL | | }); + | |______^ + +error: this `.filter_map` can be written more simply using `.map` + --> $DIR/unnecessary_filter_map.rs:14:13 + | +LL | let _ = (0..4).filter_map(|x| Some(x + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_fold.fixed b/src/tools/clippy/tests/ui/unnecessary_fold.fixed new file mode 100644 index 0000000000..52300a3b64 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_fold.fixed @@ -0,0 +1,52 @@ +// run-rustfix + +#![allow(dead_code)] + +/// Calls which should trigger the `UNNECESSARY_FOLD` lint +fn unnecessary_fold() { + // Can be replaced by .any + let _ = (0..3).any(|x| x > 2); + // Can be replaced by .all + let _ = (0..3).all(|x| x > 2); + // Can be replaced by .sum + let _: i32 = (0..3).sum(); + // Can be replaced by .product + let _: i32 = (0..3).product(); +} + +/// Should trigger the `UNNECESSARY_FOLD` lint, with an error span including exactly `.fold(...)` +fn unnecessary_fold_span_for_multi_element_chain() { + let _: bool = (0..3).map(|x| 2 * x).any(|x| x > 2); +} + +/// Calls which should not trigger the `UNNECESSARY_FOLD` lint +fn unnecessary_fold_should_ignore() { + let _ = (0..3).fold(true, |acc, x| acc || x > 2); + let _ = (0..3).fold(false, |acc, x| acc && x > 2); + let _ = (0..3).fold(1, |acc, x| acc + x); + let _ = (0..3).fold(0, |acc, x| acc * x); + let _ = (0..3).fold(0, |acc, x| 1 + acc + x); + + // We only match against an accumulator on the left + // hand side. We could lint for .sum and .product when + // it's on the right, but don't for now (and this wouldn't + // be valid if we extended the lint to cover arbitrary numeric + // types). + let _ = (0..3).fold(false, |acc, x| x > 2 || acc); + let _ = (0..3).fold(true, |acc, x| x > 2 && acc); + let _ = (0..3).fold(0, |acc, x| x + acc); + let _ = (0..3).fold(1, |acc, x| x * acc); + + let _ = [(0..2), (0..3)].iter().fold(0, |a, b| a + b.len()); + let _ = [(0..2), (0..3)].iter().fold(1, |a, b| a * b.len()); +} + +/// Should lint only the line containing the fold +fn unnecessary_fold_over_multiple_lines() { + let _ = (0..3) + .map(|x| x + 1) + .filter(|x| x % 2 == 0) + .any(|x| x > 2); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unnecessary_fold.rs b/src/tools/clippy/tests/ui/unnecessary_fold.rs new file mode 100644 index 0000000000..4028d80c0a --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_fold.rs @@ -0,0 +1,52 @@ +// run-rustfix + +#![allow(dead_code)] + +/// Calls which should trigger the `UNNECESSARY_FOLD` lint +fn unnecessary_fold() { + // Can be replaced by .any + let _ = (0..3).fold(false, |acc, x| acc || x > 2); + // Can be replaced by .all + let _ = (0..3).fold(true, |acc, x| acc && x > 2); + // Can be replaced by .sum + let _: i32 = (0..3).fold(0, |acc, x| acc + x); + // Can be replaced by .product + let _: i32 = (0..3).fold(1, |acc, x| acc * x); +} + +/// Should trigger the `UNNECESSARY_FOLD` lint, with an error span including exactly `.fold(...)` +fn unnecessary_fold_span_for_multi_element_chain() { + let _: bool = (0..3).map(|x| 2 * x).fold(false, |acc, x| acc || x > 2); +} + +/// Calls which should not trigger the `UNNECESSARY_FOLD` lint +fn unnecessary_fold_should_ignore() { + let _ = (0..3).fold(true, |acc, x| acc || x > 2); + let _ = (0..3).fold(false, |acc, x| acc && x > 2); + let _ = (0..3).fold(1, |acc, x| acc + x); + let _ = (0..3).fold(0, |acc, x| acc * x); + let _ = (0..3).fold(0, |acc, x| 1 + acc + x); + + // We only match against an accumulator on the left + // hand side. We could lint for .sum and .product when + // it's on the right, but don't for now (and this wouldn't + // be valid if we extended the lint to cover arbitrary numeric + // types). + let _ = (0..3).fold(false, |acc, x| x > 2 || acc); + let _ = (0..3).fold(true, |acc, x| x > 2 && acc); + let _ = (0..3).fold(0, |acc, x| x + acc); + let _ = (0..3).fold(1, |acc, x| x * acc); + + let _ = [(0..2), (0..3)].iter().fold(0, |a, b| a + b.len()); + let _ = [(0..2), (0..3)].iter().fold(1, |a, b| a * b.len()); +} + +/// Should lint only the line containing the fold +fn unnecessary_fold_over_multiple_lines() { + let _ = (0..3) + .map(|x| x + 1) + .filter(|x| x % 2 == 0) + .fold(false, |acc, x| acc || x > 2); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unnecessary_fold.stderr b/src/tools/clippy/tests/ui/unnecessary_fold.stderr new file mode 100644 index 0000000000..22c44588ab --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_fold.stderr @@ -0,0 +1,40 @@ +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:8:20 + | +LL | let _ = (0..3).fold(false, |acc, x| acc || x > 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` + | + = note: `-D clippy::unnecessary-fold` implied by `-D warnings` + +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:10:20 + | +LL | let _ = (0..3).fold(true, |acc, x| acc && x > 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `all(|x| x > 2)` + +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:12:25 + | +LL | let _: i32 = (0..3).fold(0, |acc, x| acc + x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `sum()` + +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:14:25 + | +LL | let _: i32 = (0..3).fold(1, |acc, x| acc * x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `product()` + +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:19:41 + | +LL | let _: bool = (0..3).map(|x| 2 * x).fold(false, |acc, x| acc || x > 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` + +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:49:10 + | +LL | .fold(false, |acc, x| acc || x > 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_lazy_eval.fixed b/src/tools/clippy/tests/ui/unnecessary_lazy_eval.fixed new file mode 100644 index 0000000000..4ba2a0a5db --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_lazy_eval.fixed @@ -0,0 +1,122 @@ +// run-rustfix +#![warn(clippy::unnecessary_lazy_evaluations)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::bind_instead_of_map)] +#![allow(clippy::map_identity)] + +struct Deep(Option); + +#[derive(Copy, Clone)] +struct SomeStruct { + some_field: usize, +} + +impl SomeStruct { + fn return_some_field(&self) -> usize { + self.some_field + } +} + +fn some_call() -> T { + T::default() +} + +fn main() { + let astronomers_pi = 10; + let ext_arr: [usize; 1] = [2]; + let ext_str = SomeStruct { some_field: 10 }; + + let mut opt = Some(42); + let ext_opt = Some(42); + let nested_opt = Some(Some(42)); + let nested_tuple_opt = Some(Some((42, 43))); + + // Should lint - Option + let _ = opt.unwrap_or(2); + let _ = opt.unwrap_or(astronomers_pi); + let _ = opt.unwrap_or(ext_str.some_field); + let _ = opt.unwrap_or_else(|| ext_arr[0]); + let _ = opt.and(ext_opt); + let _ = opt.or(ext_opt); + let _ = opt.or(None); + let _ = opt.get_or_insert(2); + let _ = opt.ok_or(2); + let _ = nested_tuple_opt.unwrap_or(Some((1, 2))); + + // Cases when unwrap is not called on a simple variable + let _ = Some(10).unwrap_or(2); + let _ = Some(10).and(ext_opt); + let _: Option = None.or(ext_opt); + let _ = None.get_or_insert(2); + let _: Result = None.ok_or(2); + let _: Option = None.or(None); + + let mut deep = Deep(Some(42)); + let _ = deep.0.unwrap_or(2); + let _ = deep.0.and(ext_opt); + let _ = deep.0.or(None); + let _ = deep.0.get_or_insert(2); + let _ = deep.0.ok_or(2); + + // Should not lint - Option + let _ = opt.unwrap_or_else(|| ext_str.return_some_field()); + let _ = nested_opt.unwrap_or_else(|| Some(some_call())); + let _ = nested_tuple_opt.unwrap_or_else(|| Some((some_call(), some_call()))); + let _ = opt.or_else(some_call); + let _ = opt.or_else(|| some_call()); + let _: Result = opt.ok_or_else(|| some_call()); + let _: Result = opt.ok_or_else(some_call); + let _ = deep.0.get_or_insert_with(|| some_call()); + let _ = deep.0.or_else(some_call); + let _ = deep.0.or_else(|| some_call()); + let _ = opt.ok_or_else(|| ext_arr[0]); + + // should not lint, bind_instead_of_map takes priority + let _ = Some(10).and_then(|idx| Some(ext_arr[idx])); + let _ = Some(10).and_then(|idx| Some(idx)); + + // should lint, bind_instead_of_map doesn't apply + let _: Option = None.or(Some(3)); + let _ = deep.0.or(Some(3)); + let _ = opt.or(Some(3)); + + // Should lint - Result + let res: Result = Err(5); + let res2: Result = Err(SomeStruct { some_field: 5 }); + + let _ = res2.unwrap_or(2); + let _ = res2.unwrap_or(astronomers_pi); + let _ = res2.unwrap_or(ext_str.some_field); + + // Should not lint - Result + let _ = res.unwrap_or_else(|err| err); + let _ = res.unwrap_or_else(|err| ext_arr[err]); + let _ = res2.unwrap_or_else(|err| err.some_field); + let _ = res2.unwrap_or_else(|err| err.return_some_field()); + let _ = res2.unwrap_or_else(|_| ext_str.return_some_field()); + + // should not lint, bind_instead_of_map takes priority + let _: Result = res.and_then(|x| Ok(x)); + let _: Result = res.or_else(|err| Err(err)); + + let _: Result = res.and_then(|_| Ok(2)); + let _: Result = res.and_then(|_| Ok(astronomers_pi)); + let _: Result = res.and_then(|_| Ok(ext_str.some_field)); + + let _: Result = res.or_else(|_| Err(2)); + let _: Result = res.or_else(|_| Err(astronomers_pi)); + let _: Result = res.or_else(|_| Err(ext_str.some_field)); + + // should lint, bind_instead_of_map doesn't apply + let _: Result = res.and(Err(2)); + let _: Result = res.and(Err(astronomers_pi)); + let _: Result = res.and(Err(ext_str.some_field)); + + let _: Result = res.or(Ok(2)); + let _: Result = res.or(Ok(astronomers_pi)); + let _: Result = res.or(Ok(ext_str.some_field)); + + // neither bind_instead_of_map nor unnecessary_lazy_eval applies here + let _: Result = res.and_then(|x| Err(x)); + let _: Result = res.or_else(|err| Ok(err)); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_lazy_eval.rs b/src/tools/clippy/tests/ui/unnecessary_lazy_eval.rs new file mode 100644 index 0000000000..466915217e --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_lazy_eval.rs @@ -0,0 +1,122 @@ +// run-rustfix +#![warn(clippy::unnecessary_lazy_evaluations)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::bind_instead_of_map)] +#![allow(clippy::map_identity)] + +struct Deep(Option); + +#[derive(Copy, Clone)] +struct SomeStruct { + some_field: usize, +} + +impl SomeStruct { + fn return_some_field(&self) -> usize { + self.some_field + } +} + +fn some_call() -> T { + T::default() +} + +fn main() { + let astronomers_pi = 10; + let ext_arr: [usize; 1] = [2]; + let ext_str = SomeStruct { some_field: 10 }; + + let mut opt = Some(42); + let ext_opt = Some(42); + let nested_opt = Some(Some(42)); + let nested_tuple_opt = Some(Some((42, 43))); + + // Should lint - Option + let _ = opt.unwrap_or_else(|| 2); + let _ = opt.unwrap_or_else(|| astronomers_pi); + let _ = opt.unwrap_or_else(|| ext_str.some_field); + let _ = opt.unwrap_or_else(|| ext_arr[0]); + let _ = opt.and_then(|_| ext_opt); + let _ = opt.or_else(|| ext_opt); + let _ = opt.or_else(|| None); + let _ = opt.get_or_insert_with(|| 2); + let _ = opt.ok_or_else(|| 2); + let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2))); + + // Cases when unwrap is not called on a simple variable + let _ = Some(10).unwrap_or_else(|| 2); + let _ = Some(10).and_then(|_| ext_opt); + let _: Option = None.or_else(|| ext_opt); + let _ = None.get_or_insert_with(|| 2); + let _: Result = None.ok_or_else(|| 2); + let _: Option = None.or_else(|| None); + + let mut deep = Deep(Some(42)); + let _ = deep.0.unwrap_or_else(|| 2); + let _ = deep.0.and_then(|_| ext_opt); + let _ = deep.0.or_else(|| None); + let _ = deep.0.get_or_insert_with(|| 2); + let _ = deep.0.ok_or_else(|| 2); + + // Should not lint - Option + let _ = opt.unwrap_or_else(|| ext_str.return_some_field()); + let _ = nested_opt.unwrap_or_else(|| Some(some_call())); + let _ = nested_tuple_opt.unwrap_or_else(|| Some((some_call(), some_call()))); + let _ = opt.or_else(some_call); + let _ = opt.or_else(|| some_call()); + let _: Result = opt.ok_or_else(|| some_call()); + let _: Result = opt.ok_or_else(some_call); + let _ = deep.0.get_or_insert_with(|| some_call()); + let _ = deep.0.or_else(some_call); + let _ = deep.0.or_else(|| some_call()); + let _ = opt.ok_or_else(|| ext_arr[0]); + + // should not lint, bind_instead_of_map takes priority + let _ = Some(10).and_then(|idx| Some(ext_arr[idx])); + let _ = Some(10).and_then(|idx| Some(idx)); + + // should lint, bind_instead_of_map doesn't apply + let _: Option = None.or_else(|| Some(3)); + let _ = deep.0.or_else(|| Some(3)); + let _ = opt.or_else(|| Some(3)); + + // Should lint - Result + let res: Result = Err(5); + let res2: Result = Err(SomeStruct { some_field: 5 }); + + let _ = res2.unwrap_or_else(|_| 2); + let _ = res2.unwrap_or_else(|_| astronomers_pi); + let _ = res2.unwrap_or_else(|_| ext_str.some_field); + + // Should not lint - Result + let _ = res.unwrap_or_else(|err| err); + let _ = res.unwrap_or_else(|err| ext_arr[err]); + let _ = res2.unwrap_or_else(|err| err.some_field); + let _ = res2.unwrap_or_else(|err| err.return_some_field()); + let _ = res2.unwrap_or_else(|_| ext_str.return_some_field()); + + // should not lint, bind_instead_of_map takes priority + let _: Result = res.and_then(|x| Ok(x)); + let _: Result = res.or_else(|err| Err(err)); + + let _: Result = res.and_then(|_| Ok(2)); + let _: Result = res.and_then(|_| Ok(astronomers_pi)); + let _: Result = res.and_then(|_| Ok(ext_str.some_field)); + + let _: Result = res.or_else(|_| Err(2)); + let _: Result = res.or_else(|_| Err(astronomers_pi)); + let _: Result = res.or_else(|_| Err(ext_str.some_field)); + + // should lint, bind_instead_of_map doesn't apply + let _: Result = res.and_then(|_| Err(2)); + let _: Result = res.and_then(|_| Err(astronomers_pi)); + let _: Result = res.and_then(|_| Err(ext_str.some_field)); + + let _: Result = res.or_else(|_| Ok(2)); + let _: Result = res.or_else(|_| Ok(astronomers_pi)); + let _: Result = res.or_else(|_| Ok(ext_str.some_field)); + + // neither bind_instead_of_map nor unnecessary_lazy_eval applies here + let _: Result = res.and_then(|x| Err(x)); + let _: Result = res.or_else(|err| Ok(err)); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_lazy_eval.stderr b/src/tools/clippy/tests/ui/unnecessary_lazy_eval.stderr new file mode 100644 index 0000000000..cc94bd5cd9 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_lazy_eval.stderr @@ -0,0 +1,196 @@ +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:35:13 + | +LL | let _ = opt.unwrap_or_else(|| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(2)` + | + = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:36:13 + | +LL | let _ = opt.unwrap_or_else(|| astronomers_pi); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(astronomers_pi)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:37:13 + | +LL | let _ = opt.unwrap_or_else(|| ext_str.some_field); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(ext_str.some_field)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:39:13 + | +LL | let _ = opt.and_then(|_| ext_opt); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `opt.and(ext_opt)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:40:13 + | +LL | let _ = opt.or_else(|| ext_opt); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(ext_opt)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:41:13 + | +LL | let _ = opt.or_else(|| None); + | ^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(None)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:42:13 + | +LL | let _ = opt.get_or_insert_with(|| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `opt.get_or_insert(2)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:43:13 + | +LL | let _ = opt.ok_or_else(|| 2); + | ^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `opt.ok_or(2)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:44:13 + | +LL | let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `nested_tuple_opt.unwrap_or(Some((1, 2)))` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:47:13 + | +LL | let _ = Some(10).unwrap_or_else(|| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Some(10).unwrap_or(2)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:48:13 + | +LL | let _ = Some(10).and_then(|_| ext_opt); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `Some(10).and(ext_opt)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:49:28 + | +LL | let _: Option = None.or_else(|| ext_opt); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(ext_opt)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:50:13 + | +LL | let _ = None.get_or_insert_with(|| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `None.get_or_insert(2)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:51:35 + | +LL | let _: Result = None.ok_or_else(|| 2); + | ^^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `None.ok_or(2)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:52:28 + | +LL | let _: Option = None.or_else(|| None); + | ^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(None)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:55:13 + | +LL | let _ = deep.0.unwrap_or_else(|| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `deep.0.unwrap_or(2)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:56:13 + | +LL | let _ = deep.0.and_then(|_| ext_opt); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `deep.0.and(ext_opt)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:57:13 + | +LL | let _ = deep.0.or_else(|| None); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `deep.0.or(None)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:58:13 + | +LL | let _ = deep.0.get_or_insert_with(|| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `deep.0.get_or_insert(2)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:59:13 + | +LL | let _ = deep.0.ok_or_else(|| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `deep.0.ok_or(2)` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:79:28 + | +LL | let _: Option = None.or_else(|| Some(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(Some(3))` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:80:13 + | +LL | let _ = deep.0.or_else(|| Some(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `deep.0.or(Some(3))` + +error: unnecessary closure used to substitute value for `Option::None` + --> $DIR/unnecessary_lazy_eval.rs:81:13 + | +LL | let _ = opt.or_else(|| Some(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(Some(3))` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:87:13 + | +LL | let _ = res2.unwrap_or_else(|_| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(2)` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:88:13 + | +LL | let _ = res2.unwrap_or_else(|_| astronomers_pi); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(astronomers_pi)` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:89:13 + | +LL | let _ = res2.unwrap_or_else(|_| ext_str.some_field); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(ext_str.some_field)` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:111:35 + | +LL | let _: Result = res.and_then(|_| Err(2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(2))` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:112:35 + | +LL | let _: Result = res.and_then(|_| Err(astronomers_pi)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(astronomers_pi))` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:113:35 + | +LL | let _: Result = res.and_then(|_| Err(ext_str.some_field)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(ext_str.some_field))` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:115:35 + | +LL | let _: Result = res.or_else(|_| Ok(2)); + | ^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(2))` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:116:35 + | +LL | let _: Result = res.or_else(|_| Ok(astronomers_pi)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(astronomers_pi))` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:117:35 + | +LL | let _: Result = res.or_else(|_| Ok(ext_str.some_field)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(ext_str.some_field))` + +error: aborting due to 32 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_lazy_eval_unfixable.rs b/src/tools/clippy/tests/ui/unnecessary_lazy_eval_unfixable.rs new file mode 100644 index 0000000000..b05dd143bf --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_lazy_eval_unfixable.rs @@ -0,0 +1,22 @@ +#![warn(clippy::unnecessary_lazy_evaluations)] + +struct Deep(Option); + +#[derive(Copy, Clone)] +struct SomeStruct { + some_field: usize, +} + +fn main() { + // fix will break type inference + let _ = Ok(1).unwrap_or_else(|()| 2); + mod e { + pub struct E; + } + let _ = Ok(1).unwrap_or_else(|e::E| 2); + let _ = Ok(1).unwrap_or_else(|SomeStruct { .. }| 2); + + // Fix #6343 + let arr = [(Some(1),)]; + Some(&0).and_then(|&i| arr[i].0); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_lazy_eval_unfixable.stderr b/src/tools/clippy/tests/ui/unnecessary_lazy_eval_unfixable.stderr new file mode 100644 index 0000000000..75674b0a9d --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_lazy_eval_unfixable.stderr @@ -0,0 +1,22 @@ +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval_unfixable.rs:12:13 + | +LL | let _ = Ok(1).unwrap_or_else(|()| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)` + | + = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval_unfixable.rs:16:13 + | +LL | let _ = Ok(1).unwrap_or_else(|e::E| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)` + +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval_unfixable.rs:17:13 + | +LL | let _ = Ok(1).unwrap_or_else(|SomeStruct { .. }| 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_operation.fixed b/src/tools/clippy/tests/ui/unnecessary_operation.fixed new file mode 100644 index 0000000000..2fca96c4cd --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_operation.fixed @@ -0,0 +1,79 @@ +// run-rustfix + +#![feature(box_syntax)] +#![allow(clippy::deref_addrof, dead_code, unused, clippy::no_effect)] +#![warn(clippy::unnecessary_operation)] + +struct Tuple(i32); +struct Struct { + field: i32, +} +enum Enum { + Tuple(i32), + Struct { field: i32 }, +} +struct DropStruct { + field: i32, +} +impl Drop for DropStruct { + fn drop(&mut self) {} +} +struct DropTuple(i32); +impl Drop for DropTuple { + fn drop(&mut self) {} +} +enum DropEnum { + Tuple(i32), + Struct { field: i32 }, +} +impl Drop for DropEnum { + fn drop(&mut self) {} +} +struct FooString { + s: String, +} + +fn get_number() -> i32 { + 0 +} + +fn get_usize() -> usize { + 0 +} +fn get_struct() -> Struct { + Struct { field: 0 } +} +fn get_drop_struct() -> DropStruct { + DropStruct { field: 0 } +} + +fn main() { + get_number(); + get_number(); + get_struct(); + get_number(); + get_number(); + 5;get_number(); + get_number(); + get_number(); + 5;6;get_number(); + get_number(); + get_number(); + get_number(); + 5;get_number(); + 42;get_number(); + [42, 55];get_usize(); + 42;get_number(); + get_number(); + [42; 55];get_usize(); + get_number(); + String::from("blah"); + + // Do not warn + DropTuple(get_number()); + DropStruct { field: get_number() }; + DropStruct { field: get_number() }; + DropStruct { ..get_drop_struct() }; + DropEnum::Tuple(get_number()); + DropEnum::Struct { field: get_number() }; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_operation.rs b/src/tools/clippy/tests/ui/unnecessary_operation.rs new file mode 100644 index 0000000000..08cb9ab522 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_operation.rs @@ -0,0 +1,83 @@ +// run-rustfix + +#![feature(box_syntax)] +#![allow(clippy::deref_addrof, dead_code, unused, clippy::no_effect)] +#![warn(clippy::unnecessary_operation)] + +struct Tuple(i32); +struct Struct { + field: i32, +} +enum Enum { + Tuple(i32), + Struct { field: i32 }, +} +struct DropStruct { + field: i32, +} +impl Drop for DropStruct { + fn drop(&mut self) {} +} +struct DropTuple(i32); +impl Drop for DropTuple { + fn drop(&mut self) {} +} +enum DropEnum { + Tuple(i32), + Struct { field: i32 }, +} +impl Drop for DropEnum { + fn drop(&mut self) {} +} +struct FooString { + s: String, +} + +fn get_number() -> i32 { + 0 +} + +fn get_usize() -> usize { + 0 +} +fn get_struct() -> Struct { + Struct { field: 0 } +} +fn get_drop_struct() -> DropStruct { + DropStruct { field: 0 } +} + +fn main() { + Tuple(get_number()); + Struct { field: get_number() }; + Struct { ..get_struct() }; + Enum::Tuple(get_number()); + Enum::Struct { field: get_number() }; + 5 + get_number(); + *&get_number(); + &get_number(); + (5, 6, get_number()); + box get_number(); + get_number()..; + ..get_number(); + 5..get_number(); + [42, get_number()]; + [42, 55][get_usize()]; + (42, get_number()).1; + [get_number(); 55]; + [42; 55][get_usize()]; + { + get_number() + }; + FooString { + s: String::from("blah"), + }; + + // Do not warn + DropTuple(get_number()); + DropStruct { field: get_number() }; + DropStruct { field: get_number() }; + DropStruct { ..get_drop_struct() }; + DropEnum::Tuple(get_number()); + DropEnum::Struct { field: get_number() }; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_operation.stderr b/src/tools/clippy/tests/ui/unnecessary_operation.stderr new file mode 100644 index 0000000000..f88c9f9908 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_operation.stderr @@ -0,0 +1,128 @@ +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:51:5 + | +LL | Tuple(get_number()); + | ^^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + | + = note: `-D clippy::unnecessary-operation` implied by `-D warnings` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:52:5 + | +LL | Struct { field: get_number() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:53:5 + | +LL | Struct { ..get_struct() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_struct();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:54:5 + | +LL | Enum::Tuple(get_number()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:55:5 + | +LL | Enum::Struct { field: get_number() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:56:5 + | +LL | 5 + get_number(); + | ^^^^^^^^^^^^^^^^^ help: replace it with: `5;get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:57:5 + | +LL | *&get_number(); + | ^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:58:5 + | +LL | &get_number(); + | ^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:59:5 + | +LL | (5, 6, get_number()); + | ^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `5;6;get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:60:5 + | +LL | box get_number(); + | ^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:61:5 + | +LL | get_number()..; + | ^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:62:5 + | +LL | ..get_number(); + | ^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:63:5 + | +LL | 5..get_number(); + | ^^^^^^^^^^^^^^^^ help: replace it with: `5;get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:64:5 + | +LL | [42, get_number()]; + | ^^^^^^^^^^^^^^^^^^^ help: replace it with: `42;get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:65:5 + | +LL | [42, 55][get_usize()]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `[42, 55];get_usize();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:66:5 + | +LL | (42, get_number()).1; + | ^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `42;get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:67:5 + | +LL | [get_number(); 55]; + | ^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:68:5 + | +LL | [42; 55][get_usize()]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `[42; 55];get_usize();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:69:5 + | +LL | / { +LL | | get_number() +LL | | }; + | |______^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:72:5 + | +LL | / FooString { +LL | | s: String::from("blah"), +LL | | }; + | |______^ help: replace it with: `String::from("blah");` + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_ref.fixed b/src/tools/clippy/tests/ui/unnecessary_ref.fixed new file mode 100644 index 0000000000..d927bae976 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_ref.fixed @@ -0,0 +1,23 @@ +// run-rustfix + +#![feature(stmt_expr_attributes)] +#![allow(unused_variables, dead_code)] + +struct Outer { + inner: u32, +} + +#[deny(clippy::ref_in_deref)] +fn main() { + let outer = Outer { inner: 0 }; + let inner = outer.inner; +} + +struct Apple; +impl Apple { + fn hello(&self) {} +} +struct Package(pub *const Apple); +fn foobar(package: *const Package) { + unsafe { &*(*package).0 }.hello(); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_ref.rs b/src/tools/clippy/tests/ui/unnecessary_ref.rs new file mode 100644 index 0000000000..86bfb76ec2 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_ref.rs @@ -0,0 +1,23 @@ +// run-rustfix + +#![feature(stmt_expr_attributes)] +#![allow(unused_variables, dead_code)] + +struct Outer { + inner: u32, +} + +#[deny(clippy::ref_in_deref)] +fn main() { + let outer = Outer { inner: 0 }; + let inner = (&outer).inner; +} + +struct Apple; +impl Apple { + fn hello(&self) {} +} +struct Package(pub *const Apple); +fn foobar(package: *const Package) { + unsafe { &*(&*package).0 }.hello(); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_ref.stderr b/src/tools/clippy/tests/ui/unnecessary_ref.stderr new file mode 100644 index 0000000000..436f4bcf73 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_ref.stderr @@ -0,0 +1,22 @@ +error: creating a reference that is immediately dereferenced + --> $DIR/unnecessary_ref.rs:13:17 + | +LL | let inner = (&outer).inner; + | ^^^^^^^^ help: try this: `outer` + | +note: the lint level is defined here + --> $DIR/unnecessary_ref.rs:10:8 + | +LL | #[deny(clippy::ref_in_deref)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: creating a reference that is immediately dereferenced + --> $DIR/unnecessary_ref.rs:22:16 + | +LL | unsafe { &*(&*package).0 }.hello(); + | ^^^^^^^^^^^ help: try this: `(*package)` + | + = note: `-D clippy::ref-in-deref` implied by `-D warnings` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_sort_by.fixed b/src/tools/clippy/tests/ui/unnecessary_sort_by.fixed new file mode 100644 index 0000000000..b45b27d8f2 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_sort_by.fixed @@ -0,0 +1,100 @@ +// run-rustfix + +#![allow(clippy::stable_sort_primitive)] + +use std::cmp::Reverse; + +fn unnecessary_sort_by() { + fn id(x: isize) -> isize { + x + } + + let mut vec: Vec = vec![3, 6, 1, 2, 5]; + // Forward examples + vec.sort(); + vec.sort_unstable(); + vec.sort_by_key(|a| (a + 5).abs()); + vec.sort_unstable_by_key(|a| id(-a)); + // Reverse examples + vec.sort_by(|a, b| b.cmp(a)); // not linted to avoid suggesting `Reverse(b)` which would borrow + vec.sort_by_key(|b| Reverse((b + 5).abs())); + vec.sort_unstable_by_key(|b| Reverse(id(-b))); + // Negative examples (shouldn't be changed) + let c = &7; + vec.sort_by(|a, b| (b - a).cmp(&(a - b))); + vec.sort_by(|_, b| b.cmp(&5)); + vec.sort_by(|_, b| b.cmp(c)); + vec.sort_unstable_by(|a, _| a.cmp(c)); + + // Vectors of references are fine as long as the resulting key does not borrow + let mut vec: Vec<&&&isize> = vec![&&&3, &&&6, &&&1, &&&2, &&&5]; + vec.sort_by_key(|a| (***a).abs()); + vec.sort_unstable_by_key(|a| (***a).abs()); + // `Reverse(b)` would borrow in the following cases, don't lint + vec.sort_by(|a, b| b.cmp(a)); + vec.sort_unstable_by(|a, b| b.cmp(a)); +} + +// Do not suggest returning a reference to the closure parameter of `Vec::sort_by_key` +mod issue_5754 { + #[derive(Clone, Copy)] + struct Test(usize); + + #[derive(PartialOrd, Ord, PartialEq, Eq)] + struct Wrapper<'a>(&'a usize); + + impl Test { + fn name(&self) -> &usize { + &self.0 + } + + fn wrapped(&self) -> Wrapper<'_> { + Wrapper(&self.0) + } + } + + pub fn test() { + let mut args: Vec = vec![]; + + // Forward + args.sort_by(|a, b| a.name().cmp(b.name())); + args.sort_by(|a, b| a.wrapped().cmp(&b.wrapped())); + args.sort_unstable_by(|a, b| a.name().cmp(b.name())); + args.sort_unstable_by(|a, b| a.wrapped().cmp(&b.wrapped())); + // Reverse + args.sort_by(|a, b| b.name().cmp(a.name())); + args.sort_by(|a, b| b.wrapped().cmp(&a.wrapped())); + args.sort_unstable_by(|a, b| b.name().cmp(a.name())); + args.sort_unstable_by(|a, b| b.wrapped().cmp(&a.wrapped())); + } +} + +// The closure parameter is not dereferenced anymore, so non-Copy types can be linted +mod issue_6001 { + use super::*; + struct Test(String); + + impl Test { + // Return an owned type so that we don't hit the fix for 5754 + fn name(&self) -> String { + self.0.clone() + } + } + + pub fn test() { + let mut args: Vec = vec![]; + + // Forward + args.sort_by_key(|a| a.name()); + args.sort_unstable_by_key(|a| a.name()); + // Reverse + args.sort_by_key(|b| Reverse(b.name())); + args.sort_unstable_by_key(|b| Reverse(b.name())); + } +} + +fn main() { + unnecessary_sort_by(); + issue_5754::test(); + issue_6001::test(); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_sort_by.rs b/src/tools/clippy/tests/ui/unnecessary_sort_by.rs new file mode 100644 index 0000000000..be2abe7f70 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_sort_by.rs @@ -0,0 +1,100 @@ +// run-rustfix + +#![allow(clippy::stable_sort_primitive)] + +use std::cmp::Reverse; + +fn unnecessary_sort_by() { + fn id(x: isize) -> isize { + x + } + + let mut vec: Vec = vec![3, 6, 1, 2, 5]; + // Forward examples + vec.sort_by(|a, b| a.cmp(b)); + vec.sort_unstable_by(|a, b| a.cmp(b)); + vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs())); + vec.sort_unstable_by(|a, b| id(-a).cmp(&id(-b))); + // Reverse examples + vec.sort_by(|a, b| b.cmp(a)); // not linted to avoid suggesting `Reverse(b)` which would borrow + vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); + vec.sort_unstable_by(|a, b| id(-b).cmp(&id(-a))); + // Negative examples (shouldn't be changed) + let c = &7; + vec.sort_by(|a, b| (b - a).cmp(&(a - b))); + vec.sort_by(|_, b| b.cmp(&5)); + vec.sort_by(|_, b| b.cmp(c)); + vec.sort_unstable_by(|a, _| a.cmp(c)); + + // Vectors of references are fine as long as the resulting key does not borrow + let mut vec: Vec<&&&isize> = vec![&&&3, &&&6, &&&1, &&&2, &&&5]; + vec.sort_by(|a, b| (***a).abs().cmp(&(***b).abs())); + vec.sort_unstable_by(|a, b| (***a).abs().cmp(&(***b).abs())); + // `Reverse(b)` would borrow in the following cases, don't lint + vec.sort_by(|a, b| b.cmp(a)); + vec.sort_unstable_by(|a, b| b.cmp(a)); +} + +// Do not suggest returning a reference to the closure parameter of `Vec::sort_by_key` +mod issue_5754 { + #[derive(Clone, Copy)] + struct Test(usize); + + #[derive(PartialOrd, Ord, PartialEq, Eq)] + struct Wrapper<'a>(&'a usize); + + impl Test { + fn name(&self) -> &usize { + &self.0 + } + + fn wrapped(&self) -> Wrapper<'_> { + Wrapper(&self.0) + } + } + + pub fn test() { + let mut args: Vec = vec![]; + + // Forward + args.sort_by(|a, b| a.name().cmp(b.name())); + args.sort_by(|a, b| a.wrapped().cmp(&b.wrapped())); + args.sort_unstable_by(|a, b| a.name().cmp(b.name())); + args.sort_unstable_by(|a, b| a.wrapped().cmp(&b.wrapped())); + // Reverse + args.sort_by(|a, b| b.name().cmp(a.name())); + args.sort_by(|a, b| b.wrapped().cmp(&a.wrapped())); + args.sort_unstable_by(|a, b| b.name().cmp(a.name())); + args.sort_unstable_by(|a, b| b.wrapped().cmp(&a.wrapped())); + } +} + +// The closure parameter is not dereferenced anymore, so non-Copy types can be linted +mod issue_6001 { + use super::*; + struct Test(String); + + impl Test { + // Return an owned type so that we don't hit the fix for 5754 + fn name(&self) -> String { + self.0.clone() + } + } + + pub fn test() { + let mut args: Vec = vec![]; + + // Forward + args.sort_by(|a, b| a.name().cmp(&b.name())); + args.sort_unstable_by(|a, b| a.name().cmp(&b.name())); + // Reverse + args.sort_by(|a, b| b.name().cmp(&a.name())); + args.sort_unstable_by(|a, b| b.name().cmp(&a.name())); + } +} + +fn main() { + unnecessary_sort_by(); + issue_5754::test(); + issue_6001::test(); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_sort_by.stderr b/src/tools/clippy/tests/ui/unnecessary_sort_by.stderr new file mode 100644 index 0000000000..50607933e1 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_sort_by.stderr @@ -0,0 +1,76 @@ +error: use Vec::sort here instead + --> $DIR/unnecessary_sort_by.rs:14:5 + | +LL | vec.sort_by(|a, b| a.cmp(b)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort()` + | + = note: `-D clippy::unnecessary-sort-by` implied by `-D warnings` + +error: use Vec::sort here instead + --> $DIR/unnecessary_sort_by.rs:15:5 + | +LL | vec.sort_unstable_by(|a, b| a.cmp(b)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable()` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:16:5 + | +LL | vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| (a + 5).abs())` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:17:5 + | +LL | vec.sort_unstable_by(|a, b| id(-a).cmp(&id(-b))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|a| id(-a))` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:20:5 + | +LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|b| Reverse((b + 5).abs()))` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:21:5 + | +LL | vec.sort_unstable_by(|a, b| id(-b).cmp(&id(-a))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|b| Reverse(id(-b)))` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:31:5 + | +LL | vec.sort_by(|a, b| (***a).abs().cmp(&(***b).abs())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| (***a).abs())` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:32:5 + | +LL | vec.sort_unstable_by(|a, b| (***a).abs().cmp(&(***b).abs())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|a| (***a).abs())` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:88:9 + | +LL | args.sort_by(|a, b| a.name().cmp(&b.name())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_by_key(|a| a.name())` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:89:9 + | +LL | args.sort_unstable_by(|a, b| a.name().cmp(&b.name())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|a| a.name())` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:91:9 + | +LL | args.sort_by(|a, b| b.name().cmp(&a.name())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_by_key(|b| Reverse(b.name()))` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:92:9 + | +LL | args.sort_unstable_by(|a, b| b.name().cmp(&a.name())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|b| Reverse(b.name()))` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_wraps.rs b/src/tools/clippy/tests/ui/unnecessary_wraps.rs new file mode 100644 index 0000000000..54f22e3ee6 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_wraps.rs @@ -0,0 +1,144 @@ +#![warn(clippy::unnecessary_wraps)] +#![allow(clippy::no_effect)] +#![allow(clippy::needless_return)] +#![allow(clippy::if_same_then_else)] +#![allow(dead_code)] + +// should be linted +fn func1(a: bool, b: bool) -> Option { + if a && b { + return Some(42); + } + if a { + Some(-1); + Some(2) + } else { + return Some(1337); + } +} + +// should be linted +fn func2(a: bool, b: bool) -> Option { + if a && b { + return Some(10); + } + if a { Some(20) } else { Some(30) } +} + +// public fns should not be linted +pub fn func3(a: bool) -> Option { + if a { Some(1) } else { Some(1) } +} + +// should not be linted +fn func4(a: bool) -> Option { + if a { Some(1) } else { None } +} + +// should be linted +fn func5() -> Option { + Some(1) +} + +// should not be linted +fn func6() -> Option { + None +} + +// should be linted +fn func7() -> Result { + Ok(1) +} + +// should not be linted +fn func8(a: bool) -> Result { + if a { Ok(1) } else { Err(()) } +} + +// should not be linted +fn func9(a: bool) -> Result { + Err(()) +} + +// should not be linted +fn func10() -> Option<()> { + unimplemented!() +} + +struct A; + +impl A { + // should not be linted + pub fn func11() -> Option { + Some(1) + } + + // should be linted + fn func12() -> Option { + Some(1) + } +} + +trait B { + // trait impls are not linted + fn func13() -> Option { + Some(1) + } +} + +impl B for A { + // trait impls are not linted + fn func13() -> Option { + Some(0) + } +} + +fn issue_6384(s: &str) -> Option<&str> { + Some(match s { + "a" => "A", + _ => return None, + }) +} + +// should be linted +fn issue_6640_1(a: bool, b: bool) -> Option<()> { + if a && b { + return Some(()); + } + if a { + Some(()); + Some(()) + } else { + return Some(()); + } +} + +// should be linted +fn issue_6640_2(a: bool, b: bool) -> Result<(), i32> { + if a && b { + return Ok(()); + } + if a { + Ok(()) + } else { + return Ok(()); + } +} + +// should not be linted +fn issue_6640_3() -> Option<()> { + if true { Some(()) } else { None } +} + +// should not be linted +fn issue_6640_4() -> Result<(), ()> { + if true { Ok(()) } else { Err(()) } +} + +fn main() { + // method calls are not linted + func1(true, true); + func2(true, true); + issue_6640_1(true, true); + issue_6640_2(true, true); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_wraps.stderr b/src/tools/clippy/tests/ui/unnecessary_wraps.stderr new file mode 100644 index 0000000000..0e570397e2 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_wraps.stderr @@ -0,0 +1,154 @@ +error: this function's return value is unnecessarily wrapped by `Option` + --> $DIR/unnecessary_wraps.rs:8:1 + | +LL | / fn func1(a: bool, b: bool) -> Option { +LL | | if a && b { +LL | | return Some(42); +LL | | } +... | +LL | | } +LL | | } + | |_^ + | + = note: `-D clippy::unnecessary-wraps` implied by `-D warnings` +help: remove `Option` from the return type... + | +LL | fn func1(a: bool, b: bool) -> i32 { + | ^^^ +help: ...and then change returning expressions + | +LL | return 42; +LL | } +LL | if a { +LL | Some(-1); +LL | 2 +LL | } else { + ... + +error: this function's return value is unnecessarily wrapped by `Option` + --> $DIR/unnecessary_wraps.rs:21:1 + | +LL | / fn func2(a: bool, b: bool) -> Option { +LL | | if a && b { +LL | | return Some(10); +LL | | } +LL | | if a { Some(20) } else { Some(30) } +LL | | } + | |_^ + | +help: remove `Option` from the return type... + | +LL | fn func2(a: bool, b: bool) -> i32 { + | ^^^ +help: ...and then change returning expressions + | +LL | return 10; +LL | } +LL | if a { 20 } else { 30 } + | + +error: this function's return value is unnecessarily wrapped by `Option` + --> $DIR/unnecessary_wraps.rs:39:1 + | +LL | / fn func5() -> Option { +LL | | Some(1) +LL | | } + | |_^ + | +help: remove `Option` from the return type... + | +LL | fn func5() -> i32 { + | ^^^ +help: ...and then change returning expressions + | +LL | 1 + | + +error: this function's return value is unnecessarily wrapped by `Result` + --> $DIR/unnecessary_wraps.rs:49:1 + | +LL | / fn func7() -> Result { +LL | | Ok(1) +LL | | } + | |_^ + | +help: remove `Result` from the return type... + | +LL | fn func7() -> i32 { + | ^^^ +help: ...and then change returning expressions + | +LL | 1 + | + +error: this function's return value is unnecessarily wrapped by `Option` + --> $DIR/unnecessary_wraps.rs:77:5 + | +LL | / fn func12() -> Option { +LL | | Some(1) +LL | | } + | |_____^ + | +help: remove `Option` from the return type... + | +LL | fn func12() -> i32 { + | ^^^ +help: ...and then change returning expressions + | +LL | 1 + | + +error: this function's return value is unnecessary + --> $DIR/unnecessary_wraps.rs:104:1 + | +LL | / fn issue_6640_1(a: bool, b: bool) -> Option<()> { +LL | | if a && b { +LL | | return Some(()); +LL | | } +... | +LL | | } +LL | | } + | |_^ + | +help: remove the return type... + | +LL | fn issue_6640_1(a: bool, b: bool) -> Option<()> { + | ^^^^^^^^^^ +help: ...and then remove returned values + | +LL | return ; +LL | } +LL | if a { +LL | Some(()); +LL | +LL | } else { + ... + +error: this function's return value is unnecessary + --> $DIR/unnecessary_wraps.rs:117:1 + | +LL | / fn issue_6640_2(a: bool, b: bool) -> Result<(), i32> { +LL | | if a && b { +LL | | return Ok(()); +LL | | } +... | +LL | | } +LL | | } + | |_^ + | +help: remove the return type... + | +LL | fn issue_6640_2(a: bool, b: bool) -> Result<(), i32> { + | ^^^^^^^^^^^^^^^ +help: ...and then remove returned values + | +LL | return ; +LL | } +LL | if a { +LL | +LL | } else { +LL | return ; + | + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/unneeded_field_pattern.rs b/src/tools/clippy/tests/ui/unneeded_field_pattern.rs new file mode 100644 index 0000000000..fa639aa70d --- /dev/null +++ b/src/tools/clippy/tests/ui/unneeded_field_pattern.rs @@ -0,0 +1,22 @@ +#![warn(clippy::unneeded_field_pattern)] +#[allow(dead_code, unused)] + +struct Foo { + a: i32, + b: i32, + c: i32, +} + +fn main() { + let f = Foo { a: 0, b: 0, c: 0 }; + + match f { + Foo { a: _, b: 0, .. } => {}, + + Foo { a: _, b: _, c: _ } => {}, + } + match f { + Foo { b: 0, .. } => {}, // should be OK + Foo { .. } => {}, // and the Force might be with this one + } +} diff --git a/src/tools/clippy/tests/ui/unneeded_field_pattern.stderr b/src/tools/clippy/tests/ui/unneeded_field_pattern.stderr new file mode 100644 index 0000000000..b8d3c29453 --- /dev/null +++ b/src/tools/clippy/tests/ui/unneeded_field_pattern.stderr @@ -0,0 +1,19 @@ +error: you matched a field with a wildcard pattern, consider using `..` instead + --> $DIR/unneeded_field_pattern.rs:14:15 + | +LL | Foo { a: _, b: 0, .. } => {}, + | ^^^^ + | + = note: `-D clippy::unneeded-field-pattern` implied by `-D warnings` + = help: try with `Foo { b: 0, .. }` + +error: all the struct fields are matched to a wildcard pattern, consider using `..` + --> $DIR/unneeded_field_pattern.rs:16:9 + | +LL | Foo { a: _, b: _, c: _ } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: try with `Foo { .. }` instead + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.fixed b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.fixed new file mode 100644 index 0000000000..12c3461c95 --- /dev/null +++ b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.fixed @@ -0,0 +1,45 @@ +// run-rustfix +#![feature(stmt_expr_attributes)] +#![deny(clippy::unneeded_wildcard_pattern)] + +fn main() { + let t = (0, 1, 2, 3); + + if let (0, ..) = t {}; + if let (0, ..) = t {}; + if let (.., 0) = t {}; + if let (.., 0) = t {}; + if let (0, ..) = t {}; + if let (0, ..) = t {}; + if let (_, 0, ..) = t {}; + if let (.., 0, _) = t {}; + if let (0, _, _, _) = t {}; + if let (0, ..) = t {}; + if let (.., 0) = t {}; + + #[rustfmt::skip] + { + if let (0, ..,) = t {}; + } + + struct S(usize, usize, usize, usize); + + let s = S(0, 1, 2, 3); + + if let S(0, ..) = s {}; + if let S(0, ..) = s {}; + if let S(.., 0) = s {}; + if let S(.., 0) = s {}; + if let S(0, ..) = s {}; + if let S(0, ..) = s {}; + if let S(_, 0, ..) = s {}; + if let S(.., 0, _) = s {}; + if let S(0, _, _, _) = s {}; + if let S(0, ..) = s {}; + if let S(.., 0) = s {}; + + #[rustfmt::skip] + { + if let S(0, ..,) = s {}; + } +} diff --git a/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.rs b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.rs new file mode 100644 index 0000000000..4ac01d5d23 --- /dev/null +++ b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.rs @@ -0,0 +1,45 @@ +// run-rustfix +#![feature(stmt_expr_attributes)] +#![deny(clippy::unneeded_wildcard_pattern)] + +fn main() { + let t = (0, 1, 2, 3); + + if let (0, .., _) = t {}; + if let (0, _, ..) = t {}; + if let (_, .., 0) = t {}; + if let (.., _, 0) = t {}; + if let (0, _, _, ..) = t {}; + if let (0, .., _, _) = t {}; + if let (_, 0, ..) = t {}; + if let (.., 0, _) = t {}; + if let (0, _, _, _) = t {}; + if let (0, ..) = t {}; + if let (.., 0) = t {}; + + #[rustfmt::skip] + { + if let (0, .., _, _,) = t {}; + } + + struct S(usize, usize, usize, usize); + + let s = S(0, 1, 2, 3); + + if let S(0, .., _) = s {}; + if let S(0, _, ..) = s {}; + if let S(_, .., 0) = s {}; + if let S(.., _, 0) = s {}; + if let S(0, _, _, ..) = s {}; + if let S(0, .., _, _) = s {}; + if let S(_, 0, ..) = s {}; + if let S(.., 0, _) = s {}; + if let S(0, _, _, _) = s {}; + if let S(0, ..) = s {}; + if let S(.., 0) = s {}; + + #[rustfmt::skip] + { + if let S(0, .., _, _,) = s {}; + } +} diff --git a/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.stderr b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.stderr new file mode 100644 index 0000000000..716d9ecff8 --- /dev/null +++ b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.stderr @@ -0,0 +1,92 @@ +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:8:18 + | +LL | if let (0, .., _) = t {}; + | ^^^ help: remove it + | +note: the lint level is defined here + --> $DIR/unneeded_wildcard_pattern.rs:3:9 + | +LL | #![deny(clippy::unneeded_wildcard_pattern)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:9:16 + | +LL | if let (0, _, ..) = t {}; + | ^^^ help: remove it + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:10:13 + | +LL | if let (_, .., 0) = t {}; + | ^^^ help: remove it + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:11:15 + | +LL | if let (.., _, 0) = t {}; + | ^^^ help: remove it + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:12:16 + | +LL | if let (0, _, _, ..) = t {}; + | ^^^^^^ help: remove them + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:13:18 + | +LL | if let (0, .., _, _) = t {}; + | ^^^^^^ help: remove them + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:22:22 + | +LL | if let (0, .., _, _,) = t {}; + | ^^^^^^ help: remove them + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:29:19 + | +LL | if let S(0, .., _) = s {}; + | ^^^ help: remove it + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:30:17 + | +LL | if let S(0, _, ..) = s {}; + | ^^^ help: remove it + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:31:14 + | +LL | if let S(_, .., 0) = s {}; + | ^^^ help: remove it + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:32:16 + | +LL | if let S(.., _, 0) = s {}; + | ^^^ help: remove it + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:33:17 + | +LL | if let S(0, _, _, ..) = s {}; + | ^^^^^^ help: remove them + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:34:19 + | +LL | if let S(0, .., _, _) = s {}; + | ^^^^^^ help: remove them + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:43:23 + | +LL | if let S(0, .., _, _,) = s {}; + | ^^^^^^ help: remove them + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns.fixed b/src/tools/clippy/tests/ui/unnested_or_patterns.fixed new file mode 100644 index 0000000000..13a036cd80 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnested_or_patterns.fixed @@ -0,0 +1,33 @@ +// run-rustfix + +#![feature(or_patterns)] +#![feature(box_patterns)] +#![warn(clippy::unnested_or_patterns)] +#![allow(clippy::cognitive_complexity, clippy::match_ref_pats, clippy::upper_case_acronyms)] +#![allow(unreachable_patterns, irrefutable_let_patterns, unused_variables)] + +fn main() { + if let box (0 | 2) = Box::new(0) {} + if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {} + const C0: &u8 = &1; + if let &(0 | 2) | C0 = &0 {} + if let &mut (0 | 2) = &mut 0 {} + if let x @ (0 | 2) = 0 {} + if let (0, 1 | 2 | 3) = (0, 0) {} + if let (1 | 2 | 3, 0) = (0, 0) {} + if let (x, ..) | (x, 1 | 2) = (0, 1) {} + if let [0 | 1] = [0] {} + if let [x, 0 | 1] = [0, 1] {} + if let [x, 0 | 1 | 2] = [0, 1] {} + if let [x, ..] | [x, 1 | 2] = [0, 1] {} + struct TS(u8, u8); + if let TS(0 | 1, x) = TS(0, 0) {} + if let TS(1 | 2 | 3, 0) = TS(0, 0) {} + if let TS(x, ..) | TS(x, 1 | 2) = TS(0, 0) {} + struct S { + x: u8, + y: u8, + } + if let S { x: 0 | 1, y } = (S { x: 0, y: 1 }) {} + if let S { x: 0, y, .. } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} +} diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns.rs b/src/tools/clippy/tests/ui/unnested_or_patterns.rs new file mode 100644 index 0000000000..4a10cc702c --- /dev/null +++ b/src/tools/clippy/tests/ui/unnested_or_patterns.rs @@ -0,0 +1,33 @@ +// run-rustfix + +#![feature(or_patterns)] +#![feature(box_patterns)] +#![warn(clippy::unnested_or_patterns)] +#![allow(clippy::cognitive_complexity, clippy::match_ref_pats, clippy::upper_case_acronyms)] +#![allow(unreachable_patterns, irrefutable_let_patterns, unused_variables)] + +fn main() { + if let box 0 | box 2 = Box::new(0) {} + if let box ((0 | 1)) | box (2 | 3) | box 4 = Box::new(0) {} + const C0: &u8 = &1; + if let &0 | C0 | &2 = &0 {} + if let &mut 0 | &mut 2 = &mut 0 {} + if let x @ 0 | x @ 2 = 0 {} + if let (0, 1) | (0, 2) | (0, 3) = (0, 0) {} + if let (1, 0) | (2, 0) | (3, 0) = (0, 0) {} + if let (x, ..) | (x, 1) | (x, 2) = (0, 1) {} + if let [0] | [1] = [0] {} + if let [x, 0] | [x, 1] = [0, 1] {} + if let [x, 0] | [x, 1] | [x, 2] = [0, 1] {} + if let [x, ..] | [x, 1] | [x, 2] = [0, 1] {} + struct TS(u8, u8); + if let TS(0, x) | TS(1, x) = TS(0, 0) {} + if let TS(1, 0) | TS(2, 0) | TS(3, 0) = TS(0, 0) {} + if let TS(x, ..) | TS(x, 1) | TS(x, 2) = TS(0, 0) {} + struct S { + x: u8, + y: u8, + } + if let S { x: 0, y } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} + if let S { x: 0, y, .. } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} +} diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns.stderr b/src/tools/clippy/tests/ui/unnested_or_patterns.stderr new file mode 100644 index 0000000000..1899dc657d --- /dev/null +++ b/src/tools/clippy/tests/ui/unnested_or_patterns.stderr @@ -0,0 +1,179 @@ +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:10:12 + | +LL | if let box 0 | box 2 = Box::new(0) {} + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::unnested-or-patterns` implied by `-D warnings` +help: nest the patterns + | +LL | if let box (0 | 2) = Box::new(0) {} + | ^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:11:12 + | +LL | if let box ((0 | 1)) | box (2 | 3) | box 4 = Box::new(0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:13:12 + | +LL | if let &0 | C0 | &2 = &0 {} + | ^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let &(0 | 2) | C0 = &0 {} + | ^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:14:12 + | +LL | if let &mut 0 | &mut 2 = &mut 0 {} + | ^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let &mut (0 | 2) = &mut 0 {} + | ^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:15:12 + | +LL | if let x @ 0 | x @ 2 = 0 {} + | ^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let x @ (0 | 2) = 0 {} + | ^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:16:12 + | +LL | if let (0, 1) | (0, 2) | (0, 3) = (0, 0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let (0, 1 | 2 | 3) = (0, 0) {} + | ^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:17:12 + | +LL | if let (1, 0) | (2, 0) | (3, 0) = (0, 0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let (1 | 2 | 3, 0) = (0, 0) {} + | ^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:18:12 + | +LL | if let (x, ..) | (x, 1) | (x, 2) = (0, 1) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let (x, ..) | (x, 1 | 2) = (0, 1) {} + | ^^^^^^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:19:12 + | +LL | if let [0] | [1] = [0] {} + | ^^^^^^^^^ + | +help: nest the patterns + | +LL | if let [0 | 1] = [0] {} + | ^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:20:12 + | +LL | if let [x, 0] | [x, 1] = [0, 1] {} + | ^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let [x, 0 | 1] = [0, 1] {} + | ^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:21:12 + | +LL | if let [x, 0] | [x, 1] | [x, 2] = [0, 1] {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let [x, 0 | 1 | 2] = [0, 1] {} + | ^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:22:12 + | +LL | if let [x, ..] | [x, 1] | [x, 2] = [0, 1] {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let [x, ..] | [x, 1 | 2] = [0, 1] {} + | ^^^^^^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:24:12 + | +LL | if let TS(0, x) | TS(1, x) = TS(0, 0) {} + | ^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let TS(0 | 1, x) = TS(0, 0) {} + | ^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:25:12 + | +LL | if let TS(1, 0) | TS(2, 0) | TS(3, 0) = TS(0, 0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let TS(1 | 2 | 3, 0) = TS(0, 0) {} + | ^^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:26:12 + | +LL | if let TS(x, ..) | TS(x, 1) | TS(x, 2) = TS(0, 0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let TS(x, ..) | TS(x, 1 | 2) = TS(0, 0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns.rs:31:12 + | +LL | if let S { x: 0, y } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let S { x: 0 | 1, y } = (S { x: 0, y: 1 }) {} + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns2.fixed b/src/tools/clippy/tests/ui/unnested_or_patterns2.fixed new file mode 100644 index 0000000000..02a129c55a --- /dev/null +++ b/src/tools/clippy/tests/ui/unnested_or_patterns2.fixed @@ -0,0 +1,18 @@ +// run-rustfix + +#![feature(or_patterns)] +#![feature(box_patterns)] +#![warn(clippy::unnested_or_patterns)] +#![allow(clippy::cognitive_complexity, clippy::match_ref_pats)] +#![allow(unreachable_patterns, irrefutable_let_patterns, unused_variables)] + +fn main() { + if let Some(Some(0 | 1)) = None {} + if let Some(Some(0 | 1 | 2)) = None {} + if let Some(Some(0 | 1 | 2 | 3 | 4)) = None {} + if let Some(Some(0 | 1 | 2)) = None {} + if let ((0 | 1 | 2,),) = ((0,),) {} + if let 0 | 1 | 2 = 0 {} + if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {} + if let box box (0 | 2 | 4) = Box::new(Box::new(0)) {} +} diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns2.rs b/src/tools/clippy/tests/ui/unnested_or_patterns2.rs new file mode 100644 index 0000000000..acf3158989 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnested_or_patterns2.rs @@ -0,0 +1,18 @@ +// run-rustfix + +#![feature(or_patterns)] +#![feature(box_patterns)] +#![warn(clippy::unnested_or_patterns)] +#![allow(clippy::cognitive_complexity, clippy::match_ref_pats)] +#![allow(unreachable_patterns, irrefutable_let_patterns, unused_variables)] + +fn main() { + if let Some(Some(0)) | Some(Some(1)) = None {} + if let Some(Some(0)) | Some(Some(1) | Some(2)) = None {} + if let Some(Some(0 | 1) | Some(2)) | Some(Some(3) | Some(4)) = None {} + if let Some(Some(0) | Some(1 | 2)) = None {} + if let ((0,),) | ((1,) | (2,),) = ((0,),) {} + if let 0 | (1 | 2) = 0 {} + if let box (0 | 1) | (box 2 | box (3 | 4)) = Box::new(0) {} + if let box box 0 | box (box 2 | box 4) = Box::new(Box::new(0)) {} +} diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns2.stderr b/src/tools/clippy/tests/ui/unnested_or_patterns2.stderr new file mode 100644 index 0000000000..1847fd8e09 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnested_or_patterns2.stderr @@ -0,0 +1,91 @@ +error: unnested or-patterns + --> $DIR/unnested_or_patterns2.rs:10:12 + | +LL | if let Some(Some(0)) | Some(Some(1)) = None {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unnested-or-patterns` implied by `-D warnings` +help: nest the patterns + | +LL | if let Some(Some(0 | 1)) = None {} + | ^^^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns2.rs:11:12 + | +LL | if let Some(Some(0)) | Some(Some(1) | Some(2)) = None {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let Some(Some(0 | 1 | 2)) = None {} + | ^^^^^^^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns2.rs:12:12 + | +LL | if let Some(Some(0 | 1) | Some(2)) | Some(Some(3) | Some(4)) = None {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let Some(Some(0 | 1 | 2 | 3 | 4)) = None {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns2.rs:13:12 + | +LL | if let Some(Some(0) | Some(1 | 2)) = None {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let Some(Some(0 | 1 | 2)) = None {} + | ^^^^^^^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns2.rs:14:12 + | +LL | if let ((0,),) | ((1,) | (2,),) = ((0,),) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let ((0 | 1 | 2,),) = ((0,),) {} + | ^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns2.rs:15:12 + | +LL | if let 0 | (1 | 2) = 0 {} + | ^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let 0 | 1 | 2 = 0 {} + | ^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns2.rs:16:12 + | +LL | if let box (0 | 1) | (box 2 | box (3 | 4)) = Box::new(0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: unnested or-patterns + --> $DIR/unnested_or_patterns2.rs:17:12 + | +LL | if let box box 0 | box (box 2 | box 4) = Box::new(Box::new(0)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL | if let box box (0 | 2 | 4) = Box::new(Box::new(0)) {} + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns3.rs b/src/tools/clippy/tests/ui/unnested_or_patterns3.rs new file mode 100644 index 0000000000..6bd35057bf --- /dev/null +++ b/src/tools/clippy/tests/ui/unnested_or_patterns3.rs @@ -0,0 +1,6 @@ +#![warn(clippy::unnested_or_patterns)] + +// Test that `unnested_or_patterns` does not trigger without enabling `or_patterns` +fn main() { + if let (0, 1) | (0, 2) | (0, 3) = (0, 0) {} +} diff --git a/src/tools/clippy/tests/ui/unreadable_literal.fixed b/src/tools/clippy/tests/ui/unreadable_literal.fixed new file mode 100644 index 0000000000..c2e38037ad --- /dev/null +++ b/src/tools/clippy/tests/ui/unreadable_literal.fixed @@ -0,0 +1,45 @@ +// run-rustfix + +#![warn(clippy::unreadable_literal)] + +struct Foo(u64); + +macro_rules! foo { + () => { + Foo(123123123123) + }; +} + +struct Bar(f32); + +macro_rules! bar { + () => { + Bar(100200300400.100200300400500) + }; +} + +fn main() { + let _good = ( + 0b1011_i64, + 0o1_234_u32, + 0x0123_4567, + 65536, + 1_2345_6789, + 1234_f32, + 1_234.12_f32, + 1_234.123_f32, + 1.123_4_f32, + ); + let _bad = (0b11_0110_i64, 0xcafe_babe_usize, 123_456_f32, 1.234_567_f32); + let _good_sci = 1.1234e1; + let _bad_sci = 1.123_456e1; + + let _fail1 = 0x00ab_cdef; + let _fail2: u32 = 0xBAFE_BAFE; + let _fail3 = 0x0abc_deff; + let _fail4: i128 = 0x00ab_cabc_abca_bcab_cabc; + let _fail5 = 1.100_300_400; + + let _ = foo!(); + let _ = bar!(); +} diff --git a/src/tools/clippy/tests/ui/unreadable_literal.rs b/src/tools/clippy/tests/ui/unreadable_literal.rs new file mode 100644 index 0000000000..8296945b25 --- /dev/null +++ b/src/tools/clippy/tests/ui/unreadable_literal.rs @@ -0,0 +1,45 @@ +// run-rustfix + +#![warn(clippy::unreadable_literal)] + +struct Foo(u64); + +macro_rules! foo { + () => { + Foo(123123123123) + }; +} + +struct Bar(f32); + +macro_rules! bar { + () => { + Bar(100200300400.100200300400500) + }; +} + +fn main() { + let _good = ( + 0b1011_i64, + 0o1_234_u32, + 0x1_234_567, + 65536, + 1_2345_6789, + 1234_f32, + 1_234.12_f32, + 1_234.123_f32, + 1.123_4_f32, + ); + let _bad = (0b110110_i64, 0xcafebabe_usize, 123456_f32, 1.234567_f32); + let _good_sci = 1.1234e1; + let _bad_sci = 1.123456e1; + + let _fail1 = 0xabcdef; + let _fail2: u32 = 0xBAFEBAFE; + let _fail3 = 0xabcdeff; + let _fail4: i128 = 0xabcabcabcabcabcabc; + let _fail5 = 1.100300400; + + let _ = foo!(); + let _ = bar!(); +} diff --git a/src/tools/clippy/tests/ui/unreadable_literal.stderr b/src/tools/clippy/tests/ui/unreadable_literal.stderr new file mode 100644 index 0000000000..8436aac17a --- /dev/null +++ b/src/tools/clippy/tests/ui/unreadable_literal.stderr @@ -0,0 +1,72 @@ +error: digits of hex or binary literal not grouped by four + --> $DIR/unreadable_literal.rs:25:9 + | +LL | 0x1_234_567, + | ^^^^^^^^^^^ help: consider: `0x0123_4567` + | + = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:33:17 + | +LL | let _bad = (0b110110_i64, 0xcafebabe_usize, 123456_f32, 1.234567_f32); + | ^^^^^^^^^^^^ help: consider: `0b11_0110_i64` + | + = note: `-D clippy::unreadable-literal` implied by `-D warnings` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:33:31 + | +LL | let _bad = (0b110110_i64, 0xcafebabe_usize, 123456_f32, 1.234567_f32); + | ^^^^^^^^^^^^^^^^ help: consider: `0xcafe_babe_usize` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:33:49 + | +LL | let _bad = (0b110110_i64, 0xcafebabe_usize, 123456_f32, 1.234567_f32); + | ^^^^^^^^^^ help: consider: `123_456_f32` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:33:61 + | +LL | let _bad = (0b110110_i64, 0xcafebabe_usize, 123456_f32, 1.234567_f32); + | ^^^^^^^^^^^^ help: consider: `1.234_567_f32` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:35:20 + | +LL | let _bad_sci = 1.123456e1; + | ^^^^^^^^^^ help: consider: `1.123_456e1` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:37:18 + | +LL | let _fail1 = 0xabcdef; + | ^^^^^^^^ help: consider: `0x00ab_cdef` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:38:23 + | +LL | let _fail2: u32 = 0xBAFEBAFE; + | ^^^^^^^^^^ help: consider: `0xBAFE_BAFE` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:39:18 + | +LL | let _fail3 = 0xabcdeff; + | ^^^^^^^^^ help: consider: `0x0abc_deff` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:40:24 + | +LL | let _fail4: i128 = 0xabcabcabcabcabcabc; + | ^^^^^^^^^^^^^^^^^^^^ help: consider: `0x00ab_cabc_abca_bcab_cabc` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:41:18 + | +LL | let _fail5 = 1.100300400; + | ^^^^^^^^^^^ help: consider: `1.100_300_400` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/unsafe_derive_deserialize.rs b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.rs new file mode 100644 index 0000000000..690d705573 --- /dev/null +++ b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.rs @@ -0,0 +1,70 @@ +#![warn(clippy::unsafe_derive_deserialize)] +#![allow(unused, clippy::missing_safety_doc)] + +extern crate serde; + +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct A {} +impl A { + pub unsafe fn new(_a: i32, _b: i32) -> Self { + Self {} + } +} + +#[derive(Deserialize)] +pub struct B {} +impl B { + pub unsafe fn unsafe_method(&self) {} +} + +#[derive(Deserialize)] +pub struct C {} +impl C { + pub fn unsafe_block(&self) { + unsafe {} + } +} + +#[derive(Deserialize)] +pub struct D {} +impl D { + pub fn inner_unsafe_fn(&self) { + unsafe fn inner() {} + } +} + +// Does not derive `Deserialize`, should be ignored +pub struct E {} +impl E { + pub unsafe fn new(_a: i32, _b: i32) -> Self { + Self {} + } + + pub unsafe fn unsafe_method(&self) {} + + pub fn unsafe_block(&self) { + unsafe {} + } + + pub fn inner_unsafe_fn(&self) { + unsafe fn inner() {} + } +} + +// Does not have methods using `unsafe`, should be ignored +#[derive(Deserialize)] +pub struct F {} + +// Check that we honor the `allow` attribute on the ADT +#[allow(clippy::unsafe_derive_deserialize)] +#[derive(Deserialize)] +pub struct G {} +impl G { + pub fn unsafe_block(&self) { + unsafe {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unsafe_derive_deserialize.stderr b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.stderr new file mode 100644 index 0000000000..1978bd95a6 --- /dev/null +++ b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.stderr @@ -0,0 +1,39 @@ +error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` + --> $DIR/unsafe_derive_deserialize.rs:8:10 + | +LL | #[derive(Deserialize)] + | ^^^^^^^^^^^ + | + = note: `-D clippy::unsafe-derive-deserialize` implied by `-D warnings` + = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` + --> $DIR/unsafe_derive_deserialize.rs:16:10 + | +LL | #[derive(Deserialize)] + | ^^^^^^^^^^^ + | + = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` + --> $DIR/unsafe_derive_deserialize.rs:22:10 + | +LL | #[derive(Deserialize)] + | ^^^^^^^^^^^ + | + = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` + --> $DIR/unsafe_derive_deserialize.rs:30:10 + | +LL | #[derive(Deserialize)] + | ^^^^^^^^^^^ + | + = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/unsafe_removed_from_name.rs b/src/tools/clippy/tests/ui/unsafe_removed_from_name.rs new file mode 100644 index 0000000000..a1f616733b --- /dev/null +++ b/src/tools/clippy/tests/ui/unsafe_removed_from_name.rs @@ -0,0 +1,27 @@ +#![allow(unused_imports)] +#![allow(dead_code)] +#![warn(clippy::unsafe_removed_from_name)] + +use std::cell::UnsafeCell as TotallySafeCell; + +use std::cell::UnsafeCell as TotallySafeCellAgain; + +// Shouldn't error +use std::cell::RefCell as ProbablyNotUnsafe; +use std::cell::RefCell as RefCellThatCantBeUnsafe; +use std::cell::UnsafeCell as SuperDangerousUnsafeCell; +use std::cell::UnsafeCell as Dangerunsafe; +use std::cell::UnsafeCell as Bombsawayunsafe; + +mod mod_with_some_unsafe_things { + pub struct Safe {} + pub struct Unsafe {} +} + +use mod_with_some_unsafe_things::Unsafe as LieAboutModSafety; + +// Shouldn't error +use mod_with_some_unsafe_things::Safe as IPromiseItsSafeThisTime; +use mod_with_some_unsafe_things::Unsafe as SuperUnsafeModThing; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unsafe_removed_from_name.stderr b/src/tools/clippy/tests/ui/unsafe_removed_from_name.stderr new file mode 100644 index 0000000000..4f871cbe41 --- /dev/null +++ b/src/tools/clippy/tests/ui/unsafe_removed_from_name.stderr @@ -0,0 +1,22 @@ +error: removed `unsafe` from the name of `UnsafeCell` in use as `TotallySafeCell` + --> $DIR/unsafe_removed_from_name.rs:5:1 + | +LL | use std::cell::UnsafeCell as TotallySafeCell; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unsafe-removed-from-name` implied by `-D warnings` + +error: removed `unsafe` from the name of `UnsafeCell` in use as `TotallySafeCellAgain` + --> $DIR/unsafe_removed_from_name.rs:7:1 + | +LL | use std::cell::UnsafeCell as TotallySafeCellAgain; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: removed `unsafe` from the name of `Unsafe` in use as `LieAboutModSafety` + --> $DIR/unsafe_removed_from_name.rs:21:1 + | +LL | use mod_with_some_unsafe_things::Unsafe as LieAboutModSafety; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/unseparated_prefix_literals.fixed b/src/tools/clippy/tests/ui/unseparated_prefix_literals.fixed new file mode 100644 index 0000000000..3c422cc4fe --- /dev/null +++ b/src/tools/clippy/tests/ui/unseparated_prefix_literals.fixed @@ -0,0 +1,41 @@ +// run-rustfix + +#![warn(clippy::unseparated_literal_suffix)] +#![allow(dead_code)] + +#[macro_use] +extern crate clippy_mini_macro_test; + +// Test for proc-macro attribute +#[derive(ClippyMiniMacroTest)] +struct Foo; + +macro_rules! lit_from_macro { + () => { + 42_usize + }; +} + +fn main() { + let _ok1 = 1234_i32; + let _ok2 = 1234_isize; + let _ok3 = 0x123_isize; + let _fail1 = 1234_i32; + let _fail2 = 1234_u32; + let _fail3 = 1234_isize; + let _fail4 = 1234_usize; + let _fail5 = 0x123_isize; + + let _okf1 = 1.5_f32; + let _okf2 = 1_f32; + let _failf1 = 1.5_f32; + let _failf2 = 1_f32; + + // Test for macro + let _ = lit_from_macro!(); + + // Counter example + let _ = line!(); + // Because `assert!` contains `line!()` macro. + assert_eq!(4897_u32, 32223); +} diff --git a/src/tools/clippy/tests/ui/unseparated_prefix_literals.rs b/src/tools/clippy/tests/ui/unseparated_prefix_literals.rs new file mode 100644 index 0000000000..09608661e0 --- /dev/null +++ b/src/tools/clippy/tests/ui/unseparated_prefix_literals.rs @@ -0,0 +1,41 @@ +// run-rustfix + +#![warn(clippy::unseparated_literal_suffix)] +#![allow(dead_code)] + +#[macro_use] +extern crate clippy_mini_macro_test; + +// Test for proc-macro attribute +#[derive(ClippyMiniMacroTest)] +struct Foo; + +macro_rules! lit_from_macro { + () => { + 42usize + }; +} + +fn main() { + let _ok1 = 1234_i32; + let _ok2 = 1234_isize; + let _ok3 = 0x123_isize; + let _fail1 = 1234i32; + let _fail2 = 1234u32; + let _fail3 = 1234isize; + let _fail4 = 1234usize; + let _fail5 = 0x123isize; + + let _okf1 = 1.5_f32; + let _okf2 = 1_f32; + let _failf1 = 1.5f32; + let _failf2 = 1f32; + + // Test for macro + let _ = lit_from_macro!(); + + // Counter example + let _ = line!(); + // Because `assert!` contains `line!()` macro. + assert_eq!(4897u32, 32223); +} diff --git a/src/tools/clippy/tests/ui/unseparated_prefix_literals.stderr b/src/tools/clippy/tests/ui/unseparated_prefix_literals.stderr new file mode 100644 index 0000000000..d7dd526bcb --- /dev/null +++ b/src/tools/clippy/tests/ui/unseparated_prefix_literals.stderr @@ -0,0 +1,63 @@ +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:23:18 + | +LL | let _fail1 = 1234i32; + | ^^^^^^^ help: add an underscore: `1234_i32` + | + = note: `-D clippy::unseparated-literal-suffix` implied by `-D warnings` + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:24:18 + | +LL | let _fail2 = 1234u32; + | ^^^^^^^ help: add an underscore: `1234_u32` + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:25:18 + | +LL | let _fail3 = 1234isize; + | ^^^^^^^^^ help: add an underscore: `1234_isize` + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:26:18 + | +LL | let _fail4 = 1234usize; + | ^^^^^^^^^ help: add an underscore: `1234_usize` + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:27:18 + | +LL | let _fail5 = 0x123isize; + | ^^^^^^^^^^ help: add an underscore: `0x123_isize` + +error: float type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:31:19 + | +LL | let _failf1 = 1.5f32; + | ^^^^^^ help: add an underscore: `1.5_f32` + +error: float type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:32:19 + | +LL | let _failf2 = 1f32; + | ^^^^ help: add an underscore: `1_f32` + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:15:9 + | +LL | 42usize + | ^^^^^^^ help: add an underscore: `42_usize` +... +LL | let _ = lit_from_macro!(); + | ----------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:40:16 + | +LL | assert_eq!(4897u32, 32223); + | ^^^^^^^ help: add an underscore: `4897_u32` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/unused_io_amount.rs b/src/tools/clippy/tests/ui/unused_io_amount.rs new file mode 100644 index 0000000000..ebaba9629d --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_io_amount.rs @@ -0,0 +1,25 @@ +#![allow(dead_code)] +#![warn(clippy::unused_io_amount)] + +use std::io; + +fn question_mark(s: &mut T) -> io::Result<()> { + s.write(b"test")?; + let mut buf = [0u8; 4]; + s.read(&mut buf)?; + Ok(()) +} + +fn unwrap(s: &mut T) { + s.write(b"test").unwrap(); + let mut buf = [0u8; 4]; + s.read(&mut buf).unwrap(); +} + +fn vectored(s: &mut T) -> io::Result<()> { + s.read_vectored(&mut [io::IoSliceMut::new(&mut [])])?; + s.write_vectored(&[io::IoSlice::new(&[])])?; + Ok(()) +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unused_io_amount.stderr b/src/tools/clippy/tests/ui/unused_io_amount.stderr new file mode 100644 index 0000000000..5219d63980 --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_io_amount.stderr @@ -0,0 +1,40 @@ +error: written amount is not handled. Use `Write::write_all` instead + --> $DIR/unused_io_amount.rs:7:5 + | +LL | s.write(b"test")?; + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unused-io-amount` implied by `-D warnings` + +error: read amount is not handled. Use `Read::read_exact` instead + --> $DIR/unused_io_amount.rs:9:5 + | +LL | s.read(&mut buf)?; + | ^^^^^^^^^^^^^^^^^ + +error: written amount is not handled. Use `Write::write_all` instead + --> $DIR/unused_io_amount.rs:14:5 + | +LL | s.write(b"test").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: read amount is not handled. Use `Read::read_exact` instead + --> $DIR/unused_io_amount.rs:16:5 + | +LL | s.read(&mut buf).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: read amount is not handled + --> $DIR/unused_io_amount.rs:20:5 + | +LL | s.read_vectored(&mut [io::IoSliceMut::new(&mut [])])?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: written amount is not handled + --> $DIR/unused_io_amount.rs:21:5 + | +LL | s.write_vectored(&[io::IoSlice::new(&[])])?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/unused_self.rs b/src/tools/clippy/tests/ui/unused_self.rs new file mode 100644 index 0000000000..7a4bbdda1a --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_self.rs @@ -0,0 +1,140 @@ +#![warn(clippy::unused_self)] +#![allow(clippy::boxed_local, clippy::fn_params_excessive_bools)] + +mod unused_self { + use std::pin::Pin; + use std::sync::{Arc, Mutex}; + + struct A {} + + impl A { + fn unused_self_move(self) {} + fn unused_self_ref(&self) {} + fn unused_self_mut_ref(&mut self) {} + fn unused_self_pin_ref(self: Pin<&Self>) {} + fn unused_self_pin_mut_ref(self: Pin<&mut Self>) {} + fn unused_self_pin_nested(self: Pin>) {} + fn unused_self_box(self: Box) {} + fn unused_with_other_used_args(&self, x: u8, y: u8) -> u8 { + x + y + } + fn unused_self_class_method(&self) { + Self::static_method(); + } + + fn static_method() {} + } +} + +mod unused_self_allow { + struct A {} + + impl A { + // shouldn't trigger + #[allow(clippy::unused_self)] + fn unused_self_move(self) {} + } + + struct B {} + + // shouldn't trigger + #[allow(clippy::unused_self)] + impl B { + fn unused_self_move(self) {} + } + + struct C {} + + #[allow(clippy::unused_self)] + impl C { + #[warn(clippy::unused_self)] + fn some_fn((): ()) {} + + // shouldn't trigger + fn unused_self_move(self) {} + } +} + +mod used_self { + use std::pin::Pin; + + struct A { + x: u8, + } + + impl A { + fn used_self_move(self) -> u8 { + self.x + } + fn used_self_ref(&self) -> u8 { + self.x + } + fn used_self_mut_ref(&mut self) { + self.x += 1 + } + fn used_self_pin_ref(self: Pin<&Self>) -> u8 { + self.x + } + fn used_self_box(self: Box) -> u8 { + self.x + } + fn used_self_with_other_unused_args(&self, x: u8, y: u8) -> u8 { + self.x + } + fn used_in_nested_closure(&self) -> u8 { + let mut a = || -> u8 { self.x }; + a() + } + + #[allow(clippy::collapsible_if)] + fn used_self_method_nested_conditions(&self, a: bool, b: bool, c: bool, d: bool) { + if a { + if b { + if c { + if d { + self.used_self_ref(); + } + } + } + } + } + + fn foo(&self) -> u32 { + let mut sum = 0u32; + for i in 0..self.x { + sum += i as u32; + } + sum + } + + fn bar(&mut self, x: u8) -> u32 { + let mut y = 0u32; + for i in 0..x { + y += self.foo() + } + y + } + } +} + +mod not_applicable { + use std::fmt; + + struct A {} + + impl fmt::Debug for A { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "A") + } + } + + impl A { + fn method(x: u8, y: u8) {} + } + + trait B { + fn method(&self) {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unused_self.stderr b/src/tools/clippy/tests/ui/unused_self.stderr new file mode 100644 index 0000000000..0534b40eab --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_self.stderr @@ -0,0 +1,75 @@ +error: unused `self` argument + --> $DIR/unused_self.rs:11:29 + | +LL | fn unused_self_move(self) {} + | ^^^^ + | + = note: `-D clippy::unused-self` implied by `-D warnings` + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:12:28 + | +LL | fn unused_self_ref(&self) {} + | ^^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:13:32 + | +LL | fn unused_self_mut_ref(&mut self) {} + | ^^^^^^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:14:32 + | +LL | fn unused_self_pin_ref(self: Pin<&Self>) {} + | ^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:15:36 + | +LL | fn unused_self_pin_mut_ref(self: Pin<&mut Self>) {} + | ^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:16:35 + | +LL | fn unused_self_pin_nested(self: Pin>) {} + | ^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:17:28 + | +LL | fn unused_self_box(self: Box) {} + | ^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:18:40 + | +LL | fn unused_with_other_used_args(&self, x: u8, y: u8) -> u8 { + | ^^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:21:37 + | +LL | fn unused_self_class_method(&self) { + | ^^^^^ + | + = help: consider refactoring to a associated function + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/unused_unit.fixed b/src/tools/clippy/tests/ui/unused_unit.fixed new file mode 100644 index 0000000000..a192ebde3e --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_unit.fixed @@ -0,0 +1,82 @@ +// run-rustfix + +// The output for humans should just highlight the whole span without showing +// the suggested replacement, but we also want to test that suggested +// replacement only removes one set of parentheses, rather than naïvely +// stripping away any starting or ending parenthesis characters—hence this +// test of the JSON error format. + +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +#![deny(clippy::unused_unit)] +#![allow(dead_code)] +#![allow(clippy::from_over_into)] + +struct Unitter; +impl Unitter { + #[allow(clippy::no_effect)] + pub fn get_unit(&self, f: F, _g: G) + where G: Fn() { + let _y: &dyn Fn() = &f; + (); // this should not lint, as it's not in return type position + } +} + +impl Into<()> for Unitter { + #[rustfmt::skip] + fn into(self) { + + } +} + +trait Trait { + fn redundant(&self, _f: F, _g: G, _h: H) + where + G: FnMut(), + H: Fn(); +} + +impl Trait for Unitter { + fn redundant(&self, _f: F, _g: G, _h: H) + where + G: FnMut(), + H: Fn() {} +} + +fn return_unit() { } + +#[allow(clippy::needless_return)] +#[allow(clippy::never_loop)] +#[allow(clippy::unit_cmp)] +fn main() { + let u = Unitter; + assert_eq!(u.get_unit(|| {}, return_unit), u.into()); + return_unit(); + loop { + break; + } + return; +} + +// https://github.com/rust-lang/rust-clippy/issues/4076 +fn foo() { + macro_rules! foo { + (recv($r:expr) -> $res:pat => $body:expr) => { + $body + } + } + + foo! { + recv(rx) -> _x => () + } +} + +#[rustfmt::skip] +fn test(){} + +#[rustfmt::skip] +fn test2(){} + +#[rustfmt::skip] +fn test3(){} diff --git a/src/tools/clippy/tests/ui/unused_unit.rs b/src/tools/clippy/tests/ui/unused_unit.rs new file mode 100644 index 0000000000..96041a7dd8 --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_unit.rs @@ -0,0 +1,82 @@ +// run-rustfix + +// The output for humans should just highlight the whole span without showing +// the suggested replacement, but we also want to test that suggested +// replacement only removes one set of parentheses, rather than naïvely +// stripping away any starting or ending parenthesis characters—hence this +// test of the JSON error format. + +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +#![deny(clippy::unused_unit)] +#![allow(dead_code)] +#![allow(clippy::from_over_into)] + +struct Unitter; +impl Unitter { + #[allow(clippy::no_effect)] + pub fn get_unit (), G>(&self, f: F, _g: G) -> () + where G: Fn() -> () { + let _y: &dyn Fn() -> () = &f; + (); // this should not lint, as it's not in return type position + } +} + +impl Into<()> for Unitter { + #[rustfmt::skip] + fn into(self) -> () { + () + } +} + +trait Trait { + fn redundant (), G, H>(&self, _f: F, _g: G, _h: H) + where + G: FnMut() -> (), + H: Fn() -> (); +} + +impl Trait for Unitter { + fn redundant (), G, H>(&self, _f: F, _g: G, _h: H) + where + G: FnMut() -> (), + H: Fn() -> () {} +} + +fn return_unit() -> () { () } + +#[allow(clippy::needless_return)] +#[allow(clippy::never_loop)] +#[allow(clippy::unit_cmp)] +fn main() { + let u = Unitter; + assert_eq!(u.get_unit(|| {}, return_unit), u.into()); + return_unit(); + loop { + break(); + } + return(); +} + +// https://github.com/rust-lang/rust-clippy/issues/4076 +fn foo() { + macro_rules! foo { + (recv($r:expr) -> $res:pat => $body:expr) => { + $body + } + } + + foo! { + recv(rx) -> _x => () + } +} + +#[rustfmt::skip] +fn test()->(){} + +#[rustfmt::skip] +fn test2() ->(){} + +#[rustfmt::skip] +fn test3()-> (){} diff --git a/src/tools/clippy/tests/ui/unused_unit.stderr b/src/tools/clippy/tests/ui/unused_unit.stderr new file mode 100644 index 0000000000..02038b5fb6 --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_unit.stderr @@ -0,0 +1,122 @@ +error: unneeded unit return type + --> $DIR/unused_unit.rs:19:28 + | +LL | pub fn get_unit (), G>(&self, f: F, _g: G) -> () + | ^^^^^^ help: remove the `-> ()` + | +note: the lint level is defined here + --> $DIR/unused_unit.rs:12:9 + | +LL | #![deny(clippy::unused_unit)] + | ^^^^^^^^^^^^^^^^^^^ + +error: unneeded unit return type + --> $DIR/unused_unit.rs:20:18 + | +LL | where G: Fn() -> () { + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:19:58 + | +LL | pub fn get_unit (), G>(&self, f: F, _g: G) -> () + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:21:26 + | +LL | let _y: &dyn Fn() -> () = &f; + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:28:18 + | +LL | fn into(self) -> () { + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit expression + --> $DIR/unused_unit.rs:29:9 + | +LL | () + | ^^ help: remove the final `()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:34:29 + | +LL | fn redundant (), G, H>(&self, _f: F, _g: G, _h: H) + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:36:19 + | +LL | G: FnMut() -> (), + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:37:16 + | +LL | H: Fn() -> (); + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:41:29 + | +LL | fn redundant (), G, H>(&self, _f: F, _g: G, _h: H) + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:43:19 + | +LL | G: FnMut() -> (), + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:44:16 + | +LL | H: Fn() -> () {} + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:47:17 + | +LL | fn return_unit() -> () { () } + | ^^^^^^ help: remove the `-> ()` + +error: unneeded unit expression + --> $DIR/unused_unit.rs:47:26 + | +LL | fn return_unit() -> () { () } + | ^^ help: remove the final `()` + +error: unneeded `()` + --> $DIR/unused_unit.rs:57:14 + | +LL | break(); + | ^^ help: remove the `()` + +error: unneeded `()` + --> $DIR/unused_unit.rs:59:11 + | +LL | return(); + | ^^ help: remove the `()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:76:10 + | +LL | fn test()->(){} + | ^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:79:11 + | +LL | fn test2() ->(){} + | ^^^^^ help: remove the `-> ()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:82:11 + | +LL | fn test3()-> (){} + | ^^^^^ help: remove the `-> ()` + +error: aborting due to 19 previous errors + diff --git a/src/tools/clippy/tests/ui/unwrap.rs b/src/tools/clippy/tests/ui/unwrap.rs new file mode 100644 index 0000000000..a4a3cd1d37 --- /dev/null +++ b/src/tools/clippy/tests/ui/unwrap.rs @@ -0,0 +1,16 @@ +#![warn(clippy::unwrap_used)] + +fn unwrap_option() { + let opt = Some(0); + let _ = opt.unwrap(); +} + +fn unwrap_result() { + let res: Result = Ok(0); + let _ = res.unwrap(); +} + +fn main() { + unwrap_option(); + unwrap_result(); +} diff --git a/src/tools/clippy/tests/ui/unwrap.stderr b/src/tools/clippy/tests/ui/unwrap.stderr new file mode 100644 index 0000000000..4f0858005f --- /dev/null +++ b/src/tools/clippy/tests/ui/unwrap.stderr @@ -0,0 +1,19 @@ +error: used `unwrap()` on `an Option` value + --> $DIR/unwrap.rs:5:13 + | +LL | let _ = opt.unwrap(); + | ^^^^^^^^^^^^ + | + = note: `-D clippy::unwrap-used` implied by `-D warnings` + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + +error: used `unwrap()` on `a Result` value + --> $DIR/unwrap.rs:10:13 + | +LL | let _ = res.unwrap(); + | ^^^^^^^^^^^^ + | + = help: if you don't want to handle the `Err` case gracefully, consider using `expect()` to provide a better panic message + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/unwrap_in_result.rs b/src/tools/clippy/tests/ui/unwrap_in_result.rs new file mode 100644 index 0000000000..2aa842adc8 --- /dev/null +++ b/src/tools/clippy/tests/ui/unwrap_in_result.rs @@ -0,0 +1,44 @@ +#![warn(clippy::unwrap_in_result)] + +struct A; + +impl A { + // should not be detected + fn good_divisible_by_3(i_str: String) -> Result { + // checks whether a string represents a number divisible by 3 + let i_result = i_str.parse::(); + match i_result { + Err(_e) => Err("Not a number".to_string()), + Ok(i) => { + if i % 3 == 0 { + return Ok(true); + } + Err("Number is not divisible by 3".to_string()) + }, + } + } + + // should be detected + fn bad_divisible_by_3(i_str: String) -> Result { + // checks whether a string represents a number divisible by 3 + let i = i_str.parse::().unwrap(); + if i % 3 == 0 { + Ok(true) + } else { + Err("Number is not divisible by 3".to_string()) + } + } + + fn example_option_expect(i_str: String) -> Option { + let i = i_str.parse::().expect("not a number"); + if i % 3 == 0 { + return Some(true); + } + None + } +} + +fn main() { + A::bad_divisible_by_3("3".to_string()); + A::good_divisible_by_3("3".to_string()); +} diff --git a/src/tools/clippy/tests/ui/unwrap_in_result.stderr b/src/tools/clippy/tests/ui/unwrap_in_result.stderr new file mode 100644 index 0000000000..56bc2f2d1c --- /dev/null +++ b/src/tools/clippy/tests/ui/unwrap_in_result.stderr @@ -0,0 +1,41 @@ +error: used unwrap or expect in a function that returns result or option + --> $DIR/unwrap_in_result.rs:22:5 + | +LL | / fn bad_divisible_by_3(i_str: String) -> Result { +LL | | // checks whether a string represents a number divisible by 3 +LL | | let i = i_str.parse::().unwrap(); +LL | | if i % 3 == 0 { +... | +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::unwrap-in-result` implied by `-D warnings` + = help: unwrap and expect should not be used in a function that returns result or option +note: potential non-recoverable error(s) + --> $DIR/unwrap_in_result.rs:24:17 + | +LL | let i = i_str.parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: used unwrap or expect in a function that returns result or option + --> $DIR/unwrap_in_result.rs:32:5 + | +LL | / fn example_option_expect(i_str: String) -> Option { +LL | | let i = i_str.parse::().expect("not a number"); +LL | | if i % 3 == 0 { +LL | | return Some(true); +LL | | } +LL | | None +LL | | } + | |_____^ + | + = help: unwrap and expect should not be used in a function that returns result or option +note: potential non-recoverable error(s) + --> $DIR/unwrap_in_result.rs:33:17 + | +LL | let i = i_str.parse::().expect("not a number"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/unwrap_or.rs b/src/tools/clippy/tests/ui/unwrap_or.rs new file mode 100644 index 0000000000..bfb41e4394 --- /dev/null +++ b/src/tools/clippy/tests/ui/unwrap_or.rs @@ -0,0 +1,9 @@ +#![warn(clippy::all)] + +fn main() { + let s = Some(String::from("test string")).unwrap_or("Fail".to_string()).len(); +} + +fn new_lines() { + let s = Some(String::from("test string")).unwrap_or("Fail".to_string()).len(); +} diff --git a/src/tools/clippy/tests/ui/unwrap_or.stderr b/src/tools/clippy/tests/ui/unwrap_or.stderr new file mode 100644 index 0000000000..c3a7464fd4 --- /dev/null +++ b/src/tools/clippy/tests/ui/unwrap_or.stderr @@ -0,0 +1,16 @@ +error: use of `unwrap_or` followed by a function call + --> $DIR/unwrap_or.rs:4:47 + | +LL | let s = Some(String::from("test string")).unwrap_or("Fail".to_string()).len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "Fail".to_string())` + | + = note: `-D clippy::or-fun-call` implied by `-D warnings` + +error: use of `unwrap_or` followed by a function call + --> $DIR/unwrap_or.rs:8:47 + | +LL | let s = Some(String::from("test string")).unwrap_or("Fail".to_string()).len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "Fail".to_string())` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/update-all-references.sh b/src/tools/clippy/tests/ui/update-all-references.sh new file mode 100755 index 0000000000..4391499a1e --- /dev/null +++ b/src/tools/clippy/tests/ui/update-all-references.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "Please use 'cargo dev bless' instead." diff --git a/src/tools/clippy/tests/ui/upper_case_acronyms.rs b/src/tools/clippy/tests/ui/upper_case_acronyms.rs new file mode 100644 index 0000000000..735909887a --- /dev/null +++ b/src/tools/clippy/tests/ui/upper_case_acronyms.rs @@ -0,0 +1,23 @@ +#![warn(clippy::upper_case_acronyms)] + +struct HTTPResponse; // not linted by default, but with cfg option + +struct CString; // not linted + +enum Flags { + NS, // not linted + CWR, + ECE, + URG, + ACK, + PSH, + RST, + SYN, + FIN, +} + +// linted with cfg option, beware that lint suggests `GccllvmSomething` instead of +// `GccLlvmSomething` +struct GCCLLVMSomething; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/upper_case_acronyms.stderr b/src/tools/clippy/tests/ui/upper_case_acronyms.stderr new file mode 100644 index 0000000000..bbe38991e5 --- /dev/null +++ b/src/tools/clippy/tests/ui/upper_case_acronyms.stderr @@ -0,0 +1,52 @@ +error: name `CWR` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:9:5 + | +LL | CWR, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Cwr` + | + = note: `-D clippy::upper-case-acronyms` implied by `-D warnings` + +error: name `ECE` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:10:5 + | +LL | ECE, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Ece` + +error: name `URG` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:11:5 + | +LL | URG, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Urg` + +error: name `ACK` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:12:5 + | +LL | ACK, + | ^^^ help: consider making the acronym lowercase, except the initial letter (notice the capitalization): `Ack` + +error: name `PSH` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:13:5 + | +LL | PSH, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Psh` + +error: name `RST` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:14:5 + | +LL | RST, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Rst` + +error: name `SYN` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:15:5 + | +LL | SYN, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Syn` + +error: name `FIN` contains a capitalized acronym + --> $DIR/upper_case_acronyms.rs:16:5 + | +LL | FIN, + | ^^^ help: consider making the acronym lowercase, except the initial letter: `Fin` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/use_self.fixed b/src/tools/clippy/tests/ui/use_self.fixed new file mode 100644 index 0000000000..b94d5448d9 --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self.fixed @@ -0,0 +1,464 @@ +// run-rustfix +// edition:2018 +// aux-build:proc_macro_derive.rs + +#![warn(clippy::use_self)] +#![allow(dead_code)] +#![allow(clippy::should_implement_trait, clippy::upper_case_acronyms, clippy::from_over_into)] + +#[macro_use] +extern crate proc_macro_derive; + +fn main() {} + +mod use_self { + struct Foo {} + + impl Foo { + fn new() -> Self { + Self {} + } + fn test() -> Self { + Self::new() + } + } + + impl Default for Foo { + fn default() -> Self { + Self::new() + } + } +} + +mod better { + struct Foo {} + + impl Foo { + fn new() -> Self { + Self {} + } + fn test() -> Self { + Self::new() + } + } + + impl Default for Foo { + fn default() -> Self { + Self::new() + } + } +} + +mod lifetimes { + struct Foo<'a> { + foo_str: &'a str, + } + + impl<'a> Foo<'a> { + // Cannot use `Self` as return type, because the function is actually `fn foo<'b>(s: &'b str) -> + // Foo<'b>` + fn foo(s: &str) -> Foo { + Foo { foo_str: s } + } + // cannot replace with `Self`, because that's `Foo<'a>` + fn bar() -> Foo<'static> { + Foo { foo_str: "foo" } + } + + // FIXME: the lint does not handle lifetimed struct + // `Self` should be applicable here + fn clone(&self) -> Foo<'a> { + Foo { foo_str: self.foo_str } + } + } +} + +mod issue2894 { + trait IntoBytes { + fn to_bytes(&self) -> Vec; + } + + // This should not be linted + impl IntoBytes for u8 { + fn to_bytes(&self) -> Vec { + vec![*self] + } + } +} + +mod existential { + struct Foo; + + impl Foo { + fn bad(foos: &[Self]) -> impl Iterator { + foos.iter() + } + + fn good(foos: &[Self]) -> impl Iterator { + foos.iter() + } + } +} + +mod tuple_structs { + pub struct TS(i32); + + impl TS { + pub fn ts() -> Self { + Self(0) + } + } +} + +mod macros { + macro_rules! use_self_expand { + () => { + fn new() -> Foo { + Foo {} + } + }; + } + + struct Foo {} + + impl Foo { + use_self_expand!(); // Should not lint in local macros + } + + #[derive(StructAUseSelf)] // Should not lint in derives + struct A; +} + +mod nesting { + struct Foo {} + impl Foo { + fn foo() { + #[allow(unused_imports)] + use self::Foo; // Can't use Self here + struct Bar { + foo: Foo, // Foo != Self + } + + impl Bar { + fn bar() -> Self { + Self { foo: Foo {} } + } + } + + // Can't use Self here + fn baz() -> Foo { + Foo {} + } + } + + // Should lint here + fn baz() -> Self { + Self {} + } + } + + enum Enum { + A, + B(u64), + C { field: bool }, + } + impl Enum { + fn method() { + #[allow(unused_imports)] + use self::Enum::*; // Issue 3425 + static STATIC: Enum = Enum::A; // Can't use Self as type + } + + fn method2() { + let _ = Self::B(42); + let _ = Self::C { field: true }; + let _ = Self::A; + } + } +} + +mod issue3410 { + + struct A; + struct B; + + trait Trait { + fn a(v: T) -> Self; + } + + impl Trait> for Vec { + fn a(_: Vec) -> Self { + unimplemented!() + } + } + + impl Trait> for Vec + where + T: Trait, + { + fn a(v: Vec) -> Self { + >::a(v).into_iter().map(Trait::a).collect() + } + } +} + +#[allow(clippy::no_effect, path_statements)] +mod rustfix { + mod nested { + pub struct A {} + } + + impl nested::A { + const A: bool = true; + + fn fun_1() {} + + fn fun_2() { + Self::fun_1(); + Self::A; + + Self {}; + } + } +} + +mod issue3567 { + struct TestStruct {} + impl TestStruct { + fn from_something() -> Self { + Self {} + } + } + + trait Test { + fn test() -> TestStruct; + } + + impl Test for TestStruct { + fn test() -> TestStruct { + Self::from_something() + } + } +} + +mod paths_created_by_lowering { + use std::ops::Range; + + struct S {} + + impl S { + const A: usize = 0; + const B: usize = 1; + + async fn g() -> Self { + Self {} + } + + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8] { + &p[Self::A..Self::B] + } + } + + trait T { + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8]; + } + + impl T for Range { + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8] { + &p[0..1] + } + } +} + +// reused from #1997 +mod generics { + struct Foo { + value: T, + } + + impl Foo { + // `Self` is applicable here + fn foo(value: T) -> Self { + Self { value } + } + + // `Cannot` use `Self` as a return type as the generic types are different + fn bar(value: i32) -> Foo { + Foo { value } + } + } +} + +mod issue4140 { + pub struct Error { + _from: From, + _too: To, + } + + pub trait From { + type From; + type To; + + fn from(value: T) -> Self; + } + + pub trait TryFrom + where + Self: Sized, + { + type From; + type To; + + fn try_from(value: T) -> Result>; + } + + // FIXME: Suggested fix results in infinite recursion. + // impl TryFrom for T + // where + // T: From, + // { + // type From = Self::From; + // type To = Self::To; + + // fn try_from(value: F) -> Result> { + // Ok(From::from(value)) + // } + // } + + impl From for i64 { + type From = bool; + type To = Self; + + fn from(value: bool) -> Self { + if value { 100 } else { 0 } + } + } +} + +mod issue2843 { + trait Foo { + type Bar; + } + + impl Foo for usize { + type Bar = u8; + } + + impl Foo for Option { + type Bar = Option; + } +} + +mod issue3859 { + pub struct Foo; + pub struct Bar([usize; 3]); + + impl Foo { + pub const BAR: usize = 3; + + pub fn foo() { + const _X: usize = Foo::BAR; + // const _Y: usize = Self::BAR; + } + } +} + +mod issue4305 { + trait Foo: 'static {} + + struct Bar; + + impl Foo for Bar {} + + impl From for Box { + fn from(t: T) -> Self { + Box::new(t) + } + } +} + +mod lint_at_item_level { + struct Foo {} + + #[allow(clippy::use_self)] + impl Foo { + fn new() -> Foo { + Foo {} + } + } + + #[allow(clippy::use_self)] + impl Default for Foo { + fn default() -> Foo { + Foo::new() + } + } +} + +mod lint_at_impl_item_level { + struct Foo {} + + impl Foo { + #[allow(clippy::use_self)] + fn new() -> Foo { + Foo {} + } + } + + impl Default for Foo { + #[allow(clippy::use_self)] + fn default() -> Foo { + Foo::new() + } + } +} + +mod issue4734 { + #[repr(C, packed)] + pub struct X { + pub x: u32, + } + + impl From for u32 { + fn from(c: X) -> Self { + unsafe { core::mem::transmute(c) } + } + } +} + +mod nested_paths { + use std::convert::Into; + mod submod { + pub struct B {} + pub struct C {} + + impl Into for B { + fn into(self) -> C { + C {} + } + } + } + + struct A { + t: T, + } + + impl A { + fn new>(v: V) -> Self { + Self { t: Into::into(v) } + } + } + + impl A { + fn test() -> Self { + Self::new::(submod::B {}) + } + } +} + +mod issue6818 { + #[derive(serde::Deserialize)] + struct A { + a: i32, + } +} diff --git a/src/tools/clippy/tests/ui/use_self.rs b/src/tools/clippy/tests/ui/use_self.rs new file mode 100644 index 0000000000..ac99c6d9d7 --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self.rs @@ -0,0 +1,464 @@ +// run-rustfix +// edition:2018 +// aux-build:proc_macro_derive.rs + +#![warn(clippy::use_self)] +#![allow(dead_code)] +#![allow(clippy::should_implement_trait, clippy::upper_case_acronyms, clippy::from_over_into)] + +#[macro_use] +extern crate proc_macro_derive; + +fn main() {} + +mod use_self { + struct Foo {} + + impl Foo { + fn new() -> Foo { + Foo {} + } + fn test() -> Foo { + Foo::new() + } + } + + impl Default for Foo { + fn default() -> Foo { + Foo::new() + } + } +} + +mod better { + struct Foo {} + + impl Foo { + fn new() -> Self { + Self {} + } + fn test() -> Self { + Self::new() + } + } + + impl Default for Foo { + fn default() -> Self { + Self::new() + } + } +} + +mod lifetimes { + struct Foo<'a> { + foo_str: &'a str, + } + + impl<'a> Foo<'a> { + // Cannot use `Self` as return type, because the function is actually `fn foo<'b>(s: &'b str) -> + // Foo<'b>` + fn foo(s: &str) -> Foo { + Foo { foo_str: s } + } + // cannot replace with `Self`, because that's `Foo<'a>` + fn bar() -> Foo<'static> { + Foo { foo_str: "foo" } + } + + // FIXME: the lint does not handle lifetimed struct + // `Self` should be applicable here + fn clone(&self) -> Foo<'a> { + Foo { foo_str: self.foo_str } + } + } +} + +mod issue2894 { + trait IntoBytes { + fn to_bytes(&self) -> Vec; + } + + // This should not be linted + impl IntoBytes for u8 { + fn to_bytes(&self) -> Vec { + vec![*self] + } + } +} + +mod existential { + struct Foo; + + impl Foo { + fn bad(foos: &[Foo]) -> impl Iterator { + foos.iter() + } + + fn good(foos: &[Self]) -> impl Iterator { + foos.iter() + } + } +} + +mod tuple_structs { + pub struct TS(i32); + + impl TS { + pub fn ts() -> Self { + TS(0) + } + } +} + +mod macros { + macro_rules! use_self_expand { + () => { + fn new() -> Foo { + Foo {} + } + }; + } + + struct Foo {} + + impl Foo { + use_self_expand!(); // Should not lint in local macros + } + + #[derive(StructAUseSelf)] // Should not lint in derives + struct A; +} + +mod nesting { + struct Foo {} + impl Foo { + fn foo() { + #[allow(unused_imports)] + use self::Foo; // Can't use Self here + struct Bar { + foo: Foo, // Foo != Self + } + + impl Bar { + fn bar() -> Bar { + Bar { foo: Foo {} } + } + } + + // Can't use Self here + fn baz() -> Foo { + Foo {} + } + } + + // Should lint here + fn baz() -> Foo { + Foo {} + } + } + + enum Enum { + A, + B(u64), + C { field: bool }, + } + impl Enum { + fn method() { + #[allow(unused_imports)] + use self::Enum::*; // Issue 3425 + static STATIC: Enum = Enum::A; // Can't use Self as type + } + + fn method2() { + let _ = Enum::B(42); + let _ = Enum::C { field: true }; + let _ = Enum::A; + } + } +} + +mod issue3410 { + + struct A; + struct B; + + trait Trait { + fn a(v: T) -> Self; + } + + impl Trait> for Vec { + fn a(_: Vec) -> Self { + unimplemented!() + } + } + + impl Trait> for Vec + where + T: Trait, + { + fn a(v: Vec) -> Self { + >::a(v).into_iter().map(Trait::a).collect() + } + } +} + +#[allow(clippy::no_effect, path_statements)] +mod rustfix { + mod nested { + pub struct A {} + } + + impl nested::A { + const A: bool = true; + + fn fun_1() {} + + fn fun_2() { + nested::A::fun_1(); + nested::A::A; + + nested::A {}; + } + } +} + +mod issue3567 { + struct TestStruct {} + impl TestStruct { + fn from_something() -> Self { + Self {} + } + } + + trait Test { + fn test() -> TestStruct; + } + + impl Test for TestStruct { + fn test() -> TestStruct { + TestStruct::from_something() + } + } +} + +mod paths_created_by_lowering { + use std::ops::Range; + + struct S {} + + impl S { + const A: usize = 0; + const B: usize = 1; + + async fn g() -> S { + S {} + } + + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8] { + &p[S::A..S::B] + } + } + + trait T { + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8]; + } + + impl T for Range { + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8] { + &p[0..1] + } + } +} + +// reused from #1997 +mod generics { + struct Foo { + value: T, + } + + impl Foo { + // `Self` is applicable here + fn foo(value: T) -> Foo { + Foo { value } + } + + // `Cannot` use `Self` as a return type as the generic types are different + fn bar(value: i32) -> Foo { + Foo { value } + } + } +} + +mod issue4140 { + pub struct Error { + _from: From, + _too: To, + } + + pub trait From { + type From; + type To; + + fn from(value: T) -> Self; + } + + pub trait TryFrom + where + Self: Sized, + { + type From; + type To; + + fn try_from(value: T) -> Result>; + } + + // FIXME: Suggested fix results in infinite recursion. + // impl TryFrom for T + // where + // T: From, + // { + // type From = Self::From; + // type To = Self::To; + + // fn try_from(value: F) -> Result> { + // Ok(From::from(value)) + // } + // } + + impl From for i64 { + type From = bool; + type To = Self; + + fn from(value: bool) -> Self { + if value { 100 } else { 0 } + } + } +} + +mod issue2843 { + trait Foo { + type Bar; + } + + impl Foo for usize { + type Bar = u8; + } + + impl Foo for Option { + type Bar = Option; + } +} + +mod issue3859 { + pub struct Foo; + pub struct Bar([usize; 3]); + + impl Foo { + pub const BAR: usize = 3; + + pub fn foo() { + const _X: usize = Foo::BAR; + // const _Y: usize = Self::BAR; + } + } +} + +mod issue4305 { + trait Foo: 'static {} + + struct Bar; + + impl Foo for Bar {} + + impl From for Box { + fn from(t: T) -> Self { + Box::new(t) + } + } +} + +mod lint_at_item_level { + struct Foo {} + + #[allow(clippy::use_self)] + impl Foo { + fn new() -> Foo { + Foo {} + } + } + + #[allow(clippy::use_self)] + impl Default for Foo { + fn default() -> Foo { + Foo::new() + } + } +} + +mod lint_at_impl_item_level { + struct Foo {} + + impl Foo { + #[allow(clippy::use_self)] + fn new() -> Foo { + Foo {} + } + } + + impl Default for Foo { + #[allow(clippy::use_self)] + fn default() -> Foo { + Foo::new() + } + } +} + +mod issue4734 { + #[repr(C, packed)] + pub struct X { + pub x: u32, + } + + impl From for u32 { + fn from(c: X) -> Self { + unsafe { core::mem::transmute(c) } + } + } +} + +mod nested_paths { + use std::convert::Into; + mod submod { + pub struct B {} + pub struct C {} + + impl Into for B { + fn into(self) -> C { + C {} + } + } + } + + struct A { + t: T, + } + + impl A { + fn new>(v: V) -> Self { + Self { t: Into::into(v) } + } + } + + impl A { + fn test() -> Self { + A::new::(submod::B {}) + } + } +} + +mod issue6818 { + #[derive(serde::Deserialize)] + struct A { + a: i32, + } +} diff --git a/src/tools/clippy/tests/ui/use_self.stderr b/src/tools/clippy/tests/ui/use_self.stderr new file mode 100644 index 0000000000..a32a9b9157 --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self.stderr @@ -0,0 +1,166 @@ +error: unnecessary structure name repetition + --> $DIR/use_self.rs:18:21 + | +LL | fn new() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | + = note: `-D clippy::use-self` implied by `-D warnings` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:19:13 + | +LL | Foo {} + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:21:22 + | +LL | fn test() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:22:13 + | +LL | Foo::new() + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:27:25 + | +LL | fn default() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:28:13 + | +LL | Foo::new() + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:93:24 + | +LL | fn bad(foos: &[Foo]) -> impl Iterator { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:93:55 + | +LL | fn bad(foos: &[Foo]) -> impl Iterator { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:108:13 + | +LL | TS(0) + | ^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:143:29 + | +LL | fn bar() -> Bar { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:144:21 + | +LL | Bar { foo: Foo {} } + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:155:21 + | +LL | fn baz() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:156:13 + | +LL | Foo {} + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:173:21 + | +LL | let _ = Enum::B(42); + | ^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:174:21 + | +LL | let _ = Enum::C { field: true }; + | ^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:175:21 + | +LL | let _ = Enum::A; + | ^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:217:13 + | +LL | nested::A::fun_1(); + | ^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:218:13 + | +LL | nested::A::A; + | ^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:220:13 + | +LL | nested::A {}; + | ^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:239:13 + | +LL | TestStruct::from_something() + | ^^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:253:25 + | +LL | async fn g() -> S { + | ^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:254:13 + | +LL | S {} + | ^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:258:16 + | +LL | &p[S::A..S::B] + | ^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:258:22 + | +LL | &p[S::A..S::B] + | ^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:281:29 + | +LL | fn foo(value: T) -> Foo { + | ^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:282:13 + | +LL | Foo { value } + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:454:13 + | +LL | A::new::(submod::B {}) + | ^ help: use the applicable keyword: `Self` + +error: aborting due to 27 previous errors + diff --git a/src/tools/clippy/tests/ui/use_self_trait.fixed b/src/tools/clippy/tests/ui/use_self_trait.fixed new file mode 100644 index 0000000000..9bcd692fb3 --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self_trait.fixed @@ -0,0 +1,115 @@ +// run-rustfix + +#![warn(clippy::use_self)] +#![allow(dead_code)] +#![allow(clippy::should_implement_trait, clippy::boxed_local)] + +use std::ops::Mul; + +trait SelfTrait { + fn refs(p1: &Self) -> &Self; + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self; + fn mut_refs(p1: &mut Self) -> &mut Self; + fn nested(p1: Box, p2: (&u8, &Self)); + fn vals(r: Self) -> Self; +} + +#[derive(Default)] +struct Bad; + +impl SelfTrait for Bad { + fn refs(p1: &Self) -> &Self { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self { + p1 + } + + fn mut_refs(p1: &mut Self) -> &mut Self { + p1 + } + + fn nested(_p1: Box, _p2: (&u8, &Self)) {} + + fn vals(_: Self) -> Self { + Self::default() + } +} + +impl Mul for Bad { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + rhs + } +} + +impl Clone for Bad { + fn clone(&self) -> Self { + // FIXME: applicable here + Bad + } +} + +#[derive(Default)] +struct Good; + +impl SelfTrait for Good { + fn refs(p1: &Self) -> &Self { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self { + p1 + } + + fn mut_refs(p1: &mut Self) -> &mut Self { + p1 + } + + fn nested(_p1: Box, _p2: (&u8, &Self)) {} + + fn vals(_: Self) -> Self { + Self::default() + } +} + +impl Mul for Good { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + rhs + } +} + +trait NameTrait { + fn refs(p1: &u8) -> &u8; + fn ref_refs<'a>(p1: &'a &'a u8) -> &'a &'a u8; + fn mut_refs(p1: &mut u8) -> &mut u8; + fn nested(p1: Box, p2: (&u8, &u8)); + fn vals(p1: u8) -> u8; +} + +// Using `Self` instead of the type name is OK +impl NameTrait for u8 { + fn refs(p1: &Self) -> &Self { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self { + p1 + } + + fn mut_refs(p1: &mut Self) -> &mut Self { + p1 + } + + fn nested(_p1: Box, _p2: (&Self, &Self)) {} + + fn vals(_: Self) -> Self { + Self::default() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/use_self_trait.rs b/src/tools/clippy/tests/ui/use_self_trait.rs new file mode 100644 index 0000000000..de305d40f3 --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self_trait.rs @@ -0,0 +1,115 @@ +// run-rustfix + +#![warn(clippy::use_self)] +#![allow(dead_code)] +#![allow(clippy::should_implement_trait, clippy::boxed_local)] + +use std::ops::Mul; + +trait SelfTrait { + fn refs(p1: &Self) -> &Self; + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self; + fn mut_refs(p1: &mut Self) -> &mut Self; + fn nested(p1: Box, p2: (&u8, &Self)); + fn vals(r: Self) -> Self; +} + +#[derive(Default)] +struct Bad; + +impl SelfTrait for Bad { + fn refs(p1: &Bad) -> &Bad { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Bad) -> &'a &'a Bad { + p1 + } + + fn mut_refs(p1: &mut Bad) -> &mut Bad { + p1 + } + + fn nested(_p1: Box, _p2: (&u8, &Bad)) {} + + fn vals(_: Bad) -> Bad { + Bad::default() + } +} + +impl Mul for Bad { + type Output = Bad; + + fn mul(self, rhs: Bad) -> Bad { + rhs + } +} + +impl Clone for Bad { + fn clone(&self) -> Self { + // FIXME: applicable here + Bad + } +} + +#[derive(Default)] +struct Good; + +impl SelfTrait for Good { + fn refs(p1: &Self) -> &Self { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self { + p1 + } + + fn mut_refs(p1: &mut Self) -> &mut Self { + p1 + } + + fn nested(_p1: Box, _p2: (&u8, &Self)) {} + + fn vals(_: Self) -> Self { + Self::default() + } +} + +impl Mul for Good { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + rhs + } +} + +trait NameTrait { + fn refs(p1: &u8) -> &u8; + fn ref_refs<'a>(p1: &'a &'a u8) -> &'a &'a u8; + fn mut_refs(p1: &mut u8) -> &mut u8; + fn nested(p1: Box, p2: (&u8, &u8)); + fn vals(p1: u8) -> u8; +} + +// Using `Self` instead of the type name is OK +impl NameTrait for u8 { + fn refs(p1: &Self) -> &Self { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self { + p1 + } + + fn mut_refs(p1: &mut Self) -> &mut Self { + p1 + } + + fn nested(_p1: Box, _p2: (&Self, &Self)) {} + + fn vals(_: Self) -> Self { + Self::default() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/use_self_trait.stderr b/src/tools/clippy/tests/ui/use_self_trait.stderr new file mode 100644 index 0000000000..55af3ff2a9 --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self_trait.stderr @@ -0,0 +1,88 @@ +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:21:18 + | +LL | fn refs(p1: &Bad) -> &Bad { + | ^^^ help: use the applicable keyword: `Self` + | + = note: `-D clippy::use-self` implied by `-D warnings` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:21:27 + | +LL | fn refs(p1: &Bad) -> &Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:25:33 + | +LL | fn ref_refs<'a>(p1: &'a &'a Bad) -> &'a &'a Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:25:49 + | +LL | fn ref_refs<'a>(p1: &'a &'a Bad) -> &'a &'a Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:29:26 + | +LL | fn mut_refs(p1: &mut Bad) -> &mut Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:29:39 + | +LL | fn mut_refs(p1: &mut Bad) -> &mut Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:33:24 + | +LL | fn nested(_p1: Box, _p2: (&u8, &Bad)) {} + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:33:42 + | +LL | fn nested(_p1: Box, _p2: (&u8, &Bad)) {} + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:35:16 + | +LL | fn vals(_: Bad) -> Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:35:24 + | +LL | fn vals(_: Bad) -> Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:36:9 + | +LL | Bad::default() + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:41:19 + | +LL | type Output = Bad; + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:43:23 + | +LL | fn mul(self, rhs: Bad) -> Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:43:31 + | +LL | fn mul(self, rhs: Bad) -> Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/used_underscore_binding.rs b/src/tools/clippy/tests/ui/used_underscore_binding.rs new file mode 100644 index 0000000000..d8bda7e8f4 --- /dev/null +++ b/src/tools/clippy/tests/ui/used_underscore_binding.rs @@ -0,0 +1,119 @@ +// edition:2018 +// aux-build:proc_macro_derive.rs + +#![feature(rustc_private)] +#![warn(clippy::all)] +#![allow(clippy::blacklisted_name, clippy::eq_op)] +#![warn(clippy::used_underscore_binding)] + +#[macro_use] +extern crate proc_macro_derive; + +// This should not trigger the lint. There's underscore binding inside the external derive that +// would trigger the `used_underscore_binding` lint. +#[derive(DeriveSomething)] +struct Baz; + +macro_rules! test_macro { + () => {{ + let _foo = 42; + _foo + 1 + }}; +} + +/// Tests that we lint if we use a binding with a single leading underscore +fn prefix_underscore(_foo: u32) -> u32 { + _foo + 1 +} + +/// Tests that we lint if we use a `_`-variable defined outside within a macro expansion +fn in_macro_or_desugar(_foo: u32) { + println!("{}", _foo); + assert_eq!(_foo, _foo); + + test_macro!() + 1; +} + +// Struct for testing use of fields prefixed with an underscore +struct StructFieldTest { + _underscore_field: u32, +} + +/// Tests that we lint the use of a struct field which is prefixed with an underscore +fn in_struct_field() { + let mut s = StructFieldTest { _underscore_field: 0 }; + s._underscore_field += 1; +} + +/// Tests that we do not lint if the underscore is not a prefix +fn non_prefix_underscore(some_foo: u32) -> u32 { + some_foo + 1 +} + +/// Tests that we do not lint if we do not use the binding (simple case) +fn unused_underscore_simple(_foo: u32) -> u32 { + 1 +} + +/// Tests that we do not lint if we do not use the binding (complex case). This checks for +/// compatibility with the built-in `unused_variables` lint. +fn unused_underscore_complex(mut _foo: u32) -> u32 { + _foo += 1; + _foo = 2; + 1 +} + +/// Test that we do not lint for multiple underscores +fn multiple_underscores(__foo: u32) -> u32 { + __foo + 1 +} + +// Non-variable bindings with preceding underscore +fn _fn_test() {} +struct _StructTest; +enum _EnumTest { + _Empty, + _Value(_StructTest), +} + +/// Tests that we do not lint for non-variable bindings +fn non_variables() { + _fn_test(); + let _s = _StructTest; + let _e = match _EnumTest::_Value(_StructTest) { + _EnumTest::_Empty => 0, + _EnumTest::_Value(_st) => 1, + }; + let f = _fn_test; + f(); +} + +// Tests that we do not lint if the binding comes from await desugaring, +// but we do lint the awaited expression. See issue 5360. +async fn await_desugaring() { + async fn foo() {} + fn uses_i(_i: i32) {} + + foo().await; + ({ + let _i = 5; + uses_i(_i); + foo() + }) + .await +} + +fn main() { + let foo = 0u32; + // tests of unused_underscore lint + let _ = prefix_underscore(foo); + in_macro_or_desugar(foo); + in_struct_field(); + // possible false positives + let _ = non_prefix_underscore(foo); + let _ = unused_underscore_simple(foo); + let _ = unused_underscore_complex(foo); + let _ = multiple_underscores(foo); + non_variables(); + await_desugaring(); +} diff --git a/src/tools/clippy/tests/ui/used_underscore_binding.stderr b/src/tools/clippy/tests/ui/used_underscore_binding.stderr new file mode 100644 index 0000000000..2cbfc5ca2e --- /dev/null +++ b/src/tools/clippy/tests/ui/used_underscore_binding.stderr @@ -0,0 +1,40 @@ +error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used + --> $DIR/used_underscore_binding.rs:26:5 + | +LL | _foo + 1 + | ^^^^ + | + = note: `-D clippy::used-underscore-binding` implied by `-D warnings` + +error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used + --> $DIR/used_underscore_binding.rs:31:20 + | +LL | println!("{}", _foo); + | ^^^^ + +error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used + --> $DIR/used_underscore_binding.rs:32:16 + | +LL | assert_eq!(_foo, _foo); + | ^^^^ + +error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used + --> $DIR/used_underscore_binding.rs:32:22 + | +LL | assert_eq!(_foo, _foo); + | ^^^^ + +error: used binding `_underscore_field` which is prefixed with an underscore. A leading underscore signals that a binding will not be used + --> $DIR/used_underscore_binding.rs:45:5 + | +LL | s._underscore_field += 1; + | ^^^^^^^^^^^^^^^^^^^ + +error: used binding `_i` which is prefixed with an underscore. A leading underscore signals that a binding will not be used + --> $DIR/used_underscore_binding.rs:100:16 + | +LL | uses_i(_i); + | ^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/useful_asref.rs b/src/tools/clippy/tests/ui/useful_asref.rs new file mode 100644 index 0000000000..a9f0170a79 --- /dev/null +++ b/src/tools/clippy/tests/ui/useful_asref.rs @@ -0,0 +1,13 @@ +#![deny(clippy::useless_asref)] + +trait Trait { + fn as_ptr(&self); +} + +impl<'a> Trait for &'a [u8] { + fn as_ptr(&self) { + self.as_ref().as_ptr(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/useless_asref.fixed b/src/tools/clippy/tests/ui/useless_asref.fixed new file mode 100644 index 0000000000..e356f13d08 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_asref.fixed @@ -0,0 +1,135 @@ +// run-rustfix + +#![deny(clippy::useless_asref)] + +use std::fmt::Debug; + +struct FakeAsRef; + +#[allow(clippy::should_implement_trait)] +impl FakeAsRef { + fn as_ref(&self) -> &Self { + self + } +} + +struct MoreRef; + +impl<'a, 'b, 'c> AsRef<&'a &'b &'c MoreRef> for MoreRef { + fn as_ref(&self) -> &&'a &'b &'c MoreRef { + &&&&MoreRef + } +} + +fn foo_rstr(x: &str) { + println!("{:?}", x); +} +fn foo_rslice(x: &[i32]) { + println!("{:?}", x); +} +fn foo_mrslice(x: &mut [i32]) { + println!("{:?}", x); +} +fn foo_rrrrmr(_: &&&&MoreRef) { + println!("so many refs"); +} + +fn not_ok() { + let rstr: &str = "hello"; + let mut mrslice: &mut [i32] = &mut [1, 2, 3]; + + { + let rslice: &[i32] = &*mrslice; + foo_rstr(rstr); + foo_rstr(rstr); + foo_rslice(rslice); + foo_rslice(rslice); + } + { + foo_mrslice(mrslice); + foo_mrslice(mrslice); + foo_rslice(mrslice); + foo_rslice(mrslice); + } + + { + let rrrrrstr = &&&&rstr; + let rrrrrslice = &&&&&*mrslice; + foo_rslice(rrrrrslice); + foo_rslice(rrrrrslice); + foo_rstr(rrrrrstr); + foo_rstr(rrrrrstr); + } + { + let mrrrrrslice = &mut &mut &mut &mut mrslice; + foo_mrslice(mrrrrrslice); + foo_mrslice(mrrrrrslice); + foo_rslice(mrrrrrslice); + foo_rslice(mrrrrrslice); + } + #[allow(unused_parens, clippy::double_parens)] + foo_rrrrmr((&&&&MoreRef)); + + generic_not_ok(mrslice); + generic_ok(mrslice); +} + +fn ok() { + let string = "hello".to_owned(); + let mut arr = [1, 2, 3]; + let mut vec = vec![1, 2, 3]; + + { + foo_rstr(string.as_ref()); + foo_rslice(arr.as_ref()); + foo_rslice(vec.as_ref()); + } + { + foo_mrslice(arr.as_mut()); + foo_mrslice(vec.as_mut()); + } + + { + let rrrrstring = &&&&string; + let rrrrarr = &&&&arr; + let rrrrvec = &&&&vec; + foo_rstr(rrrrstring.as_ref()); + foo_rslice(rrrrarr.as_ref()); + foo_rslice(rrrrvec.as_ref()); + } + { + let mrrrrarr = &mut &mut &mut &mut arr; + let mrrrrvec = &mut &mut &mut &mut vec; + foo_mrslice(mrrrrarr.as_mut()); + foo_mrslice(mrrrrvec.as_mut()); + } + FakeAsRef.as_ref(); + foo_rrrrmr(MoreRef.as_ref()); + + generic_not_ok(arr.as_mut()); + generic_ok(&mut arr); +} + +fn foo_mrt(t: &mut T) { + println!("{:?}", t); +} +fn foo_rt(t: &T) { + println!("{:?}", t); +} + +fn generic_not_ok + AsRef + Debug + ?Sized>(mrt: &mut T) { + foo_mrt(mrt); + foo_mrt(mrt); + foo_rt(mrt); + foo_rt(mrt); +} + +fn generic_ok + AsRef + ?Sized, T: Debug + ?Sized>(mru: &mut U) { + foo_mrt(mru.as_mut()); + foo_rt(mru.as_ref()); +} + +fn main() { + not_ok(); + ok(); +} diff --git a/src/tools/clippy/tests/ui/useless_asref.rs b/src/tools/clippy/tests/ui/useless_asref.rs new file mode 100644 index 0000000000..2a80291f5d --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_asref.rs @@ -0,0 +1,135 @@ +// run-rustfix + +#![deny(clippy::useless_asref)] + +use std::fmt::Debug; + +struct FakeAsRef; + +#[allow(clippy::should_implement_trait)] +impl FakeAsRef { + fn as_ref(&self) -> &Self { + self + } +} + +struct MoreRef; + +impl<'a, 'b, 'c> AsRef<&'a &'b &'c MoreRef> for MoreRef { + fn as_ref(&self) -> &&'a &'b &'c MoreRef { + &&&&MoreRef + } +} + +fn foo_rstr(x: &str) { + println!("{:?}", x); +} +fn foo_rslice(x: &[i32]) { + println!("{:?}", x); +} +fn foo_mrslice(x: &mut [i32]) { + println!("{:?}", x); +} +fn foo_rrrrmr(_: &&&&MoreRef) { + println!("so many refs"); +} + +fn not_ok() { + let rstr: &str = "hello"; + let mut mrslice: &mut [i32] = &mut [1, 2, 3]; + + { + let rslice: &[i32] = &*mrslice; + foo_rstr(rstr.as_ref()); + foo_rstr(rstr); + foo_rslice(rslice.as_ref()); + foo_rslice(rslice); + } + { + foo_mrslice(mrslice.as_mut()); + foo_mrslice(mrslice); + foo_rslice(mrslice.as_ref()); + foo_rslice(mrslice); + } + + { + let rrrrrstr = &&&&rstr; + let rrrrrslice = &&&&&*mrslice; + foo_rslice(rrrrrslice.as_ref()); + foo_rslice(rrrrrslice); + foo_rstr(rrrrrstr.as_ref()); + foo_rstr(rrrrrstr); + } + { + let mrrrrrslice = &mut &mut &mut &mut mrslice; + foo_mrslice(mrrrrrslice.as_mut()); + foo_mrslice(mrrrrrslice); + foo_rslice(mrrrrrslice.as_ref()); + foo_rslice(mrrrrrslice); + } + #[allow(unused_parens, clippy::double_parens)] + foo_rrrrmr((&&&&MoreRef).as_ref()); + + generic_not_ok(mrslice); + generic_ok(mrslice); +} + +fn ok() { + let string = "hello".to_owned(); + let mut arr = [1, 2, 3]; + let mut vec = vec![1, 2, 3]; + + { + foo_rstr(string.as_ref()); + foo_rslice(arr.as_ref()); + foo_rslice(vec.as_ref()); + } + { + foo_mrslice(arr.as_mut()); + foo_mrslice(vec.as_mut()); + } + + { + let rrrrstring = &&&&string; + let rrrrarr = &&&&arr; + let rrrrvec = &&&&vec; + foo_rstr(rrrrstring.as_ref()); + foo_rslice(rrrrarr.as_ref()); + foo_rslice(rrrrvec.as_ref()); + } + { + let mrrrrarr = &mut &mut &mut &mut arr; + let mrrrrvec = &mut &mut &mut &mut vec; + foo_mrslice(mrrrrarr.as_mut()); + foo_mrslice(mrrrrvec.as_mut()); + } + FakeAsRef.as_ref(); + foo_rrrrmr(MoreRef.as_ref()); + + generic_not_ok(arr.as_mut()); + generic_ok(&mut arr); +} + +fn foo_mrt(t: &mut T) { + println!("{:?}", t); +} +fn foo_rt(t: &T) { + println!("{:?}", t); +} + +fn generic_not_ok + AsRef + Debug + ?Sized>(mrt: &mut T) { + foo_mrt(mrt.as_mut()); + foo_mrt(mrt); + foo_rt(mrt.as_ref()); + foo_rt(mrt); +} + +fn generic_ok + AsRef + ?Sized, T: Debug + ?Sized>(mru: &mut U) { + foo_mrt(mru.as_mut()); + foo_rt(mru.as_ref()); +} + +fn main() { + not_ok(); + ok(); +} diff --git a/src/tools/clippy/tests/ui/useless_asref.stderr b/src/tools/clippy/tests/ui/useless_asref.stderr new file mode 100644 index 0000000000..5876b54aca --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_asref.stderr @@ -0,0 +1,74 @@ +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:43:18 + | +LL | foo_rstr(rstr.as_ref()); + | ^^^^^^^^^^^^^ help: try this: `rstr` + | +note: the lint level is defined here + --> $DIR/useless_asref.rs:3:9 + | +LL | #![deny(clippy::useless_asref)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:45:20 + | +LL | foo_rslice(rslice.as_ref()); + | ^^^^^^^^^^^^^^^ help: try this: `rslice` + +error: this call to `as_mut` does nothing + --> $DIR/useless_asref.rs:49:21 + | +LL | foo_mrslice(mrslice.as_mut()); + | ^^^^^^^^^^^^^^^^ help: try this: `mrslice` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:51:20 + | +LL | foo_rslice(mrslice.as_ref()); + | ^^^^^^^^^^^^^^^^ help: try this: `mrslice` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:58:20 + | +LL | foo_rslice(rrrrrslice.as_ref()); + | ^^^^^^^^^^^^^^^^^^^ help: try this: `rrrrrslice` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:60:18 + | +LL | foo_rstr(rrrrrstr.as_ref()); + | ^^^^^^^^^^^^^^^^^ help: try this: `rrrrrstr` + +error: this call to `as_mut` does nothing + --> $DIR/useless_asref.rs:65:21 + | +LL | foo_mrslice(mrrrrrslice.as_mut()); + | ^^^^^^^^^^^^^^^^^^^^ help: try this: `mrrrrrslice` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:67:20 + | +LL | foo_rslice(mrrrrrslice.as_ref()); + | ^^^^^^^^^^^^^^^^^^^^ help: try this: `mrrrrrslice` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:71:16 + | +LL | foo_rrrrmr((&&&&MoreRef).as_ref()); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `(&&&&MoreRef)` + +error: this call to `as_mut` does nothing + --> $DIR/useless_asref.rs:121:13 + | +LL | foo_mrt(mrt.as_mut()); + | ^^^^^^^^^^^^ help: try this: `mrt` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:123:12 + | +LL | foo_rt(mrt.as_ref()); + | ^^^^^^^^^^^^ help: try this: `mrt` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/useless_attribute.fixed b/src/tools/clippy/tests/ui/useless_attribute.fixed new file mode 100644 index 0000000000..a5fcde768f --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_attribute.fixed @@ -0,0 +1,69 @@ +// run-rustfix +// aux-build:proc_macro_derive.rs + +#![warn(clippy::useless_attribute)] +#![warn(unreachable_pub)] +#![feature(rustc_private)] + +#![allow(dead_code)] +#![cfg_attr(feature = "cargo-clippy", allow(dead_code))] +#[rustfmt::skip] +#[allow(unused_imports)] +#[allow(unused_extern_crates)] +#[macro_use] +extern crate rustc_middle; + +#[macro_use] +extern crate proc_macro_derive; + +// don't lint on unused_import for `use` items +#[allow(unused_imports)] +use std::collections; + +// don't lint on unused for `use` items +#[allow(unused)] +use std::option; + +// don't lint on deprecated for `use` items +mod foo { + #[deprecated] + pub struct Bar; +} +#[allow(deprecated)] +pub use foo::Bar; + +// This should not trigger the lint. There's lint level definitions inside the external derive +// that would trigger the useless_attribute lint. +#[derive(DeriveSomething)] +struct Baz; + +// don't lint on unreachable_pub for `use` items +mod a { + mod b { + #[allow(dead_code)] + #[allow(unreachable_pub)] + pub struct C {} + } + + #[allow(unreachable_pub)] + pub use self::b::C; +} + +// don't lint on clippy::wildcard_imports for `use` items +#[allow(clippy::wildcard_imports)] +pub use std::io::prelude::*; + +// don't lint on clippy::enum_glob_use for `use` items +#[allow(clippy::enum_glob_use)] +pub use std::cmp::Ordering::*; + +fn test_indented_attr() { + #![allow(clippy::almost_swapped)] + use std::collections::HashSet; + + let _ = HashSet::::default(); +} + +fn main() { + test_indented_attr(); +} diff --git a/src/tools/clippy/tests/ui/useless_attribute.rs b/src/tools/clippy/tests/ui/useless_attribute.rs new file mode 100644 index 0000000000..0396d39e3d --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_attribute.rs @@ -0,0 +1,69 @@ +// run-rustfix +// aux-build:proc_macro_derive.rs + +#![warn(clippy::useless_attribute)] +#![warn(unreachable_pub)] +#![feature(rustc_private)] + +#[allow(dead_code)] +#[cfg_attr(feature = "cargo-clippy", allow(dead_code))] +#[rustfmt::skip] +#[allow(unused_imports)] +#[allow(unused_extern_crates)] +#[macro_use] +extern crate rustc_middle; + +#[macro_use] +extern crate proc_macro_derive; + +// don't lint on unused_import for `use` items +#[allow(unused_imports)] +use std::collections; + +// don't lint on unused for `use` items +#[allow(unused)] +use std::option; + +// don't lint on deprecated for `use` items +mod foo { + #[deprecated] + pub struct Bar; +} +#[allow(deprecated)] +pub use foo::Bar; + +// This should not trigger the lint. There's lint level definitions inside the external derive +// that would trigger the useless_attribute lint. +#[derive(DeriveSomething)] +struct Baz; + +// don't lint on unreachable_pub for `use` items +mod a { + mod b { + #[allow(dead_code)] + #[allow(unreachable_pub)] + pub struct C {} + } + + #[allow(unreachable_pub)] + pub use self::b::C; +} + +// don't lint on clippy::wildcard_imports for `use` items +#[allow(clippy::wildcard_imports)] +pub use std::io::prelude::*; + +// don't lint on clippy::enum_glob_use for `use` items +#[allow(clippy::enum_glob_use)] +pub use std::cmp::Ordering::*; + +fn test_indented_attr() { + #[allow(clippy::almost_swapped)] + use std::collections::HashSet; + + let _ = HashSet::::default(); +} + +fn main() { + test_indented_attr(); +} diff --git a/src/tools/clippy/tests/ui/useless_attribute.stderr b/src/tools/clippy/tests/ui/useless_attribute.stderr new file mode 100644 index 0000000000..d0194e4bbb --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_attribute.stderr @@ -0,0 +1,22 @@ +error: useless lint attribute + --> $DIR/useless_attribute.rs:8:1 + | +LL | #[allow(dead_code)] + | ^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![allow(dead_code)]` + | + = note: `-D clippy::useless-attribute` implied by `-D warnings` + +error: useless lint attribute + --> $DIR/useless_attribute.rs:9:1 + | +LL | #[cfg_attr(feature = "cargo-clippy", allow(dead_code))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![cfg_attr(feature = "cargo-clippy", allow(dead_code)` + +error: useless lint attribute + --> $DIR/useless_attribute.rs:61:5 + | +LL | #[allow(clippy::almost_swapped)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![allow(clippy::almost_swapped)]` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/useless_conversion.fixed b/src/tools/clippy/tests/ui/useless_conversion.fixed new file mode 100644 index 0000000000..03977de945 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_conversion.fixed @@ -0,0 +1,73 @@ +// run-rustfix + +#![deny(clippy::useless_conversion)] +#![allow(clippy::unnecessary_wraps)] + +fn test_generic(val: T) -> T { + let _ = val; + val +} + +fn test_generic2 + Into, U: From>(val: T) { + // ok + let _: i32 = val.into(); + let _: U = val.into(); + let _ = U::from(val); +} + +fn test_questionmark() -> Result<(), ()> { + { + let _: i32 = 0i32; + Ok(Ok(())) + }??; + Ok(()) +} + +fn test_issue_3913() -> Result<(), std::io::Error> { + use std::fs; + use std::path::Path; + + let path = Path::new("."); + for _ in fs::read_dir(path)? {} + + Ok(()) +} + +fn test_issue_5833() -> Result<(), ()> { + let text = "foo\r\nbar\n\nbaz\n"; + let lines = text.lines(); + if Some("ok") == lines.into_iter().next() {} + + Ok(()) +} + +fn main() { + test_generic(10i32); + test_generic2::(10i32); + test_questionmark().unwrap(); + test_issue_3913().unwrap(); + test_issue_5833().unwrap(); + + let _: String = "foo".into(); + let _: String = From::from("foo"); + let _ = String::from("foo"); + #[allow(clippy::useless_conversion)] + { + let _: String = "foo".into(); + let _ = String::from("foo"); + let _ = "".lines().into_iter(); + } + + let _: String = "foo".to_string(); + let _: String = "foo".to_string(); + let _ = "foo".to_string(); + let _ = format!("A: {:04}", 123); + let _ = "".lines(); + let _ = vec![1, 2, 3].into_iter(); + let _: String = format!("Hello {}", "world"); + + // keep parenthesis around `a + b` for suggestion (see #4750) + let a: i32 = 1; + let b: i32 = 1; + let _ = (a + b) * 3; +} diff --git a/src/tools/clippy/tests/ui/useless_conversion.rs b/src/tools/clippy/tests/ui/useless_conversion.rs new file mode 100644 index 0000000000..f6e094c166 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_conversion.rs @@ -0,0 +1,73 @@ +// run-rustfix + +#![deny(clippy::useless_conversion)] +#![allow(clippy::unnecessary_wraps)] + +fn test_generic(val: T) -> T { + let _ = T::from(val); + val.into() +} + +fn test_generic2 + Into, U: From>(val: T) { + // ok + let _: i32 = val.into(); + let _: U = val.into(); + let _ = U::from(val); +} + +fn test_questionmark() -> Result<(), ()> { + { + let _: i32 = 0i32.into(); + Ok(Ok(())) + }??; + Ok(()) +} + +fn test_issue_3913() -> Result<(), std::io::Error> { + use std::fs; + use std::path::Path; + + let path = Path::new("."); + for _ in fs::read_dir(path)? {} + + Ok(()) +} + +fn test_issue_5833() -> Result<(), ()> { + let text = "foo\r\nbar\n\nbaz\n"; + let lines = text.lines(); + if Some("ok") == lines.into_iter().next() {} + + Ok(()) +} + +fn main() { + test_generic(10i32); + test_generic2::(10i32); + test_questionmark().unwrap(); + test_issue_3913().unwrap(); + test_issue_5833().unwrap(); + + let _: String = "foo".into(); + let _: String = From::from("foo"); + let _ = String::from("foo"); + #[allow(clippy::useless_conversion)] + { + let _: String = "foo".into(); + let _ = String::from("foo"); + let _ = "".lines().into_iter(); + } + + let _: String = "foo".to_string().into(); + let _: String = From::from("foo".to_string()); + let _ = String::from("foo".to_string()); + let _ = String::from(format!("A: {:04}", 123)); + let _ = "".lines().into_iter(); + let _ = vec![1, 2, 3].into_iter().into_iter(); + let _: String = format!("Hello {}", "world").into(); + + // keep parenthesis around `a + b` for suggestion (see #4750) + let a: i32 = 1; + let b: i32 = 1; + let _ = i32::from(a + b) * 3; +} diff --git a/src/tools/clippy/tests/ui/useless_conversion.stderr b/src/tools/clippy/tests/ui/useless_conversion.stderr new file mode 100644 index 0000000000..26a3359503 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_conversion.stderr @@ -0,0 +1,74 @@ +error: useless conversion to the same type: `T` + --> $DIR/useless_conversion.rs:7:13 + | +LL | let _ = T::from(val); + | ^^^^^^^^^^^^ help: consider removing `T::from()`: `val` + | +note: the lint level is defined here + --> $DIR/useless_conversion.rs:3:9 + | +LL | #![deny(clippy::useless_conversion)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: useless conversion to the same type: `T` + --> $DIR/useless_conversion.rs:8:5 + | +LL | val.into() + | ^^^^^^^^^^ help: consider removing `.into()`: `val` + +error: useless conversion to the same type: `i32` + --> $DIR/useless_conversion.rs:20:22 + | +LL | let _: i32 = 0i32.into(); + | ^^^^^^^^^^^ help: consider removing `.into()`: `0i32` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion.rs:61:21 + | +LL | let _: String = "foo".to_string().into(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `"foo".to_string()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion.rs:62:21 + | +LL | let _: String = From::from("foo".to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `From::from()`: `"foo".to_string()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion.rs:63:13 + | +LL | let _ = String::from("foo".to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `"foo".to_string()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion.rs:64:13 + | +LL | let _ = String::from(format!("A: {:04}", 123)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `format!("A: {:04}", 123)` + +error: useless conversion to the same type: `std::str::Lines` + --> $DIR/useless_conversion.rs:65:13 + | +LL | let _ = "".lines().into_iter(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `"".lines()` + +error: useless conversion to the same type: `std::vec::IntoIter` + --> $DIR/useless_conversion.rs:66:13 + | +LL | let _ = vec![1, 2, 3].into_iter().into_iter(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![1, 2, 3].into_iter()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion.rs:67:21 + | +LL | let _: String = format!("Hello {}", "world").into(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `format!("Hello {}", "world")` + +error: useless conversion to the same type: `i32` + --> $DIR/useless_conversion.rs:72:13 + | +LL | let _ = i32::from(a + b) * 3; + | ^^^^^^^^^^^^^^^^ help: consider removing `i32::from()`: `(a + b)` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/useless_conversion_try.rs b/src/tools/clippy/tests/ui/useless_conversion_try.rs new file mode 100644 index 0000000000..3787ea9914 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_conversion_try.rs @@ -0,0 +1,42 @@ +#![deny(clippy::useless_conversion)] + +use std::convert::{TryFrom, TryInto}; + +fn test_generic(val: T) -> T { + let _ = T::try_from(val).unwrap(); + val.try_into().unwrap() +} + +fn test_generic2 + Into, U: From>(val: T) { + // ok + let _: i32 = val.try_into().unwrap(); + let _: U = val.try_into().unwrap(); + let _ = U::try_from(val).unwrap(); +} + +fn main() { + test_generic(10i32); + test_generic2::(10i32); + + let _: String = "foo".try_into().unwrap(); + let _: String = TryFrom::try_from("foo").unwrap(); + let _ = String::try_from("foo").unwrap(); + #[allow(clippy::useless_conversion)] + { + let _ = String::try_from("foo").unwrap(); + let _: String = "foo".try_into().unwrap(); + } + let _: String = "foo".to_string().try_into().unwrap(); + let _: String = TryFrom::try_from("foo".to_string()).unwrap(); + let _ = String::try_from("foo".to_string()).unwrap(); + let _ = String::try_from(format!("A: {:04}", 123)).unwrap(); + let _: String = format!("Hello {}", "world").try_into().unwrap(); + let _: String = "".to_owned().try_into().unwrap(); + let _: String = match String::from("_").try_into() { + Ok(a) => a, + Err(_) => "".into(), + }; + // FIXME this is a false negative + #[allow(clippy::cmp_owned)] + if String::from("a") == TryInto::::try_into(String::from("a")).unwrap() {} +} diff --git a/src/tools/clippy/tests/ui/useless_conversion_try.stderr b/src/tools/clippy/tests/ui/useless_conversion_try.stderr new file mode 100644 index 0000000000..2e0d9129bf --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_conversion_try.stderr @@ -0,0 +1,79 @@ +error: useless conversion to the same type: `T` + --> $DIR/useless_conversion_try.rs:6:13 + | +LL | let _ = T::try_from(val).unwrap(); + | ^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/useless_conversion_try.rs:1:9 + | +LL | #![deny(clippy::useless_conversion)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider removing `T::try_from()` + +error: useless conversion to the same type: `T` + --> $DIR/useless_conversion_try.rs:7:5 + | +LL | val.try_into().unwrap() + | ^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion_try.rs:29:21 + | +LL | let _: String = "foo".to_string().try_into().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion_try.rs:30:21 + | +LL | let _: String = TryFrom::try_from("foo".to_string()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `TryFrom::try_from()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion_try.rs:31:13 + | +LL | let _ = String::try_from("foo".to_string()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `String::try_from()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion_try.rs:32:13 + | +LL | let _ = String::try_from(format!("A: {:04}", 123)).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `String::try_from()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion_try.rs:33:21 + | +LL | let _: String = format!("Hello {}", "world").try_into().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion_try.rs:34:21 + | +LL | let _: String = "".to_owned().try_into().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: useless conversion to the same type: `std::string::String` + --> $DIR/useless_conversion_try.rs:35:27 + | +LL | let _: String = match String::from("_").try_into() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/vec.fixed b/src/tools/clippy/tests/ui/vec.fixed new file mode 100644 index 0000000000..8567715962 --- /dev/null +++ b/src/tools/clippy/tests/ui/vec.fixed @@ -0,0 +1,62 @@ +// run-rustfix + +#![warn(clippy::useless_vec)] + +#[derive(Debug)] +struct NonCopy; + +fn on_slice(_: &[u8]) {} +#[allow(clippy::ptr_arg)] +fn on_vec(_: &Vec) {} + +struct Line { + length: usize, +} + +impl Line { + fn length(&self) -> usize { + self.length + } +} + +fn main() { + on_slice(&[]); + on_slice(&[]); + + on_slice(&[1, 2]); + on_slice(&[1, 2]); + + on_slice(&[1, 2]); + on_slice(&[1, 2]); + #[rustfmt::skip] + on_slice(&[1, 2]); + on_slice(&[1, 2]); + + on_slice(&[1; 2]); + on_slice(&[1; 2]); + + on_vec(&vec![]); + on_vec(&vec![1, 2]); + on_vec(&vec![1; 2]); + + // Now with non-constant expressions + let line = Line { length: 2 }; + + on_slice(&vec![2; line.length]); + on_slice(&vec![2; line.length()]); + + for a in &[1, 2, 3] { + println!("{:?}", a); + } + + for a in vec![NonCopy, NonCopy] { + println!("{:?}", a); + } + + on_vec(&vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack` + + // Ok + for a in vec![1; 201] { + println!("{:?}", a); + } +} diff --git a/src/tools/clippy/tests/ui/vec.rs b/src/tools/clippy/tests/ui/vec.rs new file mode 100644 index 0000000000..03b8ee8166 --- /dev/null +++ b/src/tools/clippy/tests/ui/vec.rs @@ -0,0 +1,62 @@ +// run-rustfix + +#![warn(clippy::useless_vec)] + +#[derive(Debug)] +struct NonCopy; + +fn on_slice(_: &[u8]) {} +#[allow(clippy::ptr_arg)] +fn on_vec(_: &Vec) {} + +struct Line { + length: usize, +} + +impl Line { + fn length(&self) -> usize { + self.length + } +} + +fn main() { + on_slice(&vec![]); + on_slice(&[]); + + on_slice(&vec![1, 2]); + on_slice(&[1, 2]); + + on_slice(&vec![1, 2]); + on_slice(&[1, 2]); + #[rustfmt::skip] + on_slice(&vec!(1, 2)); + on_slice(&[1, 2]); + + on_slice(&vec![1; 2]); + on_slice(&[1; 2]); + + on_vec(&vec![]); + on_vec(&vec![1, 2]); + on_vec(&vec![1; 2]); + + // Now with non-constant expressions + let line = Line { length: 2 }; + + on_slice(&vec![2; line.length]); + on_slice(&vec![2; line.length()]); + + for a in vec![1, 2, 3] { + println!("{:?}", a); + } + + for a in vec![NonCopy, NonCopy] { + println!("{:?}", a); + } + + on_vec(&vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack` + + // Ok + for a in vec![1; 201] { + println!("{:?}", a); + } +} diff --git a/src/tools/clippy/tests/ui/vec.stderr b/src/tools/clippy/tests/ui/vec.stderr new file mode 100644 index 0000000000..37e28ebddb --- /dev/null +++ b/src/tools/clippy/tests/ui/vec.stderr @@ -0,0 +1,40 @@ +error: useless use of `vec!` + --> $DIR/vec.rs:23:14 + | +LL | on_slice(&vec![]); + | ^^^^^^^ help: you can use a slice directly: `&[]` + | + = note: `-D clippy::useless-vec` implied by `-D warnings` + +error: useless use of `vec!` + --> $DIR/vec.rs:26:14 + | +LL | on_slice(&vec![1, 2]); + | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` + +error: useless use of `vec!` + --> $DIR/vec.rs:29:14 + | +LL | on_slice(&vec![1, 2]); + | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` + +error: useless use of `vec!` + --> $DIR/vec.rs:32:14 + | +LL | on_slice(&vec!(1, 2)); + | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` + +error: useless use of `vec!` + --> $DIR/vec.rs:35:14 + | +LL | on_slice(&vec![1; 2]); + | ^^^^^^^^^^^ help: you can use a slice directly: `&[1; 2]` + +error: useless use of `vec!` + --> $DIR/vec.rs:48:14 + | +LL | for a in vec![1, 2, 3] { + | ^^^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2, 3]` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/vec_box_sized.fixed b/src/tools/clippy/tests/ui/vec_box_sized.fixed new file mode 100644 index 0000000000..4fa28b525c --- /dev/null +++ b/src/tools/clippy/tests/ui/vec_box_sized.fixed @@ -0,0 +1,52 @@ +// run-rustfix + +#![allow(dead_code)] + +struct SizedStruct(i32); +struct UnsizedStruct([i32]); +struct BigStruct([i32; 10000]); + +/// The following should trigger the lint +mod should_trigger { + use super::SizedStruct; + + struct StructWithVecBox { + sized_type: Vec, + } + + struct A(Vec); + struct B(Vec>); +} + +/// The following should not trigger the lint +mod should_not_trigger { + use super::{BigStruct, UnsizedStruct}; + + struct C(Vec>); + struct D(Vec>); + + struct StructWithVecBoxButItsUnsized { + unsized_type: Vec>, + } + + struct TraitVec { + // Regression test for #3720. This was causing an ICE. + inner: Vec>, + } +} + +mod inner_mod { + mod inner { + pub struct S; + } + + mod inner2 { + use super::inner::S; + + pub fn f() -> Vec { + vec![] + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/vec_box_sized.rs b/src/tools/clippy/tests/ui/vec_box_sized.rs new file mode 100644 index 0000000000..7dc735cd90 --- /dev/null +++ b/src/tools/clippy/tests/ui/vec_box_sized.rs @@ -0,0 +1,52 @@ +// run-rustfix + +#![allow(dead_code)] + +struct SizedStruct(i32); +struct UnsizedStruct([i32]); +struct BigStruct([i32; 10000]); + +/// The following should trigger the lint +mod should_trigger { + use super::SizedStruct; + + struct StructWithVecBox { + sized_type: Vec>, + } + + struct A(Vec>); + struct B(Vec>>); +} + +/// The following should not trigger the lint +mod should_not_trigger { + use super::{BigStruct, UnsizedStruct}; + + struct C(Vec>); + struct D(Vec>); + + struct StructWithVecBoxButItsUnsized { + unsized_type: Vec>, + } + + struct TraitVec { + // Regression test for #3720. This was causing an ICE. + inner: Vec>, + } +} + +mod inner_mod { + mod inner { + pub struct S; + } + + mod inner2 { + use super::inner::S; + + pub fn f() -> Vec> { + vec![] + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/vec_box_sized.stderr b/src/tools/clippy/tests/ui/vec_box_sized.stderr new file mode 100644 index 0000000000..83435a40aa --- /dev/null +++ b/src/tools/clippy/tests/ui/vec_box_sized.stderr @@ -0,0 +1,28 @@ +error: `Vec` is already on the heap, the boxing is unnecessary + --> $DIR/vec_box_sized.rs:14:21 + | +LL | sized_type: Vec>, + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `Vec` + | + = note: `-D clippy::vec-box` implied by `-D warnings` + +error: `Vec` is already on the heap, the boxing is unnecessary + --> $DIR/vec_box_sized.rs:17:14 + | +LL | struct A(Vec>); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `Vec` + +error: `Vec` is already on the heap, the boxing is unnecessary + --> $DIR/vec_box_sized.rs:18:18 + | +LL | struct B(Vec>>); + | ^^^^^^^^^^^^^^^ help: try: `Vec` + +error: `Vec` is already on the heap, the boxing is unnecessary + --> $DIR/vec_box_sized.rs:46:23 + | +LL | pub fn f() -> Vec> { + | ^^^^^^^^^^^ help: try: `Vec` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/vec_init_then_push.rs b/src/tools/clippy/tests/ui/vec_init_then_push.rs new file mode 100644 index 0000000000..5099aad83b --- /dev/null +++ b/src/tools/clippy/tests/ui/vec_init_then_push.rs @@ -0,0 +1,46 @@ +#![allow(unused_variables)] +#![warn(clippy::vec_init_then_push)] + +fn main() { + let mut def_err: Vec = Default::default(); + def_err.push(0); + + let mut new_err = Vec::::new(); + new_err.push(1); + + let mut cap_err = Vec::with_capacity(2); + cap_err.push(0); + cap_err.push(1); + cap_err.push(2); + if true { + // don't include this one + cap_err.push(3); + } + + let mut cap_ok = Vec::with_capacity(10); + cap_ok.push(0); + + new_err = Vec::new(); + new_err.push(0); + + let mut vec = Vec::new(); + // control flow at block final expression + if true { + // no lint + vec.push(1); + } +} + +pub fn no_lint() -> Vec { + let mut p = Some(1); + let mut vec = Vec::new(); + loop { + match p { + None => return vec, + Some(i) => { + vec.push(i); + p = None; + }, + } + } +} diff --git a/src/tools/clippy/tests/ui/vec_init_then_push.stderr b/src/tools/clippy/tests/ui/vec_init_then_push.stderr new file mode 100644 index 0000000000..9ec3e10e62 --- /dev/null +++ b/src/tools/clippy/tests/ui/vec_init_then_push.stderr @@ -0,0 +1,34 @@ +error: calls to `push` immediately after creation + --> $DIR/vec_init_then_push.rs:5:5 + | +LL | / let mut def_err: Vec = Default::default(); +LL | | def_err.push(0); + | |____________________^ help: consider using the `vec![]` macro: `let mut def_err: Vec = vec![..];` + | + = note: `-D clippy::vec-init-then-push` implied by `-D warnings` + +error: calls to `push` immediately after creation + --> $DIR/vec_init_then_push.rs:8:5 + | +LL | / let mut new_err = Vec::::new(); +LL | | new_err.push(1); + | |____________________^ help: consider using the `vec![]` macro: `let mut new_err = vec![..];` + +error: calls to `push` immediately after creation + --> $DIR/vec_init_then_push.rs:11:5 + | +LL | / let mut cap_err = Vec::with_capacity(2); +LL | | cap_err.push(0); +LL | | cap_err.push(1); +LL | | cap_err.push(2); + | |____________________^ help: consider using the `vec![]` macro: `let mut cap_err = vec![..];` + +error: calls to `push` immediately after creation + --> $DIR/vec_init_then_push.rs:23:5 + | +LL | / new_err = Vec::new(); +LL | | new_err.push(0); + | |____________________^ help: consider using the `vec![]` macro: `new_err = vec![..];` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/vec_resize_to_zero.rs b/src/tools/clippy/tests/ui/vec_resize_to_zero.rs new file mode 100644 index 0000000000..7ed27439ec --- /dev/null +++ b/src/tools/clippy/tests/ui/vec_resize_to_zero.rs @@ -0,0 +1,15 @@ +#![warn(clippy::vec_resize_to_zero)] + +fn main() { + // applicable here + vec![1, 2, 3, 4, 5].resize(0, 5); + + // not applicable + vec![1, 2, 3, 4, 5].resize(2, 5); + + // applicable here, but only implemented for integer literals for now + vec!["foo", "bar", "baz"].resize(0, "bar"); + + // not applicable + vec!["foo", "bar", "baz"].resize(2, "bar") +} diff --git a/src/tools/clippy/tests/ui/vec_resize_to_zero.stderr b/src/tools/clippy/tests/ui/vec_resize_to_zero.stderr new file mode 100644 index 0000000000..feb846298c --- /dev/null +++ b/src/tools/clippy/tests/ui/vec_resize_to_zero.stderr @@ -0,0 +1,13 @@ +error: emptying a vector with `resize` + --> $DIR/vec_resize_to_zero.rs:5:5 + | +LL | vec![1, 2, 3, 4, 5].resize(0, 5); + | ^^^^^^^^^^^^^^^^^^^^------------ + | | + | help: ...or you can empty the vector with: `clear()` + | + = note: `-D clippy::vec-resize-to-zero` implied by `-D warnings` + = help: the arguments may be inverted... + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/verbose_file_reads.rs b/src/tools/clippy/tests/ui/verbose_file_reads.rs new file mode 100644 index 0000000000..e0065e05ad --- /dev/null +++ b/src/tools/clippy/tests/ui/verbose_file_reads.rs @@ -0,0 +1,28 @@ +#![warn(clippy::verbose_file_reads)] +use std::env::temp_dir; +use std::fs::File; +use std::io::Read; + +struct Struct; +// To make sure we only warn on File::{read_to_end, read_to_string} calls +impl Struct { + pub fn read_to_end(&self) {} + + pub fn read_to_string(&self) {} +} + +fn main() -> std::io::Result<()> { + let path = "foo.txt"; + // Lint shouldn't catch this + let s = Struct; + s.read_to_end(); + s.read_to_string(); + // Should catch this + let mut f = File::open(&path)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + // ...and this + let mut string_buffer = String::new(); + f.read_to_string(&mut string_buffer)?; + Ok(()) +} diff --git a/src/tools/clippy/tests/ui/verbose_file_reads.stderr b/src/tools/clippy/tests/ui/verbose_file_reads.stderr new file mode 100644 index 0000000000..550b6ab679 --- /dev/null +++ b/src/tools/clippy/tests/ui/verbose_file_reads.stderr @@ -0,0 +1,19 @@ +error: use of `File::read_to_end` + --> $DIR/verbose_file_reads.rs:23:5 + | +LL | f.read_to_end(&mut buffer)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::verbose-file-reads` implied by `-D warnings` + = help: consider using `fs::read` instead + +error: use of `File::read_to_string` + --> $DIR/verbose_file_reads.rs:26:5 + | +LL | f.read_to_string(&mut string_buffer)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `fs::read_to_string` instead + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/vtable_address_comparisons.rs b/src/tools/clippy/tests/ui/vtable_address_comparisons.rs new file mode 100644 index 0000000000..c91d96ee18 --- /dev/null +++ b/src/tools/clippy/tests/ui/vtable_address_comparisons.rs @@ -0,0 +1,42 @@ +use std::fmt::Debug; +use std::ptr; +use std::rc::Rc; +use std::sync::Arc; + +#[warn(clippy::vtable_address_comparisons)] +fn main() { + let a: *const dyn Debug = &1 as &dyn Debug; + let b: *const dyn Debug = &1 as &dyn Debug; + + // These should fail: + let _ = a == b; + let _ = a != b; + let _ = a < b; + let _ = a <= b; + let _ = a > b; + let _ = a >= b; + ptr::eq(a, b); + + let a = &1 as &dyn Debug; + let b = &1 as &dyn Debug; + ptr::eq(a, b); + + let a: Rc = Rc::new(1); + Rc::ptr_eq(&a, &a); + + let a: Arc = Arc::new(1); + Arc::ptr_eq(&a, &a); + + // These should be fine: + let a = &1; + ptr::eq(a, a); + + let a = Rc::new(1); + Rc::ptr_eq(&a, &a); + + let a = Arc::new(1); + Arc::ptr_eq(&a, &a); + + let a: &[u8] = b""; + ptr::eq(a, a); +} diff --git a/src/tools/clippy/tests/ui/vtable_address_comparisons.stderr b/src/tools/clippy/tests/ui/vtable_address_comparisons.stderr new file mode 100644 index 0000000000..76bd57217d --- /dev/null +++ b/src/tools/clippy/tests/ui/vtable_address_comparisons.stderr @@ -0,0 +1,83 @@ +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:12:13 + | +LL | let _ = a == b; + | ^^^^^^ + | + = note: `-D clippy::vtable-address-comparisons` implied by `-D warnings` + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:13:13 + | +LL | let _ = a != b; + | ^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:14:13 + | +LL | let _ = a < b; + | ^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:15:13 + | +LL | let _ = a <= b; + | ^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:16:13 + | +LL | let _ = a > b; + | ^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:17:13 + | +LL | let _ = a >= b; + | ^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:18:5 + | +LL | ptr::eq(a, b); + | ^^^^^^^^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:22:5 + | +LL | ptr::eq(a, b); + | ^^^^^^^^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:25:5 + | +LL | Rc::ptr_eq(&a, &a); + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:28:5 + | +LL | Arc::ptr_eq(&a, &a); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/while_let_loop.rs b/src/tools/clippy/tests/ui/while_let_loop.rs new file mode 100644 index 0000000000..3ce699f551 --- /dev/null +++ b/src/tools/clippy/tests/ui/while_let_loop.rs @@ -0,0 +1,119 @@ +#![warn(clippy::while_let_loop)] + +fn main() { + let y = Some(true); + loop { + if let Some(_x) = y { + let _v = 1; + } else { + break; + } + } + + #[allow(clippy::never_loop)] + loop { + // no error, break is not in else clause + if let Some(_x) = y { + let _v = 1; + } + break; + } + + loop { + match y { + Some(_x) => true, + None => break, + }; + } + + loop { + let x = match y { + Some(x) => x, + None => break, + }; + let _x = x; + let _str = "foo"; + } + + loop { + let x = match y { + Some(x) => x, + None => break, + }; + { + let _a = "bar"; + }; + { + let _b = "foobar"; + } + } + + loop { + // no error, else branch does something other than break + match y { + Some(_x) => true, + _ => { + let _z = 1; + break; + }, + }; + } + + while let Some(x) = y { + // no error, obviously + println!("{}", x); + } + + // #675, this used to have a wrong suggestion + loop { + let (e, l) = match "".split_whitespace().next() { + Some(word) => (word.is_empty(), word.len()), + None => break, + }; + + let _ = (e, l); + } +} + +fn issue771() { + let mut a = 100; + let b = Some(true); + loop { + if a > 10 { + break; + } + + match b { + Some(_) => a = 0, + None => break, + } + } +} + +fn issue1017() { + let r: Result = Ok(42); + let mut len = 1337; + + loop { + match r { + Err(_) => len = 0, + Ok(length) => { + len = length; + break; + }, + } + } +} + +#[allow(clippy::never_loop)] +fn issue1948() { + // should not trigger clippy::while_let_loop lint because break passes an expression + let a = Some(10); + let b = loop { + if let Some(c) = a { + break Some(c); + } else { + break None; + } + }; +} diff --git a/src/tools/clippy/tests/ui/while_let_loop.stderr b/src/tools/clippy/tests/ui/while_let_loop.stderr new file mode 100644 index 0000000000..13dd0ee224 --- /dev/null +++ b/src/tools/clippy/tests/ui/while_let_loop.stderr @@ -0,0 +1,63 @@ +error: this loop could be written as a `while let` loop + --> $DIR/while_let_loop.rs:5:5 + | +LL | / loop { +LL | | if let Some(_x) = y { +LL | | let _v = 1; +LL | | } else { +LL | | break; +LL | | } +LL | | } + | |_____^ help: try: `while let Some(_x) = y { .. }` + | + = note: `-D clippy::while-let-loop` implied by `-D warnings` + +error: this loop could be written as a `while let` loop + --> $DIR/while_let_loop.rs:22:5 + | +LL | / loop { +LL | | match y { +LL | | Some(_x) => true, +LL | | None => break, +LL | | }; +LL | | } + | |_____^ help: try: `while let Some(_x) = y { .. }` + +error: this loop could be written as a `while let` loop + --> $DIR/while_let_loop.rs:29:5 + | +LL | / loop { +LL | | let x = match y { +LL | | Some(x) => x, +LL | | None => break, +... | +LL | | let _str = "foo"; +LL | | } + | |_____^ help: try: `while let Some(x) = y { .. }` + +error: this loop could be written as a `while let` loop + --> $DIR/while_let_loop.rs:38:5 + | +LL | / loop { +LL | | let x = match y { +LL | | Some(x) => x, +LL | | None => break, +... | +LL | | } +LL | | } + | |_____^ help: try: `while let Some(x) = y { .. }` + +error: this loop could be written as a `while let` loop + --> $DIR/while_let_loop.rs:68:5 + | +LL | / loop { +LL | | let (e, l) = match "".split_whitespace().next() { +LL | | Some(word) => (word.is_empty(), word.len()), +LL | | None => break, +... | +LL | | let _ = (e, l); +LL | | } + | |_____^ help: try: `while let Some(word) = "".split_whitespace().next() { .. }` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/while_let_on_iterator.fixed b/src/tools/clippy/tests/ui/while_let_on_iterator.fixed new file mode 100644 index 0000000000..e99c98ac79 --- /dev/null +++ b/src/tools/clippy/tests/ui/while_let_on_iterator.fixed @@ -0,0 +1,218 @@ +// run-rustfix + +#![warn(clippy::while_let_on_iterator)] +#![allow(clippy::never_loop, unreachable_code, unused_mut)] +#![feature(or_patterns)] + +fn base() { + let mut iter = 1..20; + for x in iter { + println!("{}", x); + } + + let mut iter = 1..20; + for x in iter { + println!("{}", x); + } + + let mut iter = 1..20; + for _ in iter {} + + let mut iter = 1..20; + while let None = iter.next() {} // this is fine (if nonsensical) + + let mut iter = 1..20; + if let Some(x) = iter.next() { + // also fine + println!("{}", x) + } + + // the following shouldn't warn because it can't be written with a for loop + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + println!("next: {:?}", iter.next()) + } + + // neither can this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + println!("next: {:?}", iter.next()); + } + + // or this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + break; + } + println!("Remaining iter {:?}", iter); + + // or this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + iter = 1..20; + } +} + +// Issue #1188 +fn refutable() { + let a = [42, 1337]; + let mut b = a.iter(); + + // consume all the 42s + while let Some(&42) = b.next() {} + + let a = [(1, 2, 3)]; + let mut b = a.iter(); + + while let Some(&(1, 2, 3)) = b.next() {} + + let a = [Some(42)]; + let mut b = a.iter(); + + while let Some(&None) = b.next() {} + + /* This gives “refutable pattern in `for` loop binding: `&_` not covered” + for &42 in b {} + for &(1, 2, 3) in b {} + for &Option::None in b.next() {} + // */ +} + +fn refutable2() { + // Issue 3780 + { + let v = vec![1, 2, 3]; + let mut it = v.windows(2); + while let Some([x, y]) = it.next() { + println!("x: {}", x); + println!("y: {}", y); + } + + let mut it = v.windows(2); + while let Some([x, ..]) = it.next() { + println!("x: {}", x); + } + + let mut it = v.windows(2); + while let Some([.., y]) = it.next() { + println!("y: {}", y); + } + + let mut it = v.windows(2); + for [..] in it {} + + let v = vec![[1], [2], [3]]; + let mut it = v.iter(); + while let Some([1]) = it.next() {} + + let mut it = v.iter(); + for [_x] in it {} + } + + // binding + { + let v = vec![1, 2, 3]; + let mut it = v.iter(); + while let Some(x @ 1) = it.next() { + println!("{}", x); + } + + let v = vec![[1], [2], [3]]; + let mut it = v.iter(); + for x @ [_] in it { + println!("{:?}", x); + } + } + + // false negative + { + let v = vec![1, 2, 3]; + let mut it = v.iter().map(Some); + while let Some(Some(_) | None) = it.next() { + println!("1"); + } + } +} + +fn nested_loops() { + let a = [42, 1337]; + let mut y = a.iter(); + loop { + // x is reused, so don't lint here + while let Some(_) = y.next() {} + } + + let mut y = a.iter(); + for _ in 0..2 { + while let Some(_) = y.next() { + // y is reused, don't lint + } + } + + loop { + let mut y = a.iter(); + for _ in y { + // use a for loop here + } + } +} + +fn issue1121() { + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(&value) = values.iter().next() { + values.remove(&value); + } +} + +fn issue2965() { + // This should not cause an ICE and suggest: + // + // for _ in values.iter() {} + // + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(..) = values.iter().next() {} +} + +fn issue3670() { + let array = [Some(0), None, Some(1)]; + let mut iter = array.iter(); + + while let Some(elem) = iter.next() { + let _ = elem.or_else(|| *iter.next()?); + } +} + +fn issue1654() { + // should not lint if the iterator is generated on every iteration + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(..) = values.iter().next() { + values.remove(&1); + } + + while let Some(..) = values.iter().map(|x| x + 1).next() {} + + let chars = "Hello, World!".char_indices(); + while let Some((i, ch)) = chars.clone().next() { + println!("{}: {}", i, ch); + } +} + +fn main() { + base(); + refutable(); + refutable2(); + nested_loops(); + issue1121(); + issue2965(); + issue3670(); + issue1654(); +} diff --git a/src/tools/clippy/tests/ui/while_let_on_iterator.rs b/src/tools/clippy/tests/ui/while_let_on_iterator.rs new file mode 100644 index 0000000000..ba13172428 --- /dev/null +++ b/src/tools/clippy/tests/ui/while_let_on_iterator.rs @@ -0,0 +1,218 @@ +// run-rustfix + +#![warn(clippy::while_let_on_iterator)] +#![allow(clippy::never_loop, unreachable_code, unused_mut)] +#![feature(or_patterns)] + +fn base() { + let mut iter = 1..20; + while let Option::Some(x) = iter.next() { + println!("{}", x); + } + + let mut iter = 1..20; + while let Some(x) = iter.next() { + println!("{}", x); + } + + let mut iter = 1..20; + while let Some(_) = iter.next() {} + + let mut iter = 1..20; + while let None = iter.next() {} // this is fine (if nonsensical) + + let mut iter = 1..20; + if let Some(x) = iter.next() { + // also fine + println!("{}", x) + } + + // the following shouldn't warn because it can't be written with a for loop + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + println!("next: {:?}", iter.next()) + } + + // neither can this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + println!("next: {:?}", iter.next()); + } + + // or this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + break; + } + println!("Remaining iter {:?}", iter); + + // or this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + iter = 1..20; + } +} + +// Issue #1188 +fn refutable() { + let a = [42, 1337]; + let mut b = a.iter(); + + // consume all the 42s + while let Some(&42) = b.next() {} + + let a = [(1, 2, 3)]; + let mut b = a.iter(); + + while let Some(&(1, 2, 3)) = b.next() {} + + let a = [Some(42)]; + let mut b = a.iter(); + + while let Some(&None) = b.next() {} + + /* This gives “refutable pattern in `for` loop binding: `&_` not covered” + for &42 in b {} + for &(1, 2, 3) in b {} + for &Option::None in b.next() {} + // */ +} + +fn refutable2() { + // Issue 3780 + { + let v = vec![1, 2, 3]; + let mut it = v.windows(2); + while let Some([x, y]) = it.next() { + println!("x: {}", x); + println!("y: {}", y); + } + + let mut it = v.windows(2); + while let Some([x, ..]) = it.next() { + println!("x: {}", x); + } + + let mut it = v.windows(2); + while let Some([.., y]) = it.next() { + println!("y: {}", y); + } + + let mut it = v.windows(2); + while let Some([..]) = it.next() {} + + let v = vec![[1], [2], [3]]; + let mut it = v.iter(); + while let Some([1]) = it.next() {} + + let mut it = v.iter(); + while let Some([_x]) = it.next() {} + } + + // binding + { + let v = vec![1, 2, 3]; + let mut it = v.iter(); + while let Some(x @ 1) = it.next() { + println!("{}", x); + } + + let v = vec![[1], [2], [3]]; + let mut it = v.iter(); + while let Some(x @ [_]) = it.next() { + println!("{:?}", x); + } + } + + // false negative + { + let v = vec![1, 2, 3]; + let mut it = v.iter().map(Some); + while let Some(Some(_) | None) = it.next() { + println!("1"); + } + } +} + +fn nested_loops() { + let a = [42, 1337]; + let mut y = a.iter(); + loop { + // x is reused, so don't lint here + while let Some(_) = y.next() {} + } + + let mut y = a.iter(); + for _ in 0..2 { + while let Some(_) = y.next() { + // y is reused, don't lint + } + } + + loop { + let mut y = a.iter(); + while let Some(_) = y.next() { + // use a for loop here + } + } +} + +fn issue1121() { + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(&value) = values.iter().next() { + values.remove(&value); + } +} + +fn issue2965() { + // This should not cause an ICE and suggest: + // + // for _ in values.iter() {} + // + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(..) = values.iter().next() {} +} + +fn issue3670() { + let array = [Some(0), None, Some(1)]; + let mut iter = array.iter(); + + while let Some(elem) = iter.next() { + let _ = elem.or_else(|| *iter.next()?); + } +} + +fn issue1654() { + // should not lint if the iterator is generated on every iteration + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(..) = values.iter().next() { + values.remove(&1); + } + + while let Some(..) = values.iter().map(|x| x + 1).next() {} + + let chars = "Hello, World!".char_indices(); + while let Some((i, ch)) = chars.clone().next() { + println!("{}: {}", i, ch); + } +} + +fn main() { + base(); + refutable(); + refutable2(); + nested_loops(); + issue1121(); + issue2965(); + issue3670(); + issue1654(); +} diff --git a/src/tools/clippy/tests/ui/while_let_on_iterator.stderr b/src/tools/clippy/tests/ui/while_let_on_iterator.stderr new file mode 100644 index 0000000000..aa980d9965 --- /dev/null +++ b/src/tools/clippy/tests/ui/while_let_on_iterator.stderr @@ -0,0 +1,46 @@ +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:9:5 + | +LL | while let Option::Some(x) = iter.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` + | + = note: `-D clippy::while-let-on-iterator` implied by `-D warnings` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:14:5 + | +LL | while let Some(x) = iter.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:19:5 + | +LL | while let Some(_) = iter.next() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in iter` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:102:9 + | +LL | while let Some([..]) = it.next() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [..] in it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:109:9 + | +LL | while let Some([_x]) = it.next() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [_x] in it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:122:9 + | +LL | while let Some(x @ [_]) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x @ [_] in it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:154:9 + | +LL | while let Some(_) = y.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in y` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/wild_in_or_pats.rs b/src/tools/clippy/tests/ui/wild_in_or_pats.rs new file mode 100644 index 0000000000..ad600f1257 --- /dev/null +++ b/src/tools/clippy/tests/ui/wild_in_or_pats.rs @@ -0,0 +1,36 @@ +#![warn(clippy::wildcard_in_or_patterns)] + +fn main() { + match "foo" { + "a" => { + dbg!("matched a"); + }, + "bar" | _ => { + dbg!("matched (bar or) wild"); + }, + }; + match "foo" { + "a" => { + dbg!("matched a"); + }, + "bar" | "bar2" | _ => { + dbg!("matched (bar or bar2 or) wild"); + }, + }; + match "foo" { + "a" => { + dbg!("matched a"); + }, + _ | "bar" | _ => { + dbg!("matched (bar or) wild"); + }, + }; + match "foo" { + "a" => { + dbg!("matched a"); + }, + _ | "bar" => { + dbg!("matched (bar or) wild"); + }, + }; +} diff --git a/src/tools/clippy/tests/ui/wild_in_or_pats.stderr b/src/tools/clippy/tests/ui/wild_in_or_pats.stderr new file mode 100644 index 0000000000..45b87aa0f2 --- /dev/null +++ b/src/tools/clippy/tests/ui/wild_in_or_pats.stderr @@ -0,0 +1,35 @@ +error: wildcard pattern covers any other pattern as it will match anyway + --> $DIR/wild_in_or_pats.rs:8:9 + | +LL | "bar" | _ => { + | ^^^^^^^^^ + | + = note: `-D clippy::wildcard-in-or-patterns` implied by `-D warnings` + = help: consider handling `_` separately + +error: wildcard pattern covers any other pattern as it will match anyway + --> $DIR/wild_in_or_pats.rs:16:9 + | +LL | "bar" | "bar2" | _ => { + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider handling `_` separately + +error: wildcard pattern covers any other pattern as it will match anyway + --> $DIR/wild_in_or_pats.rs:24:9 + | +LL | _ | "bar" | _ => { + | ^^^^^^^^^^^^^ + | + = help: consider handling `_` separately + +error: wildcard pattern covers any other pattern as it will match anyway + --> $DIR/wild_in_or_pats.rs:32:9 + | +LL | _ | "bar" => { + | ^^^^^^^^^ + | + = help: consider handling `_` separately + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/wildcard_enum_match_arm.fixed b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.fixed new file mode 100644 index 0000000000..c266f684a3 --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.fixed @@ -0,0 +1,103 @@ +// run-rustfix + +#![deny(clippy::wildcard_enum_match_arm)] +#![allow( + unreachable_code, + unused_variables, + dead_code, + clippy::single_match, + clippy::wildcard_in_or_patterns, + clippy::unnested_or_patterns, + clippy::diverging_sub_expression +)] + +use std::io::ErrorKind; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), + Cyan, +} + +impl Color { + fn is_monochrome(self) -> bool { + match self { + Color::Red | Color::Green | Color::Blue => true, + Color::Rgb(r, g, b) => r | g == 0 || r | b == 0 || g | b == 0, + Color::Cyan => false, + } + } +} + +fn main() { + let color = Color::Rgb(0, 0, 127); + match color { + Color::Red => println!("Red"), + Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan => eprintln!("Not red"), + }; + match color { + Color::Red => println!("Red"), + _not_red @ Color::Green | _not_red @ Color::Blue | _not_red @ Color::Rgb(..) | _not_red @ Color::Cyan => eprintln!("Not red"), + }; + let _str = match color { + Color::Red => "Red".to_owned(), + not_red @ Color::Green | not_red @ Color::Blue | not_red @ Color::Rgb(..) | not_red @ Color::Cyan => format!("{:?}", not_red), + }; + match color { + Color::Red => {}, + Color::Green => {}, + Color::Blue => {}, + Color::Cyan => {}, + c if c.is_monochrome() => {}, + Color::Rgb(_, _, _) => {}, + }; + let _str = match color { + Color::Red => "Red", + c @ Color::Green | c @ Color::Blue | c @ Color::Rgb(_, _, _) | c @ Color::Cyan => "Not red", + }; + match color { + Color::Rgb(r, _, _) if r > 0 => "Some red", + Color::Red | Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan => "No red", + }; + match color { + Color::Red | Color::Green | Color::Blue | Color::Cyan => {}, + Color::Rgb(..) => {}, + }; + let x: u8 = unimplemented!(); + match x { + 0 => {}, + 140 => {}, + _ => {}, + }; + // We need to use an enum not defined in this test because non_exhaustive is ignored for the + // purposes of dead code analysis within a crate. + let error_kind = ErrorKind::NotFound; + match error_kind { + ErrorKind::NotFound => {}, + std::io::ErrorKind::PermissionDenied | std::io::ErrorKind::ConnectionRefused | std::io::ErrorKind::ConnectionReset | std::io::ErrorKind::ConnectionAborted | std::io::ErrorKind::NotConnected | std::io::ErrorKind::AddrInUse | std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::AlreadyExists | std::io::ErrorKind::WouldBlock | std::io::ErrorKind::InvalidInput | std::io::ErrorKind::InvalidData | std::io::ErrorKind::TimedOut | std::io::ErrorKind::WriteZero | std::io::ErrorKind::Interrupted | std::io::ErrorKind::Other | std::io::ErrorKind::UnexpectedEof | _ => {}, + } + match error_kind { + ErrorKind::NotFound => {}, + ErrorKind::PermissionDenied => {}, + ErrorKind::ConnectionRefused => {}, + ErrorKind::ConnectionReset => {}, + ErrorKind::ConnectionAborted => {}, + ErrorKind::NotConnected => {}, + ErrorKind::AddrInUse => {}, + ErrorKind::AddrNotAvailable => {}, + ErrorKind::BrokenPipe => {}, + ErrorKind::AlreadyExists => {}, + ErrorKind::WouldBlock => {}, + ErrorKind::InvalidInput => {}, + ErrorKind::InvalidData => {}, + ErrorKind::TimedOut => {}, + ErrorKind::WriteZero => {}, + ErrorKind::Interrupted => {}, + ErrorKind::Other => {}, + ErrorKind::UnexpectedEof => {}, + _ => {}, + } +} diff --git a/src/tools/clippy/tests/ui/wildcard_enum_match_arm.rs b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.rs new file mode 100644 index 0000000000..2dbf726d5d --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.rs @@ -0,0 +1,103 @@ +// run-rustfix + +#![deny(clippy::wildcard_enum_match_arm)] +#![allow( + unreachable_code, + unused_variables, + dead_code, + clippy::single_match, + clippy::wildcard_in_or_patterns, + clippy::unnested_or_patterns, + clippy::diverging_sub_expression +)] + +use std::io::ErrorKind; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), + Cyan, +} + +impl Color { + fn is_monochrome(self) -> bool { + match self { + Color::Red | Color::Green | Color::Blue => true, + Color::Rgb(r, g, b) => r | g == 0 || r | b == 0 || g | b == 0, + Color::Cyan => false, + } + } +} + +fn main() { + let color = Color::Rgb(0, 0, 127); + match color { + Color::Red => println!("Red"), + _ => eprintln!("Not red"), + }; + match color { + Color::Red => println!("Red"), + _not_red => eprintln!("Not red"), + }; + let _str = match color { + Color::Red => "Red".to_owned(), + not_red => format!("{:?}", not_red), + }; + match color { + Color::Red => {}, + Color::Green => {}, + Color::Blue => {}, + Color::Cyan => {}, + c if c.is_monochrome() => {}, + Color::Rgb(_, _, _) => {}, + }; + let _str = match color { + Color::Red => "Red", + c @ Color::Green | c @ Color::Blue | c @ Color::Rgb(_, _, _) | c @ Color::Cyan => "Not red", + }; + match color { + Color::Rgb(r, _, _) if r > 0 => "Some red", + _ => "No red", + }; + match color { + Color::Red | Color::Green | Color::Blue | Color::Cyan => {}, + Color::Rgb(..) => {}, + }; + let x: u8 = unimplemented!(); + match x { + 0 => {}, + 140 => {}, + _ => {}, + }; + // We need to use an enum not defined in this test because non_exhaustive is ignored for the + // purposes of dead code analysis within a crate. + let error_kind = ErrorKind::NotFound; + match error_kind { + ErrorKind::NotFound => {}, + _ => {}, + } + match error_kind { + ErrorKind::NotFound => {}, + ErrorKind::PermissionDenied => {}, + ErrorKind::ConnectionRefused => {}, + ErrorKind::ConnectionReset => {}, + ErrorKind::ConnectionAborted => {}, + ErrorKind::NotConnected => {}, + ErrorKind::AddrInUse => {}, + ErrorKind::AddrNotAvailable => {}, + ErrorKind::BrokenPipe => {}, + ErrorKind::AlreadyExists => {}, + ErrorKind::WouldBlock => {}, + ErrorKind::InvalidInput => {}, + ErrorKind::InvalidData => {}, + ErrorKind::TimedOut => {}, + ErrorKind::WriteZero => {}, + ErrorKind::Interrupted => {}, + ErrorKind::Other => {}, + ErrorKind::UnexpectedEof => {}, + _ => {}, + } +} diff --git a/src/tools/clippy/tests/ui/wildcard_enum_match_arm.stderr b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.stderr new file mode 100644 index 0000000000..0da2b68ba0 --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.stderr @@ -0,0 +1,38 @@ +error: wildcard match will miss any future added variants + --> $DIR/wildcard_enum_match_arm.rs:39:9 + | +LL | _ => eprintln!("Not red"), + | ^ help: try this: `Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan` + | +note: the lint level is defined here + --> $DIR/wildcard_enum_match_arm.rs:3:9 + | +LL | #![deny(clippy::wildcard_enum_match_arm)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: wildcard match will miss any future added variants + --> $DIR/wildcard_enum_match_arm.rs:43:9 + | +LL | _not_red => eprintln!("Not red"), + | ^^^^^^^^ help: try this: `_not_red @ Color::Green | _not_red @ Color::Blue | _not_red @ Color::Rgb(..) | _not_red @ Color::Cyan` + +error: wildcard match will miss any future added variants + --> $DIR/wildcard_enum_match_arm.rs:47:9 + | +LL | not_red => format!("{:?}", not_red), + | ^^^^^^^ help: try this: `not_red @ Color::Green | not_red @ Color::Blue | not_red @ Color::Rgb(..) | not_red @ Color::Cyan` + +error: wildcard match will miss any future added variants + --> $DIR/wildcard_enum_match_arm.rs:63:9 + | +LL | _ => "No red", + | ^ help: try this: `Color::Red | Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan` + +error: match on non-exhaustive enum doesn't explicitly match all known variants + --> $DIR/wildcard_enum_match_arm.rs:80:9 + | +LL | _ => {}, + | ^ help: try this: `std::io::ErrorKind::PermissionDenied | std::io::ErrorKind::ConnectionRefused | std::io::ErrorKind::ConnectionReset | std::io::ErrorKind::ConnectionAborted | std::io::ErrorKind::NotConnected | std::io::ErrorKind::AddrInUse | std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::AlreadyExists | std::io::ErrorKind::WouldBlock | std::io::ErrorKind::InvalidInput | std::io::ErrorKind::InvalidData | std::io::ErrorKind::TimedOut | std::io::ErrorKind::WriteZero | std::io::ErrorKind::Interrupted | std::io::ErrorKind::Other | std::io::ErrorKind::UnexpectedEof | _` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/wildcard_imports.fixed b/src/tools/clippy/tests/ui/wildcard_imports.fixed new file mode 100644 index 0000000000..ee9c9045ff --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_imports.fixed @@ -0,0 +1,233 @@ +// run-rustfix +// aux-build:wildcard_imports_helper.rs + +#![warn(clippy::wildcard_imports)] +//#![allow(clippy::redundant_pub_crate)] +#![allow(unused)] +#![allow(clippy::unnecessary_wraps)] +#![warn(unused_imports)] + +extern crate wildcard_imports_helper; + +use crate::fn_mod::foo; +use crate::mod_mod::inner_mod; +use crate::multi_fn_mod::{multi_bar, multi_foo, multi_inner_mod}; +#[macro_use] +use crate::struct_mod::{A, inner_struct_mod}; + +#[allow(unused_imports)] +use wildcard_imports_helper::inner::inner_for_self_import; +use wildcard_imports_helper::inner::inner_for_self_import::inner_extern_bar; +use wildcard_imports_helper::{ExternA, extern_foo}; + +use std::io::prelude::*; +use wildcard_imports_helper::prelude::v1::*; + +struct ReadFoo; + +impl Read for ReadFoo { + fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { + Ok(0) + } +} + +mod fn_mod { + pub fn foo() {} +} + +mod mod_mod { + pub mod inner_mod { + pub fn foo() {} + } +} + +mod multi_fn_mod { + pub fn multi_foo() {} + pub fn multi_bar() {} + pub fn multi_baz() {} + pub mod multi_inner_mod { + pub fn foo() {} + } +} + +mod struct_mod { + pub struct A; + pub struct B; + pub mod inner_struct_mod { + pub struct C; + } + + #[macro_export] + macro_rules! double_struct_import_test { + () => { + let _ = A; + }; + } +} + +fn main() { + foo(); + multi_foo(); + multi_bar(); + multi_inner_mod::foo(); + inner_mod::foo(); + extern_foo(); + inner_extern_bar(); + + let _ = A; + let _ = inner_struct_mod::C; + let _ = ExternA; + let _ = PreludeModAnywhere; + + double_struct_import_test!(); + double_struct_import_test!(); +} + +mod in_fn_test { + pub use self::inner_exported::*; + #[allow(unused_imports)] + pub(crate) use self::inner_exported2::*; + + fn test_intern() { + use crate::fn_mod::foo; + + foo(); + } + + fn test_extern() { + use wildcard_imports_helper::inner::inner_for_self_import::{self, inner_extern_foo}; + use wildcard_imports_helper::{ExternA, extern_foo}; + + inner_for_self_import::inner_extern_foo(); + inner_extern_foo(); + + extern_foo(); + + let _ = ExternA; + } + + fn test_inner_nested() { + use self::{inner::inner_foo, inner2::inner_bar}; + + inner_foo(); + inner_bar(); + } + + fn test_extern_reexported() { + use wildcard_imports_helper::{ExternExportedEnum, ExternExportedStruct, extern_exported}; + + extern_exported(); + let _ = ExternExportedStruct; + let _ = ExternExportedEnum::A; + } + + mod inner_exported { + pub fn exported() {} + pub struct ExportedStruct; + pub enum ExportedEnum { + A, + } + } + + mod inner_exported2 { + pub(crate) fn exported2() {} + } + + mod inner { + pub fn inner_foo() {} + } + + mod inner2 { + pub fn inner_bar() {} + } +} + +fn test_reexported() { + use crate::in_fn_test::{ExportedEnum, ExportedStruct, exported}; + + exported(); + let _ = ExportedStruct; + let _ = ExportedEnum::A; +} + +#[rustfmt::skip] +fn test_weird_formatting() { + use crate:: in_fn_test::exported; + use crate:: fn_mod::foo; + + exported(); + foo(); +} + +mod super_imports { + fn foofoo() {} + + mod should_be_replaced { + use super::foofoo; + + fn with_super() { + let _ = foofoo(); + } + } + + mod test_should_pass { + use super::*; + + fn with_super() { + let _ = foofoo(); + } + } + + mod test_should_pass_inside_function { + fn with_super_inside_function() { + use super::*; + let _ = foofoo(); + } + } + + mod test_should_pass_further_inside { + fn insidefoo() {} + mod inner { + use super::*; + fn with_super() { + let _ = insidefoo(); + } + } + } + + mod should_be_replaced_futher_inside { + fn insidefoo() {} + mod inner { + use super::insidefoo; + fn with_super() { + let _ = insidefoo(); + } + } + } + + mod use_explicit_should_be_replaced { + use super_imports::foofoo; + + fn with_explicit() { + let _ = foofoo(); + } + } + + mod use_double_super_should_be_replaced { + mod inner { + use super::super::foofoo; + + fn with_double_super() { + let _ = foofoo(); + } + } + } + + mod use_super_explicit_should_be_replaced { + use super::super::super_imports::foofoo; + + fn with_super_explicit() { + let _ = foofoo(); + } + } +} diff --git a/src/tools/clippy/tests/ui/wildcard_imports.rs b/src/tools/clippy/tests/ui/wildcard_imports.rs new file mode 100644 index 0000000000..efaa8f9ef6 --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_imports.rs @@ -0,0 +1,234 @@ +// run-rustfix +// aux-build:wildcard_imports_helper.rs + +#![warn(clippy::wildcard_imports)] +//#![allow(clippy::redundant_pub_crate)] +#![allow(unused)] +#![allow(clippy::unnecessary_wraps)] +#![warn(unused_imports)] + +extern crate wildcard_imports_helper; + +use crate::fn_mod::*; +use crate::mod_mod::*; +use crate::multi_fn_mod::*; +#[macro_use] +use crate::struct_mod::*; + +#[allow(unused_imports)] +use wildcard_imports_helper::inner::inner_for_self_import; +use wildcard_imports_helper::inner::inner_for_self_import::*; +use wildcard_imports_helper::*; + +use std::io::prelude::*; +use wildcard_imports_helper::prelude::v1::*; + +struct ReadFoo; + +impl Read for ReadFoo { + fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { + Ok(0) + } +} + +mod fn_mod { + pub fn foo() {} +} + +mod mod_mod { + pub mod inner_mod { + pub fn foo() {} + } +} + +mod multi_fn_mod { + pub fn multi_foo() {} + pub fn multi_bar() {} + pub fn multi_baz() {} + pub mod multi_inner_mod { + pub fn foo() {} + } +} + +mod struct_mod { + pub struct A; + pub struct B; + pub mod inner_struct_mod { + pub struct C; + } + + #[macro_export] + macro_rules! double_struct_import_test { + () => { + let _ = A; + }; + } +} + +fn main() { + foo(); + multi_foo(); + multi_bar(); + multi_inner_mod::foo(); + inner_mod::foo(); + extern_foo(); + inner_extern_bar(); + + let _ = A; + let _ = inner_struct_mod::C; + let _ = ExternA; + let _ = PreludeModAnywhere; + + double_struct_import_test!(); + double_struct_import_test!(); +} + +mod in_fn_test { + pub use self::inner_exported::*; + #[allow(unused_imports)] + pub(crate) use self::inner_exported2::*; + + fn test_intern() { + use crate::fn_mod::*; + + foo(); + } + + fn test_extern() { + use wildcard_imports_helper::inner::inner_for_self_import::{self, *}; + use wildcard_imports_helper::*; + + inner_for_self_import::inner_extern_foo(); + inner_extern_foo(); + + extern_foo(); + + let _ = ExternA; + } + + fn test_inner_nested() { + use self::{inner::*, inner2::*}; + + inner_foo(); + inner_bar(); + } + + fn test_extern_reexported() { + use wildcard_imports_helper::*; + + extern_exported(); + let _ = ExternExportedStruct; + let _ = ExternExportedEnum::A; + } + + mod inner_exported { + pub fn exported() {} + pub struct ExportedStruct; + pub enum ExportedEnum { + A, + } + } + + mod inner_exported2 { + pub(crate) fn exported2() {} + } + + mod inner { + pub fn inner_foo() {} + } + + mod inner2 { + pub fn inner_bar() {} + } +} + +fn test_reexported() { + use crate::in_fn_test::*; + + exported(); + let _ = ExportedStruct; + let _ = ExportedEnum::A; +} + +#[rustfmt::skip] +fn test_weird_formatting() { + use crate:: in_fn_test:: * ; + use crate:: fn_mod:: + *; + + exported(); + foo(); +} + +mod super_imports { + fn foofoo() {} + + mod should_be_replaced { + use super::*; + + fn with_super() { + let _ = foofoo(); + } + } + + mod test_should_pass { + use super::*; + + fn with_super() { + let _ = foofoo(); + } + } + + mod test_should_pass_inside_function { + fn with_super_inside_function() { + use super::*; + let _ = foofoo(); + } + } + + mod test_should_pass_further_inside { + fn insidefoo() {} + mod inner { + use super::*; + fn with_super() { + let _ = insidefoo(); + } + } + } + + mod should_be_replaced_futher_inside { + fn insidefoo() {} + mod inner { + use super::*; + fn with_super() { + let _ = insidefoo(); + } + } + } + + mod use_explicit_should_be_replaced { + use super_imports::*; + + fn with_explicit() { + let _ = foofoo(); + } + } + + mod use_double_super_should_be_replaced { + mod inner { + use super::super::*; + + fn with_double_super() { + let _ = foofoo(); + } + } + } + + mod use_super_explicit_should_be_replaced { + use super::super::super_imports::*; + + fn with_super_explicit() { + let _ = foofoo(); + } + } +} diff --git a/src/tools/clippy/tests/ui/wildcard_imports.stderr b/src/tools/clippy/tests/ui/wildcard_imports.stderr new file mode 100644 index 0000000000..66267dd27b --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_imports.stderr @@ -0,0 +1,126 @@ +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:12:5 + | +LL | use crate::fn_mod::*; + | ^^^^^^^^^^^^^^^^ help: try: `crate::fn_mod::foo` + | + = note: `-D clippy::wildcard-imports` implied by `-D warnings` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:13:5 + | +LL | use crate::mod_mod::*; + | ^^^^^^^^^^^^^^^^^ help: try: `crate::mod_mod::inner_mod` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:14:5 + | +LL | use crate::multi_fn_mod::*; + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate::multi_fn_mod::{multi_bar, multi_foo, multi_inner_mod}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:16:5 + | +LL | use crate::struct_mod::*; + | ^^^^^^^^^^^^^^^^^^^^ help: try: `crate::struct_mod::{A, inner_struct_mod}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:20:5 + | +LL | use wildcard_imports_helper::inner::inner_for_self_import::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::inner::inner_for_self_import::inner_extern_bar` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:21:5 + | +LL | use wildcard_imports_helper::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:92:13 + | +LL | use crate::fn_mod::*; + | ^^^^^^^^^^^^^^^^ help: try: `crate::fn_mod::foo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:98:75 + | +LL | use wildcard_imports_helper::inner::inner_for_self_import::{self, *}; + | ^ help: try: `inner_extern_foo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:99:13 + | +LL | use wildcard_imports_helper::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:110:20 + | +LL | use self::{inner::*, inner2::*}; + | ^^^^^^^^ help: try: `inner::inner_foo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:110:30 + | +LL | use self::{inner::*, inner2::*}; + | ^^^^^^^^^ help: try: `inner2::inner_bar` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:117:13 + | +LL | use wildcard_imports_helper::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternExportedEnum, ExternExportedStruct, extern_exported}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:146:9 + | +LL | use crate::in_fn_test::*; + | ^^^^^^^^^^^^^^^^^^^^ help: try: `crate::in_fn_test::{ExportedEnum, ExportedStruct, exported}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:155:9 + | +LL | use crate:: in_fn_test:: * ; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate:: in_fn_test::exported` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:156:9 + | +LL | use crate:: fn_mod:: + | _________^ +LL | | *; + | |_________^ help: try: `crate:: fn_mod::foo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:167:13 + | +LL | use super::*; + | ^^^^^^^^ help: try: `super::foofoo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:202:17 + | +LL | use super::*; + | ^^^^^^^^ help: try: `super::insidefoo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:210:13 + | +LL | use super_imports::*; + | ^^^^^^^^^^^^^^^^ help: try: `super_imports::foofoo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:219:17 + | +LL | use super::super::*; + | ^^^^^^^^^^^^^^^ help: try: `super::super::foofoo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:228:13 + | +LL | use super::super::super_imports::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `super::super::super_imports::foofoo` + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/write_literal.rs b/src/tools/clippy/tests/ui/write_literal.rs new file mode 100644 index 0000000000..0a127858de --- /dev/null +++ b/src/tools/clippy/tests/ui/write_literal.rs @@ -0,0 +1,43 @@ +#![allow(unused_must_use)] +#![warn(clippy::write_literal)] + +use std::io::Write; + +fn main() { + let mut v = Vec::new(); + + // these should be fine + write!(&mut v, "Hello"); + writeln!(&mut v, "Hello"); + let world = "world"; + writeln!(&mut v, "Hello {}", world); + writeln!(&mut v, "Hello {world}", world = world); + writeln!(&mut v, "3 in hex is {:X}", 3); + writeln!(&mut v, "2 + 1 = {:.4}", 3); + writeln!(&mut v, "2 + 1 = {:5.4}", 3); + writeln!(&mut v, "Debug test {:?}", "hello, world"); + writeln!(&mut v, "{0:8} {1:>8}", "hello", "world"); + writeln!(&mut v, "{1:8} {0:>8}", "hello", "world"); + writeln!(&mut v, "{foo:8} {bar:>8}", foo = "hello", bar = "world"); + writeln!(&mut v, "{bar:8} {foo:>8}", foo = "hello", bar = "world"); + writeln!(&mut v, "{number:>width$}", number = 1, width = 6); + writeln!(&mut v, "{number:>0width$}", number = 1, width = 6); + writeln!(&mut v, "{} of {:b} people know binary, the other half doesn't", 1, 2); + writeln!(&mut v, "10 / 4 is {}", 2.5); + writeln!(&mut v, "2 + 1 = {}", 3); + + // these should throw warnings + write!(&mut v, "Hello {}", "world"); + writeln!(&mut v, "Hello {} {}", world, "world"); + writeln!(&mut v, "Hello {}", "world"); + + // positional args don't change the fact + // that we're using a literal -- this should + // throw a warning + writeln!(&mut v, "{0} {1}", "hello", "world"); + writeln!(&mut v, "{1} {0}", "hello", "world"); + + // named args shouldn't change anything either + writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); + writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); +} diff --git a/src/tools/clippy/tests/ui/write_literal.stderr b/src/tools/clippy/tests/ui/write_literal.stderr new file mode 100644 index 0000000000..e54d89ecf2 --- /dev/null +++ b/src/tools/clippy/tests/ui/write_literal.stderr @@ -0,0 +1,70 @@ +error: literal with an empty format string + --> $DIR/write_literal.rs:30:32 + | +LL | write!(&mut v, "Hello {}", "world"); + | ^^^^^^^ + | + = note: `-D clippy::write-literal` implied by `-D warnings` + +error: literal with an empty format string + --> $DIR/write_literal.rs:31:44 + | +LL | writeln!(&mut v, "Hello {} {}", world, "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:32:34 + | +LL | writeln!(&mut v, "Hello {}", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:37:33 + | +LL | writeln!(&mut v, "{0} {1}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:37:42 + | +LL | writeln!(&mut v, "{0} {1}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:38:33 + | +LL | writeln!(&mut v, "{1} {0}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:38:42 + | +LL | writeln!(&mut v, "{1} {0}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:41:43 + | +LL | writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:41:58 + | +LL | writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:42:43 + | +LL | writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:42:58 + | +LL | writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/write_with_newline.rs b/src/tools/clippy/tests/ui/write_with_newline.rs new file mode 100644 index 0000000000..1c1b1b5840 --- /dev/null +++ b/src/tools/clippy/tests/ui/write_with_newline.rs @@ -0,0 +1,59 @@ +// FIXME: Ideally these suggestions would be fixed via rustfix. Blocked by rust-lang/rust#53934 +// // run-rustfix + +#![allow(clippy::write_literal)] +#![warn(clippy::write_with_newline)] + +use std::io::Write; + +fn main() { + let mut v = Vec::new(); + + // These should fail + write!(&mut v, "Hello\n"); + write!(&mut v, "Hello {}\n", "world"); + write!(&mut v, "Hello {} {}\n", "world", "#2"); + write!(&mut v, "{}\n", 1265); + write!(&mut v, "\n"); + + // These should be fine + write!(&mut v, ""); + write!(&mut v, "Hello"); + writeln!(&mut v, "Hello"); + writeln!(&mut v, "Hello\n"); + writeln!(&mut v, "Hello {}\n", "world"); + write!(&mut v, "Issue\n{}", 1265); + write!(&mut v, "{}", 1265); + write!(&mut v, "\n{}", 1275); + write!(&mut v, "\n\n"); + write!(&mut v, "like eof\n\n"); + write!(&mut v, "Hello {} {}\n\n", "world", "#2"); + writeln!(&mut v, "\ndon't\nwarn\nfor\nmultiple\nnewlines\n"); // #3126 + writeln!(&mut v, "\nbla\n\n"); // #3126 + + // Escaping + write!(&mut v, "\\n"); // #3514 + write!(&mut v, "\\\n"); // should fail + write!(&mut v, "\\\\n"); + + // Raw strings + write!(&mut v, r"\n"); // #3778 + + // Literal newlines should also fail + write!( + &mut v, + " +" + ); + write!( + &mut v, + r" +" + ); + + // Don't warn on CRLF (#4208) + write!(&mut v, "\r\n"); + write!(&mut v, "foo\r\n"); + write!(&mut v, "\\r\n"); //~ ERROR + write!(&mut v, "foo\rbar\n"); +} diff --git a/src/tools/clippy/tests/ui/write_with_newline.stderr b/src/tools/clippy/tests/ui/write_with_newline.stderr new file mode 100644 index 0000000000..a14e86122e --- /dev/null +++ b/src/tools/clippy/tests/ui/write_with_newline.stderr @@ -0,0 +1,125 @@ +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:13:5 + | +LL | write!(&mut v, "Hello/n"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::write-with-newline` implied by `-D warnings` +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "Hello"); + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:14:5 + | +LL | write!(&mut v, "Hello {}/n", "world"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "Hello {}", "world"); + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:15:5 + | +LL | write!(&mut v, "Hello {} {}/n", "world", "#2"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "Hello {} {}", "world", "#2"); + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:16:5 + | +LL | write!(&mut v, "{}/n", 1265); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "{}", 1265); + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:17:5 + | +LL | write!(&mut v, "/n"); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, ); + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:36:5 + | +LL | write!(&mut v, "//n"); // should fail + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "/"); // should fail + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:43:5 + | +LL | / write!( +LL | | &mut v, +LL | | " +LL | | " +LL | | ); + | |_____^ + | +help: use `writeln!()` instead + | +LL | writeln!( +LL | &mut v, +LL | "" + | + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:48:5 + | +LL | / write!( +LL | | &mut v, +LL | | r" +LL | | " +LL | | ); + | |_____^ + | +help: use `writeln!()` instead + | +LL | writeln!( +LL | &mut v, +LL | r"" + | + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:57:5 + | +LL | write!(&mut v, "/r/n"); //~ ERROR + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "/r"); //~ ERROR + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:58:5 + | +LL | write!(&mut v, "foo/rbar/n"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "foo/rbar"); + | ^^^^^^^ -- + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/writeln_empty_string.fixed b/src/tools/clippy/tests/ui/writeln_empty_string.fixed new file mode 100644 index 0000000000..c3ac15b037 --- /dev/null +++ b/src/tools/clippy/tests/ui/writeln_empty_string.fixed @@ -0,0 +1,20 @@ +// run-rustfix + +#![allow(unused_must_use)] +#![warn(clippy::writeln_empty_string)] +use std::io::Write; + +fn main() { + let mut v = Vec::new(); + + // These should fail + writeln!(&mut v); + + let mut suggestion = Vec::new(); + writeln!(&mut suggestion); + + // These should be fine + writeln!(&mut v); + writeln!(&mut v, " "); + write!(&mut v, ""); +} diff --git a/src/tools/clippy/tests/ui/writeln_empty_string.rs b/src/tools/clippy/tests/ui/writeln_empty_string.rs new file mode 100644 index 0000000000..9a8894b6c0 --- /dev/null +++ b/src/tools/clippy/tests/ui/writeln_empty_string.rs @@ -0,0 +1,20 @@ +// run-rustfix + +#![allow(unused_must_use)] +#![warn(clippy::writeln_empty_string)] +use std::io::Write; + +fn main() { + let mut v = Vec::new(); + + // These should fail + writeln!(&mut v, ""); + + let mut suggestion = Vec::new(); + writeln!(&mut suggestion, ""); + + // These should be fine + writeln!(&mut v); + writeln!(&mut v, " "); + write!(&mut v, ""); +} diff --git a/src/tools/clippy/tests/ui/writeln_empty_string.stderr b/src/tools/clippy/tests/ui/writeln_empty_string.stderr new file mode 100644 index 0000000000..99635229b3 --- /dev/null +++ b/src/tools/clippy/tests/ui/writeln_empty_string.stderr @@ -0,0 +1,16 @@ +error: using `writeln!(&mut v, "")` + --> $DIR/writeln_empty_string.rs:11:5 + | +LL | writeln!(&mut v, ""); + | ^^^^^^^^^^^^^^^^^^^^ help: replace it with: `writeln!(&mut v)` + | + = note: `-D clippy::writeln-empty-string` implied by `-D warnings` + +error: using `writeln!(&mut suggestion, "")` + --> $DIR/writeln_empty_string.rs:14:5 + | +LL | writeln!(&mut suggestion, ""); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `writeln!(&mut suggestion)` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/wrong_self_convention.rs b/src/tools/clippy/tests/ui/wrong_self_convention.rs new file mode 100644 index 0000000000..6cfc0fcb4c --- /dev/null +++ b/src/tools/clippy/tests/ui/wrong_self_convention.rs @@ -0,0 +1,165 @@ +// edition:2018 +#![warn(clippy::wrong_self_convention)] +#![warn(clippy::wrong_pub_self_convention)] +#![allow(dead_code)] + +fn main() {} + +#[derive(Clone, Copy)] +struct Foo; + +impl Foo { + fn as_i32(self) {} + fn as_u32(&self) {} + fn into_i32(self) {} + fn is_i32(self) {} + fn is_u32(&self) {} + fn to_i32(self) {} + fn from_i32(self) {} + + pub fn as_i64(self) {} + pub fn into_i64(self) {} + pub fn is_i64(self) {} + pub fn to_i64(self) {} + pub fn from_i64(self) {} + // check whether the lint can be allowed at the function level + #[allow(clippy::wrong_self_convention)] + pub fn from_cake(self) {} + + fn as_x>(_: F) {} + fn as_y>(_: F) {} +} + +struct Bar; + +impl Bar { + fn as_i32(self) {} + fn as_u32(&self) {} + fn into_i32(&self) {} + fn into_u32(self) {} + fn is_i32(self) {} + fn is_u32(&self) {} + fn to_i32(self) {} + fn to_u32(&self) {} + fn from_i32(self) {} + + pub fn as_i64(self) {} + pub fn into_i64(&self) {} + pub fn is_i64(self) {} + pub fn to_i64(self) {} + pub fn from_i64(self) {} + + // test for false positives + fn as_(self) {} + fn into_(&self) {} + fn is_(self) {} + fn to_(self) {} + fn from_(self) {} + fn to_mut(&mut self) {} +} + +// Allow Box, Rc, Arc for methods that take conventionally take Self by value +#[allow(clippy::boxed_local)] +mod issue4293 { + use std::rc::Rc; + use std::sync::Arc; + + struct T; + + impl T { + fn into_s1(self: Box) {} + fn into_s2(self: Rc) {} + fn into_s3(self: Arc) {} + + fn into_t1(self: Box) {} + fn into_t2(self: Rc) {} + fn into_t3(self: Arc) {} + } +} + +// False positive for async (see #4037) +mod issue4037 { + pub struct Foo; + pub struct Bar; + + impl Foo { + pub async fn into_bar(self) -> Bar { + Bar + } + } +} + +// Lint also in trait definition (see #6307) +mod issue6307 { + trait T: Sized { + fn as_i32(self) {} + fn as_u32(&self) {} + fn into_i32(self) {} + fn into_i32_ref(&self) {} + fn into_u32(self) {} + fn is_i32(self) {} + fn is_u32(&self) {} + fn to_i32(self) {} + fn to_u32(&self) {} + fn from_i32(self) {} + // check whether the lint can be allowed at the function level + #[allow(clippy::wrong_self_convention)] + fn from_cake(self) {} + + // test for false positives + fn as_(self) {} + fn into_(&self) {} + fn is_(self) {} + fn to_(self) {} + fn from_(self) {} + fn to_mut(&mut self) {} + } + + trait U { + fn as_i32(self); + fn as_u32(&self); + fn into_i32(self); + fn into_i32_ref(&self); + fn into_u32(self); + fn is_i32(self); + fn is_u32(&self); + fn to_i32(self); + fn to_u32(&self); + fn from_i32(self); + // check whether the lint can be allowed at the function level + #[allow(clippy::wrong_self_convention)] + fn from_cake(self); + + // test for false positives + fn as_(self); + fn into_(&self); + fn is_(self); + fn to_(self); + fn from_(self); + fn to_mut(&mut self); + } + + trait C: Copy { + fn as_i32(self); + fn as_u32(&self); + fn into_i32(self); + fn into_i32_ref(&self); + fn into_u32(self); + fn is_i32(self); + fn is_u32(&self); + fn to_i32(self); + fn to_u32(&self); + fn from_i32(self); + // check whether the lint can be allowed at the function level + #[allow(clippy::wrong_self_convention)] + fn from_cake(self); + + // test for false positives + fn as_(self); + fn into_(&self); + fn is_(self); + fn to_(self); + fn from_(self); + fn to_mut(&mut self); + } +} diff --git a/src/tools/clippy/tests/ui/wrong_self_convention.stderr b/src/tools/clippy/tests/ui/wrong_self_convention.stderr new file mode 100644 index 0000000000..32bd9075bd --- /dev/null +++ b/src/tools/clippy/tests/ui/wrong_self_convention.stderr @@ -0,0 +1,148 @@ +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:18:17 + | +LL | fn from_i32(self) {} + | ^^^^ + | + = note: `-D clippy::wrong-self-convention` implied by `-D warnings` + +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:24:21 + | +LL | pub fn from_i64(self) {} + | ^^^^ + +error: methods called `as_*` usually take self by reference or self by mutable reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:36:15 + | +LL | fn as_i32(self) {} + | ^^^^ + +error: methods called `into_*` usually take self by value; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:38:17 + | +LL | fn into_i32(&self) {} + | ^^^^^ + +error: methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:40:15 + | +LL | fn is_i32(self) {} + | ^^^^ + +error: methods called `to_*` usually take self by reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:42:15 + | +LL | fn to_i32(self) {} + | ^^^^ + +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:44:17 + | +LL | fn from_i32(self) {} + | ^^^^ + +error: methods called `as_*` usually take self by reference or self by mutable reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:46:19 + | +LL | pub fn as_i64(self) {} + | ^^^^ + +error: methods called `into_*` usually take self by value; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:47:21 + | +LL | pub fn into_i64(&self) {} + | ^^^^^ + +error: methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:48:19 + | +LL | pub fn is_i64(self) {} + | ^^^^ + +error: methods called `to_*` usually take self by reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:49:19 + | +LL | pub fn to_i64(self) {} + | ^^^^ + +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:50:21 + | +LL | pub fn from_i64(self) {} + | ^^^^ + +error: methods called `as_*` usually take self by reference or self by mutable reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:95:19 + | +LL | fn as_i32(self) {} + | ^^^^ + +error: methods called `into_*` usually take self by value; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:98:25 + | +LL | fn into_i32_ref(&self) {} + | ^^^^^ + +error: methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:100:19 + | +LL | fn is_i32(self) {} + | ^^^^ + +error: methods called `to_*` usually take self by reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:102:19 + | +LL | fn to_i32(self) {} + | ^^^^ + +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:104:21 + | +LL | fn from_i32(self) {} + | ^^^^ + +error: methods called `as_*` usually take self by reference or self by mutable reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:119:19 + | +LL | fn as_i32(self); + | ^^^^ + +error: methods called `into_*` usually take self by value; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:122:25 + | +LL | fn into_i32_ref(&self); + | ^^^^^ + +error: methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:124:19 + | +LL | fn is_i32(self); + | ^^^^ + +error: methods called `to_*` usually take self by reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:126:19 + | +LL | fn to_i32(self); + | ^^^^ + +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:128:21 + | +LL | fn from_i32(self); + | ^^^^ + +error: methods called `into_*` usually take self by value; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:146:25 + | +LL | fn into_i32_ref(&self); + | ^^^^^ + +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:152:21 + | +LL | fn from_i32(self); + | ^^^^ + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/zero_div_zero.rs b/src/tools/clippy/tests/ui/zero_div_zero.rs new file mode 100644 index 0000000000..ed3a9208fc --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_div_zero.rs @@ -0,0 +1,13 @@ +#[allow(unused_variables)] +#[warn(clippy::zero_divided_by_zero)] +fn main() { + let nan = 0.0 / 0.0; + let f64_nan = 0.0 / 0.0f64; + let other_f64_nan = 0.0f64 / 0.0; + let one_more_f64_nan = 0.0f64 / 0.0f64; + let zero = 0.0; + let other_zero = 0.0; + let other_nan = zero / other_zero; // fine - this lint doesn't propagate constants. + let not_nan = 2.0 / 0.0; // not an error: 2/0 = inf + let also_not_nan = 0.0 / 2.0; // not an error: 0/2 = 0 +} diff --git a/src/tools/clippy/tests/ui/zero_div_zero.stderr b/src/tools/clippy/tests/ui/zero_div_zero.stderr new file mode 100644 index 0000000000..0931dd32e7 --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_div_zero.stderr @@ -0,0 +1,61 @@ +error: equal expressions as operands to `/` + --> $DIR/zero_div_zero.rs:4:15 + | +LL | let nan = 0.0 / 0.0; + | ^^^^^^^^^ + | + = note: `#[deny(clippy::eq_op)]` on by default + +error: constant division of `0.0` with `0.0` will always result in NaN + --> $DIR/zero_div_zero.rs:4:15 + | +LL | let nan = 0.0 / 0.0; + | ^^^^^^^^^ + | + = note: `-D clippy::zero-divided-by-zero` implied by `-D warnings` + = help: consider using `f64::NAN` if you would like a constant representing NaN + +error: equal expressions as operands to `/` + --> $DIR/zero_div_zero.rs:5:19 + | +LL | let f64_nan = 0.0 / 0.0f64; + | ^^^^^^^^^^^^ + +error: constant division of `0.0` with `0.0` will always result in NaN + --> $DIR/zero_div_zero.rs:5:19 + | +LL | let f64_nan = 0.0 / 0.0f64; + | ^^^^^^^^^^^^ + | + = help: consider using `f64::NAN` if you would like a constant representing NaN + +error: equal expressions as operands to `/` + --> $DIR/zero_div_zero.rs:6:25 + | +LL | let other_f64_nan = 0.0f64 / 0.0; + | ^^^^^^^^^^^^ + +error: constant division of `0.0` with `0.0` will always result in NaN + --> $DIR/zero_div_zero.rs:6:25 + | +LL | let other_f64_nan = 0.0f64 / 0.0; + | ^^^^^^^^^^^^ + | + = help: consider using `f64::NAN` if you would like a constant representing NaN + +error: equal expressions as operands to `/` + --> $DIR/zero_div_zero.rs:7:28 + | +LL | let one_more_f64_nan = 0.0f64 / 0.0f64; + | ^^^^^^^^^^^^^^^ + +error: constant division of `0.0` with `0.0` will always result in NaN + --> $DIR/zero_div_zero.rs:7:28 + | +LL | let one_more_f64_nan = 0.0f64 / 0.0f64; + | ^^^^^^^^^^^^^^^ + | + = help: consider using `f64::NAN` if you would like a constant representing NaN + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/zero_offset.rs b/src/tools/clippy/tests/ui/zero_offset.rs new file mode 100644 index 0000000000..2de904376a --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_offset.rs @@ -0,0 +1,12 @@ +fn main() { + unsafe { + let x = &() as *const (); + x.offset(0); + x.wrapping_add(0); + x.sub(0); + x.wrapping_sub(0); + + let y = &1 as *const u8; + y.offset(0); + } +} diff --git a/src/tools/clippy/tests/ui/zero_offset.stderr b/src/tools/clippy/tests/ui/zero_offset.stderr new file mode 100644 index 0000000000..cfcd7de2b3 --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_offset.stderr @@ -0,0 +1,9 @@ +error[E0606]: casting `&i32` as `*const u8` is invalid + --> $DIR/zero_offset.rs:9:17 + | +LL | let y = &1 as *const u8; + | ^^^^^^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0606`. diff --git a/src/tools/clippy/tests/ui/zero_ptr.fixed b/src/tools/clippy/tests/ui/zero_ptr.fixed new file mode 100644 index 0000000000..489aa4121a --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_ptr.fixed @@ -0,0 +1,14 @@ +// run-rustfix +pub fn foo(_const: *const f32, _mut: *mut i64) {} + +fn main() { + let _ = std::ptr::null::(); + let _ = std::ptr::null_mut::(); + let _: *const u8 = std::ptr::null(); + + foo(0 as _, 0 as _); + foo(std::ptr::null(), std::ptr::null_mut()); + + let z = 0; + let _ = z as *const usize; // this is currently not caught +} diff --git a/src/tools/clippy/tests/ui/zero_ptr.rs b/src/tools/clippy/tests/ui/zero_ptr.rs new file mode 100644 index 0000000000..c3b55ef9eb --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_ptr.rs @@ -0,0 +1,14 @@ +// run-rustfix +pub fn foo(_const: *const f32, _mut: *mut i64) {} + +fn main() { + let _ = 0 as *const usize; + let _ = 0 as *mut f64; + let _: *const u8 = 0 as *const _; + + foo(0 as _, 0 as _); + foo(0 as *const _, 0 as *mut _); + + let z = 0; + let _ = z as *const usize; // this is currently not caught +} diff --git a/src/tools/clippy/tests/ui/zero_ptr.stderr b/src/tools/clippy/tests/ui/zero_ptr.stderr new file mode 100644 index 0000000000..4ee5e9a261 --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_ptr.stderr @@ -0,0 +1,34 @@ +error: `0 as *const _` detected + --> $DIR/zero_ptr.rs:5:13 + | +LL | let _ = 0 as *const usize; + | ^^^^^^^^^^^^^^^^^ help: try: `std::ptr::null::()` + | + = note: `-D clippy::zero-ptr` implied by `-D warnings` + +error: `0 as *mut _` detected + --> $DIR/zero_ptr.rs:6:13 + | +LL | let _ = 0 as *mut f64; + | ^^^^^^^^^^^^^ help: try: `std::ptr::null_mut::()` + +error: `0 as *const _` detected + --> $DIR/zero_ptr.rs:7:24 + | +LL | let _: *const u8 = 0 as *const _; + | ^^^^^^^^^^^^^ help: try: `std::ptr::null()` + +error: `0 as *const _` detected + --> $DIR/zero_ptr.rs:10:9 + | +LL | foo(0 as *const _, 0 as *mut _); + | ^^^^^^^^^^^^^ help: try: `std::ptr::null()` + +error: `0 as *mut _` detected + --> $DIR/zero_ptr.rs:10:24 + | +LL | foo(0 as *const _, 0 as *mut _); + | ^^^^^^^^^^^ help: try: `std::ptr::null_mut()` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/zero_sized_btreemap_values.rs b/src/tools/clippy/tests/ui/zero_sized_btreemap_values.rs new file mode 100644 index 0000000000..5cd254787d --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_sized_btreemap_values.rs @@ -0,0 +1,68 @@ +#![warn(clippy::zero_sized_map_values)] +use std::collections::BTreeMap; + +const CONST_OK: Option> = None; +const CONST_NOT_OK: Option> = None; + +static STATIC_OK: Option> = None; +static STATIC_NOT_OK: Option> = None; + +type OkMap = BTreeMap; +type NotOkMap = BTreeMap; + +enum TestEnum { + Ok(BTreeMap), + NotOk(BTreeMap), +} + +struct Test { + ok: BTreeMap, + not_ok: BTreeMap, + + also_not_ok: Vec>, +} + +trait TestTrait { + type Output; + + fn produce_output() -> Self::Output; + + fn weird_map(&self, map: BTreeMap); +} + +impl Test { + fn ok(&self) -> BTreeMap { + todo!() + } + + fn not_ok(&self) -> BTreeMap { + todo!() + } +} + +impl TestTrait for Test { + type Output = BTreeMap; + + fn produce_output() -> Self::Output { + todo!(); + } + + fn weird_map(&self, map: BTreeMap) { + todo!(); + } +} + +fn test(map: BTreeMap, key: &str) -> BTreeMap { + todo!(); +} + +fn test2(map: BTreeMap, key: &str) -> BTreeMap { + todo!(); +} + +fn main() { + let _: BTreeMap = BTreeMap::new(); + let _: BTreeMap = BTreeMap::new(); + + let _: BTreeMap<_, _> = std::iter::empty::<(String, ())>().collect(); +} diff --git a/src/tools/clippy/tests/ui/zero_sized_btreemap_values.stderr b/src/tools/clippy/tests/ui/zero_sized_btreemap_values.stderr new file mode 100644 index 0000000000..d924f33797 --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_sized_btreemap_values.stderr @@ -0,0 +1,107 @@ +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:5:28 + | +LL | const CONST_NOT_OK: Option> = None; + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::zero-sized-map-values` implied by `-D warnings` + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:8:30 + | +LL | static STATIC_NOT_OK: Option> = None; + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:11:17 + | +LL | type NotOkMap = BTreeMap; + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:15:11 + | +LL | NotOk(BTreeMap), + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:20:13 + | +LL | not_ok: BTreeMap, + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:22:22 + | +LL | also_not_ok: Vec>, + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:30:30 + | +LL | fn weird_map(&self, map: BTreeMap); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:38:25 + | +LL | fn not_ok(&self) -> BTreeMap { + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:55:14 + | +LL | fn test(map: BTreeMap, key: &str) -> BTreeMap { + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:55:50 + | +LL | fn test(map: BTreeMap, key: &str) -> BTreeMap { + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:64:35 + | +LL | let _: BTreeMap = BTreeMap::new(); + | ^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:64:12 + | +LL | let _: BTreeMap = BTreeMap::new(); + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:67:12 + | +LL | let _: BTreeMap<_, _> = std::iter::empty::<(String, ())>().collect(); + | ^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/zero_sized_hashmap_values.rs b/src/tools/clippy/tests/ui/zero_sized_hashmap_values.rs new file mode 100644 index 0000000000..a1608d863f --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_sized_hashmap_values.rs @@ -0,0 +1,68 @@ +#![warn(clippy::zero_sized_map_values)] +use std::collections::HashMap; + +const CONST_OK: Option> = None; +const CONST_NOT_OK: Option> = None; + +static STATIC_OK: Option> = None; +static STATIC_NOT_OK: Option> = None; + +type OkMap = HashMap; +type NotOkMap = HashMap; + +enum TestEnum { + Ok(HashMap), + NotOk(HashMap), +} + +struct Test { + ok: HashMap, + not_ok: HashMap, + + also_not_ok: Vec>, +} + +trait TestTrait { + type Output; + + fn produce_output() -> Self::Output; + + fn weird_map(&self, map: HashMap); +} + +impl Test { + fn ok(&self) -> HashMap { + todo!() + } + + fn not_ok(&self) -> HashMap { + todo!() + } +} + +impl TestTrait for Test { + type Output = HashMap; + + fn produce_output() -> Self::Output { + todo!(); + } + + fn weird_map(&self, map: HashMap) { + todo!(); + } +} + +fn test(map: HashMap, key: &str) -> HashMap { + todo!(); +} + +fn test2(map: HashMap, key: &str) -> HashMap { + todo!(); +} + +fn main() { + let _: HashMap = HashMap::new(); + let _: HashMap = HashMap::new(); + + let _: HashMap<_, _> = std::iter::empty::<(String, ())>().collect(); +} diff --git a/src/tools/clippy/tests/ui/zero_sized_hashmap_values.stderr b/src/tools/clippy/tests/ui/zero_sized_hashmap_values.stderr new file mode 100644 index 0000000000..79770bf90d --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_sized_hashmap_values.stderr @@ -0,0 +1,107 @@ +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:5:28 + | +LL | const CONST_NOT_OK: Option> = None; + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::zero-sized-map-values` implied by `-D warnings` + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:8:30 + | +LL | static STATIC_NOT_OK: Option> = None; + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:11:17 + | +LL | type NotOkMap = HashMap; + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:15:11 + | +LL | NotOk(HashMap), + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:20:13 + | +LL | not_ok: HashMap, + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:22:22 + | +LL | also_not_ok: Vec>, + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:30:30 + | +LL | fn weird_map(&self, map: HashMap); + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:38:25 + | +LL | fn not_ok(&self) -> HashMap { + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:55:14 + | +LL | fn test(map: HashMap, key: &str) -> HashMap { + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:55:49 + | +LL | fn test(map: HashMap, key: &str) -> HashMap { + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:64:34 + | +LL | let _: HashMap = HashMap::new(); + | ^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:64:12 + | +LL | let _: HashMap = HashMap::new(); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:67:12 + | +LL | let _: HashMap<_, _> = std::iter::empty::<(String, ())>().collect(); + | ^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/versioncheck.rs b/src/tools/clippy/tests/versioncheck.rs new file mode 100644 index 0000000000..1eaec4a50a --- /dev/null +++ b/src/tools/clippy/tests/versioncheck.rs @@ -0,0 +1,88 @@ +#![allow(clippy::single_match_else)] +use rustc_tools_util::VersionInfo; + +#[test] +fn check_that_clippy_lints_and_clippy_utils_have_the_same_version_as_clippy() { + // do not run this test inside the upstream rustc repo: + // https://github.com/rust-lang/rust-clippy/issues/6683 + if option_env!("RUSTC_TEST_SUITE").is_some() { + return; + } + + let clippy_meta = cargo_metadata::MetadataCommand::new() + .no_deps() + .exec() + .expect("could not obtain cargo metadata"); + + for krate in &["clippy_lints", "clippy_utils"] { + let krate_meta = cargo_metadata::MetadataCommand::new() + .current_dir(std::env::current_dir().unwrap().join(krate)) + .no_deps() + .exec() + .expect("could not obtain cargo metadata"); + assert_eq!(krate_meta.packages[0].version, clippy_meta.packages[0].version); + for package in &clippy_meta.packages[0].dependencies { + if package.name == *krate { + assert!(package.req.matches(&krate_meta.packages[0].version)); + break; + } + } + } +} + +#[test] +fn check_that_clippy_has_the_same_major_version_as_rustc() { + // do not run this test inside the upstream rustc repo: + // https://github.com/rust-lang/rust-clippy/issues/6683 + if option_env!("RUSTC_TEST_SUITE").is_some() { + return; + } + + let clippy_version = rustc_tools_util::get_version_info!(); + let clippy_major = clippy_version.major; + let clippy_minor = clippy_version.minor; + let clippy_patch = clippy_version.patch; + + // get the rustc version either from the rustc installed with the toolchain file or from + // `RUSTC_REAL` if Clippy is build in the Rust repo with `./x.py`. + let rustc = std::env::var("RUSTC_REAL").unwrap_or_else(|_| "rustc".to_string()); + let rustc_version = String::from_utf8( + std::process::Command::new(&rustc) + .arg("--version") + .output() + .expect("failed to run `rustc --version`") + .stdout, + ) + .unwrap(); + // extract "1 XX 0" from "rustc 1.XX.0-nightly ( )" + let vsplit: Vec<&str> = rustc_version + .split(' ') + .nth(1) + .unwrap() + .split('-') + .next() + .unwrap() + .split('.') + .collect(); + match vsplit.as_slice() { + [rustc_major, rustc_minor, _rustc_patch] => { + // clippy 0.1.XX should correspond to rustc 1.XX.0 + assert_eq!(clippy_major, 0); // this will probably stay the same for a long time + assert_eq!( + clippy_minor.to_string(), + *rustc_major, + "clippy minor version does not equal rustc major version" + ); + assert_eq!( + clippy_patch.to_string(), + *rustc_minor, + "clippy patch version does not equal rustc minor version" + ); + // do not check rustc_patch because when a stable-patch-release is made (like 1.50.2), + // we don't want our tests failing suddenly + }, + _ => { + panic!("Failed to parse rustc version: {:?}", vsplit); + }, + }; +} diff --git a/src/tools/clippy/triagebot.toml b/src/tools/clippy/triagebot.toml new file mode 100644 index 0000000000..b0a13f827d --- /dev/null +++ b/src/tools/clippy/triagebot.toml @@ -0,0 +1,7 @@ +[relabel] +allow-unauthenticated = [ + "A-*", "C-*", "E-*", "I-*", "L-*", "P-*", "S-*", "T-*", + "good-first-issue" +] + +[assign] diff --git a/src/tools/clippy/util/cov.sh b/src/tools/clippy/util/cov.sh new file mode 100755 index 0000000000..3f9a6b06f7 --- /dev/null +++ b/src/tools/clippy/util/cov.sh @@ -0,0 +1,37 @@ +#!/usr/bin/bash + +# This run `kcov` on Clippy. The coverage report will be at +# `./target/cov/index.html`. +# `compile-test` is special. `kcov` does not work directly on it so these files +# are compiled manually. + +tests=$(find tests/ -maxdepth 1 -name '*.rs' ! -name compile-test.rs -exec basename {} .rs \;) +tmpdir=$(mktemp -d) + +cargo test --no-run --verbose + +for t in $tests; do + kcov \ + --verify \ + --include-path="$(pwd)/src,$(pwd)/clippy_lints/src" \ + "$tmpdir/$t" \ + cargo test --test "$t" +done + +for t in ./tests/compile-fail/*.rs; do + kcov \ + --verify \ + --include-path="$(pwd)/src,$(pwd)/clippy_lints/src" \ + "$tmpdir/compile-fail-$(basename "$t")" \ + cargo run -- -L target/debug -L target/debug/deps -Z no-trans "$t" +done + +for t in ./tests/run-pass/*.rs; do + kcov \ + --verify \ + --include-path="$(pwd)/src,$(pwd)/clippy_lints/src" \ + "$tmpdir/run-pass-$(basename "$t")" \ + cargo run -- -L target/debug -L target/debug/deps -Z no-trans "$t" +done + +kcov --verify --merge target/cov "$tmpdir"/* diff --git a/src/tools/clippy/util/export.py b/src/tools/clippy/util/export.py new file mode 100755 index 0000000000..1248e6b6a2 --- /dev/null +++ b/src/tools/clippy/util/export.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +# Build the gh-pages + +from collections import OrderedDict +import re +import sys +import json + +from lintlib import parse_all, log + +lint_subheadline = re.compile(r'''^\*\*([\w\s]+?)[:?.!]?\*\*(.*)''') +rust_code_block = re.compile(r'''```rust.+?```''', flags=re.DOTALL) + +CONF_TEMPLATE = """\ +This lint has the following configuration variables: + +* `%s: %s`: %s (defaults to `%s`).""" + + +def parse_code_block(match): + lines = [] + + for line in match.group(0).split('\n'): + # fix syntax highlighting for headers like ```rust,ignore + if line.startswith('```rust'): + lines.append('```rust') + elif not line.startswith('# '): + lines.append(line) + + return '\n'.join(lines) + + +def parse_lint_def(lint): + lint_dict = {} + lint_dict['id'] = lint.name + lint_dict['group'] = lint.group + lint_dict['level'] = lint.level + lint_dict['docs'] = OrderedDict() + + last_section = None + + for line in lint.doc: + match = re.match(lint_subheadline, line) + if match: + last_section = match.groups()[0] + text = match.groups()[1] + else: + text = line + + if not last_section: + log.warning("Skipping comment line as it was not preceded by a heading") + log.debug("in lint `%s`, line `%s`", lint.name, line) + + if last_section not in lint_dict['docs']: + lint_dict['docs'][last_section] = "" + + lint_dict['docs'][last_section] += text + "\n" + + for section in lint_dict['docs']: + lint_dict['docs'][section] = re.sub(rust_code_block, parse_code_block, lint_dict['docs'][section].strip()) + + return lint_dict + + +def main(): + lintlist, configs = parse_all() + lints = {} + for lint in lintlist: + lints[lint.name] = parse_lint_def(lint) + if lint.name in configs: + lints[lint.name]['docs']['Configuration'] = \ + CONF_TEMPLATE % configs[lint.name] + + outfile = sys.argv[1] if len(sys.argv) > 1 else "util/gh-pages/lints.json" + with open(outfile, "w") as fp: + lints = list(lints.values()) + lints.sort(key=lambda x: x['id']) + json.dump(lints, fp, indent=2) + log.info("wrote JSON for great justice") + + +if __name__ == "__main__": + main() diff --git a/src/tools/clippy/util/fetch_prs_between.sh b/src/tools/clippy/util/fetch_prs_between.sh new file mode 100755 index 0000000000..6865abf971 --- /dev/null +++ b/src/tools/clippy/util/fetch_prs_between.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Fetches the merge commits between two git commits and prints the PR URL +# together with the full commit message +# +# If you want to use this to update the Clippy changelog, be sure to manually +# exclude the non-user facing changes like 'rustup' PRs, typo fixes, etc. + +first=$1 +last=$2 + +IFS=' +' +for pr in $(git log --oneline --grep "Merge #" --grep "Merge pull request" --grep "Auto merge of" --grep "Rollup merge of" "$first...$last" | sort -rn | uniq); do + id=$(echo "$pr" | rg -o '#[0-9]{3,5}' | cut -c 2-) + commit=$(echo "$pr" | cut -d' ' -f 1) + message=$(git --no-pager show --pretty=medium "$commit") + if [[ -n $(echo "$message" | rg "^[\s]{4}changelog: [nN]one\.*$") ]]; then + continue + fi + + echo "URL: https://github.com/rust-lang/rust-clippy/pull/$id" + echo "Markdown URL: [#$id](https://github.com/rust-lang/rust-clippy/pull/$id)" + echo "$message" + echo "---------------------------------------------------------" + echo +done diff --git a/src/tools/clippy/util/lintlib.py b/src/tools/clippy/util/lintlib.py new file mode 100644 index 0000000000..d0d9beb9b2 --- /dev/null +++ b/src/tools/clippy/util/lintlib.py @@ -0,0 +1,114 @@ +# Common utils for the several housekeeping scripts. + +import os +import re +import collections + +import logging as log +log.basicConfig(level=log.INFO, format='%(levelname)s: %(message)s') + +Lint = collections.namedtuple('Lint', 'name level doc sourcefile group') +Config = collections.namedtuple('Config', 'name ty doc default') + +lintname_re = re.compile(r'''pub\s+([A-Z_][A-Z_0-9]*)''') +group_re = re.compile(r'''\s*([a-z_][a-z_0-9]+)''') +conf_re = re.compile(r'''define_Conf! {\n([^}]*)\n}''', re.MULTILINE) +confvar_re = re.compile( + r'''/// Lint: ([\w,\s]+)\. (.*)\n\s*\([^,]+,\s+"([^"]+)":\s+([^,]+),\s+([^\.\)]+).*\),''', re.MULTILINE) +comment_re = re.compile(r'''\s*/// ?(.*)''') + +lint_levels = { + "correctness": 'Deny', + "style": 'Warn', + "complexity": 'Warn', + "perf": 'Warn', + "restriction": 'Allow', + "pedantic": 'Allow', + "nursery": 'Allow', + "cargo": 'Allow', +} + + +def parse_lints(lints, filepath): + comment = [] + clippy = False + deprecated = False + name = "" + + with open(filepath) as fp: + for line in fp: + if clippy or deprecated: + m = lintname_re.search(line) + if m: + name = m.group(1).lower() + line = next(fp) + + if deprecated: + level = "Deprecated" + group = "deprecated" + else: + while True: + g = group_re.search(line) + if g: + group = g.group(1).lower() + level = lint_levels.get(group, None) + break + line = next(fp) + + if level is None: + continue + + log.info("found %s with level %s in %s", + name, level, filepath) + lints.append(Lint(name, level, comment, filepath, group)) + comment = [] + + clippy = False + deprecated = False + name = "" + else: + m = comment_re.search(line) + if m: + comment.append(m.group(1)) + elif line.startswith("declare_clippy_lint!"): + clippy = True + deprecated = False + elif line.startswith("declare_deprecated_lint!"): + clippy = False + deprecated = True + elif line.startswith("declare_lint!"): + import sys + print( + "don't use `declare_lint!` in Clippy, " + "use `declare_clippy_lint!` instead" + ) + sys.exit(42) + + +def parse_configs(path): + configs = {} + with open(os.path.join(path, 'utils/conf.rs')) as fp: + contents = fp.read() + + match = re.search(conf_re, contents) + confvars = re.findall(confvar_re, match.group(1)) + + for (lints, doc, name, ty, default) in confvars: + for lint in lints.split(','): + configs[lint.strip().lower()] = Config(name.replace("_", "-"), ty, doc, default) + return configs + + +def parse_all(path="clippy_lints/src"): + lints = [] + for root, dirs, files in os.walk(path): + for fn in files: + if fn.endswith('.rs'): + parse_lints(lints, os.path.join(root, fn)) + + log.info("got %s lints", len(lints)) + + configs = parse_configs(path) + log.info("got %d configs", len(configs)) + + return lints, configs diff --git a/src/tools/clippy/util/versions.py b/src/tools/clippy/util/versions.py new file mode 100755 index 0000000000..5cdc7313f5 --- /dev/null +++ b/src/tools/clippy/util/versions.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +import json +import os +import sys + +from lintlib import log + + +def key(v): + if v == 'master': + return float('inf') + if v == 'stable': + return sys.maxsize + if v == 'beta': + return sys.maxsize - 1 + + v = v.replace('v', '').replace('rust-', '') + + s = 0 + for i, val in enumerate(v.split('.')[::-1]): + s += int(val) * 100**i + + return s + + +def main(): + if len(sys.argv) < 2: + print("Error: specify output directory") + return + + outdir = sys.argv[1] + versions = [ + dir for dir in os.listdir(outdir) if not dir.startswith(".") and os.path.isdir(os.path.join(outdir, dir)) + ] + versions.sort(key=key) + + with open(os.path.join(outdir, "versions.json"), "w") as fp: + json.dump(versions, fp, indent=2) + log.info("wrote JSON for great justice") + + +if __name__ == "__main__": + main() diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 7aa3d4ab09..6aebf4b604 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -229,7 +229,15 @@ pub fn run(config: Config, testpaths: &TestPaths, revision: Option<&str>) { print!("\n\n"); } debug!("running {:?}", testpaths.file.display()); - let props = TestProps::from_file(&testpaths.file, revision, &config); + let mut props = TestProps::from_file(&testpaths.file, revision, &config); + + // Currently, incremental is soft disabled unless this environment + // variable is set. A bunch of our tests assume it's enabled, though - so + // just enable it for our tests. + // + // This is deemed preferable to ignoring those tests; we still want to test + // incremental somewhat, as users can opt in to it. + props.rustc_env.push((String::from("RUSTC_FORCE_INCREMENTAL"), String::from("1"))); let cx = TestCx { config: &config, props: &props, testpaths, revision }; create_dir_all(&cx.output_base_dir()).unwrap(); @@ -240,7 +248,11 @@ pub fn run(config: Config, testpaths: &TestPaths, revision: Option<&str>) { assert!(!props.revisions.is_empty(), "Incremental tests require revisions."); cx.init_incremental_test(); for revision in &props.revisions { - let revision_props = TestProps::from_file(&testpaths.file, Some(revision), &config); + let mut revision_props = TestProps::from_file(&testpaths.file, Some(revision), &config); + // See above - need to enable it explicitly for now. + revision_props + .rustc_env + .push((String::from("RUSTC_FORCE_INCREMENTAL"), String::from("1"))); let rev_cx = TestCx { config: &config, props: &revision_props, diff --git a/src/tools/rustfmt/.editorconfig b/src/tools/rustfmt/.editorconfig new file mode 100644 index 0000000000..5bb92df3e0 --- /dev/null +++ b/src/tools/rustfmt/.editorconfig @@ -0,0 +1,26 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.rs] +indent_size = 4 + +[tests/**/*.rs] +charset = utf-8 +end_of_line = unset +indent_size = unset +indent_style = unset +trim_trailing_whitespace = unset +insert_final_newline = unset + +[appveyor.yml] +end_of_line = unset diff --git a/src/tools/rustfmt/.github/workflows/integration.yml b/src/tools/rustfmt/.github/workflows/integration.yml new file mode 100644 index 0000000000..2e832b6b62 --- /dev/null +++ b/src/tools/rustfmt/.github/workflows/integration.yml @@ -0,0 +1,88 @@ +name: integration +on: + push: + branches: + - master + pull_request: + +jobs: + integration-tests: + runs-on: ubuntu-latest + name: ${{ matrix.integration }} + strategy: + # https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits + # There's a limit of 60 concurrent jobs across all repos in the rust-lang organization. + # In order to prevent overusing too much of that 60 limit, we throttle the + # number of rustfmt jobs that will run concurrently. + max-parallel: 4 + fail-fast: false + matrix: + integration: [ + bitflags, + error-chain, + log, + mdbook, + packed_simd, + rust-semverver, + tempdir, + futures-rs, + rust-clippy, + failure, + ] + include: + # Allowed Failures + # Actions doesn't yet support explicitly marking matrix legs as allowed failures + # https://github.community/t5/GitHub-Actions/continue-on-error-allow-failure-UI-indication/td-p/37033 + # https://github.community/t5/GitHub-Actions/Why-a-matrix-step-will-be-canceled-if-another-one-failed/td-p/30920 + # Instead, leverage `continue-on-error` + # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error + # + # Failing due to breaking changes in rustfmt 2.0 where empty + # match blocks have trailing commas removed + # https://github.com/rust-lang/rustfmt/pull/4226 + - integration: chalk + allow-failure: true + - integration: crater + allow-failure: true + - integration: glob + allow-failure: true + - integration: stdsimd + allow-failure: true + # Using old rustfmt configuration option + - integration: rand + allow-failure: true + # Keep this as an allowed failure as it's fragile to breaking changes of rustc. + - integration: rust-clippy + allow-failure: true + # Using old rustfmt configuration option + - integration: packed_simd + allow-failure: true + # calebcartwright (2019-12-24) + # Keeping this as an allowed failure since it was flagged as such in the TravisCI config, even though + # it appears to have been passing for quite some time. + # Original comment was: temporal build failure due to breaking changes in the nightly compiler + - integration: rust-semverver + allow-failure: true + # Can be moved back to include section after https://github.com/rust-lang-nursery/failure/pull/298 is merged + - integration: failure + allow-failure: true + + steps: + - name: checkout + uses: actions/checkout@v2 + + # Run build + - name: setup + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-x86_64-unknown-linux-gnu + target: x86_64-unknown-linux-gnu + override: true + profile: minimal + default: true + - name: run integration tests + env: + INTEGRATION: ${{ matrix.integration }} + TARGET: x86_64-unknown-linux-gnu + run: ./ci/integration.sh + continue-on-error: ${{ matrix.allow-failure == true }} diff --git a/src/tools/rustfmt/.github/workflows/linux.yml b/src/tools/rustfmt/.github/workflows/linux.yml new file mode 100644 index 0000000000..acbf666955 --- /dev/null +++ b/src/tools/rustfmt/.github/workflows/linux.yml @@ -0,0 +1,54 @@ +name: linux +on: + push: + branches: + - master + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + name: (${{ matrix.target }}, ${{ matrix.channel }}, ${{ matrix.cfg-release-channel }}) + strategy: + # https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits + # There's a limit of 60 concurrent jobs across all repos in the rust-lang organization. + # In order to prevent overusing too much of that 60 limit, we throttle the + # number of rustfmt jobs that will run concurrently. + max-parallel: 1 + fail-fast: false + matrix: + target: [ + x86_64-unknown-linux-gnu, + ] + channel: [ nightly ] + cfg-release-channel: [ + beta, + nightly, + ] + + env: + CFG_RELEASE_CHANNEL: ${{ matrix.cfg-release-channel }} + CFG_RELEASE: ${{ matrix.cfg-release-channel }} + + steps: + - name: checkout + uses: actions/checkout@v2 + + # Run build + - name: setup + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.channel }}-${{ matrix.target }} + target: ${{ matrix.target }} + override: true + profile: minimal + default: true + + - name: build + run: | + rustc -Vv + cargo -V + cargo build + + - name: test + run: cargo test diff --git a/src/tools/rustfmt/.github/workflows/mac.yml b/src/tools/rustfmt/.github/workflows/mac.yml new file mode 100644 index 0000000000..afbc8a8d74 --- /dev/null +++ b/src/tools/rustfmt/.github/workflows/mac.yml @@ -0,0 +1,47 @@ +name: mac +on: + push: + branches: + - master + pull_request: + +jobs: + test: + # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners#supported-runners-and-hardware-resources + # macOS Catalina 10.15 + runs-on: macos-latest + name: (${{ matrix.target }}, ${{ matrix.channel }}) + strategy: + fail-fast: false + matrix: + target: [ + x86_64-apple-darwin, + ] + channel: [ nightly ] + + env: + CFG_RELEASE_CHANNEL: nightly + CFG_RELEASE: nightly + + steps: + - name: checkout + uses: actions/checkout@v2 + + # Run build + - name: setup + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.channel }}-${{ matrix.target }} + target: ${{ matrix.target }} + override: true + profile: minimal + default: true + + - name: build + run: | + rustc -Vv + cargo -V + cargo build + + - name: test + run: cargo test diff --git a/src/tools/rustfmt/.github/workflows/upload-assets.yml b/src/tools/rustfmt/.github/workflows/upload-assets.yml new file mode 100644 index 0000000000..9a5fd0dd1d --- /dev/null +++ b/src/tools/rustfmt/.github/workflows/upload-assets.yml @@ -0,0 +1,80 @@ +name: upload + +on: + release: + types: [created] + +jobs: + build-release: + name: build-release + strategy: + matrix: + build: [linux-x86_64, macos-x86_64, windows-x86_64-gnu, windows-x86_64-msvc] + include: + - build: linux-x86_64 + os: ubuntu-latest + rust: nightly + - build: macos-x86_64 + os: macos-latest + rust: nightly + - build: windows-x86_64-gnu + os: windows-latest + rust: nightly-x86_64-gnu + - build: windows-x86_64-msvc + os: windows-latest + rust: nightly-x86_64-msvc + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + + - name: Add mingw64 to path for x86_64-gnu + run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH + if: matrix.rust == 'nightly-x86_64-gnu' + shell: bash + + - name: Install cargo-make + uses: actions-rs/cargo@v1 + with: + command: install + args: --force cargo-make + + - name: Build release binaries + uses: actions-rs/cargo@v1 + with: + command: make + args: release + + - name: Build archive + shell: bash + run: | + staging="rustfmt_${{ matrix.build }}_${{ github.event.release.tag_name }}" + mkdir -p "$staging" + + cp {README.md,Configurations.md,CHANGELOG.md,LICENSE-MIT,LICENSE-APACHE} "$staging/" + + if [ "${{ matrix.os }}" = "windows-latest" ]; then + cp target/release/{rustfmt.exe,cargo-fmt.exe,rustfmt-format-diff.exe,git-rustfmt.exe} "$staging/" + 7z a "$staging.zip" "$staging" + echo "ASSET=$staging.zip" >> $GITHUB_ENV + else + cp target/release/{rustfmt,cargo-fmt,rustfmt-format-diff,git-rustfmt} "$staging/" + tar czf "$staging.tar.gz" "$staging" + echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV + fi + + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ env.ASSET }} + asset_name: ${{ env.ASSET }} + asset_content_type: application/octet-stream diff --git a/src/tools/rustfmt/.github/workflows/windows.yml b/src/tools/rustfmt/.github/workflows/windows.yml new file mode 100644 index 0000000000..156848c165 --- /dev/null +++ b/src/tools/rustfmt/.github/workflows/windows.yml @@ -0,0 +1,76 @@ +name: windows +on: + push: + branches: + - master + pull_request: + +jobs: + test: + runs-on: windows-latest + name: (${{ matrix.target }}, ${{ matrix.channel }}) + strategy: + # https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits + # There's a limit of 60 concurrent jobs across all repos in the rust-lang organization. + # In order to prevent overusing too much of that 60 limit, we throttle the + # number of rustfmt jobs that will run concurrently. + max-parallel: 2 + fail-fast: false + matrix: + target: [ + i686-pc-windows-gnu, + i686-pc-windows-msvc, + x86_64-pc-windows-gnu, + x86_64-pc-windows-msvc, + ] + channel: [ nightly ] + include: + - channel: nightly + target: i686-pc-windows-gnu + + env: + CFG_RELEASE_CHANNEL: nightly + CFG_RELEASE: nightly + + steps: + # The Windows runners have autocrlf enabled by default + # which causes failures for some of rustfmt's line-ending sensitive tests + - name: disable git eol translation + run: git config --global core.autocrlf false + - name: checkout + uses: actions/checkout@v2 + + # Run build + - name: setup + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.channel }}-${{ matrix.target }} + target: ${{ matrix.target }} + override: true + profile: minimal + default: true + + - name: Add mingw32 to path for i686-gnu + run: | + echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH + if: matrix.target == 'i686-pc-windows-gnu' && matrix.channel == 'nightly' + shell: bash + + - name: Add mingw64 to path for x86_64-gnu + run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH + if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly' + shell: bash + + - name: cargo-make + run: cargo install --force cargo-make + + - name: build + run: | + rustc -Vv + cargo -V + cargo build + shell: cmd + + - name: test + run: cargo test + shell: cmd diff --git a/src/tools/rustfmt/.travis.yml b/src/tools/rustfmt/.travis.yml new file mode 100644 index 0000000000..d699bd842e --- /dev/null +++ b/src/tools/rustfmt/.travis.yml @@ -0,0 +1,77 @@ +sudo: false +language: rust +rust: nightly +os: linux +cache: + directories: + - $HOME/.cargo + +addons: + apt: + packages: + - libcurl4-openssl-dev + - libelf-dev + - libdw-dev + +matrix: + include: + - env: DEPLOY=LINUX + - env: CFG_RELEASE_CHANNEL=beta + - os: osx + - env: INTEGRATION=bitflags + - env: INTEGRATION=chalk + - env: INTEGRATION=crater + - env: INTEGRATION=error-chain + - env: INTEGRATION=glob + - env: INTEGRATION=log + - env: INTEGRATION=mdbook + - env: INTEGRATION=packed_simd + - env: INTEGRATION=rust-semverver + - env: INTEGRATION=stdsimd TARGET=x86_64-unknown-linux-gnu + - env: INTEGRATION=tempdir + - env: INTEGRATION=futures-rs + allow_failures: + # Using old configuration option + - env: INTEGRATION=rand + # Doesn't build - keep this in allow_failures as it's fragile to breaking changes of rustc. + - env: INTEGRATION=rust-clippy + # Doesn't build - seems to be because of an option + - env: INTEGRATION=packed_simd + # Doesn't build - a temporal build failure due to breaking changes in the nightly compilre + - env: INTEGRATION=rust-semverver + # can be moved back to include section after https://github.com/rust-lang-nursery/failure/pull/298 is merged + - env: INTEGRATION=failure + # `cargo test` doesn't finish - disabling for now. + # - env: INTEGRATION=cargo + +script: + - | + if [ -z ${INTEGRATION} ]; then + export CFG_RELEASE_CHANNEL=nightly + export CFG_RELEASE=nightly + cargo build + cargo test + cargo test -- --ignored + else + ./ci/integration.sh + fi + +after_success: +- if [ -z ${INTEGRATION} ]; then travis-cargo coveralls --no-sudo; fi + +before_deploy: + # TODO: cross build + - cargo build --release --target=x86_64-unknown-linux-gnu + - tar czf rustfmt-x86_64-unknown-linux-gnu.tar.gz Contributing.md Design.md README.md -C target/x86_64-unknown-linux-gnu/release/rustfmt rustfmt + +deploy: + provider: releases + api_key: + secure: "your own encrypted key" + file: + - rustfmt-x86_64-unknown-linux-gnu.tar.gz + on: + repo: nrc/rustfmt + tags: true + condition: "$DEPLOY = LINUX" + skip_cleanup: true diff --git a/src/tools/rustfmt/CHANGELOG.md b/src/tools/rustfmt/CHANGELOG.md new file mode 100644 index 0000000000..f52b45f7e5 --- /dev/null +++ b/src/tools/rustfmt/CHANGELOG.md @@ -0,0 +1,1152 @@ +# Changelog + +## [Unreleased] + +## [1.4.36] 2021-02-07 + +### Changed + +- `rustc-ap-*` crates updated to v705.0.0 + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - *pending* +- **GitHub Release Binaries** - [Release v1.4.36](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.36) +- **Build from source** - [Tag v1.4.36](https://github.com/rust-lang/rustfmt/tree/v1.4.36), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.35] 2021-02-03 + +### Changed + +- `rustc-ap-*` crates updated to v702.0.0 + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - *n/a (superseded by [v1.4.36](#1436-2021-02-07)) +- **GitHub Release Binaries** - [Release v1.4.35](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.35) +- **Build from source** - [Tag v1.4.35](https://github.com/rust-lang/rustfmt/tree/v1.4.35), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.34] 2021-01-28 + +### Fixed +- Don't insert trailing comma on (base-less) rest in struct literals within macros ([#4675](https://github.com/rust-lang/rustfmt/issues/4675)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - Starting in `2021-01-31` +- **GitHub Release Binaries** - [Release v1.4.34](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.34) +- **Build from source** - [Tag v1.4.34](https://github.com/rust-lang/rustfmt/tree/v1.4.34), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.33] 2021-01-27 + +### Changed +- `merge_imports` configuration has been deprecated in favor of the new `imports_granularity` option. Any existing usage of `merge_imports` will be automatically mapped to the corresponding value on `imports_granularity` with a warning message printed to encourage users to update their config files. + +### Added +- New `imports_granularity` option has been added which succeeds `merge_imports`. This new option supports several additional variants which allow users to merge imports at different levels (crate or module), and even flatten imports to have a single use statement per item. ([PR #4634](https://github.com/rust-lang/rustfmt/pull/4634), [PR #4639](https://github.com/rust-lang/rustfmt/pull/4639)) + +See the section on the configuration site for more information +https://rust-lang.github.io/rustfmt/?version=v1.4.33&search=#imports_granularity + +### Fixed +- Fix erroneous removal of `const` keyword on const trait impl ([#4084](https://github.com/rust-lang/rustfmt/issues/4084)) +- Fix incorrect span usage wit const generics in supertraits ([#4204](https://github.com/rust-lang/rustfmt/issues/4204)) +- Use correct span for const generic params ([#4263](https://github.com/rust-lang/rustfmt/issues/4263)) +- Correct span on const generics to include type bounds ([#4310](https://github.com/rust-lang/rustfmt/issues/4310)) +- Idempotence issue on blocks containing only empty statements ([#4627](https://github.com/rust-lang/rustfmt/issues/4627) and [#3868](https://github.com/rust-lang/rustfmt/issues/3868)) +- Fix issue with semicolon placement on required functions that have a trailing comment that ends in a line-style comment before the semicolon ([#4646](https://github.com/rust-lang/rustfmt/issues/4646)) +- Avoid shared interned cfg_if symbol since rustfmt can re-initialize the rustc_ast globals on multiple inputs ([#4656](https://github.com/rust-lang/rustfmt/issues/4656)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - n/a (superseded by [v1.4.34](#1434-2021-01-28)) +- **GitHub Release Binaries** - [Release v1.4.33](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.33) +- **Build from source** - [Tag v1.4.33](https://github.com/rust-lang/rustfmt/tree/v1.4.33), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.32] 2021-01-16 + +### Fixed +- Indentation now correct on first bound in cases where the generic bounds are multiline formatted and the first bound itself is multiline formatted ([#4636](https://github.com/rust-lang/rustfmt/issues/4636)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - Starting in `2021-01-18` +- **GitHub Release Binaries** - [Release v1.4.32](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.32) +- **Build from source** - [Tag v1.4.32](https://github.com/rust-lang/rustfmt/tree/v1.4.32), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.31] 2021-01-09 + +### Changed + +- `rustc-ap-*` crates updated to v697.0.0 + +### Added +- Support for 2021 Edition [#4618](https://github.com/rust-lang/rustfmt/pull/4618)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - Starting in `2021-01-16` +- **GitHub Release Binaries** - [Release v1.4.31](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.31) +- **Build from source** - [Tag v1.4.31](https://github.com/rust-lang/rustfmt/tree/v1.4.31), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.30] 2020-12-20 + +### Fixed +- Last character in derive no longer erroneously stripped when `indent_style` is overridden to `Visual`. ([#4584](https://github.com/rust-lang/rustfmt/issues/4584)) +- Brace wrapping of closure bodies maintained in cases where the closure has an explicit return type and the body consists of a single expression statement. ([#4577](https://github.com/rust-lang/rustfmt/issues/4577)) +- No more panics on invalid code with `err` and `typeof` types ([#4357](https://github.com/rust-lang/rustfmt/issues/4357), [#4586](https://github.com/rust-lang/rustfmt/issues/4586)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - Starting in `2020-12-25` +- **GitHub Release Binaries** - [Release v1.4.30](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.30) +- **Build from source** - [Tag v1.4.30](https://github.com/rust-lang/rustfmt/tree/v1.4.30), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.29] 2020-12-04 + +### Fixed +- Negative polarity on non-trait impl now preserved. ([#4566](https://github.com/rust-lang/rustfmt/issues/4566)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - Starting in `2020-12-07` +- **GitHub Release Binaries** - [Release v1.4.29](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.29) +- **Build from source** - [Tag v1.4.29](https://github.com/rust-lang/rustfmt/tree/v1.4.29), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.28] 2020-11-29 + +### Changed + +- `rustc-ap-*` crates updated to v691.0.0 +- In the event of an invalid inner attribute on a `cfg_if` condition, rustfmt will now attempt to continue and format the imported modules. Previously rustfmt would emit the parser error about an inner attribute being invalid in this position, but for rustfmt's purposes the invalid attribute doesn't prevent nor impact module formatting. + +### Added + +- [`group_imports`][group-imports-config-docs] - a new configuration option that allows users to control the strategy used for grouping imports ([#4107](https://github.com/rust-lang/rustfmt/issues/4107)) + +[group-imports-config-docs]: https://github.com/rust-lang/rustfmt/blob/v1.4.28/Configurations.md#group_imports + +### Fixed +- Formatting of malformed derived attributes is no longer butchered. ([#3898](https://github.com/rust-lang/rustfmt/issues/3898), [#4029](https://github.com/rust-lang/rustfmt/issues/4029), [#4115](https://github.com/rust-lang/rustfmt/issues/4115), [#4545](https://github.com/rust-lang/rustfmt/issues/4545)) +- Correct indentation used in macro branches when `hard_tabs` is enabled. ([#4152](https://github.com/rust-lang/rustfmt/issues/4152)) +- Comments between the visibility modifier and item name are no longer dropped. ([#2781](https://github.com/rust-lang/rustfmt/issues/2781)) +- Comments preceding the assignment operator in type aliases are no longer dropped. ([#4244](https://github.com/rust-lang/rustfmt/issues/4244)) +- Comments between {`&` operator, lifetime, `mut` kw, type} are no longer dropped. ([#4245](https://github.com/rust-lang/rustfmt/issues/4245)) +- Comments between type bounds are no longer dropped. ([#4243](https://github.com/rust-lang/rustfmt/issues/4243)) +- Function headers are no longer dropped on foreign function items. ([#4288](https://github.com/rust-lang/rustfmt/issues/4288)) +- Foreign function blocks are no longer dropped. ([#4313](https://github.com/rust-lang/rustfmt/issues/4313)) +- `where_single_line` is no longer incorrectly applied to multiline function signatures that have no `where` clause. ([#4547](https://github.com/rust-lang/rustfmt/issues/4547)) +- `matches!` expressions with multiple patterns and a destructure pattern are now able to be formatted. ([#4512](https://github.com/rust-lang/rustfmt/issues/4512)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - n/a (superseded by [v1.4.29](#1429-2020-12-04)) +- **GitHub Release Binaries** - [Release v1.4.28](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.28) +- **Build from source** - [Tag v1.4.28](https://github.com/rust-lang/rustfmt/tree/v1.4.28), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.27] 2020-11-16 + +### Fixed + +- Leading comments in an extern block are no longer dropped (a bug that exists in v1.4.26). ([#4528](https://github.com/rust-lang/rustfmt/issues/4528)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - Starting in `2020-11-18` +- **GitHub Release Binaries** - [Release v1.4.27](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.27) +- **Build from source** - [Tag v1.4.27](https://github.com/rust-lang/rustfmt/tree/v1.4.27), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.26] 2020-11-14 + +### Changed + +- Original comment indentation for trailing comments within an `if` is now taken into account when determining the indentation level to use for the trailing comment in formatted code. This does not modify any existing code formatted with rustfmt; it simply gives the programmer discretion to specify whether the comment is associated to the `else` block, or if the trailing comment is just a member of the `if` block. ([#1575](https://github.com/rust-lang/rustfmt/issues/1575), [#4120](https://github.com/rust-lang/rustfmt/issues/4120), [#4506](https://github.com/rust-lang/rustfmt/issues/4506)) + +In this example the `// else comment` refers to the `else`: +```rust +// if comment +if cond { + "if" +// else comment +} else { + "else" +} +``` + +Whereas in this case the `// continue` comments are members of their respective blocks and do not refer to the `else` below. +```rust +if toks.eat_token(Token::Word("modify"))? && toks.eat_token(Token::Word("labels"))? { + if toks.eat_token(Token::Colon)? { + // ate the token + } else if toks.eat_token(Token::Word("to"))? { + // optionally eat the colon after to, e.g.: + // @rustbot modify labels to: -S-waiting-on-author, +S-waiting-on-review + toks.eat_token(Token::Colon)?; + } else { + // It's okay if there's no to or colon, we can just eat labels + // afterwards. + } + 1 + 2; + // continue +} else if toks.eat_token(Token::Word("label"))? { + // continue +} else { + return Ok(None); +} +``` + +### Fixed +- Formatting of empty blocks with attributes which only contained comments is no longer butchered.([#4475](https://github.com/rust-lang/rustfmt/issues/4475), [#4467](https://github.com/rust-lang/rustfmt/issues/4467), [#4452](https://github.com/rust-lang/rustfmt/issues/4452#issuecomment-705886282), [#4522](https://github.com/rust-lang/rustfmt/issues/4522)) +- Indentation of trailing comments in non-empty extern blocks is now correct. ([#4120](https://github.com/rust-lang/rustfmt/issues/4120#issuecomment-696491872)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - Starting in `2020-11-16` +- **GitHub Release Binaries** - [Release v1.4.26](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.26) +- **Build from source** - [Tag v1.4.26](https://github.com/rust-lang/rustfmt/tree/v1.4.26), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.25] 2020-11-10 + +### Changed + +- Semicolons are no longer automatically inserted on trailing expressions in macro definition arms ([#4507](https://github.com/rust-lang/rustfmt/pull/4507)). This gives the programmer control and discretion over whether there should be semicolons in these scenarios so that potential expansion issues can be avoided. + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - Starting in `2020-11-14` +- **GitHub Release Binaries** - [Release v1.4.25](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.25) +- **Build from source** - [Tag v1.4.25](https://github.com/rust-lang/rustfmt/tree/v1.4.25), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.24] 2020-11-05 + +### Changed + +- Block wrapped match arm bodies containing a single macro call expression are no longer flattened ([#4496](https://github.com/rust-lang/rustfmt/pull/4496)). This allows programmer discretion so that the block wrapping can be preserved in cases where needed to prevent issues in expansion, such as with trailing semicolons, and aligns with updated [Style Guide guidance](https://github.com/rust-dev-tools/fmt-rfcs/blob/master/guide/expressions.md#macro-call-expressions) for such scenarios. + +### Fixed +- Remove useless `deprecated` attribute on a trait impl block in the rustfmt lib, as these now trigger errors ([rust-lang/rust/#78626](https://github.com/rust-lang/rust/pull/78626)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - Starting in `2020-11-09` +- **GitHub Release Binaries** - [Release v1.4.24](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.24) +- **Build from source** - [Tag v1.4.24](https://github.com/rust-lang/rustfmt/tree/v1.4.24), see instructions for how to [install rustfmt from source][install-from-source] + +## [1.4.23] 2020-10-30 + +### Changed + +- Update `rustc-ap-*` crates to v686.0.0 + +### Added +- Initial support for formatting new ConstBlock syntax ([#4478](https://github.com/rust-lang/rustfmt/pull/4478)) + +### Fixed +- Handling of unclosed delimiter-only parsing errors in input files ([#4466](https://github.com/rust-lang/rustfmt/issues/4466)) +- Misc. minor parser bugs ([#4418](https://github.com/rust-lang/rustfmt/issues/4418) and [#4431](https://github.com/rust-lang/rustfmt/issues/4431)) +- Panic on nested tuple access ([#4355](https://github.com/rust-lang/rustfmt/issues/4355)) +- Unable to disable license template path via cli override ([#4487](https://github.com/rust-lang/rustfmt/issues/4487)) +- Preserve comments in empty statements [#4018](https://github.com/rust-lang/rustfmt/issues/4018)) +- Indentation on skipped code [#4398](https://github.com/rust-lang/rustfmt/issues/4398)) + +### Install/Download Options +- **crates.io package** - *pending* +- **rustup (nightly)** - n/a (superseded by [v1.4.24](#1424-2020-11-05)) +- **GitHub Release Binaries** - [Release v1.4.23](https://github.com/rust-lang/rustfmt/releases/tag/v1.4.23) +- **Build from source** - [Tag v1.4.23](https://github.com/rust-lang/rustfmt/tree/v1.4.23), see instructions for how to [install rustfmt from source][install-from-source] + + + +## [1.4.22] 2020-10-04 + +### Changed + +- Update `rustc-ap-*` crates to v679.0.0 +- Add config option to allow control of leading match arm pipes +- Support `RUSTFMT` environment variable in `cargo fmt` to run specified `rustfmt` instance + +### Fixed + +- Fix preservation of type aliases within extern blocks + + +## [1.4.9] 2019-10-07 + +### Changed + +- Update `rustc-ap-*` crates to 606.0.0. + +### Fixed + +- Fix aligning comments of different group +- Fix flattening imports with a single `self`. +- Fix removing attributes on function parameters. +- Fix removing `impl` keyword from opaque type. + +## [1.4.8] 2019-09-08 + +### Changed + +- Update `rustc-ap-*` crates to 583.0.0. + +## [1.4.7] 2019-09-06 + +### Added + +- Add `--config` command line option. + +### Changed + +- Update `rustc-ap-*` crates to 581.0.0. +- rustfmt now do not warn against trailing whitespaces inside macro calls. + +### Fixed + +- Fix `merge_imports` generating invalid code. +- Fix removing discriminant values on enum variants. +- Fix modules defined inside `cfg_if!` not being formatted. +- Fix minor formatting issues. + +## [1.4.6] 2019-08-28 + +### Added + +- Add `--message-format` command line option to `cargo-fmt`. +- Add `-l,--files-with-diff` command line option to `rustfmt`. +- Add `json` emit mode. + +### Fixed + +- Fix removing attributes on struct pattern's fields. +- Fix non-idempotent formatting of match arm. +- Fix `merge_imports` generating invalid code. +- Fix imports with `#![macro_use]` getting reordered with `reorder_imports`. +- Fix calculation of line numbers in checkstyle output. +- Fix poor formatting of complex fn type. + +## [1.4.5] 2019-08-13 + +### Fixed + +- Fix generating invalid code when formatting an impl block with const generics inside a where clause. +- Fix adding a trailing space after a `dyn` keyword which is used as a macro argument by itself. + +## [1.4.4] 2019-08-06 + +### Fixed + +- Fix `cargo fmt` incorrectly formatting crates that is not part of the workspace or the path dependencies. +- Fix removing a trailing comma from a tuple pattern. + +## [1.4.3] 2019-08-02 + +### Changed + +- Update `rustc-ap-*` crates to 546.0.0. + +### Fixed + +- Fix an underscore pattern getting removed. + +## [1.4.2] 2019-07-31 + +### Changed + +- Explicitly require the version of `rustfmt-config_proc_macro` to be 0.1.2 or later. + +## [1.4.1] 2019-07-30 + +### Changed + +- Update `rustc-ap-*` crates to 542.0.0. + +## [1.4.0] 2019-07-29 + +### Added + +- Add new attribute `rustfmt::skip::attributes` to prevent rustfmt +from formatting an attribute #3665 + +### Changed + +- Update `rustc-ap-*` crates to 541.0.0. +- Remove multiple semicolons. + +## [1.3.3] 2019-07-15 + +### Added + +- Add `--manifest-path` support to `cargo fmt` (#3683). + +### Fixed + +- Fix `cargo fmt -- --help` printing nothing (#3620). +- Fix inserting an extra comma (#3677). +- Fix incorrect handling of CRLF with `file-lines` (#3684). +- Fix `print-config=minimal` option (#3687). + +## [1.3.2] 2019-07-06 + +### Fixed + +- Fix rustfmt crashing when `await!` macro call is used in a method chain. +- Fix rustfmt not recognizing a package whose name differs from its directory's name. + +## [1.3.1] 2019-06-30 + +### Added + +- Implement the `Display` trait on the types of `Config`. + +### Changed + +- `ignore` configuration option now only supports paths separated by `/`. Windows-style paths are not supported. +- Running `cargo fmt` in a sub-directory of a project is now supported. + +### Fixed + +- Fix bugs that may cause rustfmt to crash. + +## [1.3.0] 2019-06-09 + +### Added + +- Format modules defined inside `cfg_if` macro calls #3600 + +### Changed + +- Change option `format_doc_comment` to `format_code_in_doc_comment`. +- `use_small_heuristics` changed to be an enum and stabilised. Configuration + options are now ready for 1.0. +- Stabilise `fn_args_density` configuration option and rename it to `fn_args_layout` #3581 +- Update `rustc-ap-*` crates to 486.0.0 +- Ignore sub-modules when skip-children is used #3607 +- Removed bitrig support #3608 + +### Fixed + +- `wrap_comments` should not imply `format_doc_comments` #3535 +- Incorrect handling of const generics #3555 +- Add the handling for `vec!` with paren inside macro #3576 +- Format trait aliases with where clauses #3586 +- Catch panics from the parser while rewriting macro calls #3589 +- Fix erasing inner attributes in struct #3593 +- Inline the attribute with its item even with the `macro_use` attribute or when `reorder_imports` is disabled #3598 +- Fix the bug add unwanted code to impl #3602 + +## [1.2.2] 2019-04-24 + +### Fixed + +- Fix processing of `ignore` paths #3522 +- Attempt to format attributes if only they exist #3523 + +## [1.2.1] 2019-04-18 + +### Added + +- Add `--print-config current` CLI option b473e65 +- Create GitHub [page](https://rust-lang.github.io/rustfmt/) for Configuration.md #3485 + +### Fixed + +- Keep comment appearing between parameter's name and its type #3491 +- Do not delete semicolon after macro call with square brackets #3500 +- Fix `--version` CLI option #3506 +- Fix duplication of attributes on a match arm's body #3510 +- Avoid overflowing item with attributes #3511 + +## [1.2.0] 2019-03-27 + +### Added + +- Add new attribute `rustfmt::skip::macros` to prevent rustfmt from formatting a macro #3454 + +### Changed + +- Discard error report in silent_emitter #3466 + +### Fixed + +- Fix bad performance on deeply nested binary expressions #3467 +- Use BTreeMap to guarantee consistent ordering b4d4b57 + +## [1.1.1] 2019-03-21 + +### Fixed + +- Avoid panic on macro inside deeply nested block c9479de +- Fix line numbering in missed spans and handle file_lines in edge cases cdd08da +- Fix formatting of async blocks 1fa06ec +- Avoid duplication on the presence of spaces between macro name and `!` #3464 + +## [1.1.0] 2019-03-17 + +### Added + +- Add `inline_attribute_width` configuration option to write an item and its attribute on the same line if their combined width is below a threshold #3409 +- Support `const` generics f0c861b +- Support path clarity module #3448 + +### Changed + +- Align loop and while formatting 7d9a2ef +- Support `EmitMode::ModifiedLines` with stdin input #3424 +- Update `rustc-ap-*` crates to 407.0.0 +- Remove trailing whitespaces in missing spans 2d5bc69 + +### Fixed + +- Do not remove comment in the case of no arg 8e3ef3e +- Fix `Ident of macro+ident gets duplicated` error 40ff078 +- Format the if expression at the end of the block in a single line 5f3dfe6 + +## [1.0.3] 2019-02-14 + +### Added + +- Point unstable options to tracking issues 412dcc7 + +### Changed + +- Update `rustc-ap-*` crates to 373.0.0 + +## [1.0.2] 2019-02-12 + +### Added + +- Add a [section](https://github.com/rust-lang/rustfmt/blob/ae331be/Contributing.md#version-gate-formatting-changes) to the Contributing.md file about version-gating formatting changes 36e2cb0 +- Allow specifying package with `-p` CLI option a8d2591 +- Support `rustfmt::skip` on imports #3289 +- Support global `rustfmt.toml` to be written in user config directory #3280 +- Format visibility on trait alias 96a3df3 + +### Changed + +- Do not modify original source code inside macro call #3260 +- Recognize strings inside comments in order to avoid indenting them baa62c6 +- Use Unicode-standard char width to wrap comments or strings a01990c +- Change new line point in the case of no args #3294 +- Use the same formatting rule between functions and macros #3298 +- Update rustc-ap-rustc_target to 366.0.0, rustc-ap-syntax to 366.0.0, and rustc-ap-syntax_pos to 366.0.0 + +### Fixed + +- rewrite_comment: fix block fallback when failing to rewrite an itemized block ab7f4e1 +- Catch possible tokenizer panics #3240 +- Fix macro indentation on Windows #3266 +- Fix shape when formatting return or break expr on statement position #3259 +- rewrite_comment: fix block fallback when failing to rewrite an itemized block +- Keep leading double-colon to respect the 2018 edition of rust's paths a2bfc02 +- Fix glob and nested global imports 2125ad2 +- Do not force trailing comma when using mixed layout #3306 +- Prioritize `single_line_fn` and `empty_item_single_line` over `brace_style` #3308 +- Fix `internal error: left behind trailing whitespace` with long lines c2534f5 +- Fix attribute duplication #3325 +- Fix formatting of strings within a macro 813aa79 +- Handle a macro argument with a single keyword 9a7ea6a + +## [1.0.1] 2018-12-09 + +### Added + +- Add a `version` option 378994b + +### Changed + +- End expressions like return/continue/break with a semicolon #3223 +- Update rustc-ap-rustc_target to 306.0.0, rustc-ap-syntax to 306.0.0, and rustc-ap-syntax_pos to 306.0.0 + +### Fixed + +- Allow to run a rustfmt command from cargo-fmt even when there is no target a2da636 +- Fix `un-closed delimiter` errors when formatting break labels 40174e9 + +## [1.0.0] 2018-11-19 + +### Changed + +- Preserve possibly one whitespace for brace macros 1a3bc79 +- Prefer to break arguments over putting output type on the next line 1dd54e6 + +## [0.99.9] 2018-11-15 + +### Changed + +- Update rustc-ap-rustc_target to 297.0.0, rustc-ap-syntax to 297.0.0, to rustc-ap-syntax_pos to 297.0.0 +- Don't align comments on `extern crate`s dd7add7 + +## [0.99.8] 2018-11-14 + +### Added + +- Add `overflow_delimited_expr` config option to more aggressively allow overflow #3175 + +### Fixed + +- Fix the logic for retaining a comment before the arrow in a match #3181 +- Do not wrap comments in doctest to avoid failing doctest runs #3183 +- Fix comment rewriting that was wrapping code into a line comment #3188 +- Fix formatting of unit-struct with `where`-clause #3200 + +## [0.99.7] 2018-11-07 + +### Changed + +- Force a newline after the `if` condition if there is a different indentation level #3109 +- Use correct width when formatting type on local statement #3126 +- Treat crates non-alphabetically when ordering 799005f +- Fix formatting of code that is annotated with rustfmt::skip #3113 +- Stabilize `edition` configuration option 9c3ae2d +- cargo-fmt: detect Rust edition in use #3129 +- Trim the indentation on macros which heuristically appear to use block-style indentation #3178 + +### Fixed + +- Do not remove path disambiugator inside macro #3142 +- Improve handling of Windows newlines #3141 +- Fix alignment of a struct's fields (`struct_field_align_threshold` option) with the Visual `indent_style` #3165 +- Fix a bug in formatting markdown lists within comments #3172 + +## [0.99.6] 2018-10-18 + +### Added + +- Add `enum_discrim_align_threshold` option to vertically align enum discriminants cc22869 +- Add `println!`-like heuristic to the `fail` attribute #3067 +- Handle itemized items inside comments #3083 +- Add `format_doc_comments` configuration option to control the formatting of code snippets inside comments #3089 + +### Changed + +- Makes brace behavior consistent with empty bodies for traits and impls 2727d41 +- Consider a multi-lined array as a block-like expression #3969 +- Improve formatting of strings #3073 +- Get rid of extra commas in Visual struct literal formatting #3077 +- Update rustc-ap-rustc_target to 274.0.0, rustc-ap-syntax to 274.0.0, and rustc-ap-syntax_pos to 274.0.0 +- Format macro calls with item-like arguments #3080 +- Avoid control flow expressions conditions to go multi line ef59b34 +- Simplify multi-lining binop expressions #3101 + +### Fixed + +- Do not format a code block in documentation if it is annotated with ignore or text 2bcc3a9 +- Fix inconsistent overflow behavior in Visual style #3078 +- Fix corner cases of the string formatting implementation #3083 +- Do not add parens around lifetimes 0ac68c9 +- Catch parser panic in format_snippet 8c4e92a + +## [0.99.5] 2018-09-25 + +### Added + +- Handle leading module separator for 2018 Edition #2952 +- Add configuration option `normalize_doc_attributes`: convert doc attributes to comments #3002 + +### Changed + +- Accept 2015 and 2018 instead of Edition2015 and Edition2018 for edition option eec7436 +- Support platforms without a timer 46e2a2e +- Update rustc-ap-rustc_target to 263.0.0, rustc-ap-syntax to 263.0.0, and rustc-ap-syntax_pos to 263.0.0 + +### Fixed + +- Format of attributes with commas #2971 +- Fix optional arg condensing #2972 +- Improve formatting of long function parameters #2981 +- Fix formatting of raw string literals #2983 +- Handle chain with try operators with spaces #2986 +- Use correct shape in Visual tuple rewriting #2987 +- Impove formatting of arguments with `visual_style = "Visual"` option #2988 +- Change `print_diff` to output the correct line number 992b179 +- Propagate errors about failing to rewrite a macro 6f318e3 +- Handle formatting of long function signature #3010 +- Fix indent computation of a macro with braces c3edf6d +- Format generics on associated types #3035 +- Incorrect indentation of multiline block match expression #3042 +- Fix bug in import where two consecutive module separators were possible 98a0ef2 +- Prevent right-shifting of block comments with bare lines 5fdb6db + +## [0.99.4] 2018-08-27 + +### Added + +- Handle formatting of underscore imports #2951 +- Handle formatting of try blocks #2965 + +### Changed + +- Update rustc-ap-rustc_target to 237.0.0, rustc-ap-syntax to 237.0.0, and rustc-ap-syntax_pos to 237.0.0 ca19c9a +- Consider `dev` channel as nightly for unstable features #2948 + +### Fixed + +- Fix formatting of patterns with ellipsis # 2942 + +## [0.99.3] 2018-08-23 + +### Added + +- Use path attribute when searching for modules #2901 +- Expose FileLines JSON representation to allow external libraries to use the file_lines option #2915 + +### Changed + +- Replace '--conifig-help' with '--config=help' cb10e06 +- Improve formatting of slice patterns #2912 + +### Fixed + +- Format chains with comment #2899 +- Fix indentation of formatted macro body #2920 +- Fix indentation of block comments f23e6aa + +## [0.99.2] 2018-08-07 + +### Changed + +- Update rustc-ap-rustc_target to 218.0.0, rustc-ap-syntax to 218.0.0, and rustc-ap-syntax_pos to 218.0.0 5c9a2b6 +- Combine function-like attributes #2900 + +### Fixed + +- Explicitly handle semicolon after the item in statement position d96e3ca +- Fix parsing '#'-hiding of rustdoc 2eca09e + +## [0.99.1] 2018-08-04 + +### Fixed + +- fix use statements ordering when a number is present 1928ae7 + +## [0.99.0] 2018-08-03 + +- 1.0 RC release + +### Changed + +- Clarification in README.md 30fe66b + +## [0.9.0] 2018-08-01 + +### Added + +- Handle raw identifiers 3027c21 +- Format async closure 60ce411 +- Add max_width option for all heuristics c2ae39e +- Add config option `format_macro_matchers` to format the metavariable matching patterns in macros 79c5ee8 +- Add config option `format_macro_bodies` to format the bodies of macros 79c5ee8 +- Format exitential type fc307ff +- Support raw identifiers in struct expressions f121b1a +- Format Async block and async function 0b25f60 + +### Changed + +- Update rustc-ap-rustc_target to 211.0.0, rustc-ap-syntax to 211.0.0, and rustc-ap-syntax_pos to 211.0.0 +- Put each nested import on its own line while putting non-nested imports on the same line as much as possible 42ab258 +- Respect `empty_item_single_line` config option when formatting empty impls. Put the `where` on its own line to improve readability #2771 +- Strip leading `|` in match arm patterns 1d4b988 +- Apply short function call heuristic to attributes 3abebf9 +- Indent a match guard if the pattern is multiline be4d37d +- Change default newline style to `Native` 9d8f381 +- Improve formatting of series of binop expressions a4cdb68 +- Trigger an internal error if we skip formatting due to a lost comment b085113 +- Refactor chain formatting #2838 + +### Fixed + +- Do not insert spaces around braces with empty body or multiple lines 2f65852 +- Allow using mixed layout with comments #2766 +- Handle break labels #2726 +- fix rewrite_string when a line feed is present 472a2ed +- Fix an anomaly with comments and array literals b28a0cd +- Check for comments after the `=>` in a match arm 6899471 + +## [0.8.0,0.8.1,0.8.2] 2018-05-28 + +### Added + +- Use scoped attributes for skip attribute https://github.com/rust-lang/rustfmt/pull/2703 + +### Changed + +- Comment options `wrap_comments` and `normalize_comments` are reverted back to unstable 416bc4c +- Stabilise `reorder_imports` and `reorder_modules` options 7b6d2b4 +- Remove `spaces_within_parens_and_brackets` option d726492 +- Stabilise shorthand options: `use_try_shorthand`, `use_field_init_shorthand`, and `force_explicit_abi` 8afe367 +- Stabilise `remove_nested_parens` and set default to true a70f716 +- Unstabilise `unstable_features` dd9c15a +- Remove `remove_blank_lines_at_start_or_end_of_block` option 2ee8b0e +- Update rustc-ap-syntax to 146.0.0 and rustc-ap-rustc_target to 146.0.0 2c275a2 +- Audit the public API #2639 + +### Fixed + +- Handle code block in doc comment without rust prefix f1974e2 + +## [0.7.0] 2018-05-14 + +### Added + +- Add integration tests against crates in the rust-lang-nursery c79f39a + +### Changed + +- Update rustc-ap-syntax to 128.0.0 and ustc-ap-rustc_target to 128.0.0 195395f +- Put operands on its own line when each fits in a single line f8439ce +- Improve CLI options 55ac062 1869888 798bffb 4d9de48 eca7796 8396da1 5d9f5aa + +### Fixed + +- Use correct line width for list attribute 61a401a +- Avoid flip-flopping impl items when reordering them 37c216c +- Formatting breaks short lines when max_width is less than 100 9b36156 +- Fix variant "Mixed" of imports_layout option 8c8676c +- Improve handling of long lines f885039 +- Fix up lines exceeding max width 51c07f4 +- Fix handling of modules in non_modrs_mods style cf573e8 +- Do not duplicate attributes on use items e59ceaf +- Do not insert an extra brace in macros with native newlines 4c9ef93 + +## [0.6.1] 2018-05-01 + +### Changed + +- Change the default value of imports_indent to IndentStyle::Block https://github.com/rust-lang/rustfmt/pull/2662 + +### Fixed + +- Handle formatting of auto traits 5b5a72c +- Use consistent formatting for empty enum and struct https://github.com/rust-lang/rustfmt/pull/2656 + +## [0.6.0] 2018-04-20 + +### Changed + +- Improve public API 8669004 + +## [0.5.0] 2018-04-20 + +### Added + +- Add `verbose-diff` CLI option 5194984 + +### Changed + +- Update rustc-ap-syntax to 103.0.0 dd807e2 +- Refactor to make a sensible public API ca610d3 + +### Fixed + +- Add spaces between consecutive `..` `..=` 61d29eb + +## [0.4.2] 2018-04-12 + +### Added + +- Handle binary operators and lifetimes 0fd174d +- Add reorder_impl_items config option 94f5a05 +- Add `--unstable-features` CLI option to list unstable options from the `--help` output 8208f8a +- Add merge_imports config option 5dd203e + +### Changed + +- Format macro arguments with vertical layout ec71459 +- Reorder imports by default 164cf7d +- Do not collapse block around expr with condition on match arm 5b9b7d5 +- Use vertical layout for complex attributes c77708f +- Format array using heuristics for function calls 98c6f7b +- Implement stable ordering for impl items with the the following item priority: type, const, macro, then method fa80ddf +- Reorder imports by default 164cf7d +- Group `extern crate` by default 3a138a2 +- Make `error_on_line_overflow` false by default f146711 +- Merge imports with the same prefix into a single nested import 1954513 +- Squash the various 'reorder imports' option into one 911395a + +### Fixed + +- Print version is missing the channel ca6fc67 +- Do not add the beginning vert to the match arm 1e1d9d4 +- Follow indent style config when formatting attributes efd295a +- Do not insert newline when item is empty a8022f3 +- Do not indent or unindent inside string literal ec1907b + +## [0.4.1] 2018-03-16 + +### Added + +- Add `ignore` configuration option. +- Add `license_template_path` configuration option. +- Format `lazy_static!`. + +### Fixed + +- Fix formatting bugs. +- Fix setting `reorder_modules` removing inline modules. +- Format attributes on block expressions. +- Support `dyn trait` syntax. +- Support multiple patterns in `if let` and `while let`. +- Support a pattern with parentheses. + +## [0.4.0] 2018-03-02 + +### Changed + +- Do not print verbose outputs when formatting with stdin. +- Preserve trailing whitespaces in doc comments. +- Scale the values of width heuristics by `max_width`. + +### Fixed + +- Do not reorder items with `#[macro_use]`. +- Fix formatting bugs. +- Support the beginning `|` on a match arm. + +## [0.3.8] 2018-02-04 + +### Added + +- Format (or at least try to format) `macro_rules!`. + +## [0.3.7] 2018-02-01 + +### Added + +- Add `use_field_init_shorthand` config option. +- Add `reorder_modules` configuration option. + +## [0.3.6] 2018-01-18 + +### Fixed + +- Fix panicking on formatting certain macros (#2371). + +## [0.3.5] 2018-01-15 + +### Changed + +- Format code block in comments when `wrap_comments` is set to `true`. +- Remove `same_line_attributes` configuration option. +- Rename `git-fmt` to `git-rustfmt`. + +### Fixed + +- Rustup to `rustc 1.25.0-nightly (e6072a7b3 2018-01-13)`. +- Fix formatting bugs. + +## [0.3.4] 2017-12-23 + +### Added + +- Add `--version` flag to `cargo-fmt`, allow `cargo fmt --version`. + +### Fixed + +- Rustup to `rustc 1.24.0-nightly (5165ee9e2 2017-12-22)`. + +## [0.3.3] 2017-12-22 + +### Added + +- Format trait aliases. + +### Changed + +- `cargo fmt` will format every workspace member. + +### Fixed + +- Rustup to `rustc 1.24.0-nightly (250b49205 2017-12-21)` +- Fix formatting bugs. + +## [0.3.2] 2017-12-15 + +### Changed + +- Warn when unknown configuration option is used. + +### Fixed + +- Rustup to `rustc 1.24.0-nightly (0077d128d 2017-12-14)`. + +## [0.3.1] 2017-12-11 + +### Added + +- Add `error_on_unformatted` configuration option. +- Add `--error-on-unformatted` command line option. + +### Changed + +- Do not report formatting errors on comments or strings by default. +- Rename `error_on_line_overflow_comments` to `error_on_unformatted`. + +### Fixed + +- Fix formatting bugs. +- Fix adding a trailing whitespace inside code block when `wrap_comments = true`. + +## [0.3.0] 2017-12-11 + +### Added + +- Support nested imports. + +### Changed + +- Do not report errors on skipped items. +- Do not format code block inside comments when `wrap_comments = true`. +- Keep vertical spaces between items within range. +- Format `format!` and its variants using compressed style. +- Format `write!` and its variants using compressed style. +- Format **simple** array using compressed style. + +### Fixed + +- Fix `rustfmt --package package_name` not working properly. +- Fix formatting bugs. + +## [0.2.17] 2017-12-03 + +### Added + +- Add `blank_lines_lower_bound` and `blank_lines_upper_bound` configuration options. + +### Changed + +- Combine configuration options related to width heuristic into `width_heuristic`. +- If the match arm's body is `if` expression, force to use block. + +### Fixed + +- Fix `cargo fmt --all` being trapped in an infinite loop. +- Fix many formatting bugs. + +### Removed + +- Remove legacy configuration options. + +## [0.2.16] 2017-11-21 + +### Added + +- Remove empty lines at the beginning of the file. +- Soft wrapping on doc comments. + +### Changed + +- Break before `|` when using multiple lines for match arm patterns. +- Combine `control_style`, `where_style` and `*_indent` config options into `indent_style`. +- Combine `item_brace_style` and `fn_brace_style` config options into `brace_style`. +- Combine config options related spacing around colons into `space_before_colon` and `space_after_colon`. + +### Fixed + +- Fix many bugs. + +## [0.2.15] 2017-11-08 + +### Added + +- Add git-fmt tool +- `where_single_line` configuration option. + +### Changed + +- Rename `chain_one_line_max` to `chain_width`. +- Change the suffix of indent-related configuration options to `_indent`. + +## [0.2.14] 2017-11-06 + +### Fixed + +- Rustup to the latest nightly. + +## [0.2.13] 2017-10-30 + +### Fixed + +- Rustup to the latest nightly. + +## [0.2.12] 2017-10-29 + +### Fixed + +- Fix a bug that `cargo fmt` hangs forever. + +## [0.2.11] 2017-10-29 + +### Fixed + +- Fix a bug that `cargo fmt` crashes. + +## [0.2.10] 2017-10-28 + +## [0.2.9] 2017-10-16 + +## [0.2.8] 2017-09-28 + +## [0.2.7] 2017-09-21 + +### Added + +- `binop_separator` configuration option (#1964). + +### Changed + +- Use horizontal layout for function call with a single argument. + +### Fixed + +- Fix panicking when calling `cargo fmt --all` (#1963). +- Refactorings & faster rustfmt. + +## [0.2.6] 2017-09-14 + +### Fixed + +- Fix a performance issue with nested block (#1940). +- Refactorings & faster rustfmt. + +## [0.2.5] 2017-08-31 + +### Added + +- Format and preserve attributes on statements (#1933). + +### Fixed + +- Use getters to access `Span` fields (#1899). + +## [0.2.4] 2017-08-30 + +### Added + +- Add support for `Yield` (#1928). + +## [0.2.3] 2017-08-30 + +### Added + +- `multiline_closure_forces_block` configuration option (#1898). +- `multiline_match_arm_forces_block` configuration option (#1898). +- `merge_derives` configuration option (#1910). +- `struct_remove_empty_braces` configuration option (#1930). +- Various refactorings. + +### Changed + +- Put single-lined block comments on the same line with list-like structure's item (#1923). +- Preserve blank line between doc comment and attribute (#1925). +- Put the opening and the closing braces of enum and struct on the same line, even when `item_brace_style = "AlwaysNextLine"` (#1930). + +### Fixed + +- Format attributes on `ast::ForeignItem` and take max width into account (#1916). +- Ignore empty lines when calculating the shortest indent width inside macro with braces (#1918). +- Handle tabs properly inside macro with braces (#1918). +- Fix a typo in `compute_budgets_for_args()` (#1924). +- Recover comment between keyword (`impl` and `trait`) and `{` which used to get removed (#1925). + + +[install-from-source]: https://github.com/rust-lang/rustfmt#installing-from-source diff --git a/src/tools/rustfmt/CODE_OF_CONDUCT.md b/src/tools/rustfmt/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..d70b2b52ac --- /dev/null +++ b/src/tools/rustfmt/CODE_OF_CONDUCT.md @@ -0,0 +1,40 @@ +# The Rust Code of Conduct + +A version of this document [can be found online](https://www.rust-lang.org/conduct.html). + +## Conduct + +**Contact**: [rust-mods@rust-lang.org](mailto:rust-mods@rust-lang.org) + +* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. +* On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all. +* Please be kind and courteous. There's no need to be mean or rude. +* Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. +* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. +* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. +* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [Rust moderation team][mod_team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back. +* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. + +## Moderation + + +These are the policies for upholding our community's standards of conduct. If you feel that a thread needs moderation, please contact the [Rust moderation team][mod_team]. + +1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) +2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. +3. Moderators will first respond to such remarks with a warning. +4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off. +5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. +6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology. +7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed. +8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others. + +In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. + +And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. + +The enforcement policies listed above apply to all official Rust venues; including official IRC channels (#rust, #rust-internals, #rust-tools, #rust-libs, #rustc, #rust-beginners, #rust-docs, #rust-community, #rust-lang, and #cargo); GitHub repositories under rust-lang, rust-lang-nursery, and rust-lang-deprecated; and all forums under rust-lang.org (users.rust-lang.org, internals.rust-lang.org). For other projects adopting the Rust Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion. + +*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* + +[mod_team]: https://www.rust-lang.org/team.html#Moderation-team diff --git a/src/tools/rustfmt/Cargo.lock b/src/tools/rustfmt/Cargo.lock new file mode 100644 index 0000000000..ff23a4775d --- /dev/null +++ b/src/tools/rustfmt/Cargo.lock @@ -0,0 +1,1710 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" +dependencies = [ + "memchr", +] + +[[package]] +name = "annotate-snippets" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7" +dependencies = [ + "ansi_term", +] + +[[package]] +name = "annotate-snippets" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78ea013094e5ea606b1c05fe35f1dd7ea1eb1ea259908d040b25bd5ec677ee5" + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9267dff192e68f3399525901e709a48c1d3982c9c072fa32f2127a0cb0babf14" + +[[package]] +name = "arrayref" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + +[[package]] +name = "atty" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" +dependencies = [ + "backtrace-sys", + "cfg-if 0.1.10", + "libc", + "rustc-demangle", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "blake2b_simd" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182" +dependencies = [ + "arrayref", + "arrayvec 0.4.12", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245" +dependencies = [ + "memchr", +] + +[[package]] +name = "bytecount" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0017894339f586ccb943b01b9555de56770c11cda818e7e3d8bd93f4ed7f46e" +dependencies = [ + "packed_simd", +] + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" + +[[package]] +name = "cargo_metadata" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "700b3731fd7d357223d0000f4dbf1808401b694609035c3c411fbc0cd375c426" +dependencies = [ + "semver", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "cloudabi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" +dependencies = [ + "bitflags", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" + +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + +[[package]] +name = "crossbeam-channel" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" +dependencies = [ + "crossbeam-utils 0.7.0", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils 0.6.5", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" +dependencies = [ + "arrayvec 0.4.12", + "cfg-if 0.1.10", + "crossbeam-utils 0.6.5", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +dependencies = [ + "crossbeam-utils 0.6.5", +] + +[[package]] +name = "crossbeam-utils" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" +dependencies = [ + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" +dependencies = [ + "autocfg 0.1.7", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "derive-new" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71f31892cd5c62e414316f2963c5689242c43d8e7bbcaaeca97e5e28c95d91d9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + +[[package]] +name = "ena" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +dependencies = [ + "log", +] + +[[package]] +name = "env_logger" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "failure" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "wasi", +] + +[[package]] +name = "globset" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "hashbrown" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" +dependencies = [ + "autocfg 1.0.1", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "ignore" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "522daefc3b69036f80c7d2990b28ff9e0471c683bad05ca258e0a01dd22c5a1e" +dependencies = [ + "crossbeam-channel", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local 1.0.1", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e47a3566dd4fd4eec714ae6ceabdee0caec795be835c223d92c2d40f1e8cf1c" +dependencies = [ + "autocfg 1.0.1", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "itertools" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" + +[[package]] +name = "jobserver" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b1d42ef453b30b7387e113da1c83ab1605d90c5b4e0eb8e96d016ed3b8c160" +dependencies = [ + "getrandom", + "libc", + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" + +[[package]] +name = "lock_api" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer", + "digest", + "opaque-debug", +] + +[[package]] +name = "measureme" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22bf8d885d073610aee20e7fa205c4341ed32a761dbde96da5fd96301a8d3e82" +dependencies = [ + "parking_lot", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "memchr" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" + +[[package]] +name = "memoffset" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num_cpus" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +dependencies = [ + "libc", +] + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "packed_simd" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "parking_lot" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi 0.1.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" + +[[package]] +name = "proc-macro-error" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "syn-mid", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "psm" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659ecfea2142a458893bb7673134bad50b752fea932349c213d6a23874ce3aa7" +dependencies = [ + "cc", +] + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" + +[[package]] +name = "quote" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi 0.0.3", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "redox_users" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" +dependencies = [ + "failure", + "rand_os", + "redox_syscall", + "rust-argon2", +] + +[[package]] +name = "regex" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local 0.3.6", +] + +[[package]] +name = "regex-syntax" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rust-argon2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" +dependencies = [ + "base64", + "blake2b_simd", + "crossbeam-utils 0.6.5", +] + +[[package]] +name = "rustc-ap-rustc_arena" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93575affa286089b92c8208aea4e60fe9fdd251a619a09b566d6e4e2cc123212" +dependencies = [ + "smallvec", +] + +[[package]] +name = "rustc-ap-rustc_ast" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c700f2d3b25aa8d6446dd2936048737b08b2d547bd86e2a70afa9fee4e9c522" +dependencies = [ + "bitflags", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_index", + "rustc-ap-rustc_lexer", + "rustc-ap-rustc_macros", + "rustc-ap-rustc_serialize", + "rustc-ap-rustc_span", + "smallvec", + "tracing", +] + +[[package]] +name = "rustc-ap-rustc_ast_passes" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e01f63e5259ee397bbe2e395d34a2e6b6b24f10c184d30fbbee1dcd7117f4f3" +dependencies = [ + "itertools 0.9.0", + "rustc-ap-rustc_ast", + "rustc-ap-rustc_ast_pretty", + "rustc-ap-rustc_attr", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_errors", + "rustc-ap-rustc_feature", + "rustc-ap-rustc_parse", + "rustc-ap-rustc_session", + "rustc-ap-rustc_span", + "tracing", +] + +[[package]] +name = "rustc-ap-rustc_ast_pretty" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d644c69c55deb24257cb0cb5261265fe5134f6f545e9062e1c18b07e422c68" +dependencies = [ + "rustc-ap-rustc_ast", + "rustc-ap-rustc_span", + "tracing", +] + +[[package]] +name = "rustc-ap-rustc_attr" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "797fc68816d5396870f04e03d35164f5275d2502403239d4caec7ce063683f41" +dependencies = [ + "rustc-ap-rustc_ast", + "rustc-ap-rustc_ast_pretty", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_errors", + "rustc-ap-rustc_feature", + "rustc-ap-rustc_lexer", + "rustc-ap-rustc_macros", + "rustc-ap-rustc_serialize", + "rustc-ap-rustc_session", + "rustc-ap-rustc_span", +] + +[[package]] +name = "rustc-ap-rustc_data_structures" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d840c4e6198b57982a54543ae604d634c7ceb7107f0c75970b88ebaff077ac5" +dependencies = [ + "arrayvec 0.5.1", + "bitflags", + "cfg-if 0.1.10", + "crossbeam-utils 0.7.0", + "ena", + "indexmap", + "jobserver", + "libc", + "measureme", + "parking_lot", + "rustc-ap-rustc_graphviz", + "rustc-ap-rustc_index", + "rustc-ap-rustc_macros", + "rustc-ap-rustc_serialize", + "rustc-hash", + "rustc-rayon", + "rustc-rayon-core", + "smallvec", + "stable_deref_trait", + "stacker", + "tempfile", + "tracing", + "winapi", +] + +[[package]] +name = "rustc-ap-rustc_errors" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f2f99bdc828ad417636d9016611dc9047b641fadcb7f533b8b0e9616d81f90b" +dependencies = [ + "annotate-snippets 0.8.0", + "atty", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_lint_defs", + "rustc-ap-rustc_macros", + "rustc-ap-rustc_serialize", + "rustc-ap-rustc_span", + "termcolor", + "termize", + "tracing", + "unicode-width", + "winapi", +] + +[[package]] +name = "rustc-ap-rustc_expand" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27008b4c7ded287bf5cb20b84d6d5a6566329140f2e2bc8f6e68b37a34898595" +dependencies = [ + "rustc-ap-rustc_ast", + "rustc-ap-rustc_ast_passes", + "rustc-ap-rustc_ast_pretty", + "rustc-ap-rustc_attr", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_errors", + "rustc-ap-rustc_feature", + "rustc-ap-rustc_lexer", + "rustc-ap-rustc_lint_defs", + "rustc-ap-rustc_macros", + "rustc-ap-rustc_parse", + "rustc-ap-rustc_serialize", + "rustc-ap-rustc_session", + "rustc-ap-rustc_span", + "smallvec", + "tracing", +] + +[[package]] +name = "rustc-ap-rustc_feature" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb47b53670f1263ed1389dda932d5b5a6daf98579c1f076c2ee7d7f22709b7c" +dependencies = [ + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_span", +] + +[[package]] +name = "rustc-ap-rustc_fs_util" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdaddc4bae5ffab17037553e172f5014686db600050429aaa60aec14fe780e84" + +[[package]] +name = "rustc-ap-rustc_graphviz" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d73c72543311e88786f7380a3bfd946395579c1a0c0441a879a97fcdea79130" + +[[package]] +name = "rustc-ap-rustc_index" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba8d74ed4bad44a5b4264cf2a51ad0bd458ed56caa5bb090e989b8002ec6327" +dependencies = [ + "arrayvec 0.5.1", + "rustc-ap-rustc_macros", + "rustc-ap-rustc_serialize", +] + +[[package]] +name = "rustc-ap-rustc_lexer" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a030d00510966cd31e13dca5e6c1bd40d303a932c54eca40e854188bca8c49e" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "rustc-ap-rustc_lint_defs" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdff95da1b5d979183ef5c285817ba6cc67a1ac11296ef1e87b1b5bbaf57213c" +dependencies = [ + "rustc-ap-rustc_ast", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_macros", + "rustc-ap-rustc_serialize", + "rustc-ap-rustc_span", + "rustc-ap-rustc_target", + "tracing", +] + +[[package]] +name = "rustc-ap-rustc_macros" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3ed7401bf6f5a256d58cd0e1c1e2e77eec25e60a0d7ad75313962edcb4e396" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "rustc-ap-rustc_parse" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "609a624baffa3f99847d57d30c96ee6732ce0912f8df4be239b6fd91533910d6" +dependencies = [ + "bitflags", + "rustc-ap-rustc_ast", + "rustc-ap-rustc_ast_pretty", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_errors", + "rustc-ap-rustc_feature", + "rustc-ap-rustc_lexer", + "rustc-ap-rustc_session", + "rustc-ap-rustc_span", + "smallvec", + "tracing", + "unicode-normalization", +] + +[[package]] +name = "rustc-ap-rustc_serialize" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc232e2a351d8131c8f1386ce372ee22ef7b1b0b897bbf817a8ce4792029a564" +dependencies = [ + "indexmap", + "smallvec", +] + +[[package]] +name = "rustc-ap-rustc_session" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18acf94c820cd0c64ee1cbd811fd1f4d5ba18987c457c88771359b90cb1a12f5" +dependencies = [ + "bitflags", + "getopts", + "num_cpus", + "rustc-ap-rustc_ast", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_errors", + "rustc-ap-rustc_feature", + "rustc-ap-rustc_fs_util", + "rustc-ap-rustc_lint_defs", + "rustc-ap-rustc_macros", + "rustc-ap-rustc_serialize", + "rustc-ap-rustc_span", + "rustc-ap-rustc_target", + "tracing", +] + +[[package]] +name = "rustc-ap-rustc_span" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3479f453a38b6a5572938d035fc2b3cb6ec379c57f598b8682b512eb90c7858" +dependencies = [ + "cfg-if 0.1.10", + "md-5", + "rustc-ap-rustc_arena", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_index", + "rustc-ap-rustc_macros", + "rustc-ap-rustc_serialize", + "scoped-tls", + "sha-1", + "sha2", + "tracing", + "unicode-width", +] + +[[package]] +name = "rustc-ap-rustc_target" +version = "705.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cacaf829778cf07bb97a9f4604896789de12392175f3743e74a30ed370f1c1" +dependencies = [ + "bitflags", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_index", + "rustc-ap-rustc_macros", + "rustc-ap-rustc_serialize", + "rustc-ap-rustc_span", + "tracing", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-rayon" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f32767f90d938f1b7199a174ef249ae1924f6e5bbdb9d112fea141e016f25b3a" +dependencies = [ + "crossbeam-deque", + "either", + "rustc-rayon-core", +] + +[[package]] +name = "rustc-rayon-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2427831f0053ea3ea73559c8eabd893133a51b251d142bacee53c62a288cb3" +dependencies = [ + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils 0.6.5", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "rustc-workspace-hack" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustfmt-config_proc_macro" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rustfmt-nightly" +version = "1.4.36" +dependencies = [ + "annotate-snippets 0.6.1", + "anyhow", + "bytecount", + "cargo_metadata", + "derive-new", + "diff", + "dirs", + "env_logger", + "getopts", + "ignore", + "itertools 0.8.0", + "lazy_static", + "log", + "regex", + "rustc-ap-rustc_ast", + "rustc-ap-rustc_ast_pretty", + "rustc-ap-rustc_attr", + "rustc-ap-rustc_data_structures", + "rustc-ap-rustc_errors", + "rustc-ap-rustc_expand", + "rustc-ap-rustc_parse", + "rustc-ap-rustc_session", + "rustc-ap-rustc_span", + "rustc-workspace-hack", + "rustfmt-config_proc_macro", + "serde", + "serde_json", + "structopt", + "term", + "thiserror", + "toml", + "unicode-segmentation", + "unicode-width", + "unicode_categories", +] + +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" + +[[package]] +name = "same-file" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpuid-bool", + "digest", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpuid-bool", + "digest", + "opaque-debug", +] + +[[package]] +name = "smallvec" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" + +[[package]] +name = "stable_deref_trait" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" + +[[package]] +name = "stacker" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ccb4c06ec57bc82d0f610f1a2963d7648700e43a6f513e564b9c89f7991786" +dependencies = [ + "cc", + "cfg-if 0.1.10", + "libc", + "psm", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fe43617218c0805c6eb37160119dc3c548110a67786da7218d1c6555212f073" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e79c80e0f4efd86ca960218d4e056249be189ff1c42824dcd9a7f51a56f0bd" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "synstructure" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "term" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" +dependencies = [ + "dirs", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" +dependencies = [ + "wincolor", +] + +[[package]] +name = "termize" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1706be6b564323ce7092f5f7e6b118a14c8ef7ed0e69c8c5329c914a9f101295" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b305ec0e323c7b6cfff6098a22516e0063d0bb7c3d88660a890217dca099a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ba8d810d9c48fc456b7ad54574e8bfb7c7918a57ad7a6e6a0985d7959e8597" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "toml" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d79ca061b032d6ce30c660fded31189ca0b9922bf483cd70759f13a2d86786c" +dependencies = [ + "cfg-if 0.1.10", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f0e00789804e99b20f12bc7003ca416309d28a6f495d6af58d1e2c2842461b5" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicode-normalization" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +dependencies = [ + "smallvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" + +[[package]] +name = "unicode-width" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" + +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" + +[[package]] +name = "walkdir" +version = "2.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "wincolor" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9" +dependencies = [ + "winapi", + "winapi-util", +] diff --git a/src/tools/rustfmt/Cargo.toml b/src/tools/rustfmt/Cargo.toml new file mode 100644 index 0000000000..28696c5608 --- /dev/null +++ b/src/tools/rustfmt/Cargo.toml @@ -0,0 +1,101 @@ +[package] + +name = "rustfmt-nightly" +version = "1.4.36" +authors = ["Nicholas Cameron ", "The Rustfmt developers"] +description = "Tool to find and fix Rust formatting issues" +repository = "https://github.com/rust-lang/rustfmt" +readme = "README.md" +license = "Apache-2.0/MIT" +build = "build.rs" +categories = ["development-tools"] +edition = "2018" + +[[bin]] +name = "rustfmt" +path = "src/bin/main.rs" + +[[bin]] +name = "cargo-fmt" +path = "src/cargo-fmt/main.rs" + +[[bin]] +name = "rustfmt-format-diff" +path = "src/format-diff/main.rs" + +[[bin]] +name = "git-rustfmt" +path = "src/git-rustfmt/main.rs" + +[features] +default = ["cargo-fmt", "rustfmt-format-diff"] +cargo-fmt = [] +rustfmt-format-diff = [] +generic-simd = ["bytecount/generic-simd"] + +[dependencies] +itertools = "0.8" +toml = "0.5" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +unicode-segmentation = "1.0.0" +regex = "1.0" +term = "0.6" +diff = "0.1" +log = "0.4" +env_logger = "0.6" +getopts = "0.2" +derive-new = "0.5" +cargo_metadata = "0.8" +bytecount = "0.6" +unicode-width = "0.1.5" +unicode_categories = "0.1.1" +dirs = "2.0.1" +ignore = "0.4.11" +annotate-snippets = { version = "0.6", features = ["ansi_term"] } +structopt = "0.3" +rustfmt-config_proc_macro = { version = "0.2", path = "config_proc_macro" } +lazy_static = "1.0.0" +anyhow = "1.0" +thiserror = "1.0" + +# 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" + +[dependencies.rustc_ast] +package = "rustc-ap-rustc_ast" +version = "705.0.0" + +[dependencies.rustc_ast_pretty] +package = "rustc-ap-rustc_ast_pretty" +version = "705.0.0" + +[dependencies.rustc_attr] +package = "rustc-ap-rustc_attr" +version = "705.0.0" + +[dependencies.rustc_data_structures] +package = "rustc-ap-rustc_data_structures" +version = "705.0.0" + +[dependencies.rustc_errors] +package = "rustc-ap-rustc_errors" +version = "705.0.0" + +[dependencies.rustc_expand] +package = "rustc-ap-rustc_expand" +version = "705.0.0" + +[dependencies.rustc_parse] +package = "rustc-ap-rustc_parse" +version = "705.0.0" + +[dependencies.rustc_session] +package = "rustc-ap-rustc_session" +version = "705.0.0" + +[dependencies.rustc_span] +package = "rustc-ap-rustc_span" +version = "705.0.0" diff --git a/src/tools/rustfmt/Configurations.md b/src/tools/rustfmt/Configurations.md new file mode 100644 index 0000000000..c92be5df01 --- /dev/null +++ b/src/tools/rustfmt/Configurations.md @@ -0,0 +1,2641 @@ +# Configuring Rustfmt + +Rustfmt is designed to be very configurable. You can create a TOML file called `rustfmt.toml` or `.rustfmt.toml`, place it in the project or any other parent directory and it will apply the options in that file. If none of these directories contain such a file, both your home directory and a directory called `rustfmt` in your [global config directory](https://docs.rs/dirs/1.0.4/dirs/fn.config_dir.html) (e.g. `.config/rustfmt/`) are checked as well. + +A possible content of `rustfmt.toml` or `.rustfmt.toml` might look like this: + +```toml +indent_style = "Block" +reorder_imports = false +``` + +Each configuration option is either stable or unstable. +Stable options can be used directly, while unstable options are opt-in. +To enable unstable options, set `unstable_features = true` in `rustfmt.toml` or pass `--unstable-features` to rustfmt. + +# Configuration Options + +Below you find a detailed visual guide on all the supported configuration options of rustfmt: + + +## `binop_separator` + +Where to put a binary operator when a binary expression goes multiline. + +- **Default value**: `"Front"` +- **Possible values**: `"Front"`, `"Back"` +- **Stable**: No (tracking issue: #3368) + +#### `"Front"` (default): + +```rust +fn main() { + let or = foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo + || barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar; + + let sum = 123456789012345678901234567890 + + 123456789012345678901234567890 + + 123456789012345678901234567890; + + let range = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ..bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; +} +``` + +#### `"Back"`: + +```rust +fn main() { + let or = foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo || + barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar; + + let sum = 123456789012345678901234567890 + + 123456789012345678901234567890 + + 123456789012345678901234567890; + + let range = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.. + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; +} +``` + +## `blank_lines_lower_bound` + +Minimum number of blank lines which must be put between items. If two items have fewer blank lines between +them, additional blank lines are inserted. + +- **Default value**: `0` +- **Possible values**: *unsigned integer* +- **Stable**: No (tracking issue: #3382) + +### Example +Original Code (rustfmt will not change it with the default value of `0`): + +```rust +#![rustfmt::skip] + +fn foo() { + println!("a"); +} +fn bar() { + println!("b"); + println!("c"); +} +``` + +#### `1` +```rust +fn foo() { + + println!("a"); +} + +fn bar() { + + println!("b"); + + println!("c"); +} +``` + + +## `blank_lines_upper_bound` + +Maximum number of blank lines which can be put between items. If more than this number of consecutive empty +lines are found, they are trimmed down to match this integer. + +- **Default value**: `1` +- **Possible values**: any non-negative integer +- **Stable**: No (tracking issue: #3381) + +### Example +Original Code: + +```rust +#![rustfmt::skip] + +fn foo() { + println!("a"); +} + + + +fn bar() { + println!("b"); + + + println!("c"); +} +``` + +#### `1` (default): +```rust +fn foo() { + println!("a"); +} + +fn bar() { + println!("b"); + + println!("c"); +} +``` + +#### `2`: +```rust +fn foo() { + println!("a"); +} + + +fn bar() { + println!("b"); + + + println!("c"); +} +``` + +See also: [`blank_lines_lower_bound`](#blank_lines_lower_bound) + +## `brace_style` + +Brace style for items + +- **Default value**: `"SameLineWhere"` +- **Possible values**: `"AlwaysNextLine"`, `"PreferSameLine"`, `"SameLineWhere"` +- **Stable**: No (tracking issue: #3376) + +### Functions + +#### `"SameLineWhere"` (default): + +```rust +fn lorem() { + // body +} + +fn lorem(ipsum: usize) { + // body +} + +fn lorem(ipsum: T) +where + T: Add + Sub + Mul + Div, +{ + // body +} +``` + +#### `"AlwaysNextLine"`: + +```rust +fn lorem() +{ + // body +} + +fn lorem(ipsum: usize) +{ + // body +} + +fn lorem(ipsum: T) +where + T: Add + Sub + Mul + Div, +{ + // body +} +``` + +#### `"PreferSameLine"`: + +```rust +fn lorem() { + // body +} + +fn lorem(ipsum: usize) { + // body +} + +fn lorem(ipsum: T) +where + T: Add + Sub + Mul + Div, { + // body +} +``` + +### Structs and enums + +#### `"SameLineWhere"` (default): + +```rust +struct Lorem { + ipsum: bool, +} + +struct Dolor +where + T: Eq, +{ + sit: T, +} +``` + +#### `"AlwaysNextLine"`: + +```rust +struct Lorem +{ + ipsum: bool, +} + +struct Dolor +where + T: Eq, +{ + sit: T, +} +``` + +#### `"PreferSameLine"`: + +```rust +struct Lorem { + ipsum: bool, +} + +struct Dolor +where + T: Eq, { + sit: T, +} +``` + + +## `color` + +Whether to use colored output or not. + +- **Default value**: `"Auto"` +- **Possible values**: "Auto", "Always", "Never" +- **Stable**: No (tracking issue: #3385) + +## `combine_control_expr` + +Combine control expressions with function calls. + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3369) + +#### `true` (default): + +```rust +fn example() { + // If + foo!(if x { + foo(); + } else { + bar(); + }); + + // IfLet + foo!(if let Some(..) = x { + foo(); + } else { + bar(); + }); + + // While + foo!(while x { + foo(); + bar(); + }); + + // WhileLet + foo!(while let Some(..) = x { + foo(); + bar(); + }); + + // ForLoop + foo!(for x in y { + foo(); + bar(); + }); + + // Loop + foo!(loop { + foo(); + bar(); + }); +} +``` + +#### `false`: + +```rust +fn example() { + // If + foo!( + if x { + foo(); + } else { + bar(); + } + ); + + // IfLet + foo!( + if let Some(..) = x { + foo(); + } else { + bar(); + } + ); + + // While + foo!( + while x { + foo(); + bar(); + } + ); + + // WhileLet + foo!( + while let Some(..) = x { + foo(); + bar(); + } + ); + + // ForLoop + foo!( + for x in y { + foo(); + bar(); + } + ); + + // Loop + foo!( + loop { + foo(); + bar(); + } + ); +} +``` + +## `comment_width` + +Maximum length of comments. No effect unless`wrap_comments = true`. + +- **Default value**: `80` +- **Possible values**: any positive integer +- **Stable**: No (tracking issue: #3349) + +**Note:** A value of `0` results in [`wrap_comments`](#wrap_comments) being applied regardless of a line's width. + +#### `80` (default; comments shorter than `comment_width`): +```rust +// Lorem ipsum dolor sit amet, consectetur adipiscing elit. +``` + +#### `60` (comments longer than `comment_width`): +```rust +// Lorem ipsum dolor sit amet, +// consectetur adipiscing elit. +``` + +See also [`wrap_comments`](#wrap_comments). + +## `condense_wildcard_suffixes` + +Replace strings of _ wildcards by a single .. in tuple patterns + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3384) + +#### `false` (default): + +```rust +fn main() { + let (lorem, ipsum, _, _) = (1, 2, 3, 4); + let (lorem, ipsum, ..) = (1, 2, 3, 4); +} +``` + +#### `true`: + +```rust +fn main() { + let (lorem, ipsum, ..) = (1, 2, 3, 4); +} +``` + +## `control_brace_style` + +Brace style for control flow constructs + +- **Default value**: `"AlwaysSameLine"` +- **Possible values**: `"AlwaysNextLine"`, `"AlwaysSameLine"`, `"ClosingNextLine"` +- **Stable**: No (tracking issue: #3377) + +#### `"AlwaysSameLine"` (default): + +```rust +fn main() { + if lorem { + println!("ipsum!"); + } else { + println!("dolor!"); + } +} +``` + +#### `"AlwaysNextLine"`: + +```rust +fn main() { + if lorem + { + println!("ipsum!"); + } + else + { + println!("dolor!"); + } +} +``` + +#### `"ClosingNextLine"`: + +```rust +fn main() { + if lorem { + println!("ipsum!"); + } + else { + println!("dolor!"); + } +} +``` + +## `disable_all_formatting` + +Don't reformat anything + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3388) + +## `edition` + +Specifies which edition is used by the parser. + +- **Default value**: `"2015"` +- **Possible values**: `"2015"`, `"2018"`, `"2021"` +- **Stable**: Yes + +Rustfmt is able to pick up the edition used by reading the `Cargo.toml` file if executed +through the Cargo's formatting tool `cargo fmt`. Otherwise, the edition needs to be specified +in your config file: + +```toml +edition = "2018" +``` + +## `empty_item_single_line` + +Put empty-body functions and impls on a single line + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3356) + +#### `true` (default): + +```rust +fn lorem() {} + +impl Lorem {} +``` + +#### `false`: + +```rust +fn lorem() { +} + +impl Lorem { +} +``` + +See also [`brace_style`](#brace_style), [`control_brace_style`](#control_brace_style). + + +## `enum_discrim_align_threshold` + +The maximum length of enum variant having discriminant, that gets vertically aligned with others. +Variants without discriminants would be ignored for the purpose of alignment. + +Note that this is not how much whitespace is inserted, but instead the longest variant name that +doesn't get ignored when aligning. + +- **Default value** : 0 +- **Possible values**: any positive integer +- **Stable**: No (tracking issue: #3372) + +#### `0` (default): + +```rust +enum Bar { + A = 0, + Bb = 1, + RandomLongVariantGoesHere = 10, + Ccc = 71, +} + +enum Bar { + VeryLongVariantNameHereA = 0, + VeryLongVariantNameHereBb = 1, + VeryLongVariantNameHereCcc = 2, +} +``` + +#### `20`: + +```rust +enum Foo { + A = 0, + Bb = 1, + RandomLongVariantGoesHere = 10, + Ccc = 2, +} + +enum Bar { + VeryLongVariantNameHereA = 0, + VeryLongVariantNameHereBb = 1, + VeryLongVariantNameHereCcc = 2, +} +``` + + +## `error_on_line_overflow` + +Error if Rustfmt is unable to get all lines within `max_width`, except for comments and string +literals. If this happens, then it is a bug in Rustfmt. You might be able to work around the bug by +refactoring your code to avoid long/complex expressions, usually by extracting a local variable or +using a shorter name. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3391) + +See also [`max_width`](#max_width). + +## `error_on_unformatted` + +Error if unable to get comments or string literals within `max_width`, or they are left with +trailing whitespaces. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3392) + +## `fn_args_layout` + +Control the layout of arguments in a function + +- **Default value**: `"Tall"` +- **Possible values**: `"Compressed"`, `"Tall"`, `"Vertical"` +- **Stable**: Yes + +#### `"Tall"` (default): + +```rust +trait Lorem { + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { + // body + } + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: Consectetur, + adipiscing: Adipiscing, + elit: Elit, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: Consectetur, + adipiscing: Adipiscing, + elit: Elit, + ) { + // body + } +} +``` + +#### `"Compressed"`: + +```rust +trait Lorem { + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { + // body + } + + fn lorem( + ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: Consectetur, + adipiscing: Adipiscing, elit: Elit, + ); + + fn lorem( + ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: Consectetur, + adipiscing: Adipiscing, elit: Elit, + ) { + // body + } +} +``` + +#### `"Vertical"`: + +```rust +trait Lorem { + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + ) { + // body + } + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: Consectetur, + adipiscing: Adipiscing, + elit: Elit, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: Consectetur, + adipiscing: Adipiscing, + elit: Elit, + ) { + // body + } +} +``` + + +## `fn_single_line` + +Put single-expression functions on a single line + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3358) + +#### `false` (default): + +```rust +fn lorem() -> usize { + 42 +} + +fn lorem() -> usize { + let ipsum = 42; + ipsum +} +``` + +#### `true`: + +```rust +fn lorem() -> usize { 42 } + +fn lorem() -> usize { + let ipsum = 42; + ipsum +} +``` + +See also [`control_brace_style`](#control_brace_style). + + +## `force_explicit_abi` + +Always print the abi for extern items + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: Yes + +**Note:** Non-"C" ABIs are always printed. If `false` then "C" is removed. + +#### `true` (default): + +```rust +extern "C" { + pub static lorem: c_int; +} +``` + +#### `false`: + +```rust +extern { + pub static lorem: c_int; +} +``` + +## `force_multiline_blocks` + +Force multiline closure and match arm bodies to be wrapped in a block + +- **Default value**: `false` +- **Possible values**: `false`, `true` +- **Stable**: No (tracking issue: #3374) + +#### `false` (default): + +```rust +fn main() { + result.and_then(|maybe_value| match maybe_value { + None => foo(), + Some(value) => bar(), + }); + + match lorem { + None => |ipsum| { + println!("Hello World"); + }, + Some(dolor) => foo(), + } +} +``` + +#### `true`: + +```rust +fn main() { + result.and_then(|maybe_value| { + match maybe_value { + None => foo(), + Some(value) => bar(), + } + }); + + match lorem { + None => { + |ipsum| { + println!("Hello World"); + } + } + Some(dolor) => foo(), + } +} +``` + + +## `format_code_in_doc_comments` + +Format code snippet included in doc comments. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3348) + +#### `false` (default): + +```rust +/// Adds one to the number given. +/// +/// # Examples +/// +/// ```rust +/// let five=5; +/// +/// assert_eq!( +/// 6, +/// add_one(5) +/// ); +/// # fn add_one(x: i32) -> i32 { +/// # x + 1 +/// # } +/// ``` +fn add_one(x: i32) -> i32 { + x + 1 +} +``` + +#### `true` + +```rust +/// Adds one to the number given. +/// +/// # Examples +/// +/// ```rust +/// let five = 5; +/// +/// assert_eq!(6, add_one(5)); +/// # fn add_one(x: i32) -> i32 { +/// # x + 1 +/// # } +/// ``` +fn add_one(x: i32) -> i32 { + x + 1 +} +``` + +## `format_macro_matchers` + +Format the metavariable matching patterns in macros. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3354) + +#### `false` (default): + +```rust +macro_rules! foo { + ($a: ident : $b: ty) => { + $a(42): $b; + }; + ($a: ident $b: ident $c: ident) => { + $a = $b + $c; + }; +} +``` + +#### `true`: + +```rust +macro_rules! foo { + ($a:ident : $b:ty) => { + $a(42): $b; + }; + ($a:ident $b:ident $c:ident) => { + $a = $b + $c; + }; +} +``` + +See also [`format_macro_bodies`](#format_macro_bodies). + + +## `format_macro_bodies` + +Format the bodies of macros. + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3355) + +#### `true` (default): + +```rust +macro_rules! foo { + ($a: ident : $b: ty) => { + $a(42): $b; + }; + ($a: ident $b: ident $c: ident) => { + $a = $b + $c; + }; +} +``` + +#### `false`: + +```rust +macro_rules! foo { + ($a: ident : $b: ty) => { $a(42): $b; }; + ($a: ident $b: ident $c: ident) => { $a=$b+$c; }; +} +``` + +See also [`format_macro_matchers`](#format_macro_matchers). + + +## `format_strings` + +Format string literals where necessary + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3353) + +#### `false` (default): + +```rust +fn main() { + let lorem = "ipsum dolor sit amet consectetur adipiscing elit lorem ipsum dolor sit amet consectetur adipiscing"; +} +``` + +#### `true`: + +```rust +fn main() { + let lorem = "ipsum dolor sit amet consectetur adipiscing elit lorem ipsum dolor sit amet \ + consectetur adipiscing"; +} +``` + +See also [`max_width`](#max_width). + +## `hard_tabs` + +Use tab characters for indentation, spaces for alignment + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: Yes + +#### `false` (default): + +```rust +fn lorem() -> usize { + 42 // spaces before 42 +} +``` + +#### `true`: + +```rust +fn lorem() -> usize { + 42 // tabs before 42 +} +``` + +See also: [`tab_spaces`](#tab_spaces). + + +## `hide_parse_errors` + +Do not show parse errors if the parser failed to parse files. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3390) + +## `ignore` + +Skip formatting files and directories that match the specified pattern. +The pattern format is the same as [.gitignore](https://git-scm.com/docs/gitignore#_pattern_format). Be sure to use Unix/forwardslash `/` style paths. This path style will work on all platforms. Windows style paths with backslashes `\` are not supported. + +- **Default value**: format every file +- **Possible values**: See an example below +- **Stable**: No (tracking issue: #3395) + +### Example + +If you want to ignore specific files, put the following to your config file: + +```toml +ignore = [ + "src/types.rs", + "src/foo/bar.rs", +] +``` + +If you want to ignore every file under `examples/`, put the following to your config file: + +```toml +ignore = [ + "examples", +] +``` + +If you want to ignore every file under the directory where you put your rustfmt.toml: + +```toml +ignore = ["/"] +``` + +## `imports_indent` + +Indent style of imports + +- **Default Value**: `"Block"` +- **Possible values**: `"Block"`, `"Visual"` +- **Stable**: No (tracking issue: #3360) + +#### `"Block"` (default): + +```rust +use foo::{ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, + zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz, +}; +``` + +#### `"Visual"`: + +```rust +use foo::{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, + zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz}; +``` + +See also: [`imports_layout`](#imports_layout). + +## `imports_layout` + +Item layout inside a imports block + +- **Default value**: "Mixed" +- **Possible values**: "Horizontal", "HorizontalVertical", "Mixed", "Vertical" +- **Stable**: No (tracking issue: #3361) + +#### `"Mixed"` (default): + +```rust +use foo::{xxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzz}; + +use foo::{ + aaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbb, cccccccccccccccccc, dddddddddddddddddd, + eeeeeeeeeeeeeeeeee, ffffffffffffffffff, +}; +``` + +#### `"Horizontal"`: + +**Note**: This option forces all imports onto one line and may exceed `max_width`. + +```rust +use foo::{xxx, yyy, zzz}; + +use foo::{aaa, bbb, ccc, ddd, eee, fff}; +``` + +#### `"HorizontalVertical"`: + +```rust +use foo::{xxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzz}; + +use foo::{ + aaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbb, + cccccccccccccccccc, + dddddddddddddddddd, + eeeeeeeeeeeeeeeeee, + ffffffffffffffffff, +}; +``` + +#### `"Vertical"`: + +```rust +use foo::{ + xxx, + yyy, + zzz, +}; + +use foo::{ + aaa, + bbb, + ccc, + ddd, + eee, + fff, +}; +``` + +## `indent_style` + +Indent on expressions or items. + +- **Default value**: `"Block"` +- **Possible values**: `"Block"`, `"Visual"` +- **Stable**: No (tracking issue: #3346) + +### Array + +#### `"Block"` (default): + +```rust +fn main() { + let lorem = vec![ + "ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipiscing", + "elit", + ]; +} +``` + +#### `"Visual"`: + +```rust +fn main() { + let lorem = vec!["ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipiscing", + "elit"]; +} +``` + +### Control flow + +#### `"Block"` (default): + +```rust +fn main() { + if lorem_ipsum + && dolor_sit + && amet_consectetur + && lorem_sit + && dolor_consectetur + && amet_ipsum + && lorem_consectetur + { + // ... + } +} +``` + +#### `"Visual"`: + +```rust +fn main() { + if lorem_ipsum + && dolor_sit + && amet_consectetur + && lorem_sit + && dolor_consectetur + && amet_ipsum + && lorem_consectetur + { + // ... + } +} +``` + +See also: [`control_brace_style`](#control_brace_style). + +### Function arguments + +#### `"Block"` (default): + +```rust +fn lorem() {} + +fn lorem(ipsum: usize) {} + +fn lorem( + ipsum: usize, + dolor: usize, + sit: usize, + amet: usize, + consectetur: usize, + adipiscing: usize, + elit: usize, +) { + // body +} +``` + +#### `"Visual"`: + +```rust +fn lorem() {} + +fn lorem(ipsum: usize) {} + +fn lorem(ipsum: usize, + dolor: usize, + sit: usize, + amet: usize, + consectetur: usize, + adipiscing: usize, + elit: usize) { + // body +} +``` + +### Function calls + +#### `"Block"` (default): + +```rust +fn main() { + lorem( + "lorem", + "ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipiscing", + "elit", + ); +} +``` + +#### `"Visual"`: + +```rust +fn main() { + lorem("lorem", + "ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipiscing", + "elit"); +} +``` + +### Generics + +#### `"Block"` (default): + +```rust +fn lorem< + Ipsum: Eq = usize, + Dolor: Eq = usize, + Sit: Eq = usize, + Amet: Eq = usize, + Adipiscing: Eq = usize, + Consectetur: Eq = usize, + Elit: Eq = usize, +>( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + adipiscing: Adipiscing, + consectetur: Consectetur, + elit: Elit, +) -> T { + // body +} +``` + +#### `"Visual"`: + +```rust +fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + adipiscing: Adipiscing, + consectetur: Consectetur, + elit: Elit) + -> T { + // body +} +``` + +#### Struct + +#### `"Block"` (default): + +```rust +fn main() { + let lorem = Lorem { + ipsum: dolor, + sit: amet, + }; +} +``` + +#### `"Visual"`: + +```rust +fn main() { + let lorem = Lorem { ipsum: dolor, + sit: amet }; +} +``` + +See also: [`struct_lit_single_line`](#struct_lit_single_line), [`indent_style`](#indent_style). + +### Where predicates + +#### `"Block"` (default): + +```rust +fn lorem() -> T +where + Ipsum: Eq, + Dolor: Eq, + Sit: Eq, + Amet: Eq, +{ + // body +} +``` + +#### `"Visual"`: + +```rust +fn lorem() -> T + where Ipsum: Eq, + Dolor: Eq, + Sit: Eq, + Amet: Eq +{ + // body +} +``` + +## `inline_attribute_width` + +Write an item and its attribute on the same line if their combined width is below a threshold + +- **Default value**: 0 +- **Possible values**: any positive integer +- **Stable**: No (tracking issue: #3343) + +### Example + +#### `0` (default): +```rust +#[cfg(feature = "alloc")] +use core::slice; +``` + +#### `50`: +```rust +#[cfg(feature = "alloc")] use core::slice; +``` + +## `license_template_path` + +Check whether beginnings of files match a license template. + +- **Default value**: `""` +- **Possible values**: path to a license template file +- **Stable**: No (tracking issue: #3352) + +A license template is a plain text file which is matched literally against the +beginning of each source file, except for `{}`-delimited blocks, which are +matched as regular expressions. The following license template therefore +matches strings like `// Copyright 2017 The Rust Project Developers.`, `// +Copyright 2018 The Rust Project Developers.`, etc.: + +``` +// Copyright {\d+} The Rust Project Developers. +``` + +`\{`, `\}` and `\\` match literal braces / backslashes. + +## `match_arm_blocks` + +Wrap the body of arms in blocks when it does not fit on the same line with the pattern of arms + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3373) + +#### `true` (default): + +```rust +fn main() { + match lorem { + true => { + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x) + } + false => println!("{}", sit), + } +} +``` + +#### `false`: + +```rust +fn main() { + match lorem { + true => + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + false => println!("{}", sit), + } +} +``` + +See also: [`match_block_trailing_comma`](#match_block_trailing_comma). + +## `match_arm_leading_pipes` + +Controls whether to include a leading pipe on match arms + +- **Default value**: `Never` +- **Possible values**: `Always`, `Never`, `Preserve` +- **Stable**: Yes + +#### `Never` (default): +```rust +// Leading pipes are removed from this: +// fn foo() { +// match foo { +// | "foo" | "bar" => {} +// | "baz" +// | "something relatively long" +// | "something really really really realllllllllllllly long" => println!("x"), +// | "qux" => println!("y"), +// _ => {} +// } +// } + +// Becomes +fn foo() { + match foo { + "foo" | "bar" => {} + "baz" + | "something relatively long" + | "something really really really realllllllllllllly long" => println!("x"), + "qux" => println!("y"), + _ => {} + } +} +``` + +#### `Always`: +```rust +// Leading pipes are emitted on all arms of this: +// fn foo() { +// match foo { +// "foo" | "bar" => {} +// "baz" +// | "something relatively long" +// | "something really really really realllllllllllllly long" => println!("x"), +// "qux" => println!("y"), +// _ => {} +// } +// } + +// Becomes: +fn foo() { + match foo { + | "foo" | "bar" => {} + | "baz" + | "something relatively long" + | "something really really really realllllllllllllly long" => println!("x"), + | "qux" => println!("y"), + | _ => {} + } +} +``` + +#### `Preserve`: +```rust +fn foo() { + match foo { + | "foo" | "bar" => {} + | "baz" + | "something relatively long" + | "something really really really realllllllllllllly long" => println!("x"), + | "qux" => println!("y"), + _ => {} + } + + match baz { + "qux" => {} + "foo" | "bar" => {} + _ => {} + } +} +``` + +## `match_block_trailing_comma` + +Put a trailing comma after a block based match arm (non-block arms are not affected) + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3380) + +#### `false` (default): + +```rust +fn main() { + match lorem { + Lorem::Ipsum => { + println!("ipsum"); + } + Lorem::Dolor => println!("dolor"), + } +} +``` + +#### `true`: + +```rust +fn main() { + match lorem { + Lorem::Ipsum => { + println!("ipsum"); + }, + Lorem::Dolor => println!("dolor"), + } +} +``` + +See also: [`trailing_comma`](#trailing_comma), [`match_arm_blocks`](#match_arm_blocks). + +## `max_width` + +Maximum width of each line + +- **Default value**: `100` +- **Possible values**: any positive integer +- **Stable**: Yes + +See also [`error_on_line_overflow`](#error_on_line_overflow). + +## `merge_derives` + +Merge multiple derives into a single one. + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: Yes + +#### `true` (default): + +```rust +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +pub enum Foo {} +``` + +#### `false`: + +```rust +#[derive(Eq, PartialEq)] +#[derive(Debug)] +#[derive(Copy, Clone)] +pub enum Foo {} +``` + +## `imports_granularity` + +How imports should be grouped into `use` statements. Imports will be merged or split to the configured level of granularity. + +- **Default value**: `Preserve` +- **Possible values**: `Preserve`, `Crate`, `Module`, `Item` +- **Stable**: No + +#### `Preserve` (default): + +Do not change the granularity of any imports and preserve the original structure written by the developer. + +```rust +use foo::b; +use foo::b::{f, g}; +use foo::{a, c, d::e}; +use qux::{h, i}; +``` + +#### `Crate`: + +Merge imports from the same crate into a single `use` statement. Conversely, imports from different crates are split into separate statements. + +```rust +use foo::{ + a, b, + b::{f, g}, + c, + d::e, +}; +use qux::{h, i}; +``` + +#### `Module`: + +Merge imports from the same module into a single `use` statement. Conversely, imports from different modules are split into separate statements. + +```rust +use foo::b::{f, g}; +use foo::d::e; +use foo::{a, b, c}; +use qux::{h, i}; +``` + +#### `Item`: + +Flatten imports so that each has its own `use` statement. + +```rust +use foo::a; +use foo::b; +use foo::b::f; +use foo::b::g; +use foo::c; +use foo::d::e; +use qux::h; +use qux::i; +``` + +## `merge_imports` + +This option is deprecated. Use `imports_granularity = "Crate"` instead. + +- **Default value**: `false` +- **Possible values**: `true`, `false` + +#### `false` (default): + +```rust +use foo::{a, c, d}; +use foo::{b, g}; +use foo::{e, f}; +``` + +#### `true`: + +```rust +use foo::{a, b, c, d, e, f, g}; +``` + + +## `newline_style` + +Unix or Windows line endings + +- **Default value**: `"Auto"` +- **Possible values**: `"Auto"`, `"Native"`, `"Unix"`, `"Windows"` +- **Stable**: Yes + +#### `Auto` (default): + +The newline style is detected automatically on a per-file basis. Files +with mixed line endings will be converted to the first detected line +ending style. + +#### `Native` + +Line endings will be converted to `\r\n` on Windows and `\n` on all +other platforms. + +#### `Unix` + +Line endings will be converted to `\n`. + +#### `Windows` + +Line endings will be converted to `\r\n`. + +## `normalize_comments` + +Convert /* */ comments to // comments where possible + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3350) + +#### `false` (default): + +```rust +// Lorem ipsum: +fn dolor() -> usize {} + +/* sit amet: */ +fn adipiscing() -> usize {} +``` + +#### `true`: + +```rust +// Lorem ipsum: +fn dolor() -> usize {} + +// sit amet: +fn adipiscing() -> usize {} +``` + +## `normalize_doc_attributes` + +Convert `#![doc]` and `#[doc]` attributes to `//!` and `///` doc comments. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3351) + +#### `false` (default): + +```rust +#![doc = "Example documentation"] + +#[doc = "Example item documentation"] +pub enum Foo {} +``` + +#### `true`: + +```rust +//! Example documentation + +/// Example item documentation +pub enum Foo {} +``` + +## `overflow_delimited_expr` + +When structs, slices, arrays, and block/array-like macros are used as the last +argument in an expression list, allow them to overflow (like blocks/closures) +instead of being indented on a new line. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3370) + +#### `false` (default): + +```rust +fn example() { + foo(ctx, |param| { + action(); + foo(param) + }); + + foo( + ctx, + Bar { + x: value, + y: value2, + }, + ); + + foo( + ctx, + &[ + MAROON_TOMATOES, + PURPLE_POTATOES, + ORGANE_ORANGES, + GREEN_PEARS, + RED_APPLES, + ], + ); + + foo( + ctx, + vec![ + MAROON_TOMATOES, + PURPLE_POTATOES, + ORGANE_ORANGES, + GREEN_PEARS, + RED_APPLES, + ], + ); +} +``` + +#### `true`: + +```rust +fn example() { + foo(ctx, |param| { + action(); + foo(param) + }); + + foo(ctx, Bar { + x: value, + y: value2, + }); + + foo(ctx, &[ + MAROON_TOMATOES, + PURPLE_POTATOES, + ORGANE_ORANGES, + GREEN_PEARS, + RED_APPLES, + ]); + + foo(ctx, vec![ + MAROON_TOMATOES, + PURPLE_POTATOES, + ORGANE_ORANGES, + GREEN_PEARS, + RED_APPLES, + ]); +} +``` + +## `remove_nested_parens` + +Remove nested parens. + +- **Default value**: `true`, +- **Possible values**: `true`, `false` +- **Stable**: Yes + + +#### `true` (default): +```rust +fn main() { + (foo()); +} +``` + +#### `false`: +```rust +fn main() { + ((((foo())))); +} +``` + + +## `reorder_impl_items` + +Reorder impl items. `type` and `const` are put first, then macros and methods. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3363) + +#### `false` (default) + +```rust +struct Dummy; + +impl Iterator for Dummy { + fn next(&mut self) -> Option { + None + } + + type Item = i32; +} +``` + +#### `true` + +```rust +struct Dummy; + +impl Iterator for Dummy { + type Item = i32; + + fn next(&mut self) -> Option { + None + } +} +``` + +## `reorder_imports` + +Reorder import and extern crate statements alphabetically in groups (a group is +separated by a newline). + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: Yes + +#### `true` (default): + +```rust +use dolor; +use ipsum; +use lorem; +use sit; +``` + +#### `false`: + +```rust +use lorem; +use ipsum; +use dolor; +use sit; +``` + +## `group_imports` + +Controls the strategy for how imports are grouped together. + +- **Default value**: `Preserve` +- **Possible values**: `Preserve`, `StdExternalCrate` +- **Stable**: No + +#### `Preserve` (default): + +Preserve the source file's import groups. + +```rust +use super::update::convert_publish_payload; +use chrono::Utc; + +use alloc::alloc::Layout; +use juniper::{FieldError, FieldResult}; +use uuid::Uuid; + +use std::sync::Arc; + +use broker::database::PooledConnection; + +use super::schema::{Context, Payload}; +use crate::models::Event; +use core::f32; +``` + +#### `StdExternalCrate`: + +Discard existing import groups, and create three groups for: +1. `std`, `core` and `alloc`, +2. external crates, +3. `self`, `super` and `crate` imports. + +```rust +use alloc::alloc::Layout; +use core::f32; +use std::sync::Arc; + +use broker::database::PooledConnection; +use chrono::Utc; +use juniper::{FieldError, FieldResult}; +use uuid::Uuid; + +use super::schema::{Context, Payload}; +use super::update::convert_publish_payload; +use crate::models::Event; +``` + +## `reorder_modules` + +Reorder `mod` declarations alphabetically in group. + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: Yes + +#### `true` (default) + +```rust +mod a; +mod b; + +mod dolor; +mod ipsum; +mod lorem; +mod sit; +``` + +#### `false` + +```rust +mod b; +mod a; + +mod lorem; +mod ipsum; +mod dolor; +mod sit; +``` + +**Note** `mod` with `#[macro_export]` will not be reordered since that could change the semantics +of the original source code. + +## `report_fixme` + +Report `FIXME` items in comments. + +- **Default value**: `"Never"` +- **Possible values**: `"Always"`, `"Unnumbered"`, `"Never"` +- **Stable**: No (tracking issue: #3394) + +Warns about any comments containing `FIXME` in them when set to `"Always"`. If +it contains a `#X` (with `X` being a number) in parentheses following the +`FIXME`, `"Unnumbered"` will ignore it. + +See also [`report_todo`](#report_todo). + + +## `report_todo` + +Report `TODO` items in comments. + +- **Default value**: `"Never"` +- **Possible values**: `"Always"`, `"Unnumbered"`, `"Never"` +- **Stable**: No (tracking issue: #3393) + +Warns about any comments containing `TODO` in them when set to `"Always"`. If +it contains a `#X` (with `X` being a number) in parentheses following the +`TODO`, `"Unnumbered"` will ignore it. + +See also [`report_fixme`](#report_fixme). + +## `required_version` + +Require a specific version of rustfmt. If you want to make sure that the +specific version of rustfmt is used in your CI, use this option. + +- **Default value**: `CARGO_PKG_VERSION` +- **Possible values**: any published version (e.g. `"0.3.8"`) +- **Stable**: No (tracking issue: #3386) + +## `skip_children` + +Don't reformat out of line modules + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3389) + +## `space_after_colon` + +Leave a space after the colon. + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3366) + +#### `true` (default): + +```rust +fn lorem(t: T) { + let lorem: Dolor = Lorem { + ipsum: dolor, + sit: amet, + }; +} +``` + +#### `false`: + +```rust +fn lorem(t:T) { + let lorem:Dolor = Lorem { + ipsum:dolor, + sit:amet, + }; +} +``` + +See also: [`space_before_colon`](#space_before_colon). + +## `space_before_colon` + +Leave a space before the colon. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3365) + +#### `false` (default): + +```rust +fn lorem(t: T) { + let lorem: Dolor = Lorem { + ipsum: dolor, + sit: amet, + }; +} +``` + +#### `true`: + +```rust +fn lorem(t : T) { + let lorem : Dolor = Lorem { + ipsum : dolor, + sit : amet, + }; +} +``` + +See also: [`space_after_colon`](#space_after_colon). + +## `spaces_around_ranges` + +Put spaces around the .., ..=, and ... range operators + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3367) + +#### `false` (default): + +```rust +fn main() { + let lorem = 0..10; + let ipsum = 0..=10; + + match lorem { + 1..5 => foo(), + _ => bar, + } + + match lorem { + 1..=5 => foo(), + _ => bar, + } + + match lorem { + 1...5 => foo(), + _ => bar, + } +} +``` + +#### `true`: + +```rust +fn main() { + let lorem = 0 .. 10; + let ipsum = 0 ..= 10; + + match lorem { + 1 .. 5 => foo(), + _ => bar, + } + + match lorem { + 1 ..= 5 => foo(), + _ => bar, + } + + match lorem { + 1 ... 5 => foo(), + _ => bar, + } +} +``` + +## `struct_field_align_threshold` + +The maximum diff of width between struct fields to be aligned with each other. + +- **Default value** : 0 +- **Possible values**: any non-negative integer +- **Stable**: No (tracking issue: #3371) + +#### `0` (default): + +```rust +struct Foo { + x: u32, + yy: u32, + zzz: u32, +} +``` + +#### `20`: + +```rust +struct Foo { + x: u32, + yy: u32, + zzz: u32, +} +``` + +## `struct_lit_single_line` + +Put small struct literals on a single line + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3357) + +#### `true` (default): + +```rust +fn main() { + let lorem = Lorem { foo: bar, baz: ofo }; +} +``` + +#### `false`: + +```rust +fn main() { + let lorem = Lorem { + foo: bar, + baz: ofo, + }; +} +``` + +See also: [`indent_style`](#indent_style). + + +## `tab_spaces` + +Number of spaces per tab + +- **Default value**: `4` +- **Possible values**: any positive integer +- **Stable**: Yes + +#### `4` (default): + +```rust +fn lorem() { + let ipsum = dolor(); + let sit = vec![ + "amet consectetur adipiscing elit amet", + "consectetur adipiscing elit amet consectetur.", + ]; +} +``` + +#### `2`: + +```rust +fn lorem() { + let ipsum = dolor(); + let sit = vec![ + "amet consectetur adipiscing elit amet", + "consectetur adipiscing elit amet consectetur.", + ]; +} +``` + +See also: [`hard_tabs`](#hard_tabs). + + +## `trailing_comma` + +How to handle trailing commas for lists + +- **Default value**: `"Vertical"` +- **Possible values**: `"Always"`, `"Never"`, `"Vertical"` +- **Stable**: No (tracking issue: #3379) + +#### `"Vertical"` (default): + +```rust +fn main() { + let Lorem { ipsum, dolor, sit } = amet; + let Lorem { + ipsum, + dolor, + sit, + amet, + consectetur, + adipiscing, + } = elit; +} +``` + +#### `"Always"`: + +```rust +fn main() { + let Lorem { ipsum, dolor, sit, } = amet; + let Lorem { + ipsum, + dolor, + sit, + amet, + consectetur, + adipiscing, + } = elit; +} +``` + +#### `"Never"`: + +```rust +fn main() { + let Lorem { ipsum, dolor, sit } = amet; + let Lorem { + ipsum, + dolor, + sit, + amet, + consectetur, + adipiscing + } = elit; +} +``` + +See also: [`match_block_trailing_comma`](#match_block_trailing_comma). + +## `trailing_semicolon` + +Add trailing semicolon after break, continue and return + +- **Default value**: `true` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3378) + +#### `true` (default): +```rust +fn foo() -> usize { + return 0; +} +``` + +#### `false`: +```rust +fn foo() -> usize { + return 0 +} +``` + +## `type_punctuation_density` + +Determines if `+` or `=` are wrapped in spaces in the punctuation of types + +- **Default value**: `"Wide"` +- **Possible values**: `"Compressed"`, `"Wide"` +- **Stable**: No (tracking issue: #3364) + +#### `"Wide"` (default): + +```rust +fn lorem() { + // body +} +``` + +#### `"Compressed"`: + +```rust +fn lorem() { + // body +} +``` + +## `unstable_features` + +Enable unstable features on the unstable channel. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3387) + +## `use_field_init_shorthand` + +Use field initialize shorthand if possible. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: Yes + +#### `false` (default): + +```rust +struct Foo { + x: u32, + y: u32, + z: u32, +} + +fn main() { + let x = 1; + let y = 2; + let z = 3; + let a = Foo { x: x, y: y, z: z }; +} +``` + +#### `true`: + +```rust +struct Foo { + x: u32, + y: u32, + z: u32, +} + +fn main() { + let x = 1; + let y = 2; + let z = 3; + let a = Foo { x, y, z }; +} +``` + +## `use_small_heuristics` + +Whether to use different formatting for items and expressions if they satisfy a heuristic notion of 'small'. + +- **Default value**: `"Default"` +- **Possible values**: `"Default"`, `"Off"`, `"Max"` +- **Stable**: Yes + +#### `Default` (default): + +```rust +enum Lorem { + Ipsum, + Dolor(bool), + Sit { amet: Consectetur, adipiscing: Elit }, +} + +fn main() { + lorem( + "lorem", + "ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipiscing", + ); + + let lorem = Lorem { + ipsum: dolor, + sit: amet, + }; + let lorem = Lorem { ipsum: dolor }; + + let lorem = if ipsum { dolor } else { sit }; +} +``` + +#### `Off`: + +```rust +enum Lorem { + Ipsum, + Dolor(bool), + Sit { + amet: Consectetur, + adipiscing: Elit, + }, +} + +fn main() { + lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); + + let lorem = Lorem { + ipsum: dolor, + sit: amet, + }; + + let lorem = if ipsum { + dolor + } else { + sit + }; +} +``` + +#### `Max`: + +```rust +enum Lorem { + Ipsum, + Dolor(bool), + Sit { amet: Consectetur, adipiscing: Elit }, +} + +fn main() { + lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); + + let lorem = Lorem { ipsum: dolor, sit: amet }; + + let lorem = if ipsum { dolor } else { sit }; +} +``` + +## `use_try_shorthand` + +Replace uses of the try! macro by the ? shorthand + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: Yes + +#### `false` (default): + +```rust +fn main() { + let lorem = try!(ipsum.map(|dolor| dolor.sit())); +} +``` + +#### `true`: + +```rust +fn main() { + let lorem = ipsum.map(|dolor| dolor.sit())?; +} +``` + +## `version` + +Which version of the formatting rules to use. `Version::One` is backwards-compatible +with Rustfmt 1.0. Other versions are only backwards compatible within a major +version number. + +- **Default value**: `One` +- **Possible values**: `One`, `Two` +- **Stable**: No (tracking issue: #3383) + +### Example + +```toml +version = "Two" +``` + +## `where_single_line` + +Forces the `where` clause to be laid out on a single line. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3359) + +#### `false` (default): + +```rust +impl Lorem for T +where + Option: Ipsum, +{ + // body +} +``` + +#### `true`: + +```rust +impl Lorem for T +where Option: Ipsum +{ + // body +} +``` + +See also [`brace_style`](#brace_style), [`control_brace_style`](#control_brace_style). + + +## `wrap_comments` + +Break comments to fit on the line + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: #3347) + +#### `false` (default): + +```rust +// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +``` + +#### `true`: + +```rust +// Lorem ipsum dolor sit amet, consectetur adipiscing elit, +// sed do eiusmod tempor incididunt ut labore et dolore +// magna aliqua. Ut enim ad minim veniam, quis nostrud +// exercitation ullamco laboris nisi ut aliquip ex ea +// commodo consequat. +``` + +# Internal Options + +## `emit_mode` + +Internal option + +## `make_backup` + +Internal option, use `--backup` + +## `print_misformatted_file_names` + +Internal option, use `-l` or `--files-with-diff` diff --git a/src/tools/rustfmt/Contributing.md b/src/tools/rustfmt/Contributing.md new file mode 100644 index 0000000000..131f38dd06 --- /dev/null +++ b/src/tools/rustfmt/Contributing.md @@ -0,0 +1,251 @@ +# Contributing + +There are many ways to contribute to Rustfmt. This document lays out what they +are and has information on how to get started. If you have any questions about +contributing or need help with anything, please ask in the WG-Rustfmt channel +on [Discord](https://discordapp.com/invite/rust-lang). Feel free to also ask questions +on issues, or file new issues specifically to get help. + +All contributors are expected to follow our [Code of +Conduct](CODE_OF_CONDUCT.md). + +## Test and file issues + +It would be really useful to have people use rustfmt on their projects and file +issues where it does something you don't expect. + + +## Create test cases + +Having a strong test suite for a tool like this is essential. It is very easy +to create regressions. Any tests you can add are very much appreciated. + +The tests can be run with `cargo test`. This does a number of things: +* runs the unit tests for a number of internal functions; +* makes sure that rustfmt run on every file in `./tests/source/` is equal to its + associated file in `./tests/target/`; +* runs idempotence tests on the files in `./tests/target/`. These files should + not be changed by rustfmt; +* checks that rustfmt's code is not changed by running on itself. This ensures + that the project bootstraps. + +Creating a test is as easy as creating a new file in `./tests/source/` and an +equally named one in `./tests/target/`. If it is only required that rustfmt +leaves a piece of code unformatted, it may suffice to only create a target file. + +Whenever there's a discrepancy between the expected output when running tests, a +colourised diff will be printed so that the offending line(s) can quickly be +identified. + +Without explicit settings, the tests will be run using rustfmt's default +configuration. It is possible to run a test using non-default settings in several +ways. Firstly, you can include configuration parameters in comments at the top +of the file. For example: to use 3 spaces per tab, start your test with +`// rustfmt-tab_spaces: 3`. Just remember that the comment is part of the input, +so include in both the source and target files! It is also possible to +explicitly specify the name of the expected output file in the target directory. +Use `// rustfmt-target: filename.rs` for this. You can also specify a custom +configuration by using the `rustfmt-config` directive. Rustfmt will then use +that toml file located in `./tests/config/` for its configuration. Including +`// rustfmt-config: small_tabs.toml` will run your test with the configuration +file found at `./tests/config/small_tabs.toml`. The final option is used when the +test source file contains no configuration parameter comments. In this case, the +test harness looks for a configuration file with the same filename as the test +file in the `./tests/config/` directory, so a test source file named `test-indent.rs` +would need a configuration file named `test-indent.toml` in that directory. As an +example, the `issue-1111.rs` test file is configured by the file +`./tests/config/issue-1111.toml`. + +## Debugging + +Some `rewrite_*` methods use the `debug!` macro for printing useful information. +These messages can be printed by using the environment variable `RUST_LOG=rustfmt=DEBUG`. +These traces can be helpful in understanding which part of the code was used +and get a better grasp on the execution flow. + +## Hack! + +Here are some [good starting issues](https://github.com/rust-lang/rustfmt/issues?q=is%3Aopen+is%3Aissue+label%3Agood-first-issue). + +If you've found areas which need polish and don't have issues, please submit a +PR, don't feel there needs to be an issue. + + +### Guidelines + +Rustfmt bootstraps, that is part of its test suite is running itself on its +source code. So, basically, the only style guideline is that you must pass the +tests. That ensures that the Rustfmt source code adheres to our own conventions. + +Talking of tests, if you add a new feature or fix a bug, please also add a test. +It's really easy, see above for details. Please run `cargo test` before +submitting a PR to ensure your patch passes all tests, it's pretty quick. + +Rustfmt is post-1.0 and within major version releases we strive for backwards +compatibility (at least when using the default options). That means any code +which changes Rustfmt's output must be guarded by either an option or a version +check. The latter is implemented as an option called `option`. See the section on +[configuration](#Configuration) below. + +Please try to avoid leaving `TODO`s in the code. There are a few around, but I +wish there weren't. You can leave `FIXME`s, preferably with an issue number. + + +### Version-gate formatting changes + +A change that introduces a different code-formatting should be gated on the +`version` configuration. This is to ensure the formatting of the current major +release is preserved, while allowing fixes to be implemented for the next +release. + +This is done by conditionally guarding the change like so: + +```rust +if config.version() == Version::One { // if the current major release is 1.x + // current formatting +} else { + // new formatting +} +``` + +This allows the user to apply the next formatting explicitly via the +configuration, while being stable by default. + +When the next major release is done, the code block of the previous formatting +can be deleted, e.g., the first block in the example above when going from `1.x` +to `2.x`. + +| Note: Only formatting changes with default options need to be gated. | +| --- | + +### A quick tour of Rustfmt + +Rustfmt is basically a pretty printer - that is, its mode of operation is to +take an AST (abstract syntax tree) and print it in a nice way (including staying +under the maximum permitted width for a line). In order to get that AST, we +first have to parse the source text, we use the Rust compiler's parser to do +that (see [src/lib.rs](src/lib.rs)). We shy away from doing anything too fancy, such as +algebraic approaches to pretty printing, instead relying on an heuristic +approach, 'manually' crafting a string for each AST node. This results in quite +a lot of code, but it is relatively simple. + +The AST is a tree view of source code. It carries all the semantic information +about the code, but not all of the syntax. In particular, we lose white space +and comments (although doc comments are preserved). Rustfmt uses a view of the +AST before macros are expanded, so there are still macro uses in the code. The +arguments to macros are not an AST, but raw tokens - this makes them harder to +format. + +There are different nodes for every kind of item and expression in Rust. For +more details see the source code in the compiler - +[ast.rs](https://dxr.mozilla.org/rust/source/src/libsyntax/ast.rs) - and/or the +[docs](https://doc.rust-lang.org/nightly/nightly-rustc/syntax/ast/index.html). + +Many nodes in the AST (but not all, annoyingly) have a `Span`. A `Span` is a +range in the source code, it can easily be converted to a snippet of source +text. When the AST does not contain enough information for us, we rely heavily +on `Span`s. For example, we can look between spans to try and find comments, or +parse a snippet to see how the user wrote their source code. + +The downside of using the AST is that we miss some information - primarily white +space and comments. White space is sometimes significant, although mostly we +want to ignore it and make our own. We strive to reproduce all comments, but +this is sometimes difficult. The crufty corners of Rustfmt are where we hack +around the absence of comments in the AST and try to recreate them as best we +can. + +Our primary tool here is to look between spans for text we've missed. For +example, in a function call `foo(a, b)`, we have spans for `a` and `b`, in this +case, there is only a comma and a single space between the end of `a` and the +start of `b`, so there is nothing much to do. But if we look at +`foo(a /* a comment */, b)`, then between `a` and `b` we find the comment. + +At a higher level, Rustfmt has machinery so that we account for text between +'top level' items. Then we can reproduce that text pretty much verbatim. We only +count spans we actually reformat, so if we can't format a span it is not missed +completely but is reproduced in the output without being formatted. This is +mostly handled in [src/missed_spans.rs](src/missed_spans.rs). See also `FmtVisitor::last_pos` in +[src/visitor.rs](src/visitor.rs). + + +#### Some important elements + +At the highest level, Rustfmt uses a `Visitor` implementation called `FmtVisitor` +to walk the AST. This is in [src/visitor.rs](src/visitor.rs). This is really just used to walk +items, rather than the bodies of functions. We also cover macros and attributes +here. Most methods of the visitor call out to `Rewrite` implementations that +then walk their own children. + +The `Rewrite` trait is defined in [src/rewrite.rs](src/rewrite.rs). It is implemented for many +things that can be rewritten, mostly AST nodes. It has a single function, +`rewrite`, which is called to rewrite `self` into an `Option`. The +arguments are `width` which is the horizontal space we write into and `offset` +which is how much we are currently indented from the lhs of the page. We also +take a context which contains information used for parsing, the current block +indent, and a configuration (see below). + +##### Rewrite and Indent + +To understand the indents, consider + +``` +impl Foo { + fn foo(...) { + bar(argument_one, + baz()); + } +} +``` + +When formatting the `bar` call we will format the arguments in order, after the +first one we know we are working on multiple lines (imagine it is longer than +written). So, when we come to the second argument, the indent we pass to +`rewrite` is 12, which puts us under the first argument. The current block +indent (stored in the context) is 8. The former is used for visual indenting +(when objects are vertically aligned with some marker), the latter is used for +block indenting (when objects are tabbed in from the lhs). The width available +for `baz()` will be the maximum width, minus the space used for indenting, minus +the space used for the `);`. (Note that actual argument formatting does not +quite work like this, but it's close enough). + +The `rewrite` function returns an `Option` - either we successfully rewrite and +return the rewritten string for the caller to use, or we fail to rewrite and +return `None`. This could be because Rustfmt encounters something it doesn't +know how to reformat, but more often it is because Rustfmt can't fit the item +into the required width. How to handle this is up to the caller. Often the +caller just gives up, ultimately relying on the missed spans system to paste in +the un-formatted source. A better solution (although not performed in many +places) is for the caller to shuffle around some of its other items to make +more width, then call the function again with more space. + +Since it is common for callers to bail out when a callee fails, we often use a +`?` operator to make this pattern more succinct. + +One way we might find out that we don't have enough space is when computing how much +space we have. Something like `available_space = budget - overhead`. Since +widths are unsized integers, this would cause underflow. Therefore we use +checked subtraction: `available_space = budget.checked_sub(overhead)?`. +`checked_sub` returns an `Option`, and if we would underflow `?` returns +`None`, otherwise, we proceed with the computed space. + +##### Rewrite of list-like expressions + +Much of the syntax in Rust is lists: lists of arguments, lists of fields, lists of +array elements, etc. We have some generic code to handle lists, including how to +space them in horizontal and vertical space, indentation, comments between +items, trailing separators, etc. However, since there are so many options, the +code is a bit complex. Look in [src/lists.rs](src/lists.rs). `write_list` is the key function, +and `ListFormatting` the key structure for configuration. You'll need to make a +`ListItems` for input, this is usually done using `itemize_list`. + +##### Configuration + +Rustfmt strives to be highly configurable. Often the first part of a patch is +creating a configuration option for the feature you are implementing. All +handling of configuration options is done in [src/config/mod.rs](src/config/mod.rs). Look for the +`create_config!` macro at the end of the file for all the options. The rest of +the file defines a bunch of enums used for options, and the machinery to produce +the config struct and parse a config file, etc. Checking an option is done by +accessing the correct field on the config struct, e.g., `config.max_width()`. Most +functions have a `Config`, or one can be accessed via a visitor or context of +some kind. diff --git a/src/tools/rustfmt/Design.md b/src/tools/rustfmt/Design.md new file mode 100644 index 0000000000..00a7652aee --- /dev/null +++ b/src/tools/rustfmt/Design.md @@ -0,0 +1,184 @@ +# Some thoughts on the design of rustfmt + +## Use cases + +A formatting tool can be used in different ways and the different use cases can +affect the design of the tool. The use cases I'm particularly concerned with are: + +* running on a whole repo before check-in + - in particular, to replace the `make tidy` pass on the Rust distro +* running on code from another project that you are adding to your own +* using for mass changes in code style over a project + +Some valid use cases for a formatting tool which I am explicitly not trying to +address (although it would be nice, if possible): + +* running 'as you type' in an IDE +* running on arbitrary snippets of code +* running on Rust-like code, specifically code which doesn't parse +* use as a pretty printer inside the compiler +* refactoring +* formatting totally unformatted source code + + +## Scope and vision + +I do not subscribe to the notion that a formatting tool should only change +whitespace. I believe that we should semantics preserving, but not necessarily +syntax preserving, i.e., we can change the AST of a program. + +I.e., we might change glob imports to list or single imports, re-order imports, +move bounds to where clauses, combine multiple impls into a single impl, etc. + +However, we will not change the names of variables or make any changes which +*could* change the semantics. To be ever so slightly formal, we might imagine +a compilers high level intermediate representation, we should strive to only +make changes which do not change the HIR, even if they do change the AST. + +I would like to be able to output refactoring scripts for making deeper changes +though. (E.g., renaming variables to satisfy our style guidelines). + +My long term goal is that all style lints can be moved from the compiler to +rustfmt and, as well as warning, can either fix problems or emit refactoring +scripts to do so. + +### Configurability + +I believe reformatting should be configurable to some extent. We should read in +options from a configuration file and reformat accordingly. We should supply at +least a config file which matches the Rust style guidelines. + +There should be multiple modes for running the tool. As well as simply replacing +each file, we should be able to show the user a list of the changes we would +make, or show a list of violations without corrections (the difference being +that there are multiple ways to satisfy a given set of style guidelines, and we +should distinguish violations from deviations from our own model). + + +## Implementation philosophy + +Some details of the philosophy behind the implementation. + + +### Operate on the AST + +A reformatting tool can be based on either the AST or a token stream (in Rust +this is actually a stream of token trees, but it's not a fundamental difference). +There are pros and cons to the two approaches. I have chosen to use the AST +approach. The primary reasons are that it allows us to do more sophisticated +manipulations, rather than just change whitespace, and it gives us more context +when making those changes. + +The advantage of the tokens approach is that you can operate on non-parsable +code. I don't care too much about that, it would be nice, but I think being able +to perform sophisticated transformations is more important. In the future, I hope to +(optionally) be able to use type information for informing reformatting too. One +specific case of unparsable code is macros. Using tokens is certainly easier +here, but I believe it is perfectly solvable with the AST approach. At the limit, +we can operate on just tokens in the macro case. + +I believe that there is not in fact that much difference between the two +approaches. Due to imperfect span information, under the AST approach, we +sometimes are reduced to examining tokens or do some re-lexing of our own. Under +the tokens approach, you need to implement your own (much simpler) parser. I +believe that as the tool gets more sophisticated, you end up doing more at the +token-level, or having an increasingly sophisticated parser, until at the limit +you have the same tool. + +However, I believe starting from the AST gets you more quickly to a usable and +useful tool. + + +### Heuristic rather than algorithmic + +Many formatting tools use a very general algorithmic or even algebraic tool for +pretty printing. This results in very elegant code, but I believe does not give +the best results. I prefer a more ad hoc approach where each expression/item is +formatted using custom rules. We hopefully don't end up with too much code due +to good old fashioned abstraction and code sharing. This will give a bigger code +base, but hopefully a better result. + +It also means that there will be some cases we can't format and we have to give +up. I think that is OK. Hopefully, they are rare enough that manually fixing them +is not painful. Better to have a tool that gives great code in 99% of cases and +fails in 1% than a tool which gives 50% great code and 50% ugly code, but never +fails. + + +### Incremental development + +I want rustfmt to be useful as soon as possible and to always be useful. I +specifically don't want to have to wait for a feature (or worse, the whole tool) +to be perfect before it is useful. The main ways this is achieved is to output +the source code where we can't yet reformat, be able to turn off new features +until they are ready, and the 'do no harm' principle (see next section). + + +### First, do no harm + +Until rustfmt is perfect, there will always be a trade-off between doing more and +doing existing things well. I want to err on the side of the latter. +Specifically, rustfmt should never take OK code and make it look worse. If we +can't make it better, we should leave it as is. That might mean being less +aggressive than we like or using configurability. + + +### Use the source code as guidance + +There are often multiple ways to format code and satisfy standards. Where this +is the case, we should use the source code as a hint for reformatting. +Furthermore, where the code has been formatted in a particular way that satisfies +the coding standard, it should not be changed (this is sometimes not possible or +not worthwhile due to uniformity being desirable, but it is a useful goal). + + +### Architecture details + +We use the AST from [syntex_syntax], an export of rustc's libsyntax. We use +syntex_syntax's visit module to walk the AST to find starting points for +reformatting. Eventually, we should reformat everything and we shouldn't need +the visit module. We keep track of the last formatted position in the code, and +when we reformat the next piece of code we make sure to output the span for all +the code in between (handled by missed_spans.rs). + +[syntex_syntax]: https://crates.io/crates/syntex_syntax + +We read in formatting configuration from a `rustfmt.toml` file if there is one. +The options and their defaults are defined in `config.rs`. A `Config` object is +passed throughout the formatting code, and each formatting routine looks there +for its configuration. + +Our visitor keeps track of the desired current indent due to blocks ( +`block_indent`). Each `visit_*` method reformats code according to this indent, +`config.comment_width()` and `config.max_width()`. Most reformatting that is done +in the `visit_*` methods is a bit hacky and is meant to be temporary until it can +be done properly. + +There are a bunch of methods called `rewrite_*`. They do the bulk of the +reformatting. These take the AST node to be reformatted (this may not literally +be an AST node from syntex_syntax: there might be multiple parameters +describing a logical node), the current indent, and the current width budget. +They return a `String` (or sometimes an `Option`) which formats the +code in the box given by the indent and width budget. If the method fails, it +returns `None` and the calling method then has to fallback in some way to give +the callee more space. + +So, in summary, to format a node, we calculate the width budget and then walk down +the tree from the node. At a leaf, we generate an actual string and then unwind, +combining these strings as we go back up the tree. + +For example, consider a method definition: + +``` + fn foo(a: A, b: B) { + ... + } +``` + +We start at indent 4, the rewrite function for the whole function knows it must +write `fn foo(` before the arguments and `) {` after them, assuming the max width +is 100, it thus asks the rewrite argument list function to rewrite with an indent +of 11 and in a width of 86. Assuming that is possible (obviously in this case), +it returns a string for the arguments and it can make a string for the function +header. If the arguments couldn't be fitted in that space, we might try to +fallback to a hanging indent, so we try again with indent 8 and width 89. diff --git a/src/tools/rustfmt/LICENSE-APACHE b/src/tools/rustfmt/LICENSE-APACHE new file mode 100644 index 0000000000..212ba1f318 --- /dev/null +++ b/src/tools/rustfmt/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2016-2021 The Rust Project Developers + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/tools/rustfmt/LICENSE-MIT b/src/tools/rustfmt/LICENSE-MIT new file mode 100644 index 0000000000..1baa137f60 --- /dev/null +++ b/src/tools/rustfmt/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2016-2021 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/tools/rustfmt/Makefile.toml b/src/tools/rustfmt/Makefile.toml new file mode 100644 index 0000000000..597dd12056 --- /dev/null +++ b/src/tools/rustfmt/Makefile.toml @@ -0,0 +1,71 @@ +[env] +CFG_RELEASE = { value = "${CARGO_MAKE_RUST_VERSION}", condition = { env_not_set = ["CFG_RELEASE"] } } +CFG_RELEASE_CHANNEL = { value = "${CARGO_MAKE_RUST_CHANNEL}", condition = { env_not_set = ["CFG_RELEASE_CHANNEL"] } } + +[tasks.build-bin] +command = "cargo" +args = [ + "build", + "--bin", + "rustfmt", + "--bin", + "cargo-fmt", +] + +[tasks.build-bins] +command = "cargo" +args = [ + "build", + "--bins", +] + +[tasks.install] +command = "cargo" +args = [ + "install", + "--path", + ".", + "--force", + "--locked", # Respect Cargo.lock +] + +[tasks.release] +command = "cargo" +args = [ + "build", + "--release", +] + +[tasks.test] +command = "cargo" +args = [ + "test", +] + +[tasks.test-all] +dependencies = ["build-bin"] +run_task = { name = ["test", "test-ignored"] } + +[tasks.test-ignored] +command = "cargo" +args = [ + "test", + "--", + "--ignored", +] + +[tasks.b] +alias = "build" + +[tasks.bb] +alias = "build-bin" + +[tasks.bins] +alias = "build-bins" + +[tasks.c] +alias = "check" + +[tasks.t] +alias = "test" + diff --git a/src/tools/rustfmt/Processes.md b/src/tools/rustfmt/Processes.md new file mode 100644 index 0000000000..9d86d52b12 --- /dev/null +++ b/src/tools/rustfmt/Processes.md @@ -0,0 +1,57 @@ +This document outlines processes regarding management of rustfmt. + +# Stabilising an Option + +In this Section, we describe how to stabilise an option of the rustfmt's configration. + +## Conditions + +- Is the default value correct ? +- The design and implementation of the option are sound and clean. +- The option is well tested, both in unit tests and, optimally, in real usage. +- There is no open bug about the option that prevents its use. + +## Steps + +Open a pull request that closes the tracking issue. The tracking issue is listed beside the option in `Configurations.md`. + +- Update the `Config` enum marking the option as stable. +- Update the the `Configuration.md` file marking the option as stable. +- Update `CHANGELOG.md` marking the option as stable. + +## After the stabilisation + +The option should remain backward-compatible with previous parameters of the option. For instance, if the option is an enum `enum Foo { Alice, Bob }` and the variant `Foo::Bob` is removed/renamed, existing use of the `Foo::Bob` variant should map to the new logic. Breaking changes can be applied under the condition they are version-gated. + +# Make a Release + +## 0. Update CHANGELOG.md + +## 1. Update Cargo.toml and Cargo.lock + +For example, 1.0.0 -> 1.0.1: + +```diff +-version = "1.0.0" ++version = "1.0.1" +``` + +## 2. Push the commit to the master branch + +E.g., https://github.com/rust-lang/rustfmt/commit/5274b49caa1a7db6ac10c76bf1a3d5710ccef569 + +## 3. Create a release tag + +```sh +git tag -s v1.2.3 -m "Release 1.2.3" +``` + +## 4. Publish to crates.io + +`cargo publish` + +## 5. Create a PR to rust-lang/rust to update the rustfmt submodule + +Note that if you are updating `rustc-ap-*` crates, then you need to update **every** submodules in the rust-lang/rust repository that depend on the crates to use the same version of those. + +As of 2019/05, there are two such crates: `rls` and `racer` (`racer` depends on `rustc-ap-syntax` and `rls` depends on `racer`, and `rls` is one of submodules of the rust-lang/rust repository). diff --git a/src/tools/rustfmt/README.md b/src/tools/rustfmt/README.md new file mode 100644 index 0000000000..7a97d31bab --- /dev/null +++ b/src/tools/rustfmt/README.md @@ -0,0 +1,234 @@ +# rustfmt [![Build Status](https://travis-ci.com/rust-lang/rustfmt.svg?branch=master)](https://travis-ci.com/rust-lang/rustfmt) [![Build Status](https://ci.appveyor.com/api/projects/status/github/rust-lang/rustfmt?svg=true)](https://ci.appveyor.com/project/rust-lang-libs/rustfmt) [![crates.io](https://img.shields.io/crates/v/rustfmt-nightly.svg)](https://crates.io/crates/rustfmt-nightly) [![Travis Configuration Status](https://img.shields.io/travis/davidalber/rustfmt-travis.svg?label=travis%20example)](https://travis-ci.org/davidalber/rustfmt-travis) + +A tool for formatting Rust code according to style guidelines. + +If you'd like to help out (and you should, it's a fun project!), see +[Contributing.md](Contributing.md) and our [Code of +Conduct](CODE_OF_CONDUCT.md). + +You can use rustfmt in Travis CI builds. We provide a minimal Travis CI +configuration (see [here](#checking-style-on-a-ci-server)) and verify its status +using another repository. The status of that repository's build is reported by +the "travis example" badge above. + +## Quick start + +You can run `rustfmt` with Rust 1.24 and above. + +### On the Stable toolchain + +To install: + +```sh +rustup component add rustfmt +``` + +To run on a cargo project in the current working directory: + +```sh +cargo fmt +``` + +### On the Nightly toolchain + +For the latest and greatest `rustfmt`, nightly is required. + +To install: + +```sh +rustup component add rustfmt --toolchain nightly +``` + +To run on a cargo project in the current working directory: + +```sh +cargo +nightly fmt +``` + +## Limitations + +Rustfmt tries to work on as much Rust code as possible, sometimes, the code +doesn't even need to compile! As we approach a 1.0 release we are also looking +to limit areas of instability; in particular, post-1.0, the formatting of most +code should not change as Rustfmt improves. However, there are some things that +Rustfmt can't do or can't do well (and thus where formatting might change +significantly, even post-1.0). We would like to reduce the list of limitations +over time. + +The following list enumerates areas where Rustfmt does not work or where the +stability guarantees do not apply (we don't make a distinction between the two +because in the future Rustfmt might work on code where it currently does not): + +* a program where any part of the program does not parse (parsing is an early + stage of compilation and in Rust includes macro expansion). +* Macro declarations and uses (current status: some macro declarations and uses + are formatted). +* Comments, including any AST node with a comment 'inside' (Rustfmt does not + currently attempt to format comments, it does format code with comments inside, but that formatting may change in the future). +* Rust code in code blocks in comments. +* Any fragment of a program (i.e., stability guarantees only apply to whole + programs, even where fragments of a program can be formatted today). +* Code containing non-ascii unicode characters (we believe Rustfmt mostly works + here, but do not have the test coverage or experience to be 100% sure). +* Bugs in Rustfmt (like any software, Rustfmt has bugs, we do not consider bug + fixes to break our stability guarantees). + + +## Installation + +```sh +rustup component add rustfmt +``` + +## Installing from source + +To install from source (nightly required), first checkout to the tag or branch you want to install, then issue + +```sh +cargo install --path . +``` + +This will install `rustfmt` in your `~/.cargo/bin`. Make sure to add `~/.cargo/bin` directory to +your PATH variable. + + +## Running + +You can run Rustfmt by just typing `rustfmt filename` if you used `cargo +install`. This runs rustfmt on the given file, if the file includes out of line +modules, then we reformat those too. So to run on a whole module or crate, you +just need to run on the root file (usually mod.rs or lib.rs). Rustfmt can also +read data from stdin. Alternatively, you can use `cargo fmt` to format all +binary and library targets of your crate. + +You can run `rustfmt --help` for information about available arguments. + +When running with `--check`, Rustfmt will exit with `0` if Rustfmt would not +make any formatting changes to the input, and `1` if Rustfmt would make changes. +In other modes, Rustfmt will exit with `1` if there was some error during +formatting (for example a parsing or internal error) and `0` if formatting +completed without error (whether or not changes were made). + + + +## Running Rustfmt from your editor + +* [Vim](https://github.com/rust-lang/rust.vim#formatting-with-rustfmt) +* [Emacs](https://github.com/rust-lang/rust-mode) +* [Sublime Text 3](https://packagecontrol.io/packages/RustFmt) +* [Atom](atom.md) +* Visual Studio Code using [vscode-rust](https://github.com/editor-rs/vscode-rust), [vsc-rustfmt](https://github.com/Connorcpu/vsc-rustfmt) or [rls_vscode](https://github.com/jonathandturner/rls_vscode) through RLS. +* [IntelliJ or CLion](intellij.md) + + +## Checking style on a CI server + +To keep your code base consistently formatted, it can be helpful to fail the CI build +when a pull request contains unformatted code. Using `--check` instructs +rustfmt to exit with an error code if the input is not formatted correctly. +It will also print any found differences. (Older versions of Rustfmt don't +support `--check`, use `--write-mode diff`). + +A minimal Travis setup could look like this (requires Rust 1.24.0 or greater): + +```yaml +language: rust +before_script: +- rustup component add rustfmt +script: +- cargo build +- cargo test +- cargo fmt --all -- --check +``` + +See [this blog post](https://medium.com/@ag_dubs/enforcing-style-in-ci-for-rust-projects-18f6b09ec69d) +for more info. + +## How to build and test + +`cargo build` to build. + +`cargo test` to run all tests. + +To run rustfmt after this, use `cargo run --bin rustfmt -- filename`. See the +notes above on running rustfmt. + + +## Configuring Rustfmt + +Rustfmt is designed to be very configurable. You can create a TOML file called +`rustfmt.toml` or `.rustfmt.toml`, place it in the project or any other parent +directory and it will apply the options in that file. See `rustfmt +--help=config` for the options which are available, or if you prefer to see +visual style previews, [GitHub page](https://rust-lang.github.io/rustfmt/). + +By default, Rustfmt uses a style which conforms to the [Rust style guide][style +guide] that has been formalized through the [style RFC +process][fmt rfcs]. + +Configuration options are either stable or unstable. Stable options can always +be used, while unstable ones are only available on a nightly toolchain, and opt-in. +See [GitHub page](https://rust-lang.github.io/rustfmt/) for details. + +### Rust's Editions + +Rustfmt is able to pick up the edition used by reading the `Cargo.toml` file if +executed through the Cargo's formatting tool `cargo fmt`. Otherwise, the edition +needs to be specified in `rustfmt.toml`, e.g., with `edition = "2018"`. + +## Tips + +* For things you do not want rustfmt to mangle, use `#[rustfmt::skip]` +* To prevent rustfmt from formatting a macro or an attribute, + use `#[rustfmt::skip::macros(target_macro_name)]` or + `#[rustfmt::skip::attributes(target_attribute_name)]` + + Example: + + ```rust + #![rustfmt::skip::attributes(custom_attribute)] + + #[custom_attribute(formatting , here , should , be , Skipped)] + #[rustfmt::skip::macros(html)] + fn main() { + let macro_result1 = html! {

+ Hello
+ }.to_string(); + ``` +* When you run rustfmt, place a file named `rustfmt.toml` or `.rustfmt.toml` in + target file directory or its parents to override the default settings of + rustfmt. You can generate a file containing the default configuration with + `rustfmt --print-config default rustfmt.toml` and customize as needed. +* After successful compilation, a `rustfmt` executable can be found in the + target directory. +* If you're having issues compiling Rustfmt (or compile errors when trying to + install), make sure you have the most recent version of Rust installed. + +* You can change the way rustfmt emits the changes with the --emit flag: + + Example: + + ```sh + cargo fmt -- --emit files + ``` + + Options: + + | Flag |Description| Nightly Only | + |:---:|:---:|:---:| + | files | overwrites output to files | No | + | stdout | writes output to stdout | No | + | coverage | displays how much of the input file was processed | Yes | + | checkstyle | emits in a checkstyle format | Yes | + | json | emits diffs in a json format | Yes | + +## License + +Rustfmt is distributed under the terms of both the MIT license and the +Apache License (Version 2.0). + +See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. + +[rust]: https://github.com/rust-lang/rust +[fmt rfcs]: https://github.com/rust-lang-nursery/fmt-rfcs +[style guide]: https://github.com/rust-lang-nursery/fmt-rfcs/blob/master/guide/guide.md diff --git a/src/tools/rustfmt/appveyor.yml b/src/tools/rustfmt/appveyor.yml new file mode 100644 index 0000000000..7bfe696009 --- /dev/null +++ b/src/tools/rustfmt/appveyor.yml @@ -0,0 +1,55 @@ +# This is based on https://github.com/japaric/rust-everywhere/blob/master/appveyor.yml +# and modified (mainly removal of deployment) to suit rustfmt. + +environment: + global: + PROJECT_NAME: rustfmt + matrix: + # Stable channel + # - TARGET: i686-pc-windows-gnu + # CHANNEL: stable + # - TARGET: i686-pc-windows-msvc + # CHANNEL: stable + # - TARGET: x86_64-pc-windows-gnu + # CHANNEL: stable + # - TARGET: x86_64-pc-windows-msvc + # CHANNEL: stable + # Beta channel + # - TARGET: i686-pc-windows-gnu + # CHANNEL: beta + # - TARGET: i686-pc-windows-msvc + # CHANNEL: beta + # - TARGET: x86_64-pc-windows-gnu + # CHANNEL: beta + # - TARGET: x86_64-pc-windows-msvc + # CHANNEL: beta + # Nightly channel + - TARGET: i686-pc-windows-gnu + CHANNEL: nightly + - TARGET: i686-pc-windows-msvc + CHANNEL: nightly + - TARGET: x86_64-pc-windows-gnu + CHANNEL: nightly + - TARGET: x86_64-pc-windows-msvc + CHANNEL: nightly + +# Install Rust and Cargo +# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml) +install: + - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe + - if "%TARGET%" == "i686-pc-windows-gnu" set PATH=%PATH%;C:\msys64\mingw32\bin + - if "%TARGET%" == "x86_64-pc-windows-gnu" set PATH=%PATH%;C:\msys64\mingw64\bin + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y + - rustc -Vv + - cargo -V + +# ??? +build: false + +test_script: + - set CFG_RELEASE_CHANNEL=nightly + - set CFG_RELEASE=nightly + - cargo build --verbose + - cargo test + - cargo test -- --ignored diff --git a/src/tools/rustfmt/atom.md b/src/tools/rustfmt/atom.md new file mode 100644 index 0000000000..f77ac14907 --- /dev/null +++ b/src/tools/rustfmt/atom.md @@ -0,0 +1,31 @@ +# Running Rustfmt from Atom + +## RLS + +Rustfmt is included with the Rust Language Server, itself provided by [ide-rust](https://atom.io/packages/ide-rust). + +`apm install ide-rust` + +Once installed a file is formatted with `ctrl-shift-c` or `cmd-shift-c`, also available in context menu. + +## atom-beautify + +Another way is to install [Beautify](https://atom.io/packages/atom-beautify), you +can do this by running `apm install atom-beautify`. + +There are 2 settings that need to be configured in the atom beautifier configuration. + +- Install rustfmt as per the [readme](README.md). +- Open the atom beautifier settings + + Go to Edit->Preferences. Click the packages on the left side and click on setting for atom-beautifier + +- Set rustfmt as the beautifier + + Find the setting labeled *Language Config - Rust - Default Beautifier* and make sure it is set to rustfmt as shown below. You can also set the beautifier to auto format on save here. +![image](https://cloud.githubusercontent.com/assets/6623285/11147685/c8ade16c-8a3d-11e5-9da5-bd3d998d97f9.png) + +- Set the path to your rustfmt location + + Find the setting labeled *Rust - Rustfmt Path*. This setting is towards the bottom and you will need to scroll a bit. Set it to the path for your rustfmt executable. +![image](https://cloud.githubusercontent.com/assets/6623285/11147718/f4d10224-8a3d-11e5-9f69-9e900cbe0278.png) diff --git a/src/tools/rustfmt/bootstrap.sh b/src/tools/rustfmt/bootstrap.sh new file mode 100755 index 0000000000..05ac0ce2f3 --- /dev/null +++ b/src/tools/rustfmt/bootstrap.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Make sure you double check the diffs after running this script - with great +# power comes great responsibility. +# We deliberately avoid reformatting files with rustfmt comment directives. + +cargo build --release + +target/release/rustfmt src/lib.rs +target/release/rustfmt src/bin/main.rs +target/release/rustfmt src/cargo-fmt/main.rs + +for filename in tests/target/*.rs; do + if ! grep -q "rustfmt-" "$filename"; then + target/release/rustfmt $filename + fi +done diff --git a/src/tools/rustfmt/build.rs b/src/tools/rustfmt/build.rs new file mode 100644 index 0000000000..e7b1e1b854 --- /dev/null +++ b/src/tools/rustfmt/build.rs @@ -0,0 +1,55 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn main() { + // Only check .git/HEAD dirty status if it exists - doing so when + // building dependent crates may lead to false positives and rebuilds + if Path::new(".git/HEAD").exists() { + println!("cargo:rerun-if-changed=.git/HEAD"); + } + + println!("cargo:rerun-if-env-changed=CFG_RELEASE_CHANNEL"); + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + File::create(out_dir.join("commit-info.txt")) + .unwrap() + .write_all(commit_info().as_bytes()) + .unwrap(); +} + +// Try to get hash and date of the last commit on a best effort basis. If anything goes wrong +// (git not installed or if this is not a git repository) just return an empty string. +fn commit_info() -> String { + match (channel(), commit_hash(), commit_date()) { + (channel, Some(hash), Some(date)) => format!("{} ({} {})", channel, hash.trim_end(), date), + _ => String::new(), + } +} + +fn channel() -> String { + if let Ok(channel) = env::var("CFG_RELEASE_CHANNEL") { + channel + } else { + "nightly".to_owned() + } +} + +fn commit_hash() -> Option { + Command::new("git") + .args(&["rev-parse", "--short", "HEAD"]) + .output() + .ok() + .and_then(|r| String::from_utf8(r.stdout).ok()) +} + +fn commit_date() -> Option { + Command::new("git") + .args(&["log", "-1", "--date=short", "--pretty=format:%cd"]) + .output() + .ok() + .and_then(|r| String::from_utf8(r.stdout).ok()) +} diff --git a/src/tools/rustfmt/ci/integration.sh b/src/tools/rustfmt/ci/integration.sh new file mode 100755 index 0000000000..13a3ecaa19 --- /dev/null +++ b/src/tools/rustfmt/ci/integration.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash + +set -ex + +: ${INTEGRATION?"The INTEGRATION environment variable must be set."} + +# FIXME: this means we can get a stale cargo-fmt from a previous run. +# +# `which rustfmt` fails if rustfmt is not found. Since we don't install +# `rustfmt` via `rustup`, this is the case unless we manually install it. Once +# that happens, `cargo install --force` will be called, which installs +# `rustfmt`, `cargo-fmt`, etc to `~/.cargo/bin`. This directory is cached by +# travis (see `.travis.yml`'s "cache" key), such that build-bots that arrive +# here after the first installation will find `rustfmt` and won't need to build +# it again. +# +#which cargo-fmt || cargo install --force +CFG_RELEASE=nightly CFG_RELEASE_CHANNEL=nightly cargo install --path . --force + +echo "Integration tests for: ${INTEGRATION}" +cargo fmt -- --version + +# Checks that: +# +# * `cargo fmt --all` succeeds without any warnings or errors +# * `cargo fmt --all -- --check` after formatting returns success +# * `cargo test --all` still passes (formatting did not break the build) +function check_fmt_with_all_tests { + check_fmt_base "--all" + return $? +} + +# Checks that: +# +# * `cargo fmt --all` succeeds without any warnings or errors +# * `cargo fmt --all -- --check` after formatting returns success +# * `cargo test --lib` still passes (formatting did not break the build) +function check_fmt_with_lib_tests { + check_fmt_base "--lib" + return $? +} + +function check_fmt_base { + local test_args="$1" + local build=$(cargo test $test_args 2>&1) + if [[ "$build" =~ "build failed" ]] || [[ "$build" =~ "test result: FAILED." ]]; then + return 0 + fi + touch rustfmt.toml + cargo fmt --all -v |& tee rustfmt_output + if [[ ${PIPESTATUS[0]} != 0 ]]; then + cat rustfmt_output + return 1 + fi + cat rustfmt_output + ! cat rustfmt_output | grep -q "internal error" + if [[ $? != 0 ]]; then + return 1 + fi + ! cat rustfmt_output | grep -q "warning" + if [[ $? != 0 ]]; then + return 1 + fi + ! cat rustfmt_output | grep -q "Warning" + if [[ $? != 0 ]]; then + return 1 + fi + cargo fmt --all -- --check |& tee rustfmt_check_output + if [[ ${PIPESTATUS[0]} != 0 ]]; then + cat rustfmt_check_output + return 1 + fi + cargo test $test_args + if [[ $? != 0 ]]; then + return $? + fi +} + +function show_head { + local head=$(git rev-parse HEAD) + echo "Head commit of ${INTEGRATION}: $head" +} + +case ${INTEGRATION} in + cargo) + git clone --depth=1 https://github.com/rust-lang/${INTEGRATION}.git + cd ${INTEGRATION} + show_head + export CFG_DISABLE_CROSS_TESTS=1 + check_fmt_with_all_tests + cd - + ;; + crater) + git clone --depth=1 https://github.com/rust-lang-nursery/${INTEGRATION}.git + cd ${INTEGRATION} + show_head + check_fmt_with_lib_tests + cd - + ;; + *) + git clone --depth=1 https://github.com/rust-lang-nursery/${INTEGRATION}.git + cd ${INTEGRATION} + show_head + check_fmt_with_all_tests + cd - + ;; +esac diff --git a/src/tools/rustfmt/config_proc_macro/Cargo.lock b/src/tools/rustfmt/config_proc_macro/Cargo.lock new file mode 100644 index 0000000000..abcf9654e5 --- /dev/null +++ b/src/tools/rustfmt/config_proc_macro/Cargo.lock @@ -0,0 +1,68 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "proc-macro2" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustfmt-config_proc_macro" +version = "0.1.2" +dependencies = [ + "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f" +"checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425" +"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/src/tools/rustfmt/config_proc_macro/Cargo.toml b/src/tools/rustfmt/config_proc_macro/Cargo.toml new file mode 100644 index 0000000000..cc99557160 --- /dev/null +++ b/src/tools/rustfmt/config_proc_macro/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "rustfmt-config_proc_macro" +version = "0.2.0" +authors = ["topecongiro "] +edition = "2018" +description = "A collection of procedural macros for rustfmt" +license = "Apache-2.0/MIT" +categories = ["development-tools::procedural-macro-helpers"] +repository = "https://github.com/rust-lang/rustfmt" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "1.0", features = ["full", "visit"] } + +[dev-dependencies] +serde = { version = "1.0", features = ["derive"] } + +[features] +default = [] +debug-with-rustfmt = [] diff --git a/src/tools/rustfmt/config_proc_macro/src/attrs.rs b/src/tools/rustfmt/config_proc_macro/src/attrs.rs new file mode 100644 index 0000000000..0baba046f9 --- /dev/null +++ b/src/tools/rustfmt/config_proc_macro/src/attrs.rs @@ -0,0 +1,57 @@ +//! This module provides utilities for handling attributes on variants +//! of `config_type` enum. Currently there are two types of attributes +//! that could appear on the variants of `config_type` enum: `doc_hint` +//! and `value`. Both comes in the form of name-value pair whose value +//! is string literal. + +/// Returns the value of the first `doc_hint` attribute in the given slice or +/// `None` if `doc_hint` attribute is not available. +pub fn find_doc_hint(attrs: &[syn::Attribute]) -> Option { + attrs.iter().filter_map(doc_hint).next() +} + +/// Returns `true` if the given attribute is a `doc_hint` attribute. +pub fn is_doc_hint(attr: &syn::Attribute) -> bool { + is_attr_name_value(attr, "doc_hint") +} + +/// Returns a string literal value if the given attribute is `doc_hint` +/// attribute or `None` otherwise. +pub fn doc_hint(attr: &syn::Attribute) -> Option { + get_name_value_str_lit(attr, "doc_hint") +} + +/// Returns the value of the first `value` attribute in the given slice or +/// `None` if `value` attribute is not available. +pub fn find_config_value(attrs: &[syn::Attribute]) -> Option { + attrs.iter().filter_map(config_value).next() +} + +/// Returns a string literal value if the given attribute is `value` +/// attribute or `None` otherwise. +pub fn config_value(attr: &syn::Attribute) -> Option { + get_name_value_str_lit(attr, "value") +} + +/// Returns `true` if the given attribute is a `value` attribute. +pub fn is_config_value(attr: &syn::Attribute) -> bool { + is_attr_name_value(attr, "value") +} + +fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool { + attr.parse_meta().ok().map_or(false, |meta| match meta { + syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) if path.is_ident(name) => true, + _ => false, + }) +} + +fn get_name_value_str_lit(attr: &syn::Attribute, name: &str) -> Option { + attr.parse_meta().ok().and_then(|meta| match meta { + syn::Meta::NameValue(syn::MetaNameValue { + ref path, + lit: syn::Lit::Str(ref lit_str), + .. + }) if path.is_ident(name) => Some(lit_str.value()), + _ => None, + }) +} diff --git a/src/tools/rustfmt/config_proc_macro/src/config_type.rs b/src/tools/rustfmt/config_proc_macro/src/config_type.rs new file mode 100644 index 0000000000..93a78b8463 --- /dev/null +++ b/src/tools/rustfmt/config_proc_macro/src/config_type.rs @@ -0,0 +1,15 @@ +use proc_macro2::TokenStream; + +use crate::item_enum::define_config_type_on_enum; +use crate::item_struct::define_config_type_on_struct; + +/// Defines `config_type` on enum or struct. +// FIXME: Implement this on struct. +pub fn define_config_type(input: &syn::Item) -> TokenStream { + match input { + syn::Item::Struct(st) => define_config_type_on_struct(st), + syn::Item::Enum(en) => define_config_type_on_enum(en), + _ => panic!("Expected enum or struct"), + } + .unwrap() +} diff --git a/src/tools/rustfmt/config_proc_macro/src/item_enum.rs b/src/tools/rustfmt/config_proc_macro/src/item_enum.rs new file mode 100644 index 0000000000..dcee77a854 --- /dev/null +++ b/src/tools/rustfmt/config_proc_macro/src/item_enum.rs @@ -0,0 +1,208 @@ +use proc_macro2::TokenStream; +use quote::quote; + +use crate::attrs::*; +use crate::utils::*; + +type Variants = syn::punctuated::Punctuated; + +/// Defines and implements `config_type` enum. +pub fn define_config_type_on_enum(em: &syn::ItemEnum) -> syn::Result { + let syn::ItemEnum { + vis, + enum_token, + ident, + generics, + variants, + .. + } = em; + + let mod_name_str = format!("__define_config_type_on_enum_{}", ident); + let mod_name = syn::Ident::new(&mod_name_str, ident.span()); + let variants = fold_quote(variants.iter().map(process_variant), |meta| quote!(#meta,)); + + let impl_doc_hint = impl_doc_hint(&em.ident, &em.variants); + let impl_from_str = impl_from_str(&em.ident, &em.variants); + let impl_display = impl_display(&em.ident, &em.variants); + let impl_serde = impl_serde(&em.ident, &em.variants); + let impl_deserialize = impl_deserialize(&em.ident, &em.variants); + + Ok(quote! { + #[allow(non_snake_case)] + mod #mod_name { + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub #enum_token #ident #generics { #variants } + #impl_display + #impl_doc_hint + #impl_from_str + #impl_serde + #impl_deserialize + } + #vis use #mod_name::#ident; + }) +} + +/// Remove attributes specific to `config_proc_macro` from enum variant fields. +fn process_variant(variant: &syn::Variant) -> TokenStream { + let metas = variant + .attrs + .iter() + .filter(|attr| !is_doc_hint(attr) && !is_config_value(attr)); + let attrs = fold_quote(metas, |meta| quote!(#meta)); + let syn::Variant { ident, fields, .. } = variant; + quote!(#attrs #ident #fields) +} + +fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream { + let doc_hint = variants + .iter() + .map(doc_hint_of_variant) + .collect::>() + .join("|"); + let doc_hint = format!("[{}]", doc_hint); + quote! { + use crate::config::ConfigType; + impl ConfigType for #ident { + fn doc_hint() -> String { + #doc_hint.to_owned() + } + } + } +} + +fn impl_display(ident: &syn::Ident, variants: &Variants) -> TokenStream { + let vs = variants + .iter() + .filter(|v| is_unit(v)) + .map(|v| (config_value_of_variant(v), &v.ident)); + let match_patterns = fold_quote(vs, |(s, v)| { + quote! { + #ident::#v => write!(f, "{}", #s), + } + }); + quote! { + use std::fmt; + impl fmt::Display for #ident { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + #match_patterns + _ => unimplemented!(), + } + } + } + } +} + +fn impl_from_str(ident: &syn::Ident, variants: &Variants) -> TokenStream { + let vs = variants + .iter() + .filter(|v| is_unit(v)) + .map(|v| (config_value_of_variant(v), &v.ident)); + let if_patterns = fold_quote(vs, |(s, v)| { + quote! { + if #s.eq_ignore_ascii_case(s) { + return Ok(#ident::#v); + } + } + }); + let mut err_msg = String::from("Bad variant, expected one of:"); + for v in variants.iter().filter(|v| is_unit(v)) { + err_msg.push_str(&format!(" `{}`", v.ident)); + } + + quote! { + impl ::std::str::FromStr for #ident { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + #if_patterns + return Err(#err_msg); + } + } + } +} + +fn doc_hint_of_variant(variant: &syn::Variant) -> String { + find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string()) +} + +fn config_value_of_variant(variant: &syn::Variant) -> String { + find_config_value(&variant.attrs).unwrap_or(variant.ident.to_string()) +} + +fn impl_serde(ident: &syn::Ident, variants: &Variants) -> TokenStream { + let arms = fold_quote(variants.iter(), |v| { + let v_ident = &v.ident; + let pattern = match v.fields { + syn::Fields::Named(..) => quote!(#ident::v_ident{..}), + syn::Fields::Unnamed(..) => quote!(#ident::#v_ident(..)), + syn::Fields::Unit => quote!(#ident::#v_ident), + }; + let option_value = config_value_of_variant(v); + quote! { + #pattern => serializer.serialize_str(&#option_value), + } + }); + + quote! { + impl ::serde::ser::Serialize for #ident { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::ser::Serializer, + { + use serde::ser::Error; + match self { + #arms + _ => Err(S::Error::custom(format!("Cannot serialize {:?}", self))), + } + } + } + } +} + +// Currently only unit variants are supported. +fn impl_deserialize(ident: &syn::Ident, variants: &Variants) -> TokenStream { + let supported_vs = variants.iter().filter(|v| is_unit(v)); + let if_patterns = fold_quote(supported_vs, |v| { + let config_value = config_value_of_variant(v); + let variant_ident = &v.ident; + quote! { + if #config_value.eq_ignore_ascii_case(s) { + return Ok(#ident::#variant_ident); + } + } + }); + + let supported_vs = variants.iter().filter(|v| is_unit(v)); + let allowed = fold_quote(supported_vs.map(config_value_of_variant), |s| quote!(#s,)); + + quote! { + impl<'de> serde::de::Deserialize<'de> for #ident { + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::{Error, Visitor}; + use std::marker::PhantomData; + use std::fmt; + struct StringOnly(PhantomData); + impl<'de, T> Visitor<'de> for StringOnly + where T: serde::Deserializer<'de> { + type Value = String; + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("string") + } + fn visit_str(self, value: &str) -> Result { + Ok(String::from(value)) + } + } + let s = &d.deserialize_string(StringOnly::(PhantomData))?; + + #if_patterns + + static ALLOWED: &'static[&str] = &[#allowed]; + Err(D::Error::unknown_variant(&s, ALLOWED)) + } + } + } +} diff --git a/src/tools/rustfmt/config_proc_macro/src/item_struct.rs b/src/tools/rustfmt/config_proc_macro/src/item_struct.rs new file mode 100644 index 0000000000..f03ff7e30d --- /dev/null +++ b/src/tools/rustfmt/config_proc_macro/src/item_struct.rs @@ -0,0 +1,5 @@ +use proc_macro2::TokenStream; + +pub fn define_config_type_on_struct(_st: &syn::ItemStruct) -> syn::Result { + unimplemented!() +} diff --git a/src/tools/rustfmt/config_proc_macro/src/lib.rs b/src/tools/rustfmt/config_proc_macro/src/lib.rs new file mode 100644 index 0000000000..66cfd3c727 --- /dev/null +++ b/src/tools/rustfmt/config_proc_macro/src/lib.rs @@ -0,0 +1,27 @@ +//! This crate provides a derive macro for `ConfigType`. + +#![recursion_limit = "256"] + +extern crate proc_macro; + +mod attrs; +mod config_type; +mod item_enum; +mod item_struct; +mod utils; + +use proc_macro::TokenStream; +use syn::parse_macro_input; + +#[proc_macro_attribute] +pub fn config_type(_args: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as syn::Item); + let output = config_type::define_config_type(&input); + + #[cfg(feature = "debug-with-rustfmt")] + { + utils::debug_with_rustfmt(&output); + } + + TokenStream::from(output) +} diff --git a/src/tools/rustfmt/config_proc_macro/src/utils.rs b/src/tools/rustfmt/config_proc_macro/src/utils.rs new file mode 100644 index 0000000000..5b68d27484 --- /dev/null +++ b/src/tools/rustfmt/config_proc_macro/src/utils.rs @@ -0,0 +1,52 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; + +pub fn fold_quote(input: impl Iterator, f: F) -> TokenStream +where + F: Fn(I) -> T, + T: ToTokens, +{ + input.fold(quote! {}, |acc, x| { + let y = f(x); + quote! { #acc #y } + }) +} + +pub fn is_unit(v: &syn::Variant) -> bool { + match v.fields { + syn::Fields::Unit => true, + _ => false, + } +} + +#[cfg(feature = "debug-with-rustfmt")] +/// Pretty-print the output of proc macro using rustfmt. +pub fn debug_with_rustfmt(input: &TokenStream) { + use std::io::Write; + use std::process::{Command, Stdio}; + use std::env; + use std::ffi::OsStr; + + let rustfmt_var = env::var_os("RUSTFMT"); + let rustfmt = match &rustfmt_var { + Some(rustfmt) => rustfmt, + None => OsStr::new("rustfmt"), + }; + let mut child = Command::new(rustfmt) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("Failed to spawn rustfmt in stdio mode"); + { + let stdin = child.stdin.as_mut().expect("Failed to get stdin"); + stdin + .write_all(format!("{}", input).as_bytes()) + .expect("Failed to write to stdin"); + } + let rustfmt_output = child.wait_with_output().expect("rustfmt has failed"); + + eprintln!( + "{}", + String::from_utf8(rustfmt_output.stdout).expect("rustfmt returned non-UTF8 string") + ); +} diff --git a/src/tools/rustfmt/config_proc_macro/tests/smoke.rs b/src/tools/rustfmt/config_proc_macro/tests/smoke.rs new file mode 100644 index 0000000000..940a8a0c25 --- /dev/null +++ b/src/tools/rustfmt/config_proc_macro/tests/smoke.rs @@ -0,0 +1,20 @@ +pub mod config { + pub trait ConfigType: Sized { + fn doc_hint() -> String; + } +} + +#[allow(dead_code)] +#[allow(unused_imports)] +mod tests { + use rustfmt_config_proc_macro::config_type; + + #[config_type] + enum Bar { + Foo, + Bar, + #[doc_hint = "foo_bar"] + FooBar, + FooFoo(i32), + } +} diff --git a/src/tools/rustfmt/intellij.md b/src/tools/rustfmt/intellij.md new file mode 100644 index 0000000000..7aea6222b8 --- /dev/null +++ b/src/tools/rustfmt/intellij.md @@ -0,0 +1,26 @@ +# Running Rustfmt from IntelliJ or CLion + +## Installation + +- Install [CLion](https://www.jetbrains.com/clion/), [IntelliJ Ultimate or CE](https://www.jetbrains.com/idea/) through the direct download link or using the [JetBrains Toolbox](https://www.jetbrains.com/toolbox/). + CLion provides a built-in debugger interface but its not free like IntelliJ CE - which does not provide the debugger interface. (IntelliJ seems to lack the toolchain for that, see this discussion [intellij-rust/issues/535](https://github.com/intellij-rust/intellij-rust/issues/535)) + +- Install the [Rust Plugin](https://intellij-rust.github.io/) by navigating to File -> Settings -> Plugins and press "Install JetBrains Plugin" + ![plugins](https://user-images.githubusercontent.com/1133787/47240861-f40af680-d3e9-11e8-9b82-cdd5c8d5f5b8.png) + +- Press "Install" on the rust plugin + ![install rust](https://user-images.githubusercontent.com/1133787/47240803-c0c86780-d3e9-11e8-9265-22f735e4d7ed.png) + +- Restart CLion/IntelliJ + +## Configuration + +- Open the settings window (File -> Settings) and search for "reformat" + ![keymap](https://user-images.githubusercontent.com/1133787/47240922-2ae10c80-d3ea-11e8-9d8f-c798d9749240.png) +- Right-click on "Reformat File with Rustfmt" and assign a keyboard shortcut + + ![shortcut_window](https://user-images.githubusercontent.com/1133787/47240981-5b28ab00-d3ea-11e8-882e-8b864164db74.png) +- Press "OK" + ![shortcut_after](https://user-images.githubusercontent.com/1133787/47241000-6976c700-d3ea-11e8-9342-50ebc2f9f97b.png) + +- Done. You can now use rustfmt in an opened *.rs file with your previously specified shortcut diff --git a/src/tools/rustfmt/legacy-rustfmt.toml b/src/tools/rustfmt/legacy-rustfmt.toml new file mode 100644 index 0000000000..f976fa68e4 --- /dev/null +++ b/src/tools/rustfmt/legacy-rustfmt.toml @@ -0,0 +1,2 @@ +indent_style = "Visual" +combine_control_expr = false diff --git a/src/tools/rustfmt/rust-toolchain b/src/tools/rustfmt/rust-toolchain new file mode 100644 index 0000000000..09ed382937 --- /dev/null +++ b/src/tools/rustfmt/rust-toolchain @@ -0,0 +1 @@ +nightly-2021-02-06 diff --git a/src/tools/rustfmt/rustfmt.toml b/src/tools/rustfmt/rustfmt.toml new file mode 100644 index 0000000000..eccd5f9bd1 --- /dev/null +++ b/src/tools/rustfmt/rustfmt.toml @@ -0,0 +1,3 @@ +error_on_line_overflow = true +error_on_unformatted = true +version = "Two" diff --git a/src/tools/rustfmt/src/attr.rs b/src/tools/rustfmt/src/attr.rs new file mode 100644 index 0000000000..2d25c26327 --- /dev/null +++ b/src/tools/rustfmt/src/attr.rs @@ -0,0 +1,534 @@ +//! Format attributes and meta items. + +use rustc_ast::ast; +use rustc_ast::attr::HasAttrs; +use rustc_span::{symbol::sym, Span, Symbol}; + +use self::doc_comment::DocCommentFormatter; +use crate::comment::{contains_comment, rewrite_doc_comment, CommentStyle}; +use crate::config::lists::*; +use crate::config::IndentStyle; +use crate::expr::rewrite_literal; +use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator}; +use crate::overflow; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::types::{rewrite_path, PathContext}; +use crate::utils::{count_newlines, mk_sp}; + +mod doc_comment; + +pub(crate) fn contains_name(attrs: &[ast::Attribute], name: Symbol) -> bool { + attrs.iter().any(|attr| attr.has_name(name)) +} + +pub(crate) fn first_attr_value_str_by_name( + attrs: &[ast::Attribute], + name: Symbol, +) -> Option { + attrs + .iter() + .find(|attr| attr.has_name(name)) + .and_then(|attr| attr.value_str()) +} + +/// Returns attributes on the given statement. +pub(crate) fn get_attrs_from_stmt(stmt: &ast::Stmt) -> &[ast::Attribute] { + stmt.attrs() +} + +pub(crate) fn get_span_without_attrs(stmt: &ast::Stmt) -> Span { + match stmt.kind { + ast::StmtKind::Local(ref local) => local.span, + ast::StmtKind::Item(ref item) => item.span, + ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => expr.span, + ast::StmtKind::MacCall(ref mac_stmt) => mac_stmt.mac.span(), + ast::StmtKind::Empty => stmt.span, + } +} + +/// Returns attributes that are within `outer_span`. +pub(crate) fn filter_inline_attrs( + attrs: &[ast::Attribute], + outer_span: Span, +) -> Vec { + attrs + .iter() + .filter(|a| outer_span.lo() <= a.span.lo() && a.span.hi() <= outer_span.hi()) + .cloned() + .collect() +} + +fn is_derive(attr: &ast::Attribute) -> bool { + attr.has_name(sym::derive) +} + +// The shape of the arguments to a function-like attribute. +fn argument_shape( + left: usize, + right: usize, + combine: bool, + shape: Shape, + context: &RewriteContext<'_>, +) -> Option { + match context.config.indent_style() { + IndentStyle::Block => { + if combine { + shape.offset_left(left) + } else { + Some( + shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config), + ) + } + } + IndentStyle::Visual => shape + .visual_indent(0) + .shrink_left(left) + .and_then(|s| s.sub_width(right)), + } +} + +fn format_derive( + derives: &[ast::Attribute], + shape: Shape, + context: &RewriteContext<'_>, +) -> Option { + // Collect all items from all attributes + let all_items = derives + .iter() + .map(|attr| { + // Parse the derive items and extract the span for each item; if any + // attribute is not parseable, none of the attributes will be + // reformatted. + let item_spans = attr.meta_item_list().map(|meta_item_list| { + meta_item_list + .into_iter() + .map(|nested_meta_item| nested_meta_item.span()) + })?; + + let items = itemize_list( + context.snippet_provider, + item_spans, + ")", + ",", + |span| span.lo(), + |span| span.hi(), + |span| Some(context.snippet(*span).to_owned()), + attr.span.lo(), + attr.span.hi(), + false, + ); + + Some(items) + }) + // Fail if any attribute failed. + .collect::>>()? + // Collect the results into a single, flat, Vec. + .into_iter() + .flatten() + .collect::>(); + + // Collect formatting parameters. + let prefix = attr_prefix(&derives[0]); + let argument_shape = argument_shape( + "[derive()]".len() + prefix.len(), + ")]".len(), + false, + shape, + context, + )?; + let one_line_shape = shape + .offset_left("[derive()]".len() + prefix.len())? + .sub_width("()]".len())?; + let one_line_budget = one_line_shape.width; + + let tactic = definitive_tactic( + &all_items, + ListTactic::HorizontalVertical, + Separator::Comma, + argument_shape.width, + ); + let trailing_separator = match context.config.indent_style() { + // We always add the trailing comma and remove it if it is not needed. + IndentStyle::Block => SeparatorTactic::Always, + IndentStyle::Visual => SeparatorTactic::Never, + }; + + // Format the collection of items. + let fmt = ListFormatting::new(argument_shape, context.config) + .tactic(tactic) + .trailing_separator(trailing_separator) + .ends_with_newline(false); + let item_str = write_list(&all_items, &fmt)?; + + debug!("item_str: '{}'", item_str); + + // Determine if the result will be nested, i.e. if we're using the block + // indent style and either the items are on multiple lines or we've exceeded + // our budget to fit on a single line. + let nested = context.config.indent_style() == IndentStyle::Block + && (item_str.contains('\n') || item_str.len() > one_line_budget); + + // Format the final result. + let mut result = String::with_capacity(128); + result.push_str(prefix); + result.push_str("[derive("); + if nested { + let nested_indent = argument_shape.indent.to_string_with_newline(context.config); + result.push_str(&nested_indent); + result.push_str(&item_str); + result.push_str(&shape.indent.to_string_with_newline(context.config)); + } else if let SeparatorTactic::Always = context.config.trailing_comma() { + // Retain the trailing comma. + result.push_str(&item_str); + } else if item_str.ends_with(",") { + // Remove the trailing comma. + result.push_str(&item_str[..item_str.len() - 1]); + } else { + result.push_str(&item_str); + } + result.push_str(")]"); + + Some(result) +} + +/// Returns the first group of attributes that fills the given predicate. +/// We consider two doc comments are in different group if they are separated by normal comments. +fn take_while_with_pred<'a, P>( + context: &RewriteContext<'_>, + attrs: &'a [ast::Attribute], + pred: P, +) -> &'a [ast::Attribute] +where + P: Fn(&ast::Attribute) -> bool, +{ + let mut len = 0; + let mut iter = attrs.iter().peekable(); + + while let Some(attr) = iter.next() { + if pred(attr) { + len += 1; + } else { + break; + } + if let Some(next_attr) = iter.peek() { + // Extract comments between two attributes. + let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo()); + let snippet = context.snippet(span_between_attr); + if count_newlines(snippet) >= 2 || snippet.contains('/') { + break; + } + } + } + + &attrs[..len] +} + +/// Rewrite the any doc comments which come before any other attributes. +fn rewrite_initial_doc_comments( + context: &RewriteContext<'_>, + attrs: &[ast::Attribute], + shape: Shape, +) -> Option<(usize, Option)> { + if attrs.is_empty() { + return Some((0, None)); + } + // Rewrite doc comments + let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_doc_comment()); + if !sugared_docs.is_empty() { + let snippet = sugared_docs + .iter() + .map(|a| context.snippet(a.span)) + .collect::>() + .join("\n"); + return Some(( + sugared_docs.len(), + Some(rewrite_doc_comment( + &snippet, + shape.comment(context.config), + context.config, + )?), + )); + } + + Some((0, None)) +} + +impl Rewrite for ast::NestedMetaItem { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match self { + ast::NestedMetaItem::MetaItem(ref meta_item) => meta_item.rewrite(context, shape), + ast::NestedMetaItem::Literal(ref l) => rewrite_literal(context, l, shape), + } + } +} + +fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) { + // Look at before and after comment and see if there are any empty lines. + let comment_begin = comment.find('/'); + let len = comment_begin.unwrap_or_else(|| comment.len()); + let mlb = count_newlines(&comment[..len]) > 1; + let mla = if comment_begin.is_none() { + mlb + } else { + comment + .chars() + .rev() + .take_while(|c| c.is_whitespace()) + .filter(|&c| c == '\n') + .count() + > 1 + }; + (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" }) +} + +impl Rewrite for ast::MetaItem { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + Some(match self.kind { + ast::MetaItemKind::Word => { + rewrite_path(context, PathContext::Type, None, &self.path, shape)? + } + ast::MetaItemKind::List(ref list) => { + let path = rewrite_path(context, PathContext::Type, None, &self.path, shape)?; + let has_trailing_comma = crate::expr::span_ends_with_comma(context, self.span); + overflow::rewrite_with_parens( + context, + &path, + list.iter(), + // 1 = "]" + shape.sub_width(1)?, + self.span, + context.config.width_heuristics().attr_fn_like_width, + Some(if has_trailing_comma { + SeparatorTactic::Always + } else { + SeparatorTactic::Never + }), + )? + } + ast::MetaItemKind::NameValue(ref literal) => { + let path = rewrite_path(context, PathContext::Type, None, &self.path, shape)?; + // 3 = ` = ` + let lit_shape = shape.shrink_left(path.len() + 3)?; + // `rewrite_literal` returns `None` when `literal` exceeds max + // width. Since a literal is basically unformattable unless it + // is a string literal (and only if `format_strings` is set), + // we might be better off ignoring the fact that the attribute + // is longer than the max width and continue on formatting. + // See #2479 for example. + let value = rewrite_literal(context, literal, lit_shape) + .unwrap_or_else(|| context.snippet(literal.span).to_owned()); + format!("{} = {}", path, value) + } + }) + } +} + +impl Rewrite for ast::Attribute { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let snippet = context.snippet(self.span); + if self.is_doc_comment() { + rewrite_doc_comment(snippet, shape.comment(context.config), context.config) + } else { + let should_skip = self + .ident() + .map(|s| context.skip_context.skip_attribute(&s.name.as_str())) + .unwrap_or(false); + let prefix = attr_prefix(self); + + if should_skip || contains_comment(snippet) { + return Some(snippet.to_owned()); + } + + if let Some(ref meta) = self.meta() { + // This attribute is possibly a doc attribute needing normalization to a doc comment + if context.config.normalize_doc_attributes() && meta.has_name(sym::doc) { + if let Some(ref literal) = meta.value_str() { + let comment_style = match self.style { + ast::AttrStyle::Inner => CommentStyle::Doc, + ast::AttrStyle::Outer => CommentStyle::TripleSlash, + }; + + let literal_str = literal.as_str(); + let doc_comment_formatter = + DocCommentFormatter::new(&*literal_str, comment_style); + let doc_comment = format!("{}", doc_comment_formatter); + return rewrite_doc_comment( + &doc_comment, + shape.comment(context.config), + context.config, + ); + } + } + + // 1 = `[` + let shape = shape.offset_left(prefix.len() + 1)?; + Some( + meta.rewrite(context, shape) + .map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)), + ) + } else { + Some(snippet.to_owned()) + } + } + } +} + +impl<'a> Rewrite for [ast::Attribute] { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + if self.is_empty() { + return Some(String::new()); + } + + // The current remaining attributes. + let mut attrs = self; + let mut result = String::new(); + + // This is not just a simple map because we need to handle doc comments + // (where we take as many doc comment attributes as possible) and possibly + // merging derives into a single attribute. + loop { + if attrs.is_empty() { + return Some(result); + } + + // Handle doc comments. + let (doc_comment_len, doc_comment_str) = + rewrite_initial_doc_comments(context, attrs, shape)?; + if doc_comment_len > 0 { + let doc_comment_str = doc_comment_str.expect("doc comments, but no result"); + result.push_str(&doc_comment_str); + + let missing_span = attrs + .get(doc_comment_len) + .map(|next| mk_sp(attrs[doc_comment_len - 1].span.hi(), next.span.lo())); + if let Some(missing_span) = missing_span { + let snippet = context.snippet(missing_span); + let (mla, mlb) = has_newlines_before_after_comment(snippet); + let comment = crate::comment::recover_missing_comment_in_span( + missing_span, + shape.with_max_width(context.config), + context, + 0, + )?; + let comment = if comment.is_empty() { + format!("\n{}", mlb) + } else { + format!("{}{}\n{}", mla, comment, mlb) + }; + result.push_str(&comment); + result.push_str(&shape.indent.to_string(context.config)); + } + + attrs = &attrs[doc_comment_len..]; + + continue; + } + + // Handle derives if we will merge them. + if context.config.merge_derives() && is_derive(&attrs[0]) { + let derives = take_while_with_pred(context, attrs, is_derive); + let derive_str = format_derive(derives, shape, context)?; + result.push_str(&derive_str); + + let missing_span = attrs + .get(derives.len()) + .map(|next| mk_sp(attrs[derives.len() - 1].span.hi(), next.span.lo())); + if let Some(missing_span) = missing_span { + let comment = crate::comment::recover_missing_comment_in_span( + missing_span, + shape.with_max_width(context.config), + context, + 0, + )?; + result.push_str(&comment); + if let Some(next) = attrs.get(derives.len()) { + if next.is_doc_comment() { + let snippet = context.snippet(missing_span); + let (_, mlb) = has_newlines_before_after_comment(snippet); + result.push_str(&mlb); + } + } + result.push('\n'); + result.push_str(&shape.indent.to_string(context.config)); + } + + attrs = &attrs[derives.len()..]; + + continue; + } + + // If we get here, then we have a regular attribute, just handle one + // at a time. + + let formatted_attr = attrs[0].rewrite(context, shape)?; + result.push_str(&formatted_attr); + + let missing_span = attrs + .get(1) + .map(|next| mk_sp(attrs[0].span.hi(), next.span.lo())); + if let Some(missing_span) = missing_span { + let comment = crate::comment::recover_missing_comment_in_span( + missing_span, + shape.with_max_width(context.config), + context, + 0, + )?; + result.push_str(&comment); + if let Some(next) = attrs.get(1) { + if next.is_doc_comment() { + let snippet = context.snippet(missing_span); + let (_, mlb) = has_newlines_before_after_comment(snippet); + result.push_str(&mlb); + } + } + result.push('\n'); + result.push_str(&shape.indent.to_string(context.config)); + } + + attrs = &attrs[1..]; + } + } +} + +fn attr_prefix(attr: &ast::Attribute) -> &'static str { + match attr.style { + ast::AttrStyle::Inner => "#!", + ast::AttrStyle::Outer => "#", + } +} + +pub(crate) trait MetaVisitor<'ast> { + fn visit_meta_item(&mut self, meta_item: &'ast ast::MetaItem) { + match meta_item.kind { + ast::MetaItemKind::Word => self.visit_meta_word(meta_item), + ast::MetaItemKind::List(ref list) => self.visit_meta_list(meta_item, list), + ast::MetaItemKind::NameValue(ref lit) => self.visit_meta_name_value(meta_item, lit), + } + } + + fn visit_meta_list( + &mut self, + _meta_item: &'ast ast::MetaItem, + list: &'ast [ast::NestedMetaItem], + ) { + for nm in list { + self.visit_nested_meta_item(nm); + } + } + + fn visit_meta_word(&mut self, _meta_item: &'ast ast::MetaItem) {} + + fn visit_meta_name_value(&mut self, _meta_item: &'ast ast::MetaItem, _lit: &'ast ast::Lit) {} + + fn visit_nested_meta_item(&mut self, nm: &'ast ast::NestedMetaItem) { + match nm { + ast::NestedMetaItem::MetaItem(ref meta_item) => self.visit_meta_item(meta_item), + ast::NestedMetaItem::Literal(ref lit) => self.visit_literal(lit), + } + } + + fn visit_literal(&mut self, _lit: &'ast ast::Lit) {} +} diff --git a/src/tools/rustfmt/src/attr/doc_comment.rs b/src/tools/rustfmt/src/attr/doc_comment.rs new file mode 100644 index 0000000000..c3dcb84c94 --- /dev/null +++ b/src/tools/rustfmt/src/attr/doc_comment.rs @@ -0,0 +1,83 @@ +use crate::comment::CommentStyle; +use std::fmt::{self, Display}; + +/// Formats a string as a doc comment using the given [`CommentStyle`]. +#[derive(new)] +pub(super) struct DocCommentFormatter<'a> { + literal: &'a str, + style: CommentStyle<'a>, +} + +impl Display for DocCommentFormatter<'_> { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let opener = self.style.opener().trim_end(); + let mut lines = self.literal.lines().peekable(); + + // Handle `#[doc = ""]`. + if lines.peek().is_none() { + return write!(formatter, "{}", opener); + } + + while let Some(line) = lines.next() { + let is_last_line = lines.peek().is_none(); + if is_last_line { + write!(formatter, "{}{}", opener, line)?; + } else { + writeln!(formatter, "{}{}", opener, line)?; + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn literal_controls_leading_spaces() { + test_doc_comment_is_formatted_correctly( + " Lorem ipsum", + "/// Lorem ipsum", + CommentStyle::TripleSlash, + ); + } + + #[test] + fn single_line_doc_comment_is_formatted_correctly() { + test_doc_comment_is_formatted_correctly( + "Lorem ipsum", + "///Lorem ipsum", + CommentStyle::TripleSlash, + ); + } + + #[test] + fn multi_line_doc_comment_is_formatted_correctly() { + test_doc_comment_is_formatted_correctly( + "Lorem ipsum\nDolor sit amet", + "///Lorem ipsum\n///Dolor sit amet", + CommentStyle::TripleSlash, + ); + } + + #[test] + fn whitespace_within_lines_is_preserved() { + test_doc_comment_is_formatted_correctly( + " Lorem ipsum \n Dolor sit amet ", + "/// Lorem ipsum \n/// Dolor sit amet ", + CommentStyle::TripleSlash, + ); + } + + fn test_doc_comment_is_formatted_correctly( + literal: &str, + expected_comment: &str, + style: CommentStyle<'_>, + ) { + assert_eq!( + expected_comment, + format!("{}", DocCommentFormatter::new(&literal, style)) + ); + } +} diff --git a/src/tools/rustfmt/src/bin/main.rs b/src/tools/rustfmt/src/bin/main.rs new file mode 100644 index 0000000000..9101c015fb --- /dev/null +++ b/src/tools/rustfmt/src/bin/main.rs @@ -0,0 +1,705 @@ +use anyhow::{format_err, Result}; +use env_logger; +use io::Error as IoError; +use thiserror::Error; + +use rustfmt_nightly as rustfmt; + +use std::collections::HashMap; +use std::env; +use std::fs::File; +use std::io::{self, stdout, Read, Write}; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +use getopts::{Matches, Options}; + +use crate::rustfmt::{ + load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, + FormatReportFormatterBuilder, Input, Session, Verbosity, +}; + +fn main() { + env_logger::init(); + let opts = make_opts(); + + let exit_code = match execute(&opts) { + Ok(code) => code, + Err(e) => { + eprintln!("{}", e.to_string()); + 1 + } + }; + // Make sure standard output is flushed before we exit. + std::io::stdout().flush().unwrap(); + + // Exit with given exit code. + // + // NOTE: this immediately terminates the process without doing any cleanup, + // so make sure to finish all necessary cleanup before this is called. + std::process::exit(exit_code); +} + +/// Rustfmt operations. +enum Operation { + /// Format files and their child modules. + Format { + files: Vec, + minimal_config_path: Option, + }, + /// Print the help message. + Help(HelpOp), + /// Print version information + Version, + /// Output default config to a file, or stdout if None + ConfigOutputDefault { path: Option }, + /// Output current config (as if formatting to a file) to stdout + ConfigOutputCurrent { path: Option }, + /// No file specified, read from stdin + Stdin { input: String }, +} + +/// Rustfmt operations errors. +#[derive(Error, Debug)] +pub enum OperationError { + /// An unknown help topic was requested. + #[error("Unknown help topic: `{0}`.")] + UnknownHelpTopic(String), + /// An unknown print-config option was requested. + #[error("Unknown print-config option: `{0}`.")] + UnknownPrintConfigTopic(String), + /// Attempt to generate a minimal config from standard input. + #[error("The `--print-config=minimal` option doesn't work with standard input.")] + MinimalPathWithStdin, + /// An io error during reading or writing. + #[error("{0}")] + IoError(IoError), + /// Attempt to use --check with stdin, which isn't currently + /// supported. + #[error("The `--check` option is not supported with standard input.")] + CheckWithStdin, + /// Attempt to use --emit=json with stdin, which isn't currently + /// supported. + #[error("Using `--emit` other than stdout is not supported with standard input.")] + EmitWithStdin, +} + +impl From for OperationError { + fn from(e: IoError) -> OperationError { + OperationError::IoError(e) + } +} + +/// Arguments to `--help` +enum HelpOp { + None, + Config, + FileLines, +} + +fn make_opts() -> Options { + let mut opts = Options::new(); + + opts.optflag( + "", + "check", + "Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits \ + with 1 and prints a diff if formatting is required.", + ); + let is_nightly = is_nightly(); + let emit_opts = if is_nightly { + "[files|stdout|coverage|checkstyle|json]" + } else { + "[files|stdout]" + }; + opts.optopt("", "emit", "What data to emit and how", emit_opts); + opts.optflag("", "backup", "Backup any modified files."); + opts.optopt( + "", + "config-path", + "Recursively searches the given path for the rustfmt.toml config file. If not \ + found reverts to the input file path", + "[Path for the configuration file]", + ); + opts.optopt("", "edition", "Rust edition to use", "[2015|2018]"); + opts.optopt( + "", + "color", + "Use colored output (if supported)", + "[always|never|auto]", + ); + opts.optopt( + "", + "print-config", + "Dumps a default or minimal config to PATH. A minimal config is the \ + subset of the current config file used for formatting the current program. \ + `current` writes to stdout current config as if formatting the file at PATH.", + "[default|minimal|current] PATH", + ); + opts.optflag( + "l", + "files-with-diff", + "Prints the names of mismatched files that were formatted. Prints the names of \ + files that would be formated when used with `--check` mode. ", + ); + opts.optmulti( + "", + "config", + "Set options from command line. These settings take priority over .rustfmt.toml", + "[key1=val1,key2=val2...]", + ); + + if is_nightly { + opts.optflag( + "", + "unstable-features", + "Enables unstable features. Only available on nightly channel.", + ); + opts.optopt( + "", + "file-lines", + "Format specified line ranges. Run with `--help=file-lines` for \ + more detail (unstable).", + "JSON", + ); + opts.optflag( + "", + "error-on-unformatted", + "Error if unable to get comments or string literals within max_width, \ + or they are left with trailing whitespaces (unstable).", + ); + opts.optflag( + "", + "skip-children", + "Don't reformat child modules (unstable).", + ); + } + + opts.optflag("v", "verbose", "Print verbose output"); + opts.optflag("q", "quiet", "Print less output"); + opts.optflag("V", "version", "Show version information"); + opts.optflagopt( + "h", + "help", + "Show this message or help about a specific topic: `config` or `file-lines`", + "=TOPIC", + ); + + opts +} + +fn is_nightly() -> bool { + option_env!("CFG_RELEASE_CHANNEL").map_or(true, |c| c == "nightly" || c == "dev") +} + +// Returned i32 is an exit code +fn execute(opts: &Options) -> Result { + let matches = opts.parse(env::args().skip(1))?; + let options = GetOptsOptions::from_matches(&matches)?; + + match determine_operation(&matches)? { + Operation::Help(HelpOp::None) => { + print_usage_to_stdout(opts, ""); + Ok(0) + } + Operation::Help(HelpOp::Config) => { + Config::print_docs(&mut stdout(), options.unstable_features); + Ok(0) + } + Operation::Help(HelpOp::FileLines) => { + print_help_file_lines(); + Ok(0) + } + Operation::Version => { + print_version(); + Ok(0) + } + Operation::ConfigOutputDefault { path } => { + let toml = Config::default().all_options().to_toml()?; + if let Some(path) = path { + let mut file = File::create(path)?; + file.write_all(toml.as_bytes())?; + } else { + io::stdout().write_all(toml.as_bytes())?; + } + Ok(0) + } + Operation::ConfigOutputCurrent { path } => { + let path = match path { + Some(path) => path, + None => return Err(format_err!("PATH required for `--print-config current`")), + }; + + let file = PathBuf::from(path); + let file = file.canonicalize().unwrap_or(file); + + let (config, _) = load_config(Some(file.parent().unwrap()), Some(options.clone()))?; + let toml = config.all_options().to_toml()?; + io::stdout().write_all(toml.as_bytes())?; + + Ok(0) + } + Operation::Stdin { input } => format_string(input, options), + Operation::Format { + files, + minimal_config_path, + } => format(files, minimal_config_path, &options), + } +} + +fn format_string(input: String, options: GetOptsOptions) -> Result { + // try to read config from local directory + let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?; + + if options.check { + return Err(OperationError::CheckWithStdin.into()); + } + if let Some(emit_mode) = options.emit_mode { + if emit_mode != EmitMode::Stdout { + return Err(OperationError::EmitWithStdin.into()); + } + } + // emit mode is always Stdout for Stdin. + config.set().emit_mode(EmitMode::Stdout); + config.set().verbose(Verbosity::Quiet); + + // parse file_lines + config.set().file_lines(options.file_lines); + for f in config.file_lines().files() { + match *f { + FileName::Stdin => {} + _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f), + } + } + + let out = &mut stdout(); + let mut session = Session::new(config, Some(out)); + format_and_emit_report(&mut session, Input::Text(input)); + + let exit_code = if session.has_operational_errors() || session.has_parsing_errors() { + 1 + } else { + 0 + }; + Ok(exit_code) +} + +fn format( + files: Vec, + minimal_config_path: Option, + options: &GetOptsOptions, +) -> Result { + options.verify_file_lines(&files); + let (config, config_path) = load_config(None, Some(options.clone()))?; + + if config.verbose() == Verbosity::Verbose { + if let Some(path) = config_path.as_ref() { + println!("Using rustfmt config file {}", path.display()); + } + } + + let out = &mut stdout(); + let mut session = Session::new(config, Some(out)); + + for file in files { + if !file.exists() { + eprintln!("Error: file `{}` does not exist", file.to_str().unwrap()); + session.add_operational_error(); + } else if file.is_dir() { + eprintln!("Error: `{}` is a directory", file.to_str().unwrap()); + session.add_operational_error(); + } else { + // Check the file directory if the config-path could not be read or not provided + if config_path.is_none() { + let (local_config, config_path) = + load_config(Some(file.parent().unwrap()), Some(options.clone()))?; + if local_config.verbose() == Verbosity::Verbose { + if let Some(path) = config_path { + println!( + "Using rustfmt config file {} for {}", + path.display(), + file.display() + ); + } + } + + session.override_config(local_config, |sess| { + format_and_emit_report(sess, Input::File(file)) + }); + } else { + format_and_emit_report(&mut session, Input::File(file)); + } + } + } + + // If we were given a path via dump-minimal-config, output any options + // that were used during formatting as TOML. + if let Some(path) = minimal_config_path { + let mut file = File::create(path)?; + let toml = session.config.used_options().to_toml()?; + file.write_all(toml.as_bytes())?; + } + + let exit_code = if session.has_operational_errors() + || session.has_parsing_errors() + || ((session.has_diff() || session.has_check_errors()) && options.check) + { + 1 + } else { + 0 + }; + Ok(exit_code) +} + +fn format_and_emit_report(session: &mut Session<'_, T>, input: Input) { + match session.format(input) { + Ok(report) => { + if report.has_warnings() { + eprintln!( + "{}", + FormatReportFormatterBuilder::new(&report) + .enable_colors(should_print_with_colors(session)) + .build() + ); + } + } + Err(msg) => { + eprintln!("Error writing files: {}", msg); + session.add_operational_error(); + } + } +} + +fn should_print_with_colors(session: &mut Session<'_, T>) -> bool { + match term::stderr() { + Some(ref t) + if session.config.color().use_colored_tty() + && t.supports_color() + && t.supports_attr(term::Attr::Bold) => + { + true + } + _ => false, + } +} + +fn print_usage_to_stdout(opts: &Options, reason: &str) { + let sep = if reason.is_empty() { + String::new() + } else { + format!("{}\n\n", reason) + }; + let msg = format!( + "{}Format Rust code\n\nusage: {} [options] ...", + sep, + env::args_os().next().unwrap().to_string_lossy() + ); + println!("{}", opts.usage(&msg)); +} + +fn print_help_file_lines() { + println!( + "If you want to restrict reformatting to specific sets of lines, you can +use the `--file-lines` option. Its argument is a JSON array of objects +with `file` and `range` properties, where `file` is a file name, and +`range` is an array representing a range of lines like `[7,13]`. Ranges +are 1-based and inclusive of both end points. Specifying an empty array +will result in no files being formatted. For example, + +``` +rustfmt --file-lines '[ + {{\"file\":\"src/lib.rs\",\"range\":[7,13]}}, + {{\"file\":\"src/lib.rs\",\"range\":[21,29]}}, + {{\"file\":\"src/foo.rs\",\"range\":[10,11]}}, + {{\"file\":\"src/foo.rs\",\"range\":[15,15]}}]' +``` + +would format lines `7-13` and `21-29` of `src/lib.rs`, and lines `10-11`, +and `15` of `src/foo.rs`. No other files would be formatted, even if they +are included as out of line modules from `src/lib.rs`." + ); +} + +fn print_version() { + let version_info = format!( + "{}-{}", + option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"), + include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt")) + ); + + println!("rustfmt {}", version_info); +} + +fn determine_operation(matches: &Matches) -> Result { + if matches.opt_present("h") { + let topic = matches.opt_str("h"); + if topic == None { + return Ok(Operation::Help(HelpOp::None)); + } else if topic == Some("config".to_owned()) { + return Ok(Operation::Help(HelpOp::Config)); + } else if topic == Some("file-lines".to_owned()) { + return Ok(Operation::Help(HelpOp::FileLines)); + } else { + return Err(OperationError::UnknownHelpTopic(topic.unwrap())); + } + } + let mut free_matches = matches.free.iter(); + + let mut minimal_config_path = None; + if let Some(kind) = matches.opt_str("print-config") { + let path = free_matches.next().cloned(); + match kind.as_str() { + "default" => return Ok(Operation::ConfigOutputDefault { path }), + "current" => return Ok(Operation::ConfigOutputCurrent { path }), + "minimal" => { + minimal_config_path = path; + if minimal_config_path.is_none() { + eprintln!("WARNING: PATH required for `--print-config minimal`."); + } + } + _ => { + return Err(OperationError::UnknownPrintConfigTopic(kind)); + } + } + } + + if matches.opt_present("version") { + return Ok(Operation::Version); + } + + let files: Vec<_> = free_matches + .map(|s| { + let p = PathBuf::from(s); + // we will do comparison later, so here tries to canonicalize first + // to get the expected behavior. + p.canonicalize().unwrap_or(p) + }) + .collect(); + + // if no file argument is supplied, read from stdin + if files.is_empty() { + if minimal_config_path.is_some() { + return Err(OperationError::MinimalPathWithStdin); + } + let mut buffer = String::new(); + io::stdin().read_to_string(&mut buffer)?; + + return Ok(Operation::Stdin { input: buffer }); + } + + Ok(Operation::Format { + files, + minimal_config_path, + }) +} + +const STABLE_EMIT_MODES: [EmitMode; 3] = [EmitMode::Files, EmitMode::Stdout, EmitMode::Diff]; + +/// Parsed command line options. +#[derive(Clone, Debug, Default)] +struct GetOptsOptions { + skip_children: Option, + quiet: bool, + verbose: bool, + config_path: Option, + inline_config: HashMap, + emit_mode: Option, + backup: bool, + check: bool, + edition: Option, + color: Option, + file_lines: FileLines, // Default is all lines in all files. + unstable_features: bool, + error_on_unformatted: Option, + print_misformatted_file_names: bool, +} + +impl GetOptsOptions { + pub fn from_matches(matches: &Matches) -> Result { + let mut options = GetOptsOptions::default(); + options.verbose = matches.opt_present("verbose"); + options.quiet = matches.opt_present("quiet"); + if options.verbose && options.quiet { + return Err(format_err!("Can't use both `--verbose` and `--quiet`")); + } + + let rust_nightly = is_nightly(); + + if rust_nightly { + options.unstable_features = matches.opt_present("unstable-features"); + + if options.unstable_features { + if matches.opt_present("skip-children") { + options.skip_children = Some(true); + } + if matches.opt_present("error-on-unformatted") { + options.error_on_unformatted = Some(true); + } + if let Some(ref file_lines) = matches.opt_str("file-lines") { + options.file_lines = file_lines.parse()?; + } + } else { + let mut unstable_options = vec![]; + if matches.opt_present("skip-children") { + unstable_options.push("`--skip-children`"); + } + if matches.opt_present("error-on-unformatted") { + unstable_options.push("`--error-on-unformatted`"); + } + if matches.opt_present("file-lines") { + unstable_options.push("`--file-lines`"); + } + if !unstable_options.is_empty() { + let s = if unstable_options.len() == 1 { "" } else { "s" }; + return Err(format_err!( + "Unstable option{} ({}) used without `--unstable-features`", + s, + unstable_options.join(", "), + )); + } + } + } + + options.config_path = matches.opt_str("config-path").map(PathBuf::from); + + options.inline_config = matches + .opt_strs("config") + .iter() + .flat_map(|config| config.split(",")) + .map( + |key_val| match key_val.char_indices().find(|(_, ch)| *ch == '=') { + Some((middle, _)) => { + let (key, val) = (&key_val[..middle], &key_val[middle + 1..]); + if !Config::is_valid_key_val(key, val) { + Err(format_err!("invalid key=val pair: `{}`", key_val)) + } else { + Ok((key.to_string(), val.to_string())) + } + } + + None => Err(format_err!( + "--config expects comma-separated list of key=val pairs, found `{}`", + key_val + )), + }, + ) + .collect::, _>>()?; + + options.check = matches.opt_present("check"); + if let Some(ref emit_str) = matches.opt_str("emit") { + if options.check { + return Err(format_err!("Invalid to use `--emit` and `--check`")); + } + + options.emit_mode = Some(emit_mode_from_emit_str(emit_str)?); + } + + if let Some(ref edition_str) = matches.opt_str("edition") { + options.edition = Some(edition_from_edition_str(edition_str)?); + } + + if matches.opt_present("backup") { + options.backup = true; + } + + if matches.opt_present("files-with-diff") { + options.print_misformatted_file_names = true; + } + + if !rust_nightly { + if let Some(ref emit_mode) = options.emit_mode { + if !STABLE_EMIT_MODES.contains(emit_mode) { + return Err(format_err!( + "Invalid value for `--emit` - using an unstable \ + value without `--unstable-features`", + )); + } + } + } + + if let Some(ref color) = matches.opt_str("color") { + match Color::from_str(color) { + Ok(color) => options.color = Some(color), + _ => return Err(format_err!("Invalid color: {}", color)), + } + } + + Ok(options) + } + + fn verify_file_lines(&self, files: &[PathBuf]) { + for f in self.file_lines.files() { + match *f { + FileName::Real(ref f) if files.contains(f) => {} + FileName::Real(_) => { + eprintln!("Warning: Extra file listed in file_lines option '{}'", f) + } + FileName::Stdin => eprintln!("Warning: Not a file '{}'", f), + } + } + } +} + +impl CliOptions for GetOptsOptions { + fn apply_to(self, config: &mut Config) { + if self.verbose { + config.set().verbose(Verbosity::Verbose); + } else if self.quiet { + config.set().verbose(Verbosity::Quiet); + } else { + config.set().verbose(Verbosity::Normal); + } + config.set().file_lines(self.file_lines); + config.set().unstable_features(self.unstable_features); + if let Some(skip_children) = self.skip_children { + config.set().skip_children(skip_children); + } + if let Some(error_on_unformatted) = self.error_on_unformatted { + config.set().error_on_unformatted(error_on_unformatted); + } + if let Some(edition) = self.edition { + config.set().edition(edition); + } + if self.check { + config.set().emit_mode(EmitMode::Diff); + } else if let Some(emit_mode) = self.emit_mode { + config.set().emit_mode(emit_mode); + } + if self.backup { + config.set().make_backup(true); + } + if let Some(color) = self.color { + config.set().color(color); + } + if self.print_misformatted_file_names { + config.set().print_misformatted_file_names(true); + } + + for (key, val) in self.inline_config { + config.override_value(&key, &val); + } + } + + fn config_path(&self) -> Option<&Path> { + self.config_path.as_ref().map(|p| &**p) + } +} + +fn edition_from_edition_str(edition_str: &str) -> Result { + match edition_str { + "2015" => Ok(Edition::Edition2015), + "2018" => Ok(Edition::Edition2018), + _ => Err(format_err!("Invalid value for `--edition`")), + } +} + +fn emit_mode_from_emit_str(emit_str: &str) -> Result { + match emit_str { + "files" => Ok(EmitMode::Files), + "stdout" => Ok(EmitMode::Stdout), + "coverage" => Ok(EmitMode::Coverage), + "checkstyle" => Ok(EmitMode::Checkstyle), + "json" => Ok(EmitMode::Json), + _ => Err(format_err!("Invalid value for `--emit`")), + } +} diff --git a/src/tools/rustfmt/src/cargo-fmt/main.rs b/src/tools/rustfmt/src/cargo-fmt/main.rs new file mode 100644 index 0000000000..22aeceeb53 --- /dev/null +++ b/src/tools/rustfmt/src/cargo-fmt/main.rs @@ -0,0 +1,762 @@ +// Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/). + +#![deny(warnings)] + +use cargo_metadata; + +use std::cmp::Ordering; +use std::collections::{BTreeMap, BTreeSet}; +use std::env; +use std::ffi::OsStr; +use std::fs; +use std::hash::{Hash, Hasher}; +use std::io::{self, Write}; +use std::iter::FromIterator; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::str; + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt( + bin_name = "cargo fmt", + about = "This utility formats all bin and lib files of \ + the current crate using rustfmt." +)] +pub struct Opts { + /// No output printed to stdout + #[structopt(short = "q", long = "quiet")] + quiet: bool, + + /// Use verbose output + #[structopt(short = "v", long = "verbose")] + verbose: bool, + + /// Print rustfmt version and exit + #[structopt(long = "version")] + version: bool, + + /// Specify package to format (only usable in workspaces) + #[structopt(short = "p", long = "package", value_name = "package")] + packages: Vec, + + /// Specify path to Cargo.toml + #[structopt(long = "manifest-path", value_name = "manifest-path")] + manifest_path: Option, + + /// Specify message-format: short|json|human + #[structopt(long = "message-format", value_name = "message-format")] + message_format: Option, + + /// Options passed to rustfmt + // 'raw = true' to make `--` explicit. + #[structopt(name = "rustfmt_options", raw(true))] + rustfmt_options: Vec, + + /// Format all packages (only usable in workspaces) + #[structopt(long = "all")] + format_all: bool, +} + +fn main() { + let exit_status = execute(); + std::io::stdout().flush().unwrap(); + std::process::exit(exit_status); +} + +const SUCCESS: i32 = 0; +const FAILURE: i32 = 1; + +fn execute() -> i32 { + // Drop extra `fmt` argument provided by `cargo`. + let mut found_fmt = false; + let args = env::args().filter(|x| { + if found_fmt { + true + } else { + found_fmt = x == "fmt"; + x != "fmt" + } + }); + + let opts = Opts::from_iter(args); + + let verbosity = match (opts.verbose, opts.quiet) { + (false, false) => Verbosity::Normal, + (false, true) => Verbosity::Quiet, + (true, false) => Verbosity::Verbose, + (true, true) => { + print_usage_to_stderr("quiet mode and verbose mode are not compatible"); + return FAILURE; + } + }; + + if opts.version { + return handle_command_status(get_rustfmt_info(&[String::from("--version")])); + } + if opts.rustfmt_options.iter().any(|s| { + ["--print-config", "-h", "--help", "-V", "--version"].contains(&s.as_str()) + || s.starts_with("--help=") + || s.starts_with("--print-config=") + }) { + return handle_command_status(get_rustfmt_info(&opts.rustfmt_options)); + } + + let strategy = CargoFmtStrategy::from_opts(&opts); + let mut rustfmt_args = opts.rustfmt_options; + if let Some(message_format) = opts.message_format { + if let Err(msg) = convert_message_format_to_rustfmt_args(&message_format, &mut rustfmt_args) + { + print_usage_to_stderr(&msg); + return FAILURE; + } + } + + if let Some(specified_manifest_path) = opts.manifest_path { + if !specified_manifest_path.ends_with("Cargo.toml") { + print_usage_to_stderr("the manifest-path must be a path to a Cargo.toml file"); + return FAILURE; + } + let manifest_path = PathBuf::from(specified_manifest_path); + handle_command_status(format_crate( + verbosity, + &strategy, + rustfmt_args, + Some(&manifest_path), + )) + } else { + handle_command_status(format_crate(verbosity, &strategy, rustfmt_args, None)) + } +} + +fn rustfmt_command() -> Command { + let rustfmt_var = env::var_os("RUSTFMT"); + let rustfmt = match &rustfmt_var { + Some(rustfmt) => rustfmt, + None => OsStr::new("rustfmt"), + }; + Command::new(rustfmt) +} + +fn convert_message_format_to_rustfmt_args( + message_format: &str, + rustfmt_args: &mut Vec, +) -> Result<(), String> { + let mut contains_emit_mode = false; + let mut contains_check = false; + let mut contains_list_files = false; + for arg in rustfmt_args.iter() { + if arg.starts_with("--emit") { + contains_emit_mode = true; + } + if arg == "--check" { + contains_check = true; + } + if arg == "-l" || arg == "--files-with-diff" { + contains_list_files = true; + } + } + match message_format { + "short" => { + if !contains_list_files { + rustfmt_args.push(String::from("-l")); + } + Ok(()) + } + "json" => { + if contains_emit_mode { + return Err(String::from( + "cannot include --emit arg when --message-format is set to json", + )); + } + if contains_check { + return Err(String::from( + "cannot include --check arg when --message-format is set to json", + )); + } + rustfmt_args.push(String::from("--emit")); + rustfmt_args.push(String::from("json")); + Ok(()) + } + "human" => Ok(()), + _ => { + return Err(format!( + "invalid --message-format value: {}. Allowed values are: short|json|human", + message_format + )); + } + } +} + +fn print_usage_to_stderr(reason: &str) { + eprintln!("{}", reason); + let app = Opts::clap(); + app.after_help("") + .write_help(&mut io::stderr()) + .expect("failed to write to stderr"); +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Verbosity { + Verbose, + Normal, + Quiet, +} + +fn handle_command_status(status: Result) -> i32 { + match status { + Err(e) => { + print_usage_to_stderr(&e.to_string()); + FAILURE + } + Ok(status) => status, + } +} + +fn get_rustfmt_info(args: &[String]) -> Result { + let mut command = rustfmt_command() + .stdout(std::process::Stdio::inherit()) + .args(args) + .spawn() + .map_err(|e| match e.kind() { + io::ErrorKind::NotFound => io::Error::new( + io::ErrorKind::Other, + "Could not run rustfmt, please make sure it is in your PATH.", + ), + _ => e, + })?; + let result = command.wait()?; + if result.success() { + Ok(SUCCESS) + } else { + Ok(result.code().unwrap_or(SUCCESS)) + } +} + +fn format_crate( + verbosity: Verbosity, + strategy: &CargoFmtStrategy, + rustfmt_args: Vec, + manifest_path: Option<&Path>, +) -> Result { + let targets = get_targets(strategy, manifest_path)?; + + // Currently only bin and lib files get formatted. + run_rustfmt(&targets, &rustfmt_args, verbosity) +} + +/// Target uses a `path` field for equality and hashing. +#[derive(Debug)] +pub struct Target { + /// A path to the main source file of the target. + path: PathBuf, + /// A kind of target (e.g., lib, bin, example, ...). + kind: String, + /// Rust edition for this target. + edition: String, +} + +impl Target { + pub fn from_target(target: &cargo_metadata::Target) -> Self { + let path = PathBuf::from(&target.src_path); + let canonicalized = fs::canonicalize(&path).unwrap_or(path); + + Target { + path: canonicalized, + kind: target.kind[0].clone(), + edition: target.edition.clone(), + } + } +} + +impl PartialEq for Target { + fn eq(&self, other: &Target) -> bool { + self.path == other.path + } +} + +impl PartialOrd for Target { + fn partial_cmp(&self, other: &Target) -> Option { + Some(self.path.cmp(&other.path)) + } +} + +impl Ord for Target { + fn cmp(&self, other: &Target) -> Ordering { + self.path.cmp(&other.path) + } +} + +impl Eq for Target {} + +impl Hash for Target { + fn hash(&self, state: &mut H) { + self.path.hash(state); + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum CargoFmtStrategy { + /// Format every packages and dependencies. + All, + /// Format packages that are specified by the command line argument. + Some(Vec), + /// Format the root packages only. + Root, +} + +impl CargoFmtStrategy { + pub fn from_opts(opts: &Opts) -> CargoFmtStrategy { + match (opts.format_all, opts.packages.is_empty()) { + (false, true) => CargoFmtStrategy::Root, + (true, _) => CargoFmtStrategy::All, + (false, false) => CargoFmtStrategy::Some(opts.packages.clone()), + } + } +} + +/// Based on the specified `CargoFmtStrategy`, returns a set of main source files. +fn get_targets( + strategy: &CargoFmtStrategy, + manifest_path: Option<&Path>, +) -> Result, io::Error> { + let mut targets = BTreeSet::new(); + + match *strategy { + CargoFmtStrategy::Root => get_targets_root_only(manifest_path, &mut targets)?, + CargoFmtStrategy::All => { + get_targets_recursive(manifest_path, &mut targets, &mut BTreeSet::new())? + } + CargoFmtStrategy::Some(ref hitlist) => { + get_targets_with_hitlist(manifest_path, hitlist, &mut targets)? + } + } + + if targets.is_empty() { + Err(io::Error::new( + io::ErrorKind::Other, + "Failed to find targets".to_owned(), + )) + } else { + Ok(targets) + } +} + +fn get_targets_root_only( + manifest_path: Option<&Path>, + targets: &mut BTreeSet, +) -> Result<(), io::Error> { + let metadata = get_cargo_metadata(manifest_path, false)?; + let workspace_root_path = PathBuf::from(&metadata.workspace_root).canonicalize()?; + let (in_workspace_root, current_dir_manifest) = if let Some(target_manifest) = manifest_path { + ( + workspace_root_path == target_manifest, + target_manifest.canonicalize()?, + ) + } else { + let current_dir = env::current_dir()?.canonicalize()?; + ( + workspace_root_path == current_dir, + current_dir.join("Cargo.toml"), + ) + }; + + let package_targets = match metadata.packages.len() { + 1 => metadata.packages.into_iter().next().unwrap().targets, + _ => metadata + .packages + .into_iter() + .filter(|p| { + in_workspace_root + || PathBuf::from(&p.manifest_path) + .canonicalize() + .unwrap_or_default() + == current_dir_manifest + }) + .map(|p| p.targets) + .flatten() + .collect(), + }; + + for target in package_targets { + targets.insert(Target::from_target(&target)); + } + + Ok(()) +} + +fn get_targets_recursive( + manifest_path: Option<&Path>, + mut targets: &mut BTreeSet, + visited: &mut BTreeSet, +) -> Result<(), io::Error> { + let metadata = get_cargo_metadata(manifest_path, false)?; + let metadata_with_deps = get_cargo_metadata(manifest_path, true)?; + + for package in metadata.packages { + add_targets(&package.targets, &mut targets); + + // Look for local dependencies. + for dependency in package.dependencies { + if dependency.source.is_some() || visited.contains(&dependency.name) { + continue; + } + + let dependency_package = metadata_with_deps + .packages + .iter() + .find(|p| p.name == dependency.name && p.source.is_none()); + let manifest_path = if dependency_package.is_some() { + PathBuf::from(&dependency_package.unwrap().manifest_path) + } else { + let mut package_manifest_path = PathBuf::from(&package.manifest_path); + package_manifest_path.pop(); + package_manifest_path.push(&dependency.name); + package_manifest_path.push("Cargo.toml"); + package_manifest_path + }; + + if manifest_path.exists() { + visited.insert(dependency.name); + get_targets_recursive(Some(&manifest_path), &mut targets, visited)?; + } + } + } + + Ok(()) +} + +fn get_targets_with_hitlist( + manifest_path: Option<&Path>, + hitlist: &[String], + targets: &mut BTreeSet, +) -> Result<(), io::Error> { + let metadata = get_cargo_metadata(manifest_path, false)?; + + let mut workspace_hitlist: BTreeSet<&String> = BTreeSet::from_iter(hitlist); + + for package in metadata.packages { + if workspace_hitlist.remove(&package.name) { + for target in package.targets { + targets.insert(Target::from_target(&target)); + } + } + } + + if workspace_hitlist.is_empty() { + Ok(()) + } else { + let package = workspace_hitlist.iter().next().unwrap(); + Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("package `{}` is not a member of the workspace", package), + )) + } +} + +fn add_targets(target_paths: &[cargo_metadata::Target], targets: &mut BTreeSet) { + for target in target_paths { + targets.insert(Target::from_target(target)); + } +} + +fn run_rustfmt( + targets: &BTreeSet, + fmt_args: &[String], + verbosity: Verbosity, +) -> Result { + let by_edition = targets + .iter() + .inspect(|t| { + if verbosity == Verbosity::Verbose { + println!("[{} ({})] {:?}", t.kind, t.edition, t.path) + } + }) + .fold(BTreeMap::new(), |mut h, t| { + h.entry(&t.edition).or_insert_with(Vec::new).push(&t.path); + h + }); + + let mut status = vec![]; + for (edition, files) in by_edition { + let stdout = if verbosity == Verbosity::Quiet { + std::process::Stdio::null() + } else { + std::process::Stdio::inherit() + }; + + if verbosity == Verbosity::Verbose { + print!("rustfmt"); + print!(" --edition {}", edition); + fmt_args.iter().for_each(|f| print!(" {}", f)); + files.iter().for_each(|f| print!(" {}", f.display())); + println!(); + } + + let mut command = rustfmt_command() + .stdout(stdout) + .args(files) + .args(&["--edition", edition]) + .args(fmt_args) + .spawn() + .map_err(|e| match e.kind() { + io::ErrorKind::NotFound => io::Error::new( + io::ErrorKind::Other, + "Could not run rustfmt, please make sure it is in your PATH.", + ), + _ => e, + })?; + + status.push(command.wait()?); + } + + Ok(status + .iter() + .filter_map(|s| if s.success() { None } else { s.code() }) + .next() + .unwrap_or(SUCCESS)) +} + +fn get_cargo_metadata( + manifest_path: Option<&Path>, + include_deps: bool, +) -> Result { + let mut cmd = cargo_metadata::MetadataCommand::new(); + if !include_deps { + cmd.no_deps(); + } + if let Some(manifest_path) = manifest_path { + cmd.manifest_path(manifest_path); + } + cmd.other_options(&[String::from("--offline")]); + + match cmd.exec() { + Ok(metadata) => Ok(metadata), + Err(_) => { + cmd.other_options(vec![]); + match cmd.exec() { + Ok(metadata) => Ok(metadata), + Err(error) => Err(io::Error::new(io::ErrorKind::Other, error.to_string())), + } + } + } +} + +#[cfg(test)] +mod cargo_fmt_tests { + use super::*; + + #[test] + fn default_options() { + let empty: Vec = vec![]; + let o = Opts::from_iter(&empty); + assert_eq!(false, o.quiet); + assert_eq!(false, o.verbose); + assert_eq!(false, o.version); + assert_eq!(empty, o.packages); + assert_eq!(empty, o.rustfmt_options); + assert_eq!(false, o.format_all); + assert_eq!(None, o.manifest_path); + assert_eq!(None, o.message_format); + } + + #[test] + fn good_options() { + let o = Opts::from_iter(&[ + "test", + "-q", + "-p", + "p1", + "-p", + "p2", + "--message-format", + "short", + "--", + "--edition", + "2018", + ]); + assert_eq!(true, o.quiet); + assert_eq!(false, o.verbose); + assert_eq!(false, o.version); + assert_eq!(vec!["p1", "p2"], o.packages); + assert_eq!(vec!["--edition", "2018"], o.rustfmt_options); + assert_eq!(false, o.format_all); + assert_eq!(Some(String::from("short")), o.message_format); + } + + #[test] + fn unexpected_option() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "unexpected"]) + .is_err() + ); + } + + #[test] + fn unexpected_flag() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "--flag"]) + .is_err() + ); + } + + #[test] + fn mandatory_separator() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "--check"]) + .is_err() + ); + assert!( + !Opts::clap() + .get_matches_from_safe(&["test", "--", "--check"]) + .is_err() + ); + } + + #[test] + fn multiple_packages_one_by_one() { + let o = Opts::from_iter(&[ + "test", + "-p", + "package1", + "--package", + "package2", + "-p", + "package3", + ]); + assert_eq!(3, o.packages.len()); + } + + #[test] + fn multiple_packages_grouped() { + let o = Opts::from_iter(&[ + "test", + "--package", + "package1", + "package2", + "-p", + "package3", + "package4", + ]); + assert_eq!(4, o.packages.len()); + } + + #[test] + fn empty_packages_1() { + assert!(Opts::clap().get_matches_from_safe(&["test", "-p"]).is_err()); + } + + #[test] + fn empty_packages_2() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "-p", "--", "--check"]) + .is_err() + ); + } + + #[test] + fn empty_packages_3() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "-p", "--verbose"]) + .is_err() + ); + } + + #[test] + fn empty_packages_4() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "-p", "--check"]) + .is_err() + ); + } + + mod convert_message_format_to_rustfmt_args_tests { + use super::*; + + #[test] + fn invalid_message_format() { + assert_eq!( + convert_message_format_to_rustfmt_args("awesome", &mut vec![]), + Err(String::from( + "invalid --message-format value: awesome. Allowed values are: short|json|human" + )), + ); + } + + #[test] + fn json_message_format_and_check_arg() { + let mut args = vec![String::from("--check")]; + assert_eq!( + convert_message_format_to_rustfmt_args("json", &mut args), + Err(String::from( + "cannot include --check arg when --message-format is set to json" + )), + ); + } + + #[test] + fn json_message_format_and_emit_arg() { + let mut args = vec![String::from("--emit"), String::from("checkstyle")]; + assert_eq!( + convert_message_format_to_rustfmt_args("json", &mut args), + Err(String::from( + "cannot include --emit arg when --message-format is set to json" + )), + ); + } + + #[test] + fn json_message_format() { + let mut args = vec![String::from("--edition"), String::from("2018")]; + assert!(convert_message_format_to_rustfmt_args("json", &mut args).is_ok()); + assert_eq!( + args, + vec![ + String::from("--edition"), + String::from("2018"), + String::from("--emit"), + String::from("json") + ] + ); + } + + #[test] + fn human_message_format() { + let exp_args = vec![String::from("--emit"), String::from("json")]; + let mut act_args = exp_args.clone(); + assert!(convert_message_format_to_rustfmt_args("human", &mut act_args).is_ok()); + assert_eq!(act_args, exp_args); + } + + #[test] + fn short_message_format() { + let mut args = vec![String::from("--check")]; + assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok()); + assert_eq!(args, vec![String::from("--check"), String::from("-l")]); + } + + #[test] + fn short_message_format_included_short_list_files_flag() { + let mut args = vec![String::from("--check"), String::from("-l")]; + assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok()); + assert_eq!(args, vec![String::from("--check"), String::from("-l")]); + } + + #[test] + fn short_message_format_included_long_list_files_flag() { + let mut args = vec![String::from("--check"), String::from("--files-with-diff")]; + assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok()); + assert_eq!( + args, + vec![String::from("--check"), String::from("--files-with-diff")] + ); + } + } +} diff --git a/src/tools/rustfmt/src/chains.rs b/src/tools/rustfmt/src/chains.rs new file mode 100644 index 0000000000..ac14ec9e11 --- /dev/null +++ b/src/tools/rustfmt/src/chains.rs @@ -0,0 +1,891 @@ +//! Formatting of chained expressions, i.e., expressions that are chained by +//! dots: struct and enum field access, method calls, and try shorthand (`?`). +//! +//! Instead of walking these subexpressions one-by-one, as is our usual strategy +//! for expression formatting, we collect maximal sequences of these expressions +//! and handle them simultaneously. +//! +//! Whenever possible, the entire chain is put on a single line. If that fails, +//! we put each subexpression on a separate, much like the (default) function +//! argument function argument strategy. +//! +//! Depends on config options: `chain_indent` is the indent to use for +//! blocks in the parent/root/base of the chain (and the rest of the chain's +//! alignment). +//! E.g., `let foo = { aaaa; bbb; ccc }.bar.baz();`, we would layout for the +//! following values of `chain_indent`: +//! Block: +//! +//! ```text +//! let foo = { +//! aaaa; +//! bbb; +//! ccc +//! }.bar +//! .baz(); +//! ``` +//! +//! Visual: +//! +//! ```text +//! let foo = { +//! aaaa; +//! bbb; +//! ccc +//! } +//! .bar +//! .baz(); +//! ``` +//! +//! If the first item in the chain is a block expression, we align the dots with +//! the braces. +//! Block: +//! +//! ```text +//! let a = foo.bar +//! .baz() +//! .qux +//! ``` +//! +//! Visual: +//! +//! ```text +//! let a = foo.bar +//! .baz() +//! .qux +//! ``` + +use std::borrow::Cow; +use std::cmp::min; + +use rustc_ast::{ast, ptr}; +use rustc_span::{symbol, BytePos, Span}; + +use crate::comment::{rewrite_comment, CharClasses, FullCodeCharKind, RichChar}; +use crate::config::{IndentStyle, Version}; +use crate::expr::rewrite_call; +use crate::lists::extract_pre_comment; +use crate::macros::convert_try_mac; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::source_map::SpanUtils; +use crate::utils::{ + self, first_line_width, last_line_extendable, last_line_width, mk_sp, rewrite_ident, + trimmed_last_line_width, wrap_str, +}; + +pub(crate) fn rewrite_chain( + expr: &ast::Expr, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let chain = Chain::from_ast(expr, context); + debug!("rewrite_chain {:?} {:?}", chain, shape); + + // If this is just an expression with some `?`s, then format it trivially and + // return early. + if chain.children.is_empty() { + return chain.parent.rewrite(context, shape); + } + + chain.rewrite(context, shape) +} + +#[derive(Debug)] +enum CommentPosition { + Back, + Top, +} + +// An expression plus trailing `?`s to be formatted together. +#[derive(Debug)] +struct ChainItem { + kind: ChainItemKind, + tries: usize, + span: Span, +} + +// FIXME: we can't use a reference here because to convert `try!` to `?` we +// synthesise the AST node. However, I think we could use `Cow` and that +// would remove a lot of cloning. +#[derive(Debug)] +enum ChainItemKind { + Parent(ast::Expr), + MethodCall( + ast::PathSegment, + Vec, + Vec>, + ), + StructField(symbol::Ident), + TupleField(symbol::Ident, bool), + Await, + Comment(String, CommentPosition), +} + +impl ChainItemKind { + fn is_block_like(&self, context: &RewriteContext<'_>, reps: &str) -> bool { + match self { + ChainItemKind::Parent(ref expr) => utils::is_block_expr(context, expr, reps), + ChainItemKind::MethodCall(..) + | ChainItemKind::StructField(..) + | ChainItemKind::TupleField(..) + | ChainItemKind::Await + | ChainItemKind::Comment(..) => false, + } + } + + fn is_tup_field_access(expr: &ast::Expr) -> bool { + match expr.kind { + ast::ExprKind::Field(_, ref field) => { + field.name.to_string().chars().all(|c| c.is_digit(10)) + } + _ => false, + } + } + + fn from_ast(context: &RewriteContext<'_>, expr: &ast::Expr) -> (ChainItemKind, Span) { + let (kind, span) = match expr.kind { + ast::ExprKind::MethodCall(ref segment, ref expressions, _) => { + let types = if let Some(ref generic_args) = segment.args { + if let ast::GenericArgs::AngleBracketed(ref data) = **generic_args { + data.args + .iter() + .filter_map(|x| match x { + ast::AngleBracketedArg::Arg(ref generic_arg) => { + Some(generic_arg.clone()) + } + _ => None, + }) + .collect::>() + } else { + vec![] + } + } else { + vec![] + }; + let span = mk_sp(expressions[0].span.hi(), expr.span.hi()); + let kind = ChainItemKind::MethodCall(segment.clone(), types, expressions.clone()); + (kind, span) + } + ast::ExprKind::Field(ref nested, field) => { + let kind = if Self::is_tup_field_access(expr) { + ChainItemKind::TupleField(field, Self::is_tup_field_access(nested)) + } else { + ChainItemKind::StructField(field) + }; + let span = mk_sp(nested.span.hi(), field.span.hi()); + (kind, span) + } + ast::ExprKind::Await(ref nested) => { + let span = mk_sp(nested.span.hi(), expr.span.hi()); + (ChainItemKind::Await, span) + } + _ => return (ChainItemKind::Parent(expr.clone()), expr.span), + }; + + // Remove comments from the span. + let lo = context.snippet_provider.span_before(span, "."); + (kind, mk_sp(lo, span.hi())) + } +} + +impl Rewrite for ChainItem { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let shape = shape.sub_width(self.tries)?; + let rewrite = match self.kind { + ChainItemKind::Parent(ref expr) => expr.rewrite(context, shape)?, + ChainItemKind::MethodCall(ref segment, ref types, ref exprs) => { + Self::rewrite_method_call(segment.ident, types, exprs, self.span, context, shape)? + } + ChainItemKind::StructField(ident) => format!(".{}", rewrite_ident(context, ident)), + ChainItemKind::TupleField(ident, nested) => format!( + "{}.{}", + if nested && context.config.version() == Version::One { + " " + } else { + "" + }, + rewrite_ident(context, ident) + ), + ChainItemKind::Await => ".await".to_owned(), + ChainItemKind::Comment(ref comment, _) => { + rewrite_comment(comment, false, shape, context.config)? + } + }; + Some(format!("{}{}", rewrite, "?".repeat(self.tries))) + } +} + +impl ChainItem { + fn new(context: &RewriteContext<'_>, expr: &ast::Expr, tries: usize) -> ChainItem { + let (kind, span) = ChainItemKind::from_ast(context, expr); + ChainItem { kind, tries, span } + } + + fn comment(span: Span, comment: String, pos: CommentPosition) -> ChainItem { + ChainItem { + kind: ChainItemKind::Comment(comment, pos), + tries: 0, + span, + } + } + + fn is_comment(&self) -> bool { + match self.kind { + ChainItemKind::Comment(..) => true, + _ => false, + } + } + + fn rewrite_method_call( + method_name: symbol::Ident, + types: &[ast::GenericArg], + args: &[ptr::P], + span: Span, + context: &RewriteContext<'_>, + shape: Shape, + ) -> Option { + let type_str = if types.is_empty() { + String::new() + } else { + let type_list = types + .iter() + .map(|ty| ty.rewrite(context, shape)) + .collect::>>()?; + + format!("::<{}>", type_list.join(", ")) + }; + let callee_str = format!(".{}{}", rewrite_ident(context, method_name), type_str); + rewrite_call(context, &callee_str, &args[1..], span, shape) + } +} + +#[derive(Debug)] +struct Chain { + parent: ChainItem, + children: Vec, +} + +impl Chain { + fn from_ast(expr: &ast::Expr, context: &RewriteContext<'_>) -> Chain { + let subexpr_list = Self::make_subexpr_list(expr, context); + + // Un-parse the expression tree into ChainItems + let mut rev_children = vec![]; + let mut sub_tries = 0; + for subexpr in &subexpr_list { + match subexpr.kind { + ast::ExprKind::Try(_) => sub_tries += 1, + _ => { + rev_children.push(ChainItem::new(context, subexpr, sub_tries)); + sub_tries = 0; + } + } + } + + fn is_tries(s: &str) -> bool { + s.chars().all(|c| c == '?') + } + + fn is_post_comment(s: &str) -> bool { + let comment_start_index = s.chars().position(|c| c == '/'); + if comment_start_index.is_none() { + return false; + } + + let newline_index = s.chars().position(|c| c == '\n'); + if newline_index.is_none() { + return true; + } + + comment_start_index.unwrap() < newline_index.unwrap() + } + + fn handle_post_comment( + post_comment_span: Span, + post_comment_snippet: &str, + prev_span_end: &mut BytePos, + children: &mut Vec, + ) { + let white_spaces: &[_] = &[' ', '\t']; + if post_comment_snippet + .trim_matches(white_spaces) + .starts_with('\n') + { + // No post comment. + return; + } + let trimmed_snippet = trim_tries(post_comment_snippet); + if is_post_comment(&trimmed_snippet) { + children.push(ChainItem::comment( + post_comment_span, + trimmed_snippet.trim().to_owned(), + CommentPosition::Back, + )); + *prev_span_end = post_comment_span.hi(); + } + } + + let parent = rev_children.pop().unwrap(); + let mut children = vec![]; + let mut prev_span_end = parent.span.hi(); + let mut iter = rev_children.into_iter().rev().peekable(); + if let Some(first_chain_item) = iter.peek() { + let comment_span = mk_sp(prev_span_end, first_chain_item.span.lo()); + let comment_snippet = context.snippet(comment_span); + if !is_tries(comment_snippet.trim()) { + handle_post_comment( + comment_span, + comment_snippet, + &mut prev_span_end, + &mut children, + ); + } + } + while let Some(chain_item) = iter.next() { + let comment_snippet = context.snippet(chain_item.span); + // FIXME: Figure out the way to get a correct span when converting `try!` to `?`. + let handle_comment = + !(context.config.use_try_shorthand() || is_tries(comment_snippet.trim())); + + // Pre-comment + if handle_comment { + let pre_comment_span = mk_sp(prev_span_end, chain_item.span.lo()); + let pre_comment_snippet = trim_tries(context.snippet(pre_comment_span)); + let (pre_comment, _) = extract_pre_comment(&pre_comment_snippet); + match pre_comment { + Some(ref comment) if !comment.is_empty() => { + children.push(ChainItem::comment( + pre_comment_span, + comment.to_owned(), + CommentPosition::Top, + )); + } + _ => (), + } + } + + prev_span_end = chain_item.span.hi(); + children.push(chain_item); + + // Post-comment + if !handle_comment || iter.peek().is_none() { + continue; + } + + let next_lo = iter.peek().unwrap().span.lo(); + let post_comment_span = mk_sp(prev_span_end, next_lo); + let post_comment_snippet = context.snippet(post_comment_span); + handle_post_comment( + post_comment_span, + post_comment_snippet, + &mut prev_span_end, + &mut children, + ); + } + + Chain { parent, children } + } + + // Returns a Vec of the prefixes of the chain. + // E.g., for input `a.b.c` we return [`a.b.c`, `a.b`, 'a'] + fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext<'_>) -> Vec { + let mut subexpr_list = vec![expr.clone()]; + + while let Some(subexpr) = Self::pop_expr_chain(subexpr_list.last().unwrap(), context) { + subexpr_list.push(subexpr.clone()); + } + + subexpr_list + } + + // Returns the expression's subexpression, if it exists. When the subexpr + // is a try! macro, we'll convert it to shorthand when the option is set. + fn pop_expr_chain(expr: &ast::Expr, context: &RewriteContext<'_>) -> Option { + match expr.kind { + ast::ExprKind::MethodCall(_, ref expressions, _) => { + Some(Self::convert_try(&expressions[0], context)) + } + ast::ExprKind::Field(ref subexpr, _) + | ast::ExprKind::Try(ref subexpr) + | ast::ExprKind::Await(ref subexpr) => Some(Self::convert_try(subexpr, context)), + _ => None, + } + } + + fn convert_try(expr: &ast::Expr, context: &RewriteContext<'_>) -> ast::Expr { + match expr.kind { + ast::ExprKind::MacCall(ref mac) if context.config.use_try_shorthand() => { + if let Some(subexpr) = convert_try_mac(mac, context) { + subexpr + } else { + expr.clone() + } + } + _ => expr.clone(), + } + } +} + +impl Rewrite for Chain { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + debug!("rewrite chain {:?} {:?}", self, shape); + + let mut formatter = match context.config.indent_style() { + IndentStyle::Block => { + Box::new(ChainFormatterBlock::new(self)) as Box + } + IndentStyle::Visual => { + Box::new(ChainFormatterVisual::new(self)) as Box + } + }; + + formatter.format_root(&self.parent, context, shape)?; + if let Some(result) = formatter.pure_root() { + return wrap_str(result, context.config.max_width(), shape); + } + + // Decide how to layout the rest of the chain. + let child_shape = formatter.child_shape(context, shape)?; + + formatter.format_children(context, child_shape)?; + formatter.format_last_child(context, shape, child_shape)?; + + let result = formatter.join_rewrites(context, child_shape)?; + wrap_str(result, context.config.max_width(), shape) + } +} + +// There are a few types for formatting chains. This is because there is a lot +// in common between formatting with block vs visual indent, but they are +// different enough that branching on the indent all over the place gets ugly. +// Anything that can format a chain is a ChainFormatter. +trait ChainFormatter { + // Parent is the first item in the chain, e.g., `foo` in `foo.bar.baz()`. + // Root is the parent plus any other chain items placed on the first line to + // avoid an orphan. E.g., + // ```text + // foo.bar + // .baz() + // ``` + // If `bar` were not part of the root, then foo would be orphaned and 'float'. + fn format_root( + &mut self, + parent: &ChainItem, + context: &RewriteContext<'_>, + shape: Shape, + ) -> Option<()>; + fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option; + fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()>; + fn format_last_child( + &mut self, + context: &RewriteContext<'_>, + shape: Shape, + child_shape: Shape, + ) -> Option<()>; + fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option; + // Returns `Some` if the chain is only a root, None otherwise. + fn pure_root(&mut self) -> Option; +} + +// Data and behaviour that is shared by both chain formatters. The concrete +// formatters can delegate much behaviour to `ChainFormatterShared`. +struct ChainFormatterShared<'a> { + // The current working set of child items. + children: &'a [ChainItem], + // The current rewrites of items (includes trailing `?`s, but not any way to + // connect the rewrites together). + rewrites: Vec, + // Whether the chain can fit on one line. + fits_single_line: bool, + // The number of children in the chain. This is not equal to `self.children.len()` + // because `self.children` will change size as we process the chain. + child_count: usize, +} + +impl<'a> ChainFormatterShared<'a> { + fn new(chain: &'a Chain) -> ChainFormatterShared<'a> { + ChainFormatterShared { + children: &chain.children, + rewrites: Vec::with_capacity(chain.children.len() + 1), + fits_single_line: false, + child_count: chain.children.len(), + } + } + + fn pure_root(&mut self) -> Option { + if self.children.is_empty() { + assert_eq!(self.rewrites.len(), 1); + Some(self.rewrites.pop().unwrap()) + } else { + None + } + } + + // Rewrite the last child. The last child of a chain requires special treatment. We need to + // know whether 'overflowing' the last child make a better formatting: + // + // A chain with overflowing the last child: + // ```text + // parent.child1.child2.last_child( + // a, + // b, + // c, + // ) + // ``` + // + // A chain without overflowing the last child (in vertical layout): + // ```text + // parent + // .child1 + // .child2 + // .last_child(a, b, c) + // ``` + // + // In particular, overflowing is effective when the last child is a method with a multi-lined + // block-like argument (e.g., closure): + // ```text + // parent.child1.child2.last_child(|a, b, c| { + // let x = foo(a, b, c); + // let y = bar(a, b, c); + // + // // ... + // + // result + // }) + // ``` + fn format_last_child( + &mut self, + may_extend: bool, + context: &RewriteContext<'_>, + shape: Shape, + child_shape: Shape, + ) -> Option<()> { + let last = self.children.last()?; + let extendable = may_extend && last_line_extendable(&self.rewrites[0]); + let prev_last_line_width = last_line_width(&self.rewrites[0]); + + // Total of all items excluding the last. + let almost_total = if extendable { + prev_last_line_width + } else { + self.rewrites + .iter() + .map(|rw| utils::unicode_str_width(&rw)) + .sum() + } + last.tries; + let one_line_budget = if self.child_count == 1 { + shape.width + } else { + min(shape.width, context.config.width_heuristics().chain_width) + } + .saturating_sub(almost_total); + + let all_in_one_line = !self.children.iter().any(ChainItem::is_comment) + && self.rewrites.iter().all(|s| !s.contains('\n')) + && one_line_budget > 0; + let last_shape = if all_in_one_line { + shape.sub_width(last.tries)? + } else if extendable { + child_shape.sub_width(last.tries)? + } else { + child_shape.sub_width(shape.rhs_overhead(context.config) + last.tries)? + }; + + let mut last_subexpr_str = None; + if all_in_one_line || extendable { + // First we try to 'overflow' the last child and see if it looks better than using + // vertical layout. + let one_line_shape = if context.use_block_indent() { + last_shape.offset_left(almost_total) + } else { + last_shape + .visual_indent(almost_total) + .sub_width(almost_total) + }; + + if let Some(one_line_shape) = one_line_shape { + if let Some(rw) = last.rewrite(context, one_line_shape) { + // We allow overflowing here only if both of the following conditions match: + // 1. The entire chain fits in a single line except the last child. + // 2. `last_child_str.lines().count() >= 5`. + let line_count = rw.lines().count(); + let could_fit_single_line = first_line_width(&rw) <= one_line_budget; + if could_fit_single_line && line_count >= 5 { + last_subexpr_str = Some(rw); + self.fits_single_line = all_in_one_line; + } else { + // We could not know whether overflowing is better than using vertical + // layout, just by looking at the overflowed rewrite. Now we rewrite the + // last child on its own line, and compare two rewrites to choose which is + // better. + let last_shape = child_shape + .sub_width(shape.rhs_overhead(context.config) + last.tries)?; + match last.rewrite(context, last_shape) { + Some(ref new_rw) if !could_fit_single_line => { + last_subexpr_str = Some(new_rw.clone()); + } + Some(ref new_rw) if new_rw.lines().count() >= line_count => { + last_subexpr_str = Some(rw); + self.fits_single_line = could_fit_single_line && all_in_one_line; + } + new_rw @ Some(..) => { + last_subexpr_str = new_rw; + } + _ => { + last_subexpr_str = Some(rw); + self.fits_single_line = could_fit_single_line && all_in_one_line; + } + } + } + } + } + } + + let last_shape = if context.use_block_indent() { + last_shape + } else { + child_shape.sub_width(shape.rhs_overhead(context.config) + last.tries)? + }; + + last_subexpr_str = last_subexpr_str.or_else(|| last.rewrite(context, last_shape)); + self.rewrites.push(last_subexpr_str?); + Some(()) + } + + fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option { + let connector = if self.fits_single_line { + // Yay, we can put everything on one line. + Cow::from("") + } else { + // Use new lines. + if context.force_one_line_chain.get() { + return None; + } + child_shape.to_string_with_newline(context.config) + }; + + let mut rewrite_iter = self.rewrites.iter(); + let mut result = rewrite_iter.next().unwrap().clone(); + let children_iter = self.children.iter(); + let iter = rewrite_iter.zip(children_iter); + + for (rewrite, chain_item) in iter { + match chain_item.kind { + ChainItemKind::Comment(_, CommentPosition::Back) => result.push(' '), + ChainItemKind::Comment(_, CommentPosition::Top) => result.push_str(&connector), + _ => result.push_str(&connector), + } + result.push_str(&rewrite); + } + + Some(result) + } +} + +// Formats a chain using block indent. +struct ChainFormatterBlock<'a> { + shared: ChainFormatterShared<'a>, + root_ends_with_block: bool, +} + +impl<'a> ChainFormatterBlock<'a> { + fn new(chain: &'a Chain) -> ChainFormatterBlock<'a> { + ChainFormatterBlock { + shared: ChainFormatterShared::new(chain), + root_ends_with_block: false, + } + } +} + +impl<'a> ChainFormatter for ChainFormatterBlock<'a> { + fn format_root( + &mut self, + parent: &ChainItem, + context: &RewriteContext<'_>, + shape: Shape, + ) -> Option<()> { + let mut root_rewrite: String = parent.rewrite(context, shape)?; + + let mut root_ends_with_block = parent.kind.is_block_like(context, &root_rewrite); + let tab_width = context.config.tab_spaces().saturating_sub(shape.offset); + + while root_rewrite.len() <= tab_width && !root_rewrite.contains('\n') { + let item = &self.shared.children[0]; + if let ChainItemKind::Comment(..) = item.kind { + break; + } + let shape = shape.offset_left(root_rewrite.len())?; + match &item.rewrite(context, shape) { + Some(rewrite) => root_rewrite.push_str(rewrite), + None => break, + } + + root_ends_with_block = last_line_extendable(&root_rewrite); + + self.shared.children = &self.shared.children[1..]; + if self.shared.children.is_empty() { + break; + } + } + self.shared.rewrites.push(root_rewrite); + self.root_ends_with_block = root_ends_with_block; + Some(()) + } + + fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + Some( + if self.root_ends_with_block { + shape.block_indent(0) + } else { + shape.block_indent(context.config.tab_spaces()) + } + .with_max_width(context.config), + ) + } + + fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> { + for item in &self.shared.children[..self.shared.children.len() - 1] { + let rewrite = item.rewrite(context, child_shape)?; + self.shared.rewrites.push(rewrite); + } + Some(()) + } + + fn format_last_child( + &mut self, + context: &RewriteContext<'_>, + shape: Shape, + child_shape: Shape, + ) -> Option<()> { + self.shared + .format_last_child(true, context, shape, child_shape) + } + + fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option { + self.shared.join_rewrites(context, child_shape) + } + + fn pure_root(&mut self) -> Option { + self.shared.pure_root() + } +} + +// Format a chain using visual indent. +struct ChainFormatterVisual<'a> { + shared: ChainFormatterShared<'a>, + // The extra offset from the chain's shape to the position of the `.` + offset: usize, +} + +impl<'a> ChainFormatterVisual<'a> { + fn new(chain: &'a Chain) -> ChainFormatterVisual<'a> { + ChainFormatterVisual { + shared: ChainFormatterShared::new(chain), + offset: 0, + } + } +} + +impl<'a> ChainFormatter for ChainFormatterVisual<'a> { + fn format_root( + &mut self, + parent: &ChainItem, + context: &RewriteContext<'_>, + shape: Shape, + ) -> Option<()> { + let parent_shape = shape.visual_indent(0); + let mut root_rewrite = parent.rewrite(context, parent_shape)?; + let multiline = root_rewrite.contains('\n'); + self.offset = if multiline { + last_line_width(&root_rewrite).saturating_sub(shape.used_width()) + } else { + trimmed_last_line_width(&root_rewrite) + }; + + if !multiline || parent.kind.is_block_like(context, &root_rewrite) { + let item = &self.shared.children[0]; + if let ChainItemKind::Comment(..) = item.kind { + self.shared.rewrites.push(root_rewrite); + return Some(()); + } + let child_shape = parent_shape + .visual_indent(self.offset) + .sub_width(self.offset)?; + let rewrite = item.rewrite(context, child_shape)?; + match wrap_str(rewrite, context.config.max_width(), shape) { + Some(rewrite) => root_rewrite.push_str(&rewrite), + None => { + // We couldn't fit in at the visual indent, try the last + // indent. + let rewrite = item.rewrite(context, parent_shape)?; + root_rewrite.push_str(&rewrite); + self.offset = 0; + } + } + + self.shared.children = &self.shared.children[1..]; + } + + self.shared.rewrites.push(root_rewrite); + Some(()) + } + + fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + shape + .with_max_width(context.config) + .offset_left(self.offset) + .map(|s| s.visual_indent(0)) + } + + fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> { + for item in &self.shared.children[..self.shared.children.len() - 1] { + let rewrite = item.rewrite(context, child_shape)?; + self.shared.rewrites.push(rewrite); + } + Some(()) + } + + fn format_last_child( + &mut self, + context: &RewriteContext<'_>, + shape: Shape, + child_shape: Shape, + ) -> Option<()> { + self.shared + .format_last_child(false, context, shape, child_shape) + } + + fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option { + self.shared.join_rewrites(context, child_shape) + } + + fn pure_root(&mut self) -> Option { + self.shared.pure_root() + } +} + +/// Removes try operators (`?`s) that appear in the given string. If removing +/// them leaves an empty line, remove that line as well unless it is the first +/// line (we need the first newline for detecting pre/post comment). +fn trim_tries(s: &str) -> String { + let mut result = String::with_capacity(s.len()); + let mut line_buffer = String::with_capacity(s.len()); + for (kind, rich_char) in CharClasses::new(s.chars()) { + match rich_char.get_char() { + '\n' => { + if result.is_empty() || !line_buffer.trim().is_empty() { + result.push_str(&line_buffer); + result.push('\n') + } + line_buffer.clear(); + } + '?' if kind == FullCodeCharKind::Normal => continue, + c => line_buffer.push(c), + } + } + if !line_buffer.trim().is_empty() { + result.push_str(&line_buffer); + } + result +} diff --git a/src/tools/rustfmt/src/closures.rs b/src/tools/rustfmt/src/closures.rs new file mode 100644 index 0000000000..3d65077ddc --- /dev/null +++ b/src/tools/rustfmt/src/closures.rs @@ -0,0 +1,429 @@ +use rustc_ast::{ast, ptr}; +use rustc_span::Span; + +use crate::attr::get_attrs_from_stmt; +use crate::config::lists::*; +use crate::config::Version; +use crate::expr::{block_contains_comment, is_simple_block, is_unsafe_block, rewrite_cond}; +use crate::items::{span_hi_for_param, span_lo_for_param}; +use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator}; +use crate::overflow::OverflowableItem; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::source_map::SpanUtils; +use crate::utils::{last_line_width, left_most_sub_expr, stmt_expr, NodeIdExt}; + +// This module is pretty messy because of the rules around closures and blocks: +// FIXME - the below is probably no longer true in full. +// * if there is a return type, then there must be braces, +// * given a closure with braces, whether that is parsed to give an inner block +// or not depends on if there is a return type and if there are statements +// in that block, +// * if the first expression in the body ends with a block (i.e., is a +// statement without needing a semi-colon), then adding or removing braces +// can change whether it is treated as an expression or statement. + +pub(crate) fn rewrite_closure( + capture: ast::CaptureBy, + is_async: &ast::Async, + movability: ast::Movability, + fn_decl: &ast::FnDecl, + body: &ast::Expr, + span: Span, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + debug!("rewrite_closure {:?}", body); + + let (prefix, extra_offset) = rewrite_closure_fn_decl( + capture, is_async, movability, fn_decl, body, span, context, shape, + )?; + // 1 = space between `|...|` and body. + let body_shape = shape.offset_left(extra_offset)?; + + if let ast::ExprKind::Block(ref block, _) = body.kind { + // The body of the closure is an empty block. + if block.stmts.is_empty() && !block_contains_comment(context, block) { + return body + .rewrite(context, shape) + .map(|s| format!("{} {}", prefix, s)); + } + + let result = match fn_decl.output { + ast::FnRetTy::Default(_) if !context.inside_macro() => { + try_rewrite_without_block(body, &prefix, context, shape, body_shape) + } + _ => None, + }; + + result.or_else(|| { + // Either we require a block, or tried without and failed. + rewrite_closure_block(block, &prefix, context, body_shape) + }) + } else { + rewrite_closure_expr(body, &prefix, context, body_shape).or_else(|| { + // The closure originally had a non-block expression, but we can't fit on + // one line, so we'll insert a block. + rewrite_closure_with_block(body, &prefix, context, body_shape) + }) + } +} + +fn try_rewrite_without_block( + expr: &ast::Expr, + prefix: &str, + context: &RewriteContext<'_>, + shape: Shape, + body_shape: Shape, +) -> Option { + let expr = get_inner_expr(expr, prefix, context); + + if is_block_closure_forced(context, expr) { + rewrite_closure_with_block(expr, prefix, context, shape) + } else { + rewrite_closure_expr(expr, prefix, context, body_shape) + } +} + +fn get_inner_expr<'a>( + expr: &'a ast::Expr, + prefix: &str, + context: &RewriteContext<'_>, +) -> &'a ast::Expr { + if let ast::ExprKind::Block(ref block, _) = expr.kind { + if !needs_block(block, prefix, context) { + // block.stmts.len() == 1 except with `|| {{}}`; + // https://github.com/rust-lang/rustfmt/issues/3844 + if let Some(expr) = block.stmts.first().and_then(stmt_expr) { + return get_inner_expr(expr, prefix, context); + } + } + } + + expr +} + +// Figure out if a block is necessary. +fn needs_block(block: &ast::Block, prefix: &str, context: &RewriteContext<'_>) -> bool { + let has_attributes = block.stmts.first().map_or(false, |first_stmt| { + !get_attrs_from_stmt(first_stmt).is_empty() + }); + + is_unsafe_block(block) + || block.stmts.len() > 1 + || has_attributes + || block_contains_comment(context, block) + || prefix.contains('\n') +} + +fn veto_block(e: &ast::Expr) -> bool { + match e.kind { + ast::ExprKind::Call(..) + | ast::ExprKind::Binary(..) + | ast::ExprKind::Cast(..) + | ast::ExprKind::Type(..) + | ast::ExprKind::Assign(..) + | ast::ExprKind::AssignOp(..) + | ast::ExprKind::Field(..) + | ast::ExprKind::Index(..) + | ast::ExprKind::Range(..) + | ast::ExprKind::Try(..) => true, + _ => false, + } +} + +// Rewrite closure with a single expression wrapping its body with block. +// || { #[attr] foo() } -> Block { #[attr] foo() } +fn rewrite_closure_with_block( + body: &ast::Expr, + prefix: &str, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let left_most = left_most_sub_expr(body); + let veto_block = veto_block(body) && !expr_requires_semi_to_be_stmt(left_most); + if veto_block { + return None; + } + + let block = ast::Block { + stmts: vec![ast::Stmt { + id: ast::NodeId::root(), + kind: ast::StmtKind::Expr(ptr::P(body.clone())), + span: body.span, + }], + id: ast::NodeId::root(), + rules: ast::BlockCheckMode::Default, + tokens: None, + span: body + .attrs + .first() + .map(|attr| attr.span.to(body.span)) + .unwrap_or(body.span), + }; + let block = crate::expr::rewrite_block_with_visitor( + context, + "", + &block, + Some(&body.attrs), + None, + shape, + false, + )?; + Some(format!("{} {}", prefix, block)) +} + +// Rewrite closure with a single expression without wrapping its body with block. +fn rewrite_closure_expr( + expr: &ast::Expr, + prefix: &str, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + fn allow_multi_line(expr: &ast::Expr) -> bool { + match expr.kind { + ast::ExprKind::Match(..) + | ast::ExprKind::Async(..) + | ast::ExprKind::Block(..) + | ast::ExprKind::TryBlock(..) + | ast::ExprKind::Loop(..) + | ast::ExprKind::Struct(..) => true, + + ast::ExprKind::AddrOf(_, _, ref expr) + | ast::ExprKind::Box(ref expr) + | ast::ExprKind::Try(ref expr) + | ast::ExprKind::Unary(_, ref expr) + | ast::ExprKind::Cast(ref expr, _) => allow_multi_line(expr), + + _ => false, + } + } + + // When rewriting closure's body without block, we require it to fit in a single line + // unless it is a block-like expression or we are inside macro call. + let veto_multiline = (!allow_multi_line(expr) && !context.inside_macro()) + || context.config.force_multiline_blocks(); + expr.rewrite(context, shape) + .and_then(|rw| { + if veto_multiline && rw.contains('\n') { + None + } else { + Some(rw) + } + }) + .map(|rw| format!("{} {}", prefix, rw)) +} + +// Rewrite closure whose body is block. +fn rewrite_closure_block( + block: &ast::Block, + prefix: &str, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + Some(format!("{} {}", prefix, block.rewrite(context, shape)?)) +} + +// Return type is (prefix, extra_offset) +fn rewrite_closure_fn_decl( + capture: ast::CaptureBy, + asyncness: &ast::Async, + movability: ast::Movability, + fn_decl: &ast::FnDecl, + body: &ast::Expr, + span: Span, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option<(String, usize)> { + let is_async = if asyncness.is_async() { "async " } else { "" }; + let mover = if capture == ast::CaptureBy::Value { + "move " + } else { + "" + }; + let immovable = if movability == ast::Movability::Static { + "static " + } else { + "" + }; + // 4 = "|| {".len(), which is overconservative when the closure consists of + // a single expression. + let nested_shape = shape + .shrink_left(is_async.len() + mover.len() + immovable.len())? + .sub_width(4)?; + + // 1 = | + let param_offset = nested_shape.indent + 1; + let param_shape = nested_shape.offset_left(1)?.visual_indent(0); + let ret_str = fn_decl.output.rewrite(context, param_shape)?; + + let param_items = itemize_list( + context.snippet_provider, + fn_decl.inputs.iter(), + "|", + ",", + |param| span_lo_for_param(param), + |param| span_hi_for_param(context, param), + |param| param.rewrite(context, param_shape), + context.snippet_provider.span_after(span, "|"), + body.span.lo(), + false, + ); + let item_vec = param_items.collect::>(); + // 1 = space between parameters and return type. + let horizontal_budget = nested_shape.width.saturating_sub(ret_str.len() + 1); + let tactic = definitive_tactic( + &item_vec, + ListTactic::HorizontalVertical, + Separator::Comma, + horizontal_budget, + ); + let param_shape = match tactic { + DefinitiveListTactic::Horizontal => param_shape.sub_width(ret_str.len() + 1)?, + _ => param_shape, + }; + + let fmt = ListFormatting::new(param_shape, context.config) + .tactic(tactic) + .preserve_newline(true); + let list_str = write_list(&item_vec, &fmt)?; + let mut prefix = format!("{}{}{}|{}|", is_async, immovable, mover, list_str); + + if !ret_str.is_empty() { + if prefix.contains('\n') { + prefix.push('\n'); + prefix.push_str(¶m_offset.to_string(context.config)); + } else { + prefix.push(' '); + } + prefix.push_str(&ret_str); + } + // 1 = space between `|...|` and body. + let extra_offset = last_line_width(&prefix) + 1; + + Some((prefix, extra_offset)) +} + +// Rewriting closure which is placed at the end of the function call's arg. +// Returns `None` if the reformatted closure 'looks bad'. +pub(crate) fn rewrite_last_closure( + context: &RewriteContext<'_>, + expr: &ast::Expr, + shape: Shape, +) -> Option { + if let ast::ExprKind::Closure(capture, ref is_async, movability, ref fn_decl, ref body, _) = + expr.kind + { + let body = match body.kind { + ast::ExprKind::Block(ref block, _) + if !is_unsafe_block(block) + && !context.inside_macro() + && is_simple_block(context, block, Some(&body.attrs)) => + { + stmt_expr(&block.stmts[0]).unwrap_or(body) + } + _ => body, + }; + let (prefix, extra_offset) = rewrite_closure_fn_decl( + capture, is_async, movability, fn_decl, body, expr.span, context, shape, + )?; + // If the closure goes multi line before its body, do not overflow the closure. + if prefix.contains('\n') { + return None; + } + + let body_shape = shape.offset_left(extra_offset)?; + + // We force to use block for the body of the closure for certain kinds of expressions. + if is_block_closure_forced(context, body) { + return rewrite_closure_with_block(body, &prefix, context, body_shape).and_then( + |body_str| { + match fn_decl.output { + ast::FnRetTy::Default(..) if body_str.lines().count() <= 7 => { + // If the expression can fit in a single line, we need not force block + // closure. However, if the closure has a return type, then we must + // keep the blocks. + match rewrite_closure_expr(body, &prefix, context, shape) { + Some(ref single_line_body_str) + if !single_line_body_str.contains('\n') => + { + Some(single_line_body_str.clone()) + } + _ => Some(body_str), + } + } + _ => Some(body_str), + } + }, + ); + } + + // When overflowing the closure which consists of a single control flow expression, + // force to use block if its condition uses multi line. + let is_multi_lined_cond = rewrite_cond(context, body, body_shape).map_or(false, |cond| { + cond.contains('\n') || cond.len() > body_shape.width + }); + if is_multi_lined_cond { + return rewrite_closure_with_block(body, &prefix, context, body_shape); + } + + // Seems fine, just format the closure in usual manner. + return expr.rewrite(context, shape); + } + None +} + +/// Returns `true` if the given vector of arguments has more than one `ast::ExprKind::Closure`. +pub(crate) fn args_have_many_closure(args: &[OverflowableItem<'_>]) -> bool { + args.iter() + .filter_map(OverflowableItem::to_expr) + .filter(|expr| match expr.kind { + ast::ExprKind::Closure(..) => true, + _ => false, + }) + .count() + > 1 +} + +fn is_block_closure_forced(context: &RewriteContext<'_>, expr: &ast::Expr) -> bool { + // If we are inside macro, we do not want to add or remove block from closure body. + if context.inside_macro() { + false + } else { + is_block_closure_forced_inner(expr, context.config.version()) + } +} + +fn is_block_closure_forced_inner(expr: &ast::Expr, version: Version) -> bool { + match expr.kind { + ast::ExprKind::If(..) | ast::ExprKind::While(..) | ast::ExprKind::ForLoop(..) => true, + ast::ExprKind::Loop(..) if version == Version::Two => true, + ast::ExprKind::AddrOf(_, _, ref expr) + | ast::ExprKind::Box(ref expr) + | ast::ExprKind::Try(ref expr) + | ast::ExprKind::Unary(_, ref expr) + | ast::ExprKind::Cast(ref expr, _) => is_block_closure_forced_inner(expr, version), + _ => false, + } +} + +/// Does this expression require a semicolon to be treated +/// as a statement? The negation of this: 'can this expression +/// be used as a statement without a semicolon' -- is used +/// as an early-bail-out in the parser so that, for instance, +/// if true {...} else {...} +/// |x| 5 +/// isn't parsed as (if true {...} else {...} | x) | 5 +// From https://github.com/rust-lang/rust/blob/master/src/libsyntax/parse/classify.rs. +fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool { + match e.kind { + ast::ExprKind::If(..) + | ast::ExprKind::Match(..) + | ast::ExprKind::Block(..) + | ast::ExprKind::While(..) + | ast::ExprKind::Loop(..) + | ast::ExprKind::ForLoop(..) + | ast::ExprKind::TryBlock(..) => false, + _ => true, + } +} diff --git a/src/tools/rustfmt/src/comment.rs b/src/tools/rustfmt/src/comment.rs new file mode 100644 index 0000000000..9959ef7fd9 --- /dev/null +++ b/src/tools/rustfmt/src/comment.rs @@ -0,0 +1,1902 @@ +// Formatting and tools for comments. + +use std::{self, borrow::Cow, iter}; + +use itertools::{multipeek, MultiPeek}; +use rustc_span::Span; + +use crate::config::Config; +use crate::rewrite::RewriteContext; +use crate::shape::{Indent, Shape}; +use crate::string::{rewrite_string, StringFormat}; +use crate::utils::{ + count_newlines, first_line_width, last_line_width, trim_left_preserve_layout, unicode_str_width, +}; +use crate::{ErrorKind, FormattingError}; + +fn is_custom_comment(comment: &str) -> bool { + if !comment.starts_with("//") { + false + } else if let Some(c) = comment.chars().nth(2) { + !c.is_alphanumeric() && !c.is_whitespace() + } else { + false + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub(crate) enum CommentStyle<'a> { + DoubleSlash, + TripleSlash, + Doc, + SingleBullet, + DoubleBullet, + Exclamation, + Custom(&'a str), +} + +fn custom_opener(s: &str) -> &str { + s.lines().next().map_or("", |first_line| { + first_line + .find(' ') + .map_or(first_line, |space_index| &first_line[0..=space_index]) + }) +} + +impl<'a> CommentStyle<'a> { + /// Returns `true` if the commenting style covers a line only. + pub(crate) fn is_line_comment(&self) -> bool { + match *self { + CommentStyle::DoubleSlash + | CommentStyle::TripleSlash + | CommentStyle::Doc + | CommentStyle::Custom(_) => true, + _ => false, + } + } + + /// Returns `true` if the commenting style can span over multiple lines. + pub(crate) fn is_block_comment(&self) -> bool { + match *self { + CommentStyle::SingleBullet | CommentStyle::DoubleBullet | CommentStyle::Exclamation => { + true + } + _ => false, + } + } + + /// Returns `true` if the commenting style is for documentation. + pub(crate) fn is_doc_comment(&self) -> bool { + match *self { + CommentStyle::TripleSlash | CommentStyle::Doc => true, + _ => false, + } + } + + pub(crate) fn opener(&self) -> &'a str { + match *self { + CommentStyle::DoubleSlash => "// ", + CommentStyle::TripleSlash => "/// ", + CommentStyle::Doc => "//! ", + CommentStyle::SingleBullet => "/* ", + CommentStyle::DoubleBullet => "/** ", + CommentStyle::Exclamation => "/*! ", + CommentStyle::Custom(opener) => opener, + } + } + + pub(crate) fn closer(&self) -> &'a str { + match *self { + CommentStyle::DoubleSlash + | CommentStyle::TripleSlash + | CommentStyle::Custom(..) + | CommentStyle::Doc => "", + CommentStyle::SingleBullet | CommentStyle::DoubleBullet | CommentStyle::Exclamation => { + " */" + } + } + } + + pub(crate) fn line_start(&self) -> &'a str { + match *self { + CommentStyle::DoubleSlash => "// ", + CommentStyle::TripleSlash => "/// ", + CommentStyle::Doc => "//! ", + CommentStyle::SingleBullet | CommentStyle::DoubleBullet | CommentStyle::Exclamation => { + " * " + } + CommentStyle::Custom(opener) => opener, + } + } + + pub(crate) fn to_str_tuplet(&self) -> (&'a str, &'a str, &'a str) { + (self.opener(), self.closer(), self.line_start()) + } +} + +pub(crate) fn comment_style(orig: &str, normalize_comments: bool) -> CommentStyle<'_> { + if !normalize_comments { + if orig.starts_with("/**") && !orig.starts_with("/**/") { + CommentStyle::DoubleBullet + } else if orig.starts_with("/*!") { + CommentStyle::Exclamation + } else if orig.starts_with("/*") { + CommentStyle::SingleBullet + } else if orig.starts_with("///") && orig.chars().nth(3).map_or(true, |c| c != '/') { + CommentStyle::TripleSlash + } else if orig.starts_with("//!") { + CommentStyle::Doc + } else if is_custom_comment(orig) { + CommentStyle::Custom(custom_opener(orig)) + } else { + CommentStyle::DoubleSlash + } + } else if (orig.starts_with("///") && orig.chars().nth(3).map_or(true, |c| c != '/')) + || (orig.starts_with("/**") && !orig.starts_with("/**/")) + { + CommentStyle::TripleSlash + } else if orig.starts_with("//!") || orig.starts_with("/*!") { + CommentStyle::Doc + } else if is_custom_comment(orig) { + CommentStyle::Custom(custom_opener(orig)) + } else { + CommentStyle::DoubleSlash + } +} + +/// Returns true if the last line of the passed string finishes with a block-comment. +pub(crate) fn is_last_comment_block(s: &str) -> bool { + s.trim_end().ends_with("*/") +} + +/// Combine `prev_str` and `next_str` into a single `String`. `span` may contain +/// comments between two strings. If there are such comments, then that will be +/// recovered. If `allow_extend` is true and there is no comment between the two +/// strings, then they will be put on a single line as long as doing so does not +/// exceed max width. +pub(crate) fn combine_strs_with_missing_comments( + context: &RewriteContext<'_>, + prev_str: &str, + next_str: &str, + span: Span, + shape: Shape, + allow_extend: bool, +) -> Option { + trace!( + "combine_strs_with_missing_comments `{}` `{}` {:?} {:?}", + prev_str, + next_str, + span, + shape + ); + + let mut result = + String::with_capacity(prev_str.len() + next_str.len() + shape.indent.width() + 128); + result.push_str(prev_str); + let mut allow_one_line = !prev_str.contains('\n') && !next_str.contains('\n'); + let first_sep = if prev_str.is_empty() || next_str.is_empty() { + "" + } else { + " " + }; + let mut one_line_width = + last_line_width(prev_str) + first_line_width(next_str) + first_sep.len(); + + let config = context.config; + let indent = shape.indent; + let missing_comment = rewrite_missing_comment(span, shape, context)?; + + if missing_comment.is_empty() { + if allow_extend && prev_str.len() + first_sep.len() + next_str.len() <= shape.width { + result.push_str(first_sep); + } else if !prev_str.is_empty() { + result.push_str(&indent.to_string_with_newline(config)) + } + result.push_str(next_str); + return Some(result); + } + + // We have a missing comment between the first expression and the second expression. + + // Peek the the original source code and find out whether there is a newline between the first + // expression and the second expression or the missing comment. We will preserve the original + // layout whenever possible. + let original_snippet = context.snippet(span); + let prefer_same_line = if let Some(pos) = original_snippet.find('/') { + !original_snippet[..pos].contains('\n') + } else { + !original_snippet.contains('\n') + }; + + one_line_width -= first_sep.len(); + let first_sep = if prev_str.is_empty() || missing_comment.is_empty() { + Cow::from("") + } else { + let one_line_width = last_line_width(prev_str) + first_line_width(&missing_comment) + 1; + if prefer_same_line && one_line_width <= shape.width { + Cow::from(" ") + } else { + indent.to_string_with_newline(config) + } + }; + result.push_str(&first_sep); + result.push_str(&missing_comment); + + let second_sep = if missing_comment.is_empty() || next_str.is_empty() { + Cow::from("") + } else if missing_comment.starts_with("//") { + indent.to_string_with_newline(config) + } else { + one_line_width += missing_comment.len() + first_sep.len() + 1; + allow_one_line &= !missing_comment.starts_with("//") && !missing_comment.contains('\n'); + if prefer_same_line && allow_one_line && one_line_width <= shape.width { + Cow::from(" ") + } else { + indent.to_string_with_newline(config) + } + }; + result.push_str(&second_sep); + result.push_str(next_str); + + Some(result) +} + +pub(crate) fn rewrite_doc_comment(orig: &str, shape: Shape, config: &Config) -> Option { + identify_comment(orig, false, shape, config, true) +} + +pub(crate) fn rewrite_comment( + orig: &str, + block_style: bool, + shape: Shape, + config: &Config, +) -> Option { + identify_comment(orig, block_style, shape, config, false) +} + +fn identify_comment( + orig: &str, + block_style: bool, + shape: Shape, + config: &Config, + is_doc_comment: bool, +) -> Option { + let style = comment_style(orig, false); + + // Computes the byte length of line taking into account a newline if the line is part of a + // paragraph. + fn compute_len(orig: &str, line: &str) -> usize { + if orig.len() > line.len() { + if orig.as_bytes()[line.len()] == b'\r' { + line.len() + 2 + } else { + line.len() + 1 + } + } else { + line.len() + } + } + + // Get the first group of line comments having the same commenting style. + // + // Returns a tuple with: + // - a boolean indicating if there is a blank line + // - a number indicating the size of the first group of comments + fn consume_same_line_comments( + style: CommentStyle<'_>, + orig: &str, + line_start: &str, + ) -> (bool, usize) { + let mut first_group_ending = 0; + let mut hbl = false; + + for line in orig.lines() { + let trimmed_line = line.trim_start(); + if trimmed_line.is_empty() { + hbl = true; + break; + } else if trimmed_line.starts_with(line_start) + || comment_style(trimmed_line, false) == style + { + first_group_ending += compute_len(&orig[first_group_ending..], line); + } else { + break; + } + } + (hbl, first_group_ending) + } + + let (has_bare_lines, first_group_ending) = match style { + CommentStyle::DoubleSlash | CommentStyle::TripleSlash | CommentStyle::Doc => { + let line_start = style.line_start().trim_start(); + consume_same_line_comments(style, orig, line_start) + } + CommentStyle::Custom(opener) => { + let trimmed_opener = opener.trim_end(); + consume_same_line_comments(style, orig, trimmed_opener) + } + // for a block comment, search for the closing symbol + CommentStyle::DoubleBullet | CommentStyle::SingleBullet | CommentStyle::Exclamation => { + let closer = style.closer().trim_start(); + let mut count = orig.matches(closer).count(); + let mut closing_symbol_offset = 0; + let mut hbl = false; + let mut first = true; + for line in orig.lines() { + closing_symbol_offset += compute_len(&orig[closing_symbol_offset..], line); + let mut trimmed_line = line.trim_start(); + if !trimmed_line.starts_with('*') + && !trimmed_line.starts_with("//") + && !trimmed_line.starts_with("/*") + { + hbl = true; + } + + // Remove opener from consideration when searching for closer + if first { + let opener = style.opener().trim_end(); + trimmed_line = &trimmed_line[opener.len()..]; + first = false; + } + if trimmed_line.ends_with(closer) { + count -= 1; + if count == 0 { + break; + } + } + } + (hbl, closing_symbol_offset) + } + }; + + let (first_group, rest) = orig.split_at(first_group_ending); + let rewritten_first_group = + if !config.normalize_comments() && has_bare_lines && style.is_block_comment() { + trim_left_preserve_layout(first_group, shape.indent, config)? + } else if !config.normalize_comments() + && !config.wrap_comments() + && !config.format_code_in_doc_comments() + { + light_rewrite_comment(first_group, shape.indent, config, is_doc_comment) + } else { + rewrite_comment_inner( + first_group, + block_style, + style, + shape, + config, + is_doc_comment || style.is_doc_comment(), + )? + }; + if rest.is_empty() { + Some(rewritten_first_group) + } else { + identify_comment( + rest.trim_start(), + block_style, + shape, + config, + is_doc_comment, + ) + .map(|rest_str| { + format!( + "{}\n{}{}{}", + rewritten_first_group, + // insert back the blank line + if has_bare_lines && style.is_line_comment() { + "\n" + } else { + "" + }, + shape.indent.to_string(config), + rest_str + ) + }) + } +} + +/// Attributes for code blocks in rustdoc. +/// See https://doc.rust-lang.org/rustdoc/print.html#attributes +enum CodeBlockAttribute { + Rust, + Ignore, + Text, + ShouldPanic, + NoRun, + CompileFail, +} + +impl CodeBlockAttribute { + fn new(attribute: &str) -> CodeBlockAttribute { + match attribute { + "rust" | "" => CodeBlockAttribute::Rust, + "ignore" => CodeBlockAttribute::Ignore, + "text" => CodeBlockAttribute::Text, + "should_panic" => CodeBlockAttribute::ShouldPanic, + "no_run" => CodeBlockAttribute::NoRun, + "compile_fail" => CodeBlockAttribute::CompileFail, + _ => CodeBlockAttribute::Text, + } + } +} + +/// Block that is formatted as an item. +/// +/// An item starts with either a star `*` or a dash `-`. Different level of indentation are +/// handled by shrinking the shape accordingly. +struct ItemizedBlock { + /// the lines that are identified as part of an itemized block + lines: Vec, + /// the number of whitespaces up to the item sigil + indent: usize, + /// the string that marks the start of an item + opener: String, + /// sequence of whitespaces to prefix new lines that are part of the item + line_start: String, +} + +impl ItemizedBlock { + /// Returns `true` if the line is formatted as an item + fn is_itemized_line(line: &str) -> bool { + let trimmed = line.trim_start(); + trimmed.starts_with("* ") || trimmed.starts_with("- ") + } + + /// Creates a new ItemizedBlock described with the given line. + /// The `is_itemized_line` needs to be called first. + fn new(line: &str) -> ItemizedBlock { + let space_to_sigil = line.chars().take_while(|c| c.is_whitespace()).count(); + let indent = space_to_sigil + 2; + ItemizedBlock { + lines: vec![line[indent..].to_string()], + indent, + opener: line[..indent].to_string(), + line_start: " ".repeat(indent), + } + } + + /// Returns a `StringFormat` used for formatting the content of an item. + fn create_string_format<'a>(&'a self, fmt: &'a StringFormat<'_>) -> StringFormat<'a> { + StringFormat { + opener: "", + closer: "", + line_start: "", + line_end: "", + shape: Shape::legacy(fmt.shape.width.saturating_sub(self.indent), Indent::empty()), + trim_end: true, + config: fmt.config, + } + } + + /// Returns `true` if the line is part of the current itemized block. + /// If it is, then it is added to the internal lines list. + fn add_line(&mut self, line: &str) -> bool { + if !ItemizedBlock::is_itemized_line(line) + && self.indent <= line.chars().take_while(|c| c.is_whitespace()).count() + { + self.lines.push(line.to_string()); + return true; + } + false + } + + /// Returns the block as a string, with each line trimmed at the start. + fn trimmed_block_as_string(&self) -> String { + self.lines + .iter() + .map(|line| format!("{} ", line.trim_start())) + .collect::() + } + + /// Returns the block as a string under its original form. + fn original_block_as_string(&self) -> String { + self.lines.join("\n") + } +} + +struct CommentRewrite<'a> { + result: String, + code_block_buffer: String, + is_prev_line_multi_line: bool, + code_block_attr: Option, + item_block: Option, + comment_line_separator: String, + indent_str: String, + max_width: usize, + fmt_indent: Indent, + fmt: StringFormat<'a>, + + opener: String, + closer: String, + line_start: String, +} + +impl<'a> CommentRewrite<'a> { + fn new( + orig: &'a str, + block_style: bool, + shape: Shape, + config: &'a Config, + ) -> CommentRewrite<'a> { + let (opener, closer, line_start) = if block_style { + CommentStyle::SingleBullet.to_str_tuplet() + } else { + comment_style(orig, config.normalize_comments()).to_str_tuplet() + }; + + let max_width = shape + .width + .checked_sub(closer.len() + opener.len()) + .unwrap_or(1); + let indent_str = shape.indent.to_string_with_newline(config).to_string(); + + let mut cr = CommentRewrite { + result: String::with_capacity(orig.len() * 2), + code_block_buffer: String::with_capacity(128), + is_prev_line_multi_line: false, + code_block_attr: None, + item_block: None, + comment_line_separator: format!("{}{}", indent_str, line_start), + max_width, + indent_str, + fmt_indent: shape.indent, + + fmt: StringFormat { + opener: "", + closer: "", + line_start, + line_end: "", + shape: Shape::legacy(max_width, shape.indent), + trim_end: true, + config, + }, + + opener: opener.to_owned(), + closer: closer.to_owned(), + line_start: line_start.to_owned(), + }; + cr.result.push_str(opener); + cr + } + + fn join_block(s: &str, sep: &str) -> String { + let mut result = String::with_capacity(s.len() + 128); + let mut iter = s.lines().peekable(); + while let Some(line) = iter.next() { + result.push_str(line); + result.push_str(match iter.peek() { + Some(next_line) if next_line.is_empty() => sep.trim_end(), + Some(..) => &sep, + None => "", + }); + } + result + } + + fn finish(mut self) -> String { + if !self.code_block_buffer.is_empty() { + // There is a code block that is not properly enclosed by backticks. + // We will leave them untouched. + self.result.push_str(&self.comment_line_separator); + self.result.push_str(&Self::join_block( + &trim_custom_comment_prefix(&self.code_block_buffer), + &self.comment_line_separator, + )); + } + + if let Some(ref ib) = self.item_block { + // the last few lines are part of an itemized block + self.fmt.shape = Shape::legacy(self.max_width, self.fmt_indent); + let item_fmt = ib.create_string_format(&self.fmt); + self.result.push_str(&self.comment_line_separator); + self.result.push_str(&ib.opener); + match rewrite_string( + &ib.trimmed_block_as_string(), + &item_fmt, + self.max_width.saturating_sub(ib.indent), + ) { + Some(s) => self.result.push_str(&Self::join_block( + &s, + &format!("{}{}", self.comment_line_separator, ib.line_start), + )), + None => self.result.push_str(&Self::join_block( + &ib.original_block_as_string(), + &self.comment_line_separator, + )), + }; + } + + self.result.push_str(&self.closer); + if self.result.ends_with(&self.opener) && self.opener.ends_with(' ') { + // Trailing space. + self.result.pop(); + } + + self.result + } + + fn handle_line( + &mut self, + orig: &'a str, + i: usize, + line: &'a str, + has_leading_whitespace: bool, + ) -> bool { + let is_last = i == count_newlines(orig); + + if let Some(ref mut ib) = self.item_block { + if ib.add_line(&line) { + return false; + } + self.is_prev_line_multi_line = false; + self.fmt.shape = Shape::legacy(self.max_width, self.fmt_indent); + let item_fmt = ib.create_string_format(&self.fmt); + self.result.push_str(&self.comment_line_separator); + self.result.push_str(&ib.opener); + match rewrite_string( + &ib.trimmed_block_as_string(), + &item_fmt, + self.max_width.saturating_sub(ib.indent), + ) { + Some(s) => self.result.push_str(&Self::join_block( + &s, + &format!("{}{}", self.comment_line_separator, ib.line_start), + )), + None => self.result.push_str(&Self::join_block( + &ib.original_block_as_string(), + &self.comment_line_separator, + )), + }; + } else if self.code_block_attr.is_some() { + if line.starts_with("```") { + let code_block = match self.code_block_attr.as_ref().unwrap() { + CodeBlockAttribute::Ignore | CodeBlockAttribute::Text => { + trim_custom_comment_prefix(&self.code_block_buffer) + } + _ if self.code_block_buffer.is_empty() => String::new(), + _ => { + let mut config = self.fmt.config.clone(); + config.set().wrap_comments(false); + if config.format_code_in_doc_comments() { + if let Some(s) = + crate::format_code_block(&self.code_block_buffer, &config, false) + { + trim_custom_comment_prefix(&s.snippet) + } else { + trim_custom_comment_prefix(&self.code_block_buffer) + } + } else { + trim_custom_comment_prefix(&self.code_block_buffer) + } + } + }; + if !code_block.is_empty() { + self.result.push_str(&self.comment_line_separator); + self.result + .push_str(&Self::join_block(&code_block, &self.comment_line_separator)); + } + self.code_block_buffer.clear(); + self.result.push_str(&self.comment_line_separator); + self.result.push_str(line); + self.code_block_attr = None; + } else { + self.code_block_buffer + .push_str(&hide_sharp_behind_comment(line)); + self.code_block_buffer.push('\n'); + } + return false; + } + + self.code_block_attr = None; + self.item_block = None; + if line.starts_with("```") { + self.code_block_attr = Some(CodeBlockAttribute::new(&line[3..])) + } else if self.fmt.config.wrap_comments() && ItemizedBlock::is_itemized_line(&line) { + let ib = ItemizedBlock::new(&line); + self.item_block = Some(ib); + return false; + } + + if self.result == self.opener { + let force_leading_whitespace = &self.opener == "/* " && count_newlines(orig) == 0; + if !has_leading_whitespace && !force_leading_whitespace && self.result.ends_with(' ') { + self.result.pop(); + } + if line.is_empty() { + return false; + } + } else if self.is_prev_line_multi_line && !line.is_empty() { + self.result.push(' ') + } else if is_last && line.is_empty() { + // trailing blank lines are unwanted + if !self.closer.is_empty() { + self.result.push_str(&self.indent_str); + } + return true; + } else { + self.result.push_str(&self.comment_line_separator); + if !has_leading_whitespace && self.result.ends_with(' ') { + self.result.pop(); + } + } + + if self.fmt.config.wrap_comments() + && unicode_str_width(line) > self.fmt.shape.width + && !has_url(line) + { + match rewrite_string(line, &self.fmt, self.max_width) { + Some(ref s) => { + self.is_prev_line_multi_line = s.contains('\n'); + self.result.push_str(s); + } + None if self.is_prev_line_multi_line => { + // We failed to put the current `line` next to the previous `line`. + // Remove the trailing space, then start rewrite on the next line. + self.result.pop(); + self.result.push_str(&self.comment_line_separator); + self.fmt.shape = Shape::legacy(self.max_width, self.fmt_indent); + match rewrite_string(line, &self.fmt, self.max_width) { + Some(ref s) => { + self.is_prev_line_multi_line = s.contains('\n'); + self.result.push_str(s); + } + None => { + self.is_prev_line_multi_line = false; + self.result.push_str(line); + } + } + } + None => { + self.is_prev_line_multi_line = false; + self.result.push_str(line); + } + } + + self.fmt.shape = if self.is_prev_line_multi_line { + // 1 = " " + let offset = 1 + last_line_width(&self.result) - self.line_start.len(); + Shape { + width: self.max_width.saturating_sub(offset), + indent: self.fmt_indent, + offset: self.fmt.shape.offset + offset, + } + } else { + Shape::legacy(self.max_width, self.fmt_indent) + }; + } else { + if line.is_empty() && self.result.ends_with(' ') && !is_last { + // Remove space if this is an empty comment or a doc comment. + self.result.pop(); + } + self.result.push_str(line); + self.fmt.shape = Shape::legacy(self.max_width, self.fmt_indent); + self.is_prev_line_multi_line = false; + } + + false + } +} + +fn rewrite_comment_inner( + orig: &str, + block_style: bool, + style: CommentStyle<'_>, + shape: Shape, + config: &Config, + is_doc_comment: bool, +) -> Option { + let mut rewriter = CommentRewrite::new(orig, block_style, shape, config); + + let line_breaks = count_newlines(orig.trim_end()); + let lines = orig + .lines() + .enumerate() + .map(|(i, mut line)| { + line = trim_end_unless_two_whitespaces(line.trim_start(), is_doc_comment); + // Drop old closer. + if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") { + line = line[..(line.len() - 2)].trim_end(); + } + + line + }) + .map(|s| left_trim_comment_line(s, &style)) + .map(|(line, has_leading_whitespace)| { + if orig.starts_with("/*") && line_breaks == 0 { + ( + line.trim_start(), + has_leading_whitespace || config.normalize_comments(), + ) + } else { + (line, has_leading_whitespace || config.normalize_comments()) + } + }); + + for (i, (line, has_leading_whitespace)) in lines.enumerate() { + if rewriter.handle_line(orig, i, line, has_leading_whitespace) { + break; + } + } + + Some(rewriter.finish()) +} + +const RUSTFMT_CUSTOM_COMMENT_PREFIX: &str = "//#### "; + +fn hide_sharp_behind_comment(s: &str) -> Cow<'_, str> { + let s_trimmed = s.trim(); + if s_trimmed.starts_with("# ") || s_trimmed == "#" { + Cow::from(format!("{}{}", RUSTFMT_CUSTOM_COMMENT_PREFIX, s)) + } else { + Cow::from(s) + } +} + +fn trim_custom_comment_prefix(s: &str) -> String { + s.lines() + .map(|line| { + let left_trimmed = line.trim_start(); + if left_trimmed.starts_with(RUSTFMT_CUSTOM_COMMENT_PREFIX) { + left_trimmed.trim_start_matches(RUSTFMT_CUSTOM_COMMENT_PREFIX) + } else { + line + } + }) + .collect::>() + .join("\n") +} + +/// Returns `true` if the given string MAY include URLs or alike. +fn has_url(s: &str) -> bool { + // This function may return false positive, but should get its job done in most cases. + s.contains("https://") || s.contains("http://") || s.contains("ftp://") || s.contains("file://") +} + +/// Given the span, rewrite the missing comment inside it if available. +/// Note that the given span must only include comments (or leading/trailing whitespaces). +pub(crate) fn rewrite_missing_comment( + span: Span, + shape: Shape, + context: &RewriteContext<'_>, +) -> Option { + let missing_snippet = context.snippet(span); + let trimmed_snippet = missing_snippet.trim(); + // check the span starts with a comment + let pos = trimmed_snippet.find('/'); + if !trimmed_snippet.is_empty() && pos.is_some() { + rewrite_comment(trimmed_snippet, false, shape, context.config) + } else { + Some(String::new()) + } +} + +/// Recover the missing comments in the specified span, if available. +/// The layout of the comments will be preserved as long as it does not break the code +/// and its total width does not exceed the max width. +pub(crate) fn recover_missing_comment_in_span( + span: Span, + shape: Shape, + context: &RewriteContext<'_>, + used_width: usize, +) -> Option { + let missing_comment = rewrite_missing_comment(span, shape, context)?; + if missing_comment.is_empty() { + Some(String::new()) + } else { + let missing_snippet = context.snippet(span); + let pos = missing_snippet.find('/')?; + // 1 = ` ` + let total_width = missing_comment.len() + used_width + 1; + let force_new_line_before_comment = + missing_snippet[..pos].contains('\n') || total_width > context.config.max_width(); + let sep = if force_new_line_before_comment { + shape.indent.to_string_with_newline(context.config) + } else { + Cow::from(" ") + }; + Some(format!("{}{}", sep, missing_comment)) + } +} + +/// Trim trailing whitespaces unless they consist of two or more whitespaces. +fn trim_end_unless_two_whitespaces(s: &str, is_doc_comment: bool) -> &str { + if is_doc_comment && s.ends_with(" ") { + s + } else { + s.trim_end() + } +} + +/// Trims whitespace and aligns to indent, but otherwise does not change comments. +fn light_rewrite_comment( + orig: &str, + offset: Indent, + config: &Config, + is_doc_comment: bool, +) -> String { + let lines: Vec<&str> = orig + .lines() + .map(|l| { + // This is basically just l.trim(), but in the case that a line starts + // with `*` we want to leave one space before it, so it aligns with the + // `*` in `/*`. + let first_non_whitespace = l.find(|c| !char::is_whitespace(c)); + let left_trimmed = if let Some(fnw) = first_non_whitespace { + if l.as_bytes()[fnw] == b'*' && fnw > 0 { + &l[fnw - 1..] + } else { + &l[fnw..] + } + } else { + "" + }; + // Preserve markdown's double-space line break syntax in doc comment. + trim_end_unless_two_whitespaces(left_trimmed, is_doc_comment) + }) + .collect(); + lines.join(&format!("\n{}", offset.to_string(config))) +} + +/// Trims comment characters and possibly a single space from the left of a string. +/// Does not trim all whitespace. If a single space is trimmed from the left of the string, +/// this function returns true. +fn left_trim_comment_line<'a>(line: &'a str, style: &CommentStyle<'_>) -> (&'a str, bool) { + if line.starts_with("//! ") + || line.starts_with("/// ") + || line.starts_with("/*! ") + || line.starts_with("/** ") + { + (&line[4..], true) + } else if let CommentStyle::Custom(opener) = *style { + if line.starts_with(opener) { + (&line[opener.len()..], true) + } else { + (&line[opener.trim_end().len()..], false) + } + } else if line.starts_with("/* ") + || line.starts_with("// ") + || line.starts_with("//!") + || line.starts_with("///") + || line.starts_with("** ") + || line.starts_with("/*!") + || (line.starts_with("/**") && !line.starts_with("/**/")) + { + (&line[3..], line.chars().nth(2).unwrap() == ' ') + } else if line.starts_with("/*") + || line.starts_with("* ") + || line.starts_with("//") + || line.starts_with("**") + { + (&line[2..], line.chars().nth(1).unwrap() == ' ') + } else if line.starts_with('*') { + (&line[1..], false) + } else { + (line, line.starts_with(' ')) + } +} + +pub(crate) trait FindUncommented { + fn find_uncommented(&self, pat: &str) -> Option; +} + +impl FindUncommented for str { + fn find_uncommented(&self, pat: &str) -> Option { + let mut needle_iter = pat.chars(); + for (kind, (i, b)) in CharClasses::new(self.char_indices()) { + match needle_iter.next() { + None => { + return Some(i - pat.len()); + } + Some(c) => match kind { + FullCodeCharKind::Normal | FullCodeCharKind::InString if b == c => {} + _ => { + needle_iter = pat.chars(); + } + }, + } + } + + // Handle case where the pattern is a suffix of the search string + match needle_iter.next() { + Some(_) => None, + None => Some(self.len() - pat.len()), + } + } +} + +// Returns the first byte position after the first comment. The given string +// is expected to be prefixed by a comment, including delimiters. +// Good: `/* /* inner */ outer */ code();` +// Bad: `code(); // hello\n world!` +pub(crate) fn find_comment_end(s: &str) -> Option { + let mut iter = CharClasses::new(s.char_indices()); + for (kind, (i, _c)) in &mut iter { + if kind == FullCodeCharKind::Normal || kind == FullCodeCharKind::InString { + return Some(i); + } + } + + // Handle case where the comment ends at the end of `s`. + if iter.status == CharClassesStatus::Normal { + Some(s.len()) + } else { + None + } +} + +/// Returns `true` if text contains any comment. +pub(crate) fn contains_comment(text: &str) -> bool { + CharClasses::new(text.chars()).any(|(kind, _)| kind.is_comment()) +} + +pub(crate) struct CharClasses +where + T: Iterator, + T::Item: RichChar, +{ + base: MultiPeek, + status: CharClassesStatus, +} + +pub(crate) trait RichChar { + fn get_char(&self) -> char; +} + +impl RichChar for char { + fn get_char(&self) -> char { + *self + } +} + +impl RichChar for (usize, char) { + fn get_char(&self) -> char { + self.1 + } +} + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +enum CharClassesStatus { + Normal, + /// Character is within a string + LitString, + LitStringEscape, + /// Character is within a raw string + LitRawString(u32), + RawStringPrefix(u32), + RawStringSuffix(u32), + LitChar, + LitCharEscape, + /// Character inside a block comment, with the integer indicating the nesting deepness of the + /// comment + BlockComment(u32), + /// Character inside a block-commented string, with the integer indicating the nesting deepness + /// of the comment + StringInBlockComment(u32), + /// Status when the '/' has been consumed, but not yet the '*', deepness is + /// the new deepness (after the comment opening). + BlockCommentOpening(u32), + /// Status when the '*' has been consumed, but not yet the '/', deepness is + /// the new deepness (after the comment closing). + BlockCommentClosing(u32), + /// Character is within a line comment + LineComment, +} + +/// Distinguish between functional part of code and comments +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub(crate) enum CodeCharKind { + Normal, + Comment, +} + +/// Distinguish between functional part of code and comments, +/// describing opening and closing of comments for ease when chunking +/// code from tagged characters +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub(crate) enum FullCodeCharKind { + Normal, + /// The first character of a comment, there is only one for a comment (always '/') + StartComment, + /// Any character inside a comment including the second character of comment + /// marks ("//", "/*") + InComment, + /// Last character of a comment, '\n' for a line comment, '/' for a block comment. + EndComment, + /// Start of a mutlitine string inside a comment + StartStringCommented, + /// End of a mutlitine string inside a comment + EndStringCommented, + /// Inside a commented string + InStringCommented, + /// Start of a mutlitine string + StartString, + /// End of a mutlitine string + EndString, + /// Inside a string. + InString, +} + +impl FullCodeCharKind { + pub(crate) fn is_comment(self) -> bool { + match self { + FullCodeCharKind::StartComment + | FullCodeCharKind::InComment + | FullCodeCharKind::EndComment + | FullCodeCharKind::StartStringCommented + | FullCodeCharKind::InStringCommented + | FullCodeCharKind::EndStringCommented => true, + _ => false, + } + } + + /// Returns true if the character is inside a comment + pub(crate) fn inside_comment(self) -> bool { + match self { + FullCodeCharKind::InComment + | FullCodeCharKind::StartStringCommented + | FullCodeCharKind::InStringCommented + | FullCodeCharKind::EndStringCommented => true, + _ => false, + } + } + + pub(crate) fn is_string(self) -> bool { + self == FullCodeCharKind::InString || self == FullCodeCharKind::StartString + } + + /// Returns true if the character is within a commented string + pub(crate) fn is_commented_string(self) -> bool { + self == FullCodeCharKind::InStringCommented + || self == FullCodeCharKind::StartStringCommented + } + + fn to_codecharkind(self) -> CodeCharKind { + if self.is_comment() { + CodeCharKind::Comment + } else { + CodeCharKind::Normal + } + } +} + +impl CharClasses +where + T: Iterator, + T::Item: RichChar, +{ + pub(crate) fn new(base: T) -> CharClasses { + CharClasses { + base: multipeek(base), + status: CharClassesStatus::Normal, + } + } +} + +fn is_raw_string_suffix(iter: &mut MultiPeek, count: u32) -> bool +where + T: Iterator, + T::Item: RichChar, +{ + for _ in 0..count { + match iter.peek() { + Some(c) if c.get_char() == '#' => continue, + _ => return false, + } + } + true +} + +impl Iterator for CharClasses +where + T: Iterator, + T::Item: RichChar, +{ + type Item = (FullCodeCharKind, T::Item); + + fn next(&mut self) -> Option<(FullCodeCharKind, T::Item)> { + let item = self.base.next()?; + let chr = item.get_char(); + let mut char_kind = FullCodeCharKind::Normal; + self.status = match self.status { + CharClassesStatus::LitRawString(sharps) => { + char_kind = FullCodeCharKind::InString; + match chr { + '"' => { + if sharps == 0 { + char_kind = FullCodeCharKind::Normal; + CharClassesStatus::Normal + } else if is_raw_string_suffix(&mut self.base, sharps) { + CharClassesStatus::RawStringSuffix(sharps) + } else { + CharClassesStatus::LitRawString(sharps) + } + } + _ => CharClassesStatus::LitRawString(sharps), + } + } + CharClassesStatus::RawStringPrefix(sharps) => { + char_kind = FullCodeCharKind::InString; + match chr { + '#' => CharClassesStatus::RawStringPrefix(sharps + 1), + '"' => CharClassesStatus::LitRawString(sharps), + _ => CharClassesStatus::Normal, // Unreachable. + } + } + CharClassesStatus::RawStringSuffix(sharps) => { + match chr { + '#' => { + if sharps == 1 { + CharClassesStatus::Normal + } else { + char_kind = FullCodeCharKind::InString; + CharClassesStatus::RawStringSuffix(sharps - 1) + } + } + _ => CharClassesStatus::Normal, // Unreachable + } + } + CharClassesStatus::LitString => { + char_kind = FullCodeCharKind::InString; + match chr { + '"' => CharClassesStatus::Normal, + '\\' => CharClassesStatus::LitStringEscape, + _ => CharClassesStatus::LitString, + } + } + CharClassesStatus::LitStringEscape => { + char_kind = FullCodeCharKind::InString; + CharClassesStatus::LitString + } + CharClassesStatus::LitChar => match chr { + '\\' => CharClassesStatus::LitCharEscape, + '\'' => CharClassesStatus::Normal, + _ => CharClassesStatus::LitChar, + }, + CharClassesStatus::LitCharEscape => CharClassesStatus::LitChar, + CharClassesStatus::Normal => match chr { + 'r' => match self.base.peek().map(RichChar::get_char) { + Some('#') | Some('"') => { + char_kind = FullCodeCharKind::InString; + CharClassesStatus::RawStringPrefix(0) + } + _ => CharClassesStatus::Normal, + }, + '"' => { + char_kind = FullCodeCharKind::InString; + CharClassesStatus::LitString + } + '\'' => { + // HACK: Work around mut borrow. + match self.base.peek() { + Some(next) if next.get_char() == '\\' => { + self.status = CharClassesStatus::LitChar; + return Some((char_kind, item)); + } + _ => (), + } + + match self.base.peek() { + Some(next) if next.get_char() == '\'' => CharClassesStatus::LitChar, + _ => CharClassesStatus::Normal, + } + } + '/' => match self.base.peek() { + Some(next) if next.get_char() == '*' => { + self.status = CharClassesStatus::BlockCommentOpening(1); + return Some((FullCodeCharKind::StartComment, item)); + } + Some(next) if next.get_char() == '/' => { + self.status = CharClassesStatus::LineComment; + return Some((FullCodeCharKind::StartComment, item)); + } + _ => CharClassesStatus::Normal, + }, + _ => CharClassesStatus::Normal, + }, + CharClassesStatus::StringInBlockComment(deepness) => { + char_kind = FullCodeCharKind::InStringCommented; + if chr == '"' { + CharClassesStatus::BlockComment(deepness) + } else { + CharClassesStatus::StringInBlockComment(deepness) + } + } + CharClassesStatus::BlockComment(deepness) => { + assert_ne!(deepness, 0); + char_kind = FullCodeCharKind::InComment; + match self.base.peek() { + Some(next) if next.get_char() == '/' && chr == '*' => { + CharClassesStatus::BlockCommentClosing(deepness - 1) + } + Some(next) if next.get_char() == '*' && chr == '/' => { + CharClassesStatus::BlockCommentOpening(deepness + 1) + } + _ if chr == '"' => CharClassesStatus::StringInBlockComment(deepness), + _ => self.status, + } + } + CharClassesStatus::BlockCommentOpening(deepness) => { + assert_eq!(chr, '*'); + self.status = CharClassesStatus::BlockComment(deepness); + return Some((FullCodeCharKind::InComment, item)); + } + CharClassesStatus::BlockCommentClosing(deepness) => { + assert_eq!(chr, '/'); + if deepness == 0 { + self.status = CharClassesStatus::Normal; + return Some((FullCodeCharKind::EndComment, item)); + } else { + self.status = CharClassesStatus::BlockComment(deepness); + return Some((FullCodeCharKind::InComment, item)); + } + } + CharClassesStatus::LineComment => match chr { + '\n' => { + self.status = CharClassesStatus::Normal; + return Some((FullCodeCharKind::EndComment, item)); + } + _ => { + self.status = CharClassesStatus::LineComment; + return Some((FullCodeCharKind::InComment, item)); + } + }, + }; + Some((char_kind, item)) + } +} + +/// An iterator over the lines of a string, paired with the char kind at the +/// end of the line. +pub(crate) struct LineClasses<'a> { + base: iter::Peekable>>, + kind: FullCodeCharKind, +} + +impl<'a> LineClasses<'a> { + pub(crate) fn new(s: &'a str) -> Self { + LineClasses { + base: CharClasses::new(s.chars()).peekable(), + kind: FullCodeCharKind::Normal, + } + } +} + +impl<'a> Iterator for LineClasses<'a> { + type Item = (FullCodeCharKind, String); + + fn next(&mut self) -> Option { + self.base.peek()?; + + let mut line = String::new(); + + let start_kind = match self.base.peek() { + Some((kind, _)) => *kind, + None => unreachable!(), + }; + + while let Some((kind, c)) = self.base.next() { + // needed to set the kind of the ending character on the last line + self.kind = kind; + if c == '\n' { + self.kind = match (start_kind, kind) { + (FullCodeCharKind::Normal, FullCodeCharKind::InString) => { + FullCodeCharKind::StartString + } + (FullCodeCharKind::InString, FullCodeCharKind::Normal) => { + FullCodeCharKind::EndString + } + (FullCodeCharKind::InComment, FullCodeCharKind::InStringCommented) => { + FullCodeCharKind::StartStringCommented + } + (FullCodeCharKind::InStringCommented, FullCodeCharKind::InComment) => { + FullCodeCharKind::EndStringCommented + } + _ => kind, + }; + break; + } + line.push(c); + } + + // Workaround for CRLF newline. + if line.ends_with('\r') { + line.pop(); + } + + Some((self.kind, line)) + } +} + +/// Iterator over functional and commented parts of a string. Any part of a string is either +/// functional code, either *one* block comment, either *one* line comment. Whitespace between +/// comments is functional code. Line comments contain their ending newlines. +struct UngroupedCommentCodeSlices<'a> { + slice: &'a str, + iter: iter::Peekable>>, +} + +impl<'a> UngroupedCommentCodeSlices<'a> { + fn new(code: &'a str) -> UngroupedCommentCodeSlices<'a> { + UngroupedCommentCodeSlices { + slice: code, + iter: CharClasses::new(code.char_indices()).peekable(), + } + } +} + +impl<'a> Iterator for UngroupedCommentCodeSlices<'a> { + type Item = (CodeCharKind, usize, &'a str); + + fn next(&mut self) -> Option { + let (kind, (start_idx, _)) = self.iter.next()?; + match kind { + FullCodeCharKind::Normal | FullCodeCharKind::InString => { + // Consume all the Normal code + while let Some(&(char_kind, _)) = self.iter.peek() { + if char_kind.is_comment() { + break; + } + let _ = self.iter.next(); + } + } + FullCodeCharKind::StartComment => { + // Consume the whole comment + loop { + match self.iter.next() { + Some((kind, ..)) if kind.inside_comment() => continue, + _ => break, + } + } + } + _ => panic!(), + } + let slice = match self.iter.peek() { + Some(&(_, (end_idx, _))) => &self.slice[start_idx..end_idx], + None => &self.slice[start_idx..], + }; + Some(( + if kind.is_comment() { + CodeCharKind::Comment + } else { + CodeCharKind::Normal + }, + start_idx, + slice, + )) + } +} + +/// Iterator over an alternating sequence of functional and commented parts of +/// a string. The first item is always a, possibly zero length, subslice of +/// functional text. Line style comments contain their ending newlines. +pub(crate) struct CommentCodeSlices<'a> { + slice: &'a str, + last_slice_kind: CodeCharKind, + last_slice_end: usize, +} + +impl<'a> CommentCodeSlices<'a> { + pub(crate) fn new(slice: &'a str) -> CommentCodeSlices<'a> { + CommentCodeSlices { + slice, + last_slice_kind: CodeCharKind::Comment, + last_slice_end: 0, + } + } +} + +impl<'a> Iterator for CommentCodeSlices<'a> { + type Item = (CodeCharKind, usize, &'a str); + + fn next(&mut self) -> Option { + if self.last_slice_end == self.slice.len() { + return None; + } + + let mut sub_slice_end = self.last_slice_end; + let mut first_whitespace = None; + let subslice = &self.slice[self.last_slice_end..]; + let mut iter = CharClasses::new(subslice.char_indices()); + + for (kind, (i, c)) in &mut iter { + let is_comment_connector = self.last_slice_kind == CodeCharKind::Normal + && &subslice[..2] == "//" + && [' ', '\t'].contains(&c); + + if is_comment_connector && first_whitespace.is_none() { + first_whitespace = Some(i); + } + + if kind.to_codecharkind() == self.last_slice_kind && !is_comment_connector { + let last_index = match first_whitespace { + Some(j) => j, + None => i, + }; + sub_slice_end = self.last_slice_end + last_index; + break; + } + + if !is_comment_connector { + first_whitespace = None; + } + } + + if let (None, true) = (iter.next(), sub_slice_end == self.last_slice_end) { + // This was the last subslice. + sub_slice_end = match first_whitespace { + Some(i) => self.last_slice_end + i, + None => self.slice.len(), + }; + } + + let kind = match self.last_slice_kind { + CodeCharKind::Comment => CodeCharKind::Normal, + CodeCharKind::Normal => CodeCharKind::Comment, + }; + let res = ( + kind, + self.last_slice_end, + &self.slice[self.last_slice_end..sub_slice_end], + ); + self.last_slice_end = sub_slice_end; + self.last_slice_kind = kind; + + Some(res) + } +} + +/// Checks is `new` didn't miss any comment from `span`, if it removed any, return previous text +/// (if it fits in the width/offset, else return `None`), else return `new` +pub(crate) fn recover_comment_removed( + new: String, + span: Span, + context: &RewriteContext<'_>, +) -> Option { + let snippet = context.snippet(span); + if snippet != new && changed_comment_content(snippet, &new) { + // We missed some comments. Warn and keep the original text. + if context.config.error_on_unformatted() { + context.report.append( + context.parse_sess.span_to_filename(span), + vec![FormattingError::from_span( + span, + &context.parse_sess, + ErrorKind::LostComment, + )], + ); + } + Some(snippet.to_owned()) + } else { + Some(new) + } +} + +pub(crate) fn filter_normal_code(code: &str) -> String { + let mut buffer = String::with_capacity(code.len()); + LineClasses::new(code).for_each(|(kind, line)| match kind { + FullCodeCharKind::Normal + | FullCodeCharKind::StartString + | FullCodeCharKind::InString + | FullCodeCharKind::EndString => { + buffer.push_str(&line); + buffer.push('\n'); + } + _ => (), + }); + if !code.ends_with('\n') && buffer.ends_with('\n') { + buffer.pop(); + } + buffer +} + +/// Returns `true` if the two strings of code have the same payload of comments. +/// The payload of comments is everything in the string except: +/// - actual code (not comments), +/// - comment start/end marks, +/// - whitespace, +/// - '*' at the beginning of lines in block comments. +fn changed_comment_content(orig: &str, new: &str) -> bool { + // Cannot write this as a fn since we cannot return types containing closures. + let code_comment_content = |code| { + let slices = UngroupedCommentCodeSlices::new(code); + slices + .filter(|&(ref kind, _, _)| *kind == CodeCharKind::Comment) + .flat_map(|(_, _, s)| CommentReducer::new(s)) + }; + let res = code_comment_content(orig).ne(code_comment_content(new)); + debug!( + "comment::changed_comment_content: {}\norig: '{}'\nnew: '{}'\nraw_old: {}\nraw_new: {}", + res, + orig, + new, + code_comment_content(orig).collect::(), + code_comment_content(new).collect::() + ); + res +} + +/// Iterator over the 'payload' characters of a comment. +/// It skips whitespace, comment start/end marks, and '*' at the beginning of lines. +/// The comment must be one comment, ie not more than one start mark (no multiple line comments, +/// for example). +struct CommentReducer<'a> { + is_block: bool, + at_start_line: bool, + iter: std::str::Chars<'a>, +} + +impl<'a> CommentReducer<'a> { + fn new(comment: &'a str) -> CommentReducer<'a> { + let is_block = comment.starts_with("/*"); + let comment = remove_comment_header(comment); + CommentReducer { + is_block, + // There are no supplementary '*' on the first line. + at_start_line: false, + iter: comment.chars(), + } + } +} + +impl<'a> Iterator for CommentReducer<'a> { + type Item = char; + + fn next(&mut self) -> Option { + loop { + let mut c = self.iter.next()?; + if self.is_block && self.at_start_line { + while c.is_whitespace() { + c = self.iter.next()?; + } + // Ignore leading '*'. + if c == '*' { + c = self.iter.next()?; + } + } else if c == '\n' { + self.at_start_line = true; + } + if !c.is_whitespace() { + return Some(c); + } + } + } +} + +fn remove_comment_header(comment: &str) -> &str { + if comment.starts_with("///") || comment.starts_with("//!") { + &comment[3..] + } else if comment.starts_with("//") { + &comment[2..] + } else if (comment.starts_with("/**") && !comment.starts_with("/**/")) + || comment.starts_with("/*!") + { + &comment[3..comment.len() - 2] + } else { + assert!( + comment.starts_with("/*"), + format!("string '{}' is not a comment", comment) + ); + &comment[2..comment.len() - 2] + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::shape::{Indent, Shape}; + + #[test] + fn char_classes() { + let mut iter = CharClasses::new("//\n\n".chars()); + + assert_eq!((FullCodeCharKind::StartComment, '/'), iter.next().unwrap()); + assert_eq!((FullCodeCharKind::InComment, '/'), iter.next().unwrap()); + assert_eq!((FullCodeCharKind::EndComment, '\n'), iter.next().unwrap()); + assert_eq!((FullCodeCharKind::Normal, '\n'), iter.next().unwrap()); + assert_eq!(None, iter.next()); + } + + #[test] + fn comment_code_slices() { + let input = "code(); /* test */ 1 + 1"; + let mut iter = CommentCodeSlices::new(input); + + assert_eq!((CodeCharKind::Normal, 0, "code(); "), iter.next().unwrap()); + assert_eq!( + (CodeCharKind::Comment, 8, "/* test */"), + iter.next().unwrap() + ); + assert_eq!((CodeCharKind::Normal, 18, " 1 + 1"), iter.next().unwrap()); + assert_eq!(None, iter.next()); + } + + #[test] + fn comment_code_slices_two() { + let input = "// comment\n test();"; + let mut iter = CommentCodeSlices::new(input); + + assert_eq!((CodeCharKind::Normal, 0, ""), iter.next().unwrap()); + assert_eq!( + (CodeCharKind::Comment, 0, "// comment\n"), + iter.next().unwrap() + ); + assert_eq!( + (CodeCharKind::Normal, 11, " test();"), + iter.next().unwrap() + ); + assert_eq!(None, iter.next()); + } + + #[test] + fn comment_code_slices_three() { + let input = "1 // comment\n // comment2\n\n"; + let mut iter = CommentCodeSlices::new(input); + + assert_eq!((CodeCharKind::Normal, 0, "1 "), iter.next().unwrap()); + assert_eq!( + (CodeCharKind::Comment, 2, "// comment\n // comment2\n"), + iter.next().unwrap() + ); + assert_eq!((CodeCharKind::Normal, 29, "\n"), iter.next().unwrap()); + assert_eq!(None, iter.next()); + } + + #[test] + #[rustfmt::skip] + fn format_doc_comments() { + let mut wrap_normalize_config: crate::config::Config = Default::default(); + wrap_normalize_config.set().wrap_comments(true); + wrap_normalize_config.set().normalize_comments(true); + + let mut wrap_config: crate::config::Config = Default::default(); + wrap_config.set().wrap_comments(true); + + let comment = rewrite_comment(" //test", + true, + Shape::legacy(100, Indent::new(0, 100)), + &wrap_normalize_config).unwrap(); + assert_eq!("/* test */", comment); + + let comment = rewrite_comment("// comment on a", + false, + Shape::legacy(10, Indent::empty()), + &wrap_normalize_config).unwrap(); + assert_eq!("// comment\n// on a", comment); + + let comment = rewrite_comment("// A multi line comment\n // between args.", + false, + Shape::legacy(60, Indent::new(0, 12)), + &wrap_normalize_config).unwrap(); + assert_eq!("// A multi line comment\n // between args.", comment); + + let input = "// comment"; + let expected = + "/* comment */"; + let comment = rewrite_comment(input, + true, + Shape::legacy(9, Indent::new(0, 69)), + &wrap_normalize_config).unwrap(); + assert_eq!(expected, comment); + + let comment = rewrite_comment("/* trimmed */", + true, + Shape::legacy(100, Indent::new(0, 100)), + &wrap_normalize_config).unwrap(); + assert_eq!("/* trimmed */", comment); + + // Check that different comment style are properly recognised. + let comment = rewrite_comment(r#"/// test1 + /// test2 + /* + * test3 + */"#, + false, + Shape::legacy(100, Indent::new(0, 0)), + &wrap_normalize_config).unwrap(); + assert_eq!("/// test1\n/// test2\n// test3", comment); + + // Check that the blank line marks the end of a commented paragraph. + let comment = rewrite_comment(r#"// test1 + + // test2"#, + false, + Shape::legacy(100, Indent::new(0, 0)), + &wrap_normalize_config).unwrap(); + assert_eq!("// test1\n\n// test2", comment); + + // Check that the blank line marks the end of a custom-commented paragraph. + let comment = rewrite_comment(r#"//@ test1 + + //@ test2"#, + false, + Shape::legacy(100, Indent::new(0, 0)), + &wrap_normalize_config).unwrap(); + assert_eq!("//@ test1\n\n//@ test2", comment); + + // Check that bare lines are just indented but otherwise left unchanged. + let comment = rewrite_comment(r#"// test1 + /* + a bare line! + + another bare line! + */"#, + false, + Shape::legacy(100, Indent::new(0, 0)), + &wrap_config).unwrap(); + assert_eq!("// test1\n/*\n a bare line!\n\n another bare line!\n*/", comment); + } + + // This is probably intended to be a non-test fn, but it is not used. + // We should keep this around unless it helps us test stuff to remove it. + fn uncommented(text: &str) -> String { + CharClasses::new(text.chars()) + .filter_map(|(s, c)| match s { + FullCodeCharKind::Normal | FullCodeCharKind::InString => Some(c), + _ => None, + }) + .collect() + } + + #[test] + fn test_uncommented() { + assert_eq!(&uncommented("abc/*...*/"), "abc"); + assert_eq!( + &uncommented("// .... /* \n../* /* *** / */ */a/* // */c\n"), + "..ac\n" + ); + assert_eq!(&uncommented("abc \" /* */\" qsdf"), "abc \" /* */\" qsdf"); + } + + #[test] + fn test_contains_comment() { + assert_eq!(contains_comment("abc"), false); + assert_eq!(contains_comment("abc // qsdf"), true); + assert_eq!(contains_comment("abc /* kqsdf"), true); + assert_eq!(contains_comment("abc \" /* */\" qsdf"), false); + } + + #[test] + fn test_find_uncommented() { + fn check(haystack: &str, needle: &str, expected: Option) { + assert_eq!(expected, haystack.find_uncommented(needle)); + } + + check("/*/ */test", "test", Some(6)); + check("//test\ntest", "test", Some(7)); + check("/* comment only */", "whatever", None); + check( + "/* comment */ some text /* more commentary */ result", + "result", + Some(46), + ); + check("sup // sup", "p", Some(2)); + check("sup", "x", None); + check(r#"π? /**/ π is nice!"#, r#"π is nice"#, Some(9)); + check("/*sup yo? \n sup*/ sup", "p", Some(20)); + check("hel/*lohello*/lo", "hello", None); + check("acb", "ab", None); + check(",/*A*/ ", ",", Some(0)); + check("abc", "abc", Some(0)); + check("/* abc */", "abc", None); + check("/**/abc/* */", "abc", Some(4)); + check("\"/* abc */\"", "abc", Some(4)); + check("\"/* abc", "abc", Some(4)); + } + + #[test] + fn test_filter_normal_code() { + let s = r#" +fn main() { + println!("hello, world"); +} +"#; + assert_eq!(s, filter_normal_code(s)); + let s_with_comment = r#" +fn main() { + // hello, world + println!("hello, world"); +} +"#; + assert_eq!(s, filter_normal_code(s_with_comment)); + } +} diff --git a/src/tools/rustfmt/src/config/config_type.rs b/src/tools/rustfmt/src/config/config_type.rs new file mode 100644 index 0000000000..bd4a847e0f --- /dev/null +++ b/src/tools/rustfmt/src/config/config_type.rs @@ -0,0 +1,355 @@ +use crate::config::file_lines::FileLines; +use crate::config::options::{IgnoreList, WidthHeuristics}; + +/// Trait for types that can be used in `Config`. +pub(crate) trait ConfigType: Sized { + /// Returns hint text for use in `Config::print_docs()`. For enum types, this is a + /// pipe-separated list of variants; for other types it returns "". + fn doc_hint() -> String; +} + +impl ConfigType for bool { + fn doc_hint() -> String { + String::from("") + } +} + +impl ConfigType for usize { + fn doc_hint() -> String { + String::from("") + } +} + +impl ConfigType for isize { + fn doc_hint() -> String { + String::from("") + } +} + +impl ConfigType for String { + fn doc_hint() -> String { + String::from("") + } +} + +impl ConfigType for FileLines { + fn doc_hint() -> String { + String::from("") + } +} + +impl ConfigType for WidthHeuristics { + fn doc_hint() -> String { + String::new() + } +} + +impl ConfigType for IgnoreList { + fn doc_hint() -> String { + String::from("[,..]") + } +} + +macro_rules! create_config { + ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => ( + #[cfg(test)] + use std::collections::HashSet; + use std::io::Write; + + use serde::{Deserialize, Serialize}; + + #[derive(Clone)] + #[allow(unreachable_pub)] + pub struct Config { + // if a license_template_path has been specified, successfully read, parsed and compiled + // into a regex, it will be stored here + pub license_template: Option, + // For each config item, we store a bool indicating whether it has + // been accessed and the value, and a bool whether the option was + // manually initialised, or taken from the default, + $($i: (Cell, bool, $ty, bool)),+ + } + + // Just like the Config struct but with each property wrapped + // as Option. This is used to parse a rustfmt.toml that doesn't + // specify all properties of `Config`. + // We first parse into `PartialConfig`, then create a default `Config` + // and overwrite the properties with corresponding values from `PartialConfig`. + #[derive(Deserialize, Serialize, Clone)] + #[allow(unreachable_pub)] + pub struct PartialConfig { + $(pub $i: Option<$ty>),+ + } + + // Macro hygiene won't allow us to make `set_$i()` methods on Config + // for each item, so this struct is used to give the API to set values: + // `config.set().option(false)`. It's pretty ugly. Consider replacing + // with `config.set_option(false)` if we ever get a stable/usable + // `concat_idents!()`. + #[allow(unreachable_pub)] + pub struct ConfigSetter<'a>(&'a mut Config); + + impl<'a> ConfigSetter<'a> { + $( + #[allow(unreachable_pub)] + pub fn $i(&mut self, value: $ty) { + (self.0).$i.2 = value; + match stringify!($i) { + "max_width" | "use_small_heuristics" => self.0.set_heuristics(), + "license_template_path" => self.0.set_license_template(), + "merge_imports" => self.0.set_merge_imports(), + &_ => (), + } + } + )+ + } + + // Query each option, returns true if the user set the option, false if + // a default was used. + #[allow(unreachable_pub)] + pub struct ConfigWasSet<'a>(&'a Config); + + impl<'a> ConfigWasSet<'a> { + $( + #[allow(unreachable_pub)] + pub fn $i(&self) -> bool { + (self.0).$i.1 + } + )+ + } + + impl Config { + $( + #[allow(unreachable_pub)] + pub fn $i(&self) -> $ty { + self.$i.0.set(true); + self.$i.2.clone() + } + )+ + + #[allow(unreachable_pub)] + pub fn set(&mut self) -> ConfigSetter<'_> { + ConfigSetter(self) + } + + #[allow(unreachable_pub)] + pub fn was_set(&self) -> ConfigWasSet<'_> { + ConfigWasSet(self) + } + + fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config { + $( + if let Some(val) = parsed.$i { + if self.$i.3 { + self.$i.1 = true; + self.$i.2 = val; + } else { + if crate::is_nightly_channel!() { + self.$i.1 = true; + self.$i.2 = val; + } else { + eprintln!("Warning: can't set `{} = {:?}`, unstable features are only \ + available in nightly channel.", stringify!($i), val); + } + } + } + )+ + self.set_heuristics(); + self.set_license_template(); + self.set_ignore(dir); + self.set_merge_imports(); + self + } + + /// Returns a hash set initialized with every user-facing config option name. + #[cfg(test)] + pub(crate) fn hash_set() -> HashSet { + let mut hash_set = HashSet::new(); + $( + hash_set.insert(stringify!($i).to_owned()); + )+ + hash_set + } + + pub(crate) fn is_valid_name(name: &str) -> bool { + match name { + $( + stringify!($i) => true, + )+ + _ => false, + } + } + + #[allow(unreachable_pub)] + pub fn is_valid_key_val(key: &str, val: &str) -> bool { + match key { + $( + stringify!($i) => val.parse::<$ty>().is_ok(), + )+ + _ => false, + } + } + + #[allow(unreachable_pub)] + pub fn used_options(&self) -> PartialConfig { + PartialConfig { + $( + $i: if self.$i.0.get() { + Some(self.$i.2.clone()) + } else { + None + }, + )+ + } + } + + #[allow(unreachable_pub)] + pub fn all_options(&self) -> PartialConfig { + PartialConfig { + $( + $i: Some(self.$i.2.clone()), + )+ + } + } + + #[allow(unreachable_pub)] + pub fn override_value(&mut self, key: &str, val: &str) + { + match key { + $( + stringify!($i) => { + self.$i.1 = true; + self.$i.2 = val.parse::<$ty>() + .expect(&format!("Failed to parse override for {} (\"{}\") as a {}", + stringify!($i), + val, + stringify!($ty))); + } + )+ + _ => panic!("Unknown config key in override: {}", key) + } + + match key { + "max_width" | "use_small_heuristics" => self.set_heuristics(), + "license_template_path" => self.set_license_template(), + "merge_imports" => self.set_merge_imports(), + &_ => (), + } + } + + #[allow(unreachable_pub)] + pub fn is_hidden_option(name: &str) -> bool { + const HIDE_OPTIONS: [&str; 5] = + ["verbose", "verbose_diff", "file_lines", "width_heuristics", "merge_imports"]; + HIDE_OPTIONS.contains(&name) + } + + #[allow(unreachable_pub)] + pub fn print_docs(out: &mut dyn Write, include_unstable: bool) { + use std::cmp; + let max = 0; + $( let max = cmp::max(max, stringify!($i).len()+1); )+ + let space_str = " ".repeat(max); + writeln!(out, "Configuration Options:").unwrap(); + $( + if $stb || include_unstable { + let name_raw = stringify!($i); + + if !Config::is_hidden_option(name_raw) { + let mut name_out = String::with_capacity(max); + for _ in name_raw.len()..max-1 { + name_out.push(' ') + } + name_out.push_str(name_raw); + name_out.push(' '); + let mut default_str = format!("{}", $def); + if default_str.is_empty() { + default_str = String::from("\"\""); + } + writeln!(out, + "{}{} Default: {}{}", + name_out, + <$ty>::doc_hint(), + default_str, + if !$stb { " (unstable)" } else { "" }).unwrap(); + $( + writeln!(out, "{}{}", space_str, $dstring).unwrap(); + )+ + writeln!(out).unwrap(); + } + } + )+ + } + + fn set_heuristics(&mut self) { + if self.use_small_heuristics.2 == Heuristics::Default { + let max_width = self.max_width.2; + self.set().width_heuristics(WidthHeuristics::scaled(max_width)); + } else if self.use_small_heuristics.2 == Heuristics::Max { + let max_width = self.max_width.2; + self.set().width_heuristics(WidthHeuristics::set(max_width)); + } else { + self.set().width_heuristics(WidthHeuristics::null()); + } + } + + fn set_license_template(&mut self) { + if self.was_set().license_template_path() { + let lt_path = self.license_template_path(); + if lt_path.len() > 0 { + match license::load_and_compile_template(<_path) { + Ok(re) => self.license_template = Some(re), + Err(msg) => eprintln!("Warning for license template file {:?}: {}", + lt_path, msg), + } + } else { + self.license_template = None; + } + } + } + + fn set_ignore(&mut self, dir: &Path) { + self.ignore.2.add_prefix(dir); + } + + fn set_merge_imports(&mut self) { + if self.was_set().merge_imports() { + eprintln!( + "Warning: the `merge_imports` option is deprecated. \ + Use `imports_granularity=Crate` instead" + ); + if !self.was_set().imports_granularity() { + self.imports_granularity.2 = if self.merge_imports() { + ImportGranularity::Crate + } else { + ImportGranularity::Preserve + }; + } + } + } + + #[allow(unreachable_pub)] + /// Returns `true` if the config key was explicitly set and is the default value. + pub fn is_default(&self, key: &str) -> bool { + $( + if let stringify!($i) = key { + return self.$i.1 && self.$i.2 == $def; + } + )+ + false + } + } + + // Template for the default configuration + impl Default for Config { + fn default() -> Config { + Config { + license_template: None, + $( + $i: (Cell::new(false), false, $def, $stb), + )+ + } + } + } + ) +} diff --git a/src/tools/rustfmt/src/config/file_lines.rs b/src/tools/rustfmt/src/config/file_lines.rs new file mode 100644 index 0000000000..18ae2fd2c4 --- /dev/null +++ b/src/tools/rustfmt/src/config/file_lines.rs @@ -0,0 +1,440 @@ +//! This module contains types and functions to support formatting specific line ranges. + +use itertools::Itertools; +use std::collections::HashMap; +use std::path::PathBuf; +use std::rc::Rc; +use std::{cmp, fmt, iter, str}; + +use rustc_span::{self, SourceFile}; +use serde::{ser, Deserialize, Deserializer, Serialize, Serializer}; +use serde_json as json; +use thiserror::Error; + +/// A range of lines in a file, inclusive of both ends. +pub struct LineRange { + pub file: Rc, + pub lo: usize, + pub hi: usize, +} + +/// Defines the name of an input - either a file or stdin. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FileName { + Real(PathBuf), + Stdin, +} + +impl From for FileName { + fn from(name: rustc_span::FileName) -> FileName { + match name { + rustc_span::FileName::Real(p) => FileName::Real(p.into_local_path()), + rustc_span::FileName::Custom(ref f) if f == "stdin" => FileName::Stdin, + _ => unreachable!(), + } + } +} + +impl fmt::Display for FileName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FileName::Real(p) => write!(f, "{}", p.to_str().unwrap()), + FileName::Stdin => write!(f, "stdin"), + } + } +} + +impl<'de> Deserialize<'de> for FileName { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if s == "stdin" { + Ok(FileName::Stdin) + } else { + Ok(FileName::Real(s.into())) + } + } +} + +impl Serialize for FileName { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = match self { + FileName::Stdin => Ok("stdin"), + FileName::Real(path) => path + .to_str() + .ok_or_else(|| ser::Error::custom("path can't be serialized as UTF-8 string")), + }; + + s.and_then(|s| serializer.serialize_str(s)) + } +} + +impl LineRange { + pub fn file_name(&self) -> FileName { + self.file.name.clone().into() + } +} + +/// A range that is inclusive of both ends. +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize)] +pub struct Range { + lo: usize, + hi: usize, +} + +impl<'a> From<&'a LineRange> for Range { + fn from(range: &'a LineRange) -> Range { + Range::new(range.lo, range.hi) + } +} + +impl fmt::Display for Range { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}..{}", self.lo, self.hi) + } +} + +impl Range { + pub fn new(lo: usize, hi: usize) -> Range { + Range { lo, hi } + } + + fn is_empty(self) -> bool { + self.lo > self.hi + } + + #[allow(dead_code)] + fn contains(self, other: Range) -> bool { + if other.is_empty() { + true + } else { + !self.is_empty() && self.lo <= other.lo && self.hi >= other.hi + } + } + + fn intersects(self, other: Range) -> bool { + if self.is_empty() || other.is_empty() { + false + } else { + (self.lo <= other.hi && other.hi <= self.hi) + || (other.lo <= self.hi && self.hi <= other.hi) + } + } + + fn adjacent_to(self, other: Range) -> bool { + if self.is_empty() || other.is_empty() { + false + } else { + self.hi + 1 == other.lo || other.hi + 1 == self.lo + } + } + + /// Returns a new `Range` with lines from `self` and `other` if they were adjacent or + /// intersect; returns `None` otherwise. + fn merge(self, other: Range) -> Option { + if self.adjacent_to(other) || self.intersects(other) { + Some(Range::new( + cmp::min(self.lo, other.lo), + cmp::max(self.hi, other.hi), + )) + } else { + None + } + } +} + +/// A set of lines in files. +/// +/// It is represented as a multimap keyed on file names, with values a collection of +/// non-overlapping ranges sorted by their start point. An inner `None` is interpreted to mean all +/// lines in all files. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct FileLines(Option>>); + +impl fmt::Display for FileLines { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + None => write!(f, "None")?, + Some(map) => { + for (file_name, ranges) in map.iter() { + write!(f, "{}: ", file_name)?; + write!(f, "{}\n", ranges.iter().format(", "))?; + } + } + }; + Ok(()) + } +} + +/// Normalizes the ranges so that the invariants for `FileLines` hold: ranges are non-overlapping, +/// and ordered by their start point. +fn normalize_ranges(ranges: &mut HashMap>) { + for ranges in ranges.values_mut() { + ranges.sort(); + let mut result = vec![]; + let mut iter = ranges.iter_mut().peekable(); + while let Some(next) = iter.next() { + let mut next = *next; + while let Some(&&mut peek) = iter.peek() { + if let Some(merged) = next.merge(peek) { + iter.next().unwrap(); + next = merged; + } else { + break; + } + } + result.push(next) + } + *ranges = result; + } +} + +impl FileLines { + /// Creates a `FileLines` that contains all lines in all files. + pub(crate) fn all() -> FileLines { + FileLines(None) + } + + /// Returns `true` if this `FileLines` contains all lines in all files. + pub(crate) fn is_all(&self) -> bool { + self.0.is_none() + } + + pub fn from_ranges(mut ranges: HashMap>) -> FileLines { + normalize_ranges(&mut ranges); + FileLines(Some(ranges)) + } + + /// Returns an iterator over the files contained in `self`. + pub fn files(&self) -> Files<'_> { + Files(self.0.as_ref().map(HashMap::keys)) + } + + /// Returns JSON representation as accepted by the `--file-lines JSON` arg. + pub fn to_json_spans(&self) -> Vec { + match &self.0 { + None => vec![], + Some(file_ranges) => file_ranges + .iter() + .flat_map(|(file, ranges)| ranges.iter().map(move |r| (file, r))) + .map(|(file, range)| JsonSpan { + file: file.to_owned(), + range: (range.lo, range.hi), + }) + .collect(), + } + } + + /// Returns `true` if `self` includes all lines in all files. Otherwise runs `f` on all ranges + /// in the designated file (if any) and returns true if `f` ever does. + fn file_range_matches(&self, file_name: &FileName, f: F) -> bool + where + F: FnMut(&Range) -> bool, + { + let map = match self.0 { + // `None` means "all lines in all files". + None => return true, + Some(ref map) => map, + }; + + match canonicalize_path_string(file_name).and_then(|file| map.get(&file)) { + Some(ranges) => ranges.iter().any(f), + None => false, + } + } + + /// Returns `true` if `range` is fully contained in `self`. + #[allow(dead_code)] + pub(crate) fn contains(&self, range: &LineRange) -> bool { + self.file_range_matches(&range.file_name(), |r| r.contains(Range::from(range))) + } + + /// Returns `true` if any lines in `range` are in `self`. + pub(crate) fn intersects(&self, range: &LineRange) -> bool { + self.file_range_matches(&range.file_name(), |r| r.intersects(Range::from(range))) + } + + /// Returns `true` if `line` from `file_name` is in `self`. + pub(crate) fn contains_line(&self, file_name: &FileName, line: usize) -> bool { + self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line) + } + + /// Returns `true` if all the lines between `lo` and `hi` from `file_name` are in `self`. + pub(crate) fn contains_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool { + self.file_range_matches(file_name, |r| r.contains(Range::new(lo, hi))) + } +} + +/// `FileLines` files iterator. +pub struct Files<'a>(Option<::std::collections::hash_map::Keys<'a, FileName, Vec>>); + +impl<'a> iter::Iterator for Files<'a> { + type Item = &'a FileName; + + fn next(&mut self) -> Option<&'a FileName> { + self.0.as_mut().and_then(Iterator::next) + } +} + +fn canonicalize_path_string(file: &FileName) -> Option { + match *file { + FileName::Real(ref path) => path.canonicalize().ok().map(FileName::Real), + _ => Some(file.clone()), + } +} + +#[derive(Error, Debug)] +pub enum FileLinesError { + #[error("{0}")] + Json(json::Error), + #[error("Can't canonicalize {0}")] + CannotCanonicalize(FileName), +} + +// This impl is needed for `Config::override_value` to work for use in tests. +impl str::FromStr for FileLines { + type Err = FileLinesError; + + fn from_str(s: &str) -> Result { + let v: Vec = json::from_str(s).map_err(FileLinesError::Json)?; + let mut m = HashMap::new(); + for js in v { + let (s, r) = JsonSpan::into_tuple(js)?; + m.entry(s).or_insert_with(|| vec![]).push(r); + } + Ok(FileLines::from_ranges(m)) + } +} + +// For JSON decoding. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)] +pub struct JsonSpan { + file: FileName, + range: (usize, usize), +} + +impl JsonSpan { + fn into_tuple(self) -> Result<(FileName, Range), FileLinesError> { + let (lo, hi) = self.range; + let canonical = canonicalize_path_string(&self.file) + .ok_or_else(|| FileLinesError::CannotCanonicalize(self.file))?; + Ok((canonical, Range::new(lo, hi))) + } +} + +// This impl is needed for inclusion in the `Config` struct. We don't have a toml representation +// for `FileLines`, so it will just panic instead. +impl<'de> ::serde::de::Deserialize<'de> for FileLines { + fn deserialize(_: D) -> Result + where + D: ::serde::de::Deserializer<'de>, + { + panic!( + "FileLines cannot be deserialized from a project rustfmt.toml file: please \ + specify it via the `--file-lines` option instead" + ); + } +} + +// We also want to avoid attempting to serialize a FileLines to toml. The +// `Config` struct should ensure this impl is never reached. +impl ::serde::ser::Serialize for FileLines { + fn serialize(&self, _: S) -> Result + where + S: ::serde::ser::Serializer, + { + unreachable!("FileLines cannot be serialized. This is a rustfmt bug."); + } +} + +#[cfg(test)] +mod test { + use super::Range; + + #[test] + fn test_range_intersects() { + assert!(Range::new(1, 2).intersects(Range::new(1, 1))); + assert!(Range::new(1, 2).intersects(Range::new(2, 2))); + assert!(!Range::new(1, 2).intersects(Range::new(0, 0))); + assert!(!Range::new(1, 2).intersects(Range::new(3, 10))); + assert!(!Range::new(1, 3).intersects(Range::new(5, 5))); + } + + #[test] + fn test_range_adjacent_to() { + assert!(!Range::new(1, 2).adjacent_to(Range::new(1, 1))); + assert!(!Range::new(1, 2).adjacent_to(Range::new(2, 2))); + assert!(Range::new(1, 2).adjacent_to(Range::new(0, 0))); + assert!(Range::new(1, 2).adjacent_to(Range::new(3, 10))); + assert!(!Range::new(1, 3).adjacent_to(Range::new(5, 5))); + } + + #[test] + fn test_range_contains() { + assert!(Range::new(1, 2).contains(Range::new(1, 1))); + assert!(Range::new(1, 2).contains(Range::new(2, 2))); + assert!(!Range::new(1, 2).contains(Range::new(0, 0))); + assert!(!Range::new(1, 2).contains(Range::new(3, 10))); + } + + #[test] + fn test_range_merge() { + assert_eq!(None, Range::new(1, 3).merge(Range::new(5, 5))); + assert_eq!(None, Range::new(4, 7).merge(Range::new(0, 1))); + assert_eq!( + Some(Range::new(3, 7)), + Range::new(3, 5).merge(Range::new(4, 7)) + ); + assert_eq!( + Some(Range::new(3, 7)), + Range::new(3, 5).merge(Range::new(5, 7)) + ); + assert_eq!( + Some(Range::new(3, 7)), + Range::new(3, 5).merge(Range::new(6, 7)) + ); + assert_eq!( + Some(Range::new(3, 7)), + Range::new(3, 7).merge(Range::new(4, 5)) + ); + } + + use super::json::{self, json}; + use super::{FileLines, FileName}; + use std::{collections::HashMap, path::PathBuf}; + + #[test] + fn file_lines_to_json() { + let ranges: HashMap> = [ + ( + FileName::Real(PathBuf::from("src/main.rs")), + vec![Range::new(1, 3), Range::new(5, 7)], + ), + ( + FileName::Real(PathBuf::from("src/lib.rs")), + vec![Range::new(1, 7)], + ), + ] + .iter() + .cloned() + .collect(); + + let file_lines = FileLines::from_ranges(ranges); + let mut spans = file_lines.to_json_spans(); + spans.sort(); + let json = json::to_value(&spans).unwrap(); + assert_eq!( + json, + json! {[ + {"file": "src/lib.rs", "range": [1, 7]}, + {"file": "src/main.rs", "range": [1, 3]}, + {"file": "src/main.rs", "range": [5, 7]}, + ]} + ); + } +} diff --git a/src/tools/rustfmt/src/config/license.rs b/src/tools/rustfmt/src/config/license.rs new file mode 100644 index 0000000000..121a1b1c15 --- /dev/null +++ b/src/tools/rustfmt/src/config/license.rs @@ -0,0 +1,266 @@ +use std::fmt; +use std::fs::File; +use std::io; +use std::io::Read; + +use regex; +use regex::Regex; + +#[derive(Debug)] +pub(crate) enum LicenseError { + IO(io::Error), + Regex(regex::Error), + Parse(String), +} + +impl fmt::Display for LicenseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + LicenseError::IO(ref err) => err.fmt(f), + LicenseError::Regex(ref err) => err.fmt(f), + LicenseError::Parse(ref err) => write!(f, "parsing failed, {}", err), + } + } +} + +impl From for LicenseError { + fn from(err: io::Error) -> LicenseError { + LicenseError::IO(err) + } +} + +impl From for LicenseError { + fn from(err: regex::Error) -> LicenseError { + LicenseError::Regex(err) + } +} + +// the template is parsed using a state machine +enum ParsingState { + Lit, + LitEsc, + // the u32 keeps track of brace nesting + Re(u32), + ReEsc(u32), + Abort(String), +} + +use self::ParsingState::*; + +pub(crate) struct TemplateParser { + parsed: String, + buffer: String, + state: ParsingState, + linum: u32, + open_brace_line: u32, +} + +impl TemplateParser { + fn new() -> Self { + Self { + parsed: "^".to_owned(), + buffer: String::new(), + state: Lit, + linum: 1, + // keeps track of last line on which a regex placeholder was started + open_brace_line: 0, + } + } + + /// Converts a license template into a string which can be turned into a regex. + /// + /// The license template could use regex syntax directly, but that would require a lot of manual + /// escaping, which is inconvenient. It is therefore literal by default, with optional regex + /// subparts delimited by `{` and `}`. Additionally: + /// + /// - to insert literal `{`, `}` or `\`, escape it with `\` + /// - an empty regex placeholder (`{}`) is shorthand for `{.*?}` + /// + /// This function parses this input format and builds a properly escaped *string* representation + /// of the equivalent regular expression. It **does not** however guarantee that the returned + /// string is a syntactically valid regular expression. + /// + /// # Examples + /// + /// ```text + /// assert_eq!( + /// TemplateParser::parse( + /// r" + /// // Copyright {\d+} The \} Rust \\ Project \{ Developers. See the {([A-Z]+)} + /// // file at the top-level directory of this distribution and at + /// // {}. + /// // + /// // Licensed under the Apache License, Version 2.0 or the MIT license + /// // , at your + /// // option. This file may not be copied, modified, or distributed + /// // except according to those terms. + /// " + /// ).unwrap(), + /// r"^ + /// // Copyright \d+ The \} Rust \\ Project \{ Developers\. See the ([A-Z]+) + /// // file at the top\-level directory of this distribution and at + /// // .*?\. + /// // + /// // Licensed under the Apache License, Version 2\.0 or the MIT license + /// // , at your + /// // option\. This file may not be copied, modified, or distributed + /// // except according to those terms\. + /// " + /// ); + /// ``` + pub(crate) fn parse(template: &str) -> Result { + let mut parser = Self::new(); + for chr in template.chars() { + if chr == '\n' { + parser.linum += 1; + } + parser.state = match parser.state { + Lit => parser.trans_from_lit(chr), + LitEsc => parser.trans_from_litesc(chr), + Re(brace_nesting) => parser.trans_from_re(chr, brace_nesting), + ReEsc(brace_nesting) => parser.trans_from_reesc(chr, brace_nesting), + Abort(msg) => return Err(LicenseError::Parse(msg)), + }; + } + // check if we've ended parsing in a valid state + match parser.state { + Abort(msg) => return Err(LicenseError::Parse(msg)), + Re(_) | ReEsc(_) => { + return Err(LicenseError::Parse(format!( + "escape or balance opening brace on l. {}", + parser.open_brace_line + ))); + } + LitEsc => { + return Err(LicenseError::Parse(format!( + "incomplete escape sequence on l. {}", + parser.linum + ))); + } + _ => (), + } + parser.parsed.push_str(®ex::escape(&parser.buffer)); + + Ok(parser.parsed) + } + + fn trans_from_lit(&mut self, chr: char) -> ParsingState { + match chr { + '{' => { + self.parsed.push_str(®ex::escape(&self.buffer)); + self.buffer.clear(); + self.open_brace_line = self.linum; + Re(1) + } + '}' => Abort(format!( + "escape or balance closing brace on l. {}", + self.linum + )), + '\\' => LitEsc, + _ => { + self.buffer.push(chr); + Lit + } + } + } + + fn trans_from_litesc(&mut self, chr: char) -> ParsingState { + self.buffer.push(chr); + Lit + } + + fn trans_from_re(&mut self, chr: char, brace_nesting: u32) -> ParsingState { + match chr { + '{' => { + self.buffer.push(chr); + Re(brace_nesting + 1) + } + '}' => { + match brace_nesting { + 1 => { + // default regex for empty placeholder {} + if self.buffer.is_empty() { + self.parsed.push_str(".*?"); + } else { + self.parsed.push_str(&self.buffer); + } + self.buffer.clear(); + Lit + } + _ => { + self.buffer.push(chr); + Re(brace_nesting - 1) + } + } + } + '\\' => { + self.buffer.push(chr); + ReEsc(brace_nesting) + } + _ => { + self.buffer.push(chr); + Re(brace_nesting) + } + } + } + + fn trans_from_reesc(&mut self, chr: char, brace_nesting: u32) -> ParsingState { + self.buffer.push(chr); + Re(brace_nesting) + } +} + +pub(crate) fn load_and_compile_template(path: &str) -> Result { + let mut lt_file = File::open(&path)?; + let mut lt_str = String::new(); + lt_file.read_to_string(&mut lt_str)?; + let lt_parsed = TemplateParser::parse(<_str)?; + Ok(Regex::new(<_parsed)?) +} + +#[cfg(test)] +mod test { + use super::TemplateParser; + + #[test] + fn test_parse_license_template() { + assert_eq!( + TemplateParser::parse("literal (.*)").unwrap(), + r"^literal \(\.\*\)" + ); + assert_eq!( + TemplateParser::parse(r"escaping \}").unwrap(), + r"^escaping \}" + ); + assert!(TemplateParser::parse("unbalanced } without escape").is_err()); + assert_eq!( + TemplateParser::parse(r"{\d+} place{-?}holder{s?}").unwrap(), + r"^\d+ place-?holders?" + ); + assert_eq!(TemplateParser::parse("default {}").unwrap(), "^default .*?"); + assert_eq!( + TemplateParser::parse(r"unbalanced nested braces {\{{3}}").unwrap(), + r"^unbalanced nested braces \{{3}" + ); + assert_eq!( + &TemplateParser::parse("parsing error }") + .unwrap_err() + .to_string(), + "parsing failed, escape or balance closing brace on l. 1" + ); + assert_eq!( + &TemplateParser::parse("parsing error {\nsecond line") + .unwrap_err() + .to_string(), + "parsing failed, escape or balance opening brace on l. 1" + ); + assert_eq!( + &TemplateParser::parse(r"parsing error \") + .unwrap_err() + .to_string(), + "parsing failed, incomplete escape sequence on l. 1" + ); + } +} diff --git a/src/tools/rustfmt/src/config/lists.rs b/src/tools/rustfmt/src/config/lists.rs new file mode 100644 index 0000000000..11cb17068f --- /dev/null +++ b/src/tools/rustfmt/src/config/lists.rs @@ -0,0 +1,92 @@ +//! Configuration options related to rewriting a list. + +use rustfmt_config_proc_macro::config_type; + +use crate::config::IndentStyle; + +/// The definitive formatting tactic for lists. +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +pub enum DefinitiveListTactic { + Vertical, + Horizontal, + Mixed, + /// Special case tactic for `format!()`, `write!()` style macros. + SpecialMacro(usize), +} + +impl DefinitiveListTactic { + pub fn ends_with_newline(&self, indent_style: IndentStyle) -> bool { + match indent_style { + IndentStyle::Block => *self != DefinitiveListTactic::Horizontal, + IndentStyle::Visual => false, + } + } +} + +/// Formatting tactic for lists. This will be cast down to a +/// `DefinitiveListTactic` depending on the number and length of the items and +/// their comments. +#[config_type] +pub enum ListTactic { + /// One item per row. + Vertical, + /// All items on one row. + Horizontal, + /// Try Horizontal layout, if that fails then vertical. + HorizontalVertical, + /// HorizontalVertical with a soft limit of n characters. + LimitedHorizontalVertical(usize), + /// Pack as many items as possible per row over (possibly) many rows. + Mixed, +} + +#[config_type] +pub enum SeparatorTactic { + Always, + Never, + Vertical, +} + +impl SeparatorTactic { + pub fn from_bool(b: bool) -> SeparatorTactic { + if b { + SeparatorTactic::Always + } else { + SeparatorTactic::Never + } + } +} + +/// Where to put separator. +#[config_type] +pub enum SeparatorPlace { + Front, + Back, +} + +impl SeparatorPlace { + pub fn is_front(self) -> bool { + self == SeparatorPlace::Front + } + + pub fn is_back(self) -> bool { + self == SeparatorPlace::Back + } + + pub fn from_tactic( + default: SeparatorPlace, + tactic: DefinitiveListTactic, + sep: &str, + ) -> SeparatorPlace { + match tactic { + DefinitiveListTactic::Vertical => default, + _ => { + if sep == "," { + SeparatorPlace::Back + } else { + default + } + } + } + } +} diff --git a/src/tools/rustfmt/src/config/mod.rs b/src/tools/rustfmt/src/config/mod.rs new file mode 100644 index 0000000000..dfce7977bf --- /dev/null +++ b/src/tools/rustfmt/src/config/mod.rs @@ -0,0 +1,686 @@ +use std::cell::Cell; +use std::default::Default; +use std::fs::File; +use std::io::{Error, ErrorKind, Read}; +use std::path::{Path, PathBuf}; +use std::{env, fs}; + +use regex::Regex; +use thiserror::Error; + +use crate::config::config_type::ConfigType; +#[allow(unreachable_pub)] +pub use crate::config::file_lines::{FileLines, FileName, Range}; +#[allow(unreachable_pub)] +pub use crate::config::lists::*; +#[allow(unreachable_pub)] +pub use crate::config::options::*; + +#[macro_use] +pub(crate) mod config_type; +#[macro_use] +pub(crate) mod options; + +pub(crate) mod file_lines; +pub(crate) mod license; +pub(crate) mod lists; + +// This macro defines configuration options used in rustfmt. Each option +// is defined as follows: +// +// `name: value type, default value, is stable, description;` +create_config! { + // Fundamental stuff + max_width: usize, 100, true, "Maximum width of each line"; + hard_tabs: bool, false, true, "Use tab characters for indentation, spaces for alignment"; + tab_spaces: usize, 4, true, "Number of spaces per tab"; + newline_style: NewlineStyle, NewlineStyle::Auto, true, "Unix or Windows line endings"; + use_small_heuristics: Heuristics, Heuristics::Default, true, "Whether to use different \ + formatting for items and expressions if they satisfy a heuristic notion of 'small'"; + indent_style: IndentStyle, IndentStyle::Block, false, "How do we indent expressions or items"; + + // Comments. macros, and strings + wrap_comments: bool, false, false, "Break comments to fit on the line"; + format_code_in_doc_comments: bool, false, false, "Format the code snippet in doc comments."; + comment_width: usize, 80, false, + "Maximum length of comments. No effect unless wrap_comments = true"; + normalize_comments: bool, false, false, "Convert /* */ comments to // comments where possible"; + normalize_doc_attributes: bool, false, false, "Normalize doc attributes as doc comments"; + license_template_path: String, String::default(), false, + "Beginning of file must match license template"; + format_strings: bool, false, false, "Format string literals where necessary"; + format_macro_matchers: bool, false, false, + "Format the metavariable matching patterns in macros"; + format_macro_bodies: bool, true, false, "Format the bodies of macros"; + + // Single line expressions and items + empty_item_single_line: bool, true, false, + "Put empty-body functions and impls on a single line"; + struct_lit_single_line: bool, true, false, + "Put small struct literals on a single line"; + fn_single_line: bool, false, false, "Put single-expression functions on a single line"; + where_single_line: bool, false, false, "Force where-clauses to be on a single line"; + + // Imports + imports_indent: IndentStyle, IndentStyle::Block, false, "Indent of imports"; + imports_layout: ListTactic, ListTactic::Mixed, false, "Item layout inside a import block"; + imports_granularity: ImportGranularity, ImportGranularity::Preserve, false, + "Merge or split imports to the provided granularity"; + group_imports: GroupImportsTactic, GroupImportsTactic::Preserve, false, + "Controls the strategy for how imports are grouped together"; + merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)"; + + // Ordering + reorder_imports: bool, true, true, "Reorder import and extern crate statements alphabetically"; + reorder_modules: bool, true, true, "Reorder module statements alphabetically in group"; + reorder_impl_items: bool, false, false, "Reorder impl items"; + + // Spaces around punctuation + type_punctuation_density: TypeDensity, TypeDensity::Wide, false, + "Determines if '+' or '=' are wrapped in spaces in the punctuation of types"; + space_before_colon: bool, false, false, "Leave a space before the colon"; + space_after_colon: bool, true, false, "Leave a space after the colon"; + spaces_around_ranges: bool, false, false, "Put spaces around the .. and ..= range operators"; + binop_separator: SeparatorPlace, SeparatorPlace::Front, false, + "Where to put a binary operator when a binary expression goes multiline"; + + // Misc. + remove_nested_parens: bool, true, true, "Remove nested parens"; + combine_control_expr: bool, true, false, "Combine control expressions with function calls"; + overflow_delimited_expr: bool, false, false, + "Allow trailing bracket/brace delimited expressions to overflow"; + struct_field_align_threshold: usize, 0, false, + "Align struct fields if their diffs fits within threshold"; + enum_discrim_align_threshold: usize, 0, false, + "Align enum variants discrims, if their diffs fit within threshold"; + match_arm_blocks: bool, true, false, "Wrap the body of arms in blocks when it does not fit on \ + the same line with the pattern of arms"; + match_arm_leading_pipes: MatchArmLeadingPipe, MatchArmLeadingPipe::Never, true, + "Determines whether leading pipes are emitted on match arms"; + force_multiline_blocks: bool, false, false, + "Force multiline closure bodies and match arms to be wrapped in a block"; + fn_args_layout: Density, Density::Tall, true, + "Control the layout of arguments in a function"; + brace_style: BraceStyle, BraceStyle::SameLineWhere, false, "Brace style for items"; + control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine, false, + "Brace style for control flow constructs"; + trailing_semicolon: bool, true, false, + "Add trailing semicolon after break, continue and return"; + trailing_comma: SeparatorTactic, SeparatorTactic::Vertical, false, + "How to handle trailing commas for lists"; + match_block_trailing_comma: bool, false, false, + "Put a trailing comma after a block based match arm (non-block arms are not affected)"; + blank_lines_upper_bound: usize, 1, false, + "Maximum number of blank lines which can be put between items"; + blank_lines_lower_bound: usize, 0, false, + "Minimum number of blank lines which must be put between items"; + edition: Edition, Edition::Edition2015, true, "The edition of the parser (RFC 2052)"; + version: Version, Version::One, false, "Version of formatting rules"; + inline_attribute_width: usize, 0, false, + "Write an item and its attribute on the same line \ + if their combined width is below a threshold"; + + // Options that can change the source code beyond whitespace/blocks (somewhat linty things) + merge_derives: bool, true, true, "Merge multiple `#[derive(...)]` into a single one"; + use_try_shorthand: bool, false, true, "Replace uses of the try! macro by the ? shorthand"; + use_field_init_shorthand: bool, false, true, "Use field initialization shorthand if possible"; + force_explicit_abi: bool, true, true, "Always print the abi for extern items"; + condense_wildcard_suffixes: bool, false, false, "Replace strings of _ wildcards by a single .. \ + in tuple patterns"; + + // Control options (changes the operation of rustfmt, rather than the formatting) + color: Color, Color::Auto, false, + "What Color option to use when none is supplied: Always, Never, Auto"; + required_version: String, env!("CARGO_PKG_VERSION").to_owned(), false, + "Require a specific version of rustfmt"; + unstable_features: bool, false, false, + "Enables unstable features. Only available on nightly channel"; + disable_all_formatting: bool, false, false, "Don't reformat anything"; + skip_children: bool, false, false, "Don't reformat out of line modules"; + hide_parse_errors: bool, false, false, "Hide errors from the parser"; + error_on_line_overflow: bool, false, false, "Error if unable to get all lines within max_width"; + error_on_unformatted: bool, false, false, + "Error if unable to get comments or string literals within max_width, \ + or they are left with trailing whitespaces"; + report_todo: ReportTactic, ReportTactic::Never, false, + "Report all, none or unnumbered occurrences of TODO in source file comments"; + report_fixme: ReportTactic, ReportTactic::Never, false, + "Report all, none or unnumbered occurrences of FIXME in source file comments"; + ignore: IgnoreList, IgnoreList::default(), false, + "Skip formatting the specified files and directories"; + + // Not user-facing + verbose: Verbosity, Verbosity::Normal, false, "How much to information to emit to the user"; + file_lines: FileLines, FileLines::all(), false, + "Lines to format; this is not supported in rustfmt.toml, and can only be specified \ + via the --file-lines option"; + width_heuristics: WidthHeuristics, WidthHeuristics::scaled(100), false, + "'small' heuristic values"; + emit_mode: EmitMode, EmitMode::Files, false, + "What emit Mode to use when none is supplied"; + make_backup: bool, false, false, "Backup changed files"; + print_misformatted_file_names: bool, false, true, + "Prints the names of mismatched files that were formatted. Prints the names of \ + files that would be formated when used with `--check` mode. "; +} + +#[derive(Error, Debug)] +#[error("Could not output config: {0}")] +pub struct ToTomlError(toml::ser::Error); + +impl PartialConfig { + pub fn to_toml(&self) -> Result { + // Non-user-facing options can't be specified in TOML + let mut cloned = self.clone(); + cloned.file_lines = None; + cloned.verbose = None; + cloned.width_heuristics = None; + cloned.print_misformatted_file_names = None; + cloned.merge_imports = None; + + ::toml::to_string(&cloned).map_err(ToTomlError) + } +} + +impl Config { + pub(crate) fn version_meets_requirement(&self) -> bool { + if self.was_set().required_version() { + let version = env!("CARGO_PKG_VERSION"); + let required_version = self.required_version(); + if version != required_version { + println!( + "Error: rustfmt version ({}) doesn't match the required version ({})", + version, required_version, + ); + return false; + } + } + + true + } + + /// Constructs a `Config` from the toml file specified at `file_path`. + /// + /// This method only looks at the provided path, for a method that + /// searches parents for a `rustfmt.toml` see `from_resolved_toml_path`. + /// + /// Returns a `Config` if the config could be read and parsed from + /// the file, otherwise errors. + pub(super) fn from_toml_path(file_path: &Path) -> Result { + let mut file = File::open(&file_path)?; + let mut toml = String::new(); + file.read_to_string(&mut toml)?; + Config::from_toml(&toml, file_path.parent().unwrap()) + .map_err(|err| Error::new(ErrorKind::InvalidData, err)) + } + + /// Resolves the config for input in `dir`. + /// + /// Searches for `rustfmt.toml` beginning with `dir`, and + /// recursively checking parents of `dir` if no config file is found. + /// If no config file exists in `dir` or in any parent, a + /// default `Config` will be returned (and the returned path will be empty). + /// + /// Returns the `Config` to use, and the path of the project file if there was + /// one. + pub(super) fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option), Error> { + /// Try to find a project file in the given directory and its parents. + /// Returns the path of a the nearest project file if one exists, + /// or `None` if no project file was found. + fn resolve_project_file(dir: &Path) -> Result, Error> { + let mut current = if dir.is_relative() { + env::current_dir()?.join(dir) + } else { + dir.to_path_buf() + }; + + current = fs::canonicalize(current)?; + + loop { + match get_toml_path(¤t) { + Ok(Some(path)) => return Ok(Some(path)), + Err(e) => return Err(e), + _ => (), + } + + // If the current directory has no parent, we're done searching. + if !current.pop() { + break; + } + } + + // If nothing was found, check in the home directory. + if let Some(home_dir) = dirs::home_dir() { + if let Some(path) = get_toml_path(&home_dir)? { + return Ok(Some(path)); + } + } + + // If none was found ther either, check in the user's configuration directory. + if let Some(mut config_dir) = dirs::config_dir() { + config_dir.push("rustfmt"); + if let Some(path) = get_toml_path(&config_dir)? { + return Ok(Some(path)); + } + } + + Ok(None) + } + + match resolve_project_file(dir)? { + None => Ok((Config::default(), None)), + Some(path) => Config::from_toml_path(&path).map(|config| (config, Some(path))), + } + } + + pub(crate) fn from_toml(toml: &str, dir: &Path) -> Result { + let parsed: ::toml::Value = toml + .parse() + .map_err(|e| format!("Could not parse TOML: {}", e))?; + let mut err = String::new(); + let table = parsed + .as_table() + .ok_or_else(|| String::from("Parsed config was not table"))?; + for key in table.keys() { + if !Config::is_valid_name(key) { + let msg = &format!("Warning: Unknown configuration option `{}`\n", key); + err.push_str(msg) + } + } + match parsed.try_into() { + Ok(parsed_config) => { + if !err.is_empty() { + eprint!("{}", err); + } + Ok(Config::default().fill_from_parsed_config(parsed_config, dir)) + } + Err(e) => { + err.push_str("Error: Decoding config file failed:\n"); + err.push_str(format!("{}\n", e).as_str()); + err.push_str("Please check your config file."); + Err(err) + } + } + } +} + +/// Loads a config by checking the client-supplied options and if appropriate, the +/// file system (including searching the file system for overrides). +pub fn load_config( + file_path: Option<&Path>, + options: Option, +) -> Result<(Config, Option), Error> { + let over_ride = match options { + Some(ref opts) => config_path(opts)?, + None => None, + }; + + let result = if let Some(over_ride) = over_ride { + Config::from_toml_path(over_ride.as_ref()).map(|p| (p, Some(over_ride.to_owned()))) + } else if let Some(file_path) = file_path { + Config::from_resolved_toml_path(file_path) + } else { + Ok((Config::default(), None)) + }; + + result.map(|(mut c, p)| { + if let Some(options) = options { + options.apply_to(&mut c); + } + (c, p) + }) +} + +// Check for the presence of known config file names (`rustfmt.toml, `.rustfmt.toml`) in `dir` +// +// Return the path if a config file exists, empty if no file exists, and Error for IO errors +fn get_toml_path(dir: &Path) -> Result, Error> { + const CONFIG_FILE_NAMES: [&str; 2] = [".rustfmt.toml", "rustfmt.toml"]; + for config_file_name in &CONFIG_FILE_NAMES { + let config_file = dir.join(config_file_name); + match fs::metadata(&config_file) { + // Only return if it's a file to handle the unlikely situation of a directory named + // `rustfmt.toml`. + Ok(ref md) if md.is_file() => return Ok(Some(config_file)), + // Return the error if it's something other than `NotFound`; otherwise we didn't + // find the project file yet, and continue searching. + Err(e) => { + if e.kind() != ErrorKind::NotFound { + return Err(e); + } + } + _ => {} + } + } + Ok(None) +} + +fn config_path(options: &dyn CliOptions) -> Result, Error> { + let config_path_not_found = |path: &str| -> Result, Error> { + Err(Error::new( + ErrorKind::NotFound, + format!( + "Error: unable to find a config file for the given path: `{}`", + path + ), + )) + }; + + // Read the config_path and convert to parent dir if a file is provided. + // If a config file cannot be found from the given path, return error. + match options.config_path() { + Some(path) if !path.exists() => config_path_not_found(path.to_str().unwrap()), + Some(path) if path.is_dir() => { + let config_file_path = get_toml_path(path)?; + if config_file_path.is_some() { + Ok(config_file_path) + } else { + config_path_not_found(path.to_str().unwrap()) + } + } + path => Ok(path.map(ToOwned::to_owned)), + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::str; + + #[allow(dead_code)] + mod mock { + use super::super::*; + + create_config! { + // Options that are used by the generated functions + max_width: usize, 100, true, "Maximum width of each line"; + use_small_heuristics: Heuristics, Heuristics::Default, true, + "Whether to use different formatting for items and \ + expressions if they satisfy a heuristic notion of 'small'."; + license_template_path: String, String::default(), false, + "Beginning of file must match license template"; + required_version: String, env!("CARGO_PKG_VERSION").to_owned(), false, + "Require a specific version of rustfmt."; + ignore: IgnoreList, IgnoreList::default(), false, + "Skip formatting the specified files and directories."; + verbose: Verbosity, Verbosity::Normal, false, + "How much to information to emit to the user"; + file_lines: FileLines, FileLines::all(), false, + "Lines to format; this is not supported in rustfmt.toml, and can only be specified \ + via the --file-lines option"; + width_heuristics: WidthHeuristics, WidthHeuristics::scaled(100), false, + "'small' heuristic values"; + // merge_imports deprecation + imports_granularity: ImportGranularity, ImportGranularity::Preserve, false, + "Merge imports"; + merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)"; + + // Options that are used by the tests + stable_option: bool, false, true, "A stable option"; + unstable_option: bool, false, false, "An unstable option"; + } + } + + #[test] + fn test_config_set() { + let mut config = Config::default(); + config.set().verbose(Verbosity::Quiet); + assert_eq!(config.verbose(), Verbosity::Quiet); + config.set().verbose(Verbosity::Normal); + assert_eq!(config.verbose(), Verbosity::Normal); + } + + #[test] + fn test_config_used_to_toml() { + let config = Config::default(); + + let merge_derives = config.merge_derives(); + let skip_children = config.skip_children(); + + let used_options = config.used_options(); + let toml = used_options.to_toml().unwrap(); + assert_eq!( + toml, + format!( + "merge_derives = {}\nskip_children = {}\n", + merge_derives, skip_children, + ) + ); + } + + #[test] + fn test_was_set() { + let config = Config::from_toml("hard_tabs = true", Path::new("")).unwrap(); + + assert_eq!(config.was_set().hard_tabs(), true); + assert_eq!(config.was_set().verbose(), false); + } + + #[test] + fn test_print_docs_exclude_unstable() { + use self::mock::Config; + + let mut output = Vec::new(); + Config::print_docs(&mut output, false); + + let s = str::from_utf8(&output).unwrap(); + + assert_eq!(s.contains("stable_option"), true); + assert_eq!(s.contains("unstable_option"), false); + assert_eq!(s.contains("(unstable)"), false); + } + + #[test] + fn test_print_docs_include_unstable() { + use self::mock::Config; + + let mut output = Vec::new(); + Config::print_docs(&mut output, true); + + let s = str::from_utf8(&output).unwrap(); + assert_eq!(s.contains("stable_option"), true); + assert_eq!(s.contains("unstable_option"), true); + assert_eq!(s.contains("(unstable)"), true); + } + + #[test] + fn test_empty_string_license_template_path() { + let toml = r#"license_template_path = """#; + let config = Config::from_toml(toml, Path::new("")).unwrap(); + assert!(config.license_template.is_none()); + } + + #[test] + fn test_valid_license_template_path() { + if !crate::is_nightly_channel!() { + return; + } + let toml = r#"license_template_path = "tests/license-template/lt.txt""#; + let config = Config::from_toml(toml, Path::new("")).unwrap(); + assert!(config.license_template.is_some()); + } + + #[test] + fn test_override_existing_license_with_no_license() { + if !crate::is_nightly_channel!() { + return; + } + let toml = r#"license_template_path = "tests/license-template/lt.txt""#; + let mut config = Config::from_toml(toml, Path::new("")).unwrap(); + assert!(config.license_template.is_some()); + config.override_value("license_template_path", ""); + assert!(config.license_template.is_none()); + } + + #[test] + fn test_dump_default_config() { + let default_config = format!( + r#"max_width = 100 +hard_tabs = false +tab_spaces = 4 +newline_style = "Auto" +use_small_heuristics = "Default" +indent_style = "Block" +wrap_comments = false +format_code_in_doc_comments = false +comment_width = 80 +normalize_comments = false +normalize_doc_attributes = false +license_template_path = "" +format_strings = false +format_macro_matchers = false +format_macro_bodies = true +empty_item_single_line = true +struct_lit_single_line = true +fn_single_line = false +where_single_line = false +imports_indent = "Block" +imports_layout = "Mixed" +imports_granularity = "Preserve" +group_imports = "Preserve" +reorder_imports = true +reorder_modules = true +reorder_impl_items = false +type_punctuation_density = "Wide" +space_before_colon = false +space_after_colon = true +spaces_around_ranges = false +binop_separator = "Front" +remove_nested_parens = true +combine_control_expr = true +overflow_delimited_expr = false +struct_field_align_threshold = 0 +enum_discrim_align_threshold = 0 +match_arm_blocks = true +match_arm_leading_pipes = "Never" +force_multiline_blocks = false +fn_args_layout = "Tall" +brace_style = "SameLineWhere" +control_brace_style = "AlwaysSameLine" +trailing_semicolon = true +trailing_comma = "Vertical" +match_block_trailing_comma = false +blank_lines_upper_bound = 1 +blank_lines_lower_bound = 0 +edition = "2015" +version = "One" +inline_attribute_width = 0 +merge_derives = true +use_try_shorthand = false +use_field_init_shorthand = false +force_explicit_abi = true +condense_wildcard_suffixes = false +color = "Auto" +required_version = "{}" +unstable_features = false +disable_all_formatting = false +skip_children = false +hide_parse_errors = false +error_on_line_overflow = false +error_on_unformatted = false +report_todo = "Never" +report_fixme = "Never" +ignore = [] +emit_mode = "Files" +make_backup = false +"#, + env!("CARGO_PKG_VERSION") + ); + let toml = Config::default().all_options().to_toml().unwrap(); + assert_eq!(&toml, &default_config); + } + + // FIXME(#2183): these tests cannot be run in parallel because they use env vars. + // #[test] + // fn test_as_not_nightly_channel() { + // let mut config = Config::default(); + // assert_eq!(config.was_set().unstable_features(), false); + // config.set().unstable_features(true); + // assert_eq!(config.was_set().unstable_features(), false); + // } + + // #[test] + // fn test_as_nightly_channel() { + // let v = ::std::env::var("CFG_RELEASE_CHANNEL").unwrap_or(String::from("")); + // ::std::env::set_var("CFG_RELEASE_CHANNEL", "nightly"); + // let mut config = Config::default(); + // config.set().unstable_features(true); + // assert_eq!(config.was_set().unstable_features(), false); + // config.set().unstable_features(true); + // assert_eq!(config.unstable_features(), true); + // ::std::env::set_var("CFG_RELEASE_CHANNEL", v); + // } + + // #[test] + // fn test_unstable_from_toml() { + // let mut config = Config::from_toml("unstable_features = true").unwrap(); + // assert_eq!(config.was_set().unstable_features(), false); + // let v = ::std::env::var("CFG_RELEASE_CHANNEL").unwrap_or(String::from("")); + // ::std::env::set_var("CFG_RELEASE_CHANNEL", "nightly"); + // config = Config::from_toml("unstable_features = true").unwrap(); + // assert_eq!(config.was_set().unstable_features(), true); + // assert_eq!(config.unstable_features(), true); + // ::std::env::set_var("CFG_RELEASE_CHANNEL", v); + // } + + #[cfg(test)] + mod deprecated_option_merge_imports { + use super::*; + + #[test] + fn test_old_option_set() { + if !crate::is_nightly_channel!() { + return; + } + let toml = r#" + unstable_features = true + merge_imports = true + "#; + let config = Config::from_toml(toml, Path::new("")).unwrap(); + assert_eq!(config.imports_granularity(), ImportGranularity::Crate); + } + + #[test] + fn test_both_set() { + if !crate::is_nightly_channel!() { + return; + } + let toml = r#" + unstable_features = true + merge_imports = true + imports_granularity = "Preserve" + "#; + let config = Config::from_toml(toml, Path::new("")).unwrap(); + assert_eq!(config.imports_granularity(), ImportGranularity::Preserve); + } + + #[test] + fn test_new_overridden() { + if !crate::is_nightly_channel!() { + return; + } + let toml = r#" + unstable_features = true + merge_imports = true + "#; + let mut config = Config::from_toml(toml, Path::new("")).unwrap(); + config.override_value("imports_granularity", "Preserve"); + assert_eq!(config.imports_granularity(), ImportGranularity::Preserve); + } + + #[test] + fn test_old_overridden() { + if !crate::is_nightly_channel!() { + return; + } + let toml = r#" + unstable_features = true + imports_granularity = "Module" + "#; + let mut config = Config::from_toml(toml, Path::new("")).unwrap(); + config.override_value("merge_imports", "true"); + // no effect: the new option always takes precedence + assert_eq!(config.imports_granularity(), ImportGranularity::Module); + } + } +} diff --git a/src/tools/rustfmt/src/config/options.rs b/src/tools/rustfmt/src/config/options.rs new file mode 100644 index 0000000000..c0491630c0 --- /dev/null +++ b/src/tools/rustfmt/src/config/options.rs @@ -0,0 +1,442 @@ +use std::collections::{hash_set, HashSet}; +use std::fmt; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +use itertools::Itertools; +use rustfmt_config_proc_macro::config_type; +use serde::de::{SeqAccess, Visitor}; +use serde::ser::SerializeSeq; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::config::lists::*; +use crate::config::Config; + +#[config_type] +pub enum NewlineStyle { + /// Auto-detect based on the raw source input. + Auto, + /// Force CRLF (`\r\n`). + Windows, + /// Force CR (`\n). + Unix, + /// `\r\n` in Windows, `\n`` on other platforms. + Native, +} + +#[config_type] +/// Where to put the opening brace of items (`fn`, `impl`, etc.). +pub enum BraceStyle { + /// Put the opening brace on the next line. + AlwaysNextLine, + /// Put the opening brace on the same line, if possible. + PreferSameLine, + /// Prefer the same line except where there is a where-clause, in which + /// case force the brace to be put on the next line. + SameLineWhere, +} + +#[config_type] +/// Where to put the opening brace of conditional expressions (`if`, `match`, etc.). +pub enum ControlBraceStyle { + /// K&R style, Rust community default + AlwaysSameLine, + /// Stroustrup style + ClosingNextLine, + /// Allman style + AlwaysNextLine, +} + +#[config_type] +/// How to indent. +pub enum IndentStyle { + /// First line on the same line as the opening brace, all lines aligned with + /// the first line. + Visual, + /// First line is on a new line and all lines align with **block** indent. + Block, +} + +#[config_type] +/// How to place a list-like items. +/// FIXME: Issue-3581: this should be renamed to ItemsLayout when publishing 2.0 +pub enum Density { + /// Fit as much on one line as possible. + Compressed, + /// Items are placed horizontally if sufficient space, vertically otherwise. + Tall, + /// Place every item on a separate line. + Vertical, +} + +#[config_type] +/// Spacing around type combinators. +pub enum TypeDensity { + /// No spaces around "=" and "+" + Compressed, + /// Spaces around " = " and " + " + Wide, +} + +#[config_type] +/// To what extent does rustfmt pursue its heuristics? +pub enum Heuristics { + /// Turn off any heuristics + Off, + /// Turn on max heuristics + Max, + /// Use Rustfmt's defaults + Default, +} + +impl Density { + pub fn to_list_tactic(self, len: usize) -> ListTactic { + match self { + Density::Compressed => ListTactic::Mixed, + Density::Tall => ListTactic::HorizontalVertical, + Density::Vertical if len == 1 => ListTactic::Horizontal, + Density::Vertical => ListTactic::Vertical, + } + } +} + +#[config_type] +/// Configuration for import groups, i.e. sets of imports separated by newlines. +pub enum GroupImportsTactic { + /// Keep groups as they are. + Preserve, + /// Discard existing groups, and create new groups for + /// 1. `std` / `core` / `alloc` imports + /// 2. other imports + /// 3. `self` / `crate` / `super` imports + StdExternalCrate, +} + +#[config_type] +/// How to merge imports. +pub enum ImportGranularity { + /// Do not merge imports. + Preserve, + /// Use one `use` statement per crate. + Crate, + /// Use one `use` statement per module. + Module, + /// Use one `use` statement per imported item. + Item, +} + +#[config_type] +pub enum ReportTactic { + Always, + Unnumbered, + Never, +} + +/// What Rustfmt should emit. Mostly corresponds to the `--emit` command line +/// option. +#[config_type] +pub enum EmitMode { + /// Emits to files. + Files, + /// Writes the output to stdout. + Stdout, + /// Displays how much of the input file was processed + Coverage, + /// Unfancy stdout + Checkstyle, + /// Writes the resulting diffs in a JSON format. Returns an empty array + /// `[]` if there were no diffs. + Json, + /// Output the changed lines (for internal value only) + ModifiedLines, + /// Checks if a diff can be generated. If so, rustfmt outputs a diff and + /// quits with exit code 1. + /// This option is designed to be run in CI where a non-zero exit signifies + /// non-standard code formatting. Used for `--check`. + Diff, +} + +/// Client-preference for coloured output. +#[config_type] +pub enum Color { + /// Always use color, whether it is a piped or terminal output + Always, + /// Never use color + Never, + /// Automatically use color, if supported by terminal + Auto, +} + +#[config_type] +/// rustfmt format style version. +pub enum Version { + /// 1.x.y. When specified, rustfmt will format in the same style as 1.0.0. + One, + /// 2.x.y. When specified, rustfmt will format in the the latest style. + Two, +} + +impl Color { + /// Whether we should use a coloured terminal. + pub fn use_colored_tty(self) -> bool { + match self { + Color::Always | Color::Auto => true, + Color::Never => false, + } + } +} + +/// How chatty should Rustfmt be? +#[config_type] +pub enum Verbosity { + /// Emit more. + Verbose, + /// Default. + Normal, + /// Emit as little as possible. + Quiet, +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct WidthHeuristics { + // Maximum width of the args of a function call before falling back + // to vertical formatting. + pub fn_call_width: usize, + // Maximum width of the args of a function-like attributes before falling + // back to vertical formatting. + pub attr_fn_like_width: usize, + // Maximum width in the body of a struct lit before falling back to + // vertical formatting. + pub struct_lit_width: usize, + // Maximum width in the body of a struct variant before falling back + // to vertical formatting. + pub struct_variant_width: usize, + // Maximum width of an array literal before falling back to vertical + // formatting. + pub array_width: usize, + // Maximum length of a chain to fit on a single line. + pub chain_width: usize, + // Maximum line length for single line if-else expressions. A value + // of zero means always break if-else expressions. + pub single_line_if_else_max_width: usize, +} + +impl fmt::Display for WidthHeuristics { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl WidthHeuristics { + // Using this WidthHeuristics means we ignore heuristics. + pub fn null() -> WidthHeuristics { + WidthHeuristics { + fn_call_width: usize::max_value(), + attr_fn_like_width: usize::max_value(), + struct_lit_width: 0, + struct_variant_width: 0, + array_width: usize::max_value(), + chain_width: usize::max_value(), + single_line_if_else_max_width: 0, + } + } + + pub fn set(max_width: usize) -> WidthHeuristics { + WidthHeuristics { + fn_call_width: max_width, + attr_fn_like_width: max_width, + struct_lit_width: max_width, + struct_variant_width: max_width, + array_width: max_width, + chain_width: max_width, + single_line_if_else_max_width: max_width, + } + } + + // scale the default WidthHeuristics according to max_width + pub fn scaled(max_width: usize) -> WidthHeuristics { + const DEFAULT_MAX_WIDTH: usize = 100; + let max_width_ratio = if max_width > DEFAULT_MAX_WIDTH { + let ratio = max_width as f32 / DEFAULT_MAX_WIDTH as f32; + // round to the closest 0.1 + (ratio * 10.0).round() / 10.0 + } else { + 1.0 + }; + WidthHeuristics { + fn_call_width: (60.0 * max_width_ratio).round() as usize, + attr_fn_like_width: (70.0 * max_width_ratio).round() as usize, + struct_lit_width: (18.0 * max_width_ratio).round() as usize, + struct_variant_width: (35.0 * max_width_ratio).round() as usize, + array_width: (60.0 * max_width_ratio).round() as usize, + chain_width: (60.0 * max_width_ratio).round() as usize, + single_line_if_else_max_width: (50.0 * max_width_ratio).round() as usize, + } + } +} + +impl ::std::str::FromStr for WidthHeuristics { + type Err = &'static str; + + fn from_str(_: &str) -> Result { + Err("WidthHeuristics is not parsable") + } +} + +impl Default for EmitMode { + fn default() -> EmitMode { + EmitMode::Files + } +} + +/// A set of directories, files and modules that rustfmt should ignore. +#[derive(Default, Clone, Debug, PartialEq)] +pub struct IgnoreList { + /// A set of path specified in rustfmt.toml. + path_set: HashSet, + /// A path to rustfmt.toml. + rustfmt_toml_path: PathBuf, +} + +impl fmt::Display for IgnoreList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "[{}]", + self.path_set + .iter() + .format_with(", ", |path, f| f(&format_args!( + "{}", + path.to_string_lossy() + ))) + ) + } +} + +impl Serialize for IgnoreList { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.path_set.len()))?; + for e in &self.path_set { + seq.serialize_element(e)?; + } + seq.end() + } +} + +impl<'de> Deserialize<'de> for IgnoreList { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct HashSetVisitor; + impl<'v> Visitor<'v> for HashSetVisitor { + type Value = HashSet; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a sequence of path") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'v>, + { + let mut path_set = HashSet::new(); + while let Some(elem) = seq.next_element()? { + path_set.insert(elem); + } + Ok(path_set) + } + } + Ok(IgnoreList { + path_set: deserializer.deserialize_seq(HashSetVisitor)?, + rustfmt_toml_path: PathBuf::new(), + }) + } +} + +impl<'a> IntoIterator for &'a IgnoreList { + type Item = &'a PathBuf; + type IntoIter = hash_set::Iter<'a, PathBuf>; + + fn into_iter(self) -> Self::IntoIter { + self.path_set.iter() + } +} + +impl IgnoreList { + pub fn add_prefix(&mut self, dir: &Path) { + self.rustfmt_toml_path = dir.to_path_buf(); + } + + pub fn rustfmt_toml_path(&self) -> &Path { + &self.rustfmt_toml_path + } +} + +impl FromStr for IgnoreList { + type Err = &'static str; + + fn from_str(_: &str) -> Result { + Err("IgnoreList is not parsable") + } +} + +/// Maps client-supplied options to Rustfmt's internals, mostly overriding +/// values in a config with values from the command line. +pub trait CliOptions { + fn apply_to(self, config: &mut Config); + fn config_path(&self) -> Option<&Path>; +} + +/// The edition of the syntax and semntics of code (RFC 2052). +#[config_type] +pub enum Edition { + #[value = "2015"] + #[doc_hint = "2015"] + /// Edition 2015. + Edition2015, + #[value = "2018"] + #[doc_hint = "2018"] + /// Edition 2018. + Edition2018, + #[value = "2021"] + #[doc_hint = "2021"] + /// Edition 2021. + Edition2021, +} + +impl Default for Edition { + fn default() -> Edition { + Edition::Edition2015 + } +} + +impl From for rustc_span::edition::Edition { + fn from(edition: Edition) -> Self { + match edition { + Edition::Edition2015 => Self::Edition2015, + Edition::Edition2018 => Self::Edition2018, + Edition::Edition2021 => Self::Edition2021, + } + } +} + +impl PartialOrd for Edition { + fn partial_cmp(&self, other: &Edition) -> Option { + rustc_span::edition::Edition::partial_cmp(&(*self).into(), &(*other).into()) + } +} + +/// Controls how rustfmt should handle leading pipes on match arms. +#[config_type] +pub enum MatchArmLeadingPipe { + /// Place leading pipes on all match arms + Always, + /// Never emit leading pipes on match arms + Never, + /// Preserve any existing leading pipes + Preserve, +} diff --git a/src/tools/rustfmt/src/coverage.rs b/src/tools/rustfmt/src/coverage.rs new file mode 100644 index 0000000000..f5a0497425 --- /dev/null +++ b/src/tools/rustfmt/src/coverage.rs @@ -0,0 +1,15 @@ +use crate::{Config, EmitMode}; +use std::borrow::Cow; + +pub(crate) fn transform_missing_snippet<'a>(config: &Config, string: &'a str) -> Cow<'a, str> { + match config.emit_mode() { + EmitMode::Coverage => Cow::from(replace_chars(string)), + _ => Cow::from(string), + } +} + +fn replace_chars(s: &str) -> String { + s.chars() + .map(|ch| if ch.is_whitespace() { ch } else { 'X' }) + .collect() +} diff --git a/src/tools/rustfmt/src/emitter.rs b/src/tools/rustfmt/src/emitter.rs new file mode 100644 index 0000000000..dc2c99a301 --- /dev/null +++ b/src/tools/rustfmt/src/emitter.rs @@ -0,0 +1,52 @@ +pub(crate) use self::checkstyle::*; +pub(crate) use self::diff::*; +pub(crate) use self::files::*; +pub(crate) use self::files_with_backup::*; +pub(crate) use self::json::*; +pub(crate) use self::modified_lines::*; +pub(crate) use self::stdout::*; +use crate::FileName; +use std::io::{self, Write}; +use std::path::Path; + +mod checkstyle; +mod diff; +mod files; +mod files_with_backup; +mod json; +mod modified_lines; +mod stdout; + +pub(crate) struct FormattedFile<'a> { + pub(crate) filename: &'a FileName, + pub(crate) original_text: &'a str, + pub(crate) formatted_text: &'a str, +} + +#[derive(Debug, Default, Clone)] +pub(crate) struct EmitterResult { + pub(crate) has_diff: bool, +} + +pub(crate) trait Emitter { + fn emit_formatted_file( + &mut self, + output: &mut dyn Write, + formatted_file: FormattedFile<'_>, + ) -> Result; + + fn emit_header(&self, _output: &mut dyn Write) -> Result<(), io::Error> { + Ok(()) + } + + fn emit_footer(&self, _output: &mut dyn Write) -> Result<(), io::Error> { + Ok(()) + } +} + +fn ensure_real_path(filename: &FileName) -> &Path { + match *filename { + FileName::Real(ref path) => path, + _ => panic!("cannot format `{}` and emit to files", filename), + } +} diff --git a/src/tools/rustfmt/src/emitter/checkstyle.rs b/src/tools/rustfmt/src/emitter/checkstyle.rs new file mode 100644 index 0000000000..4448214f3f --- /dev/null +++ b/src/tools/rustfmt/src/emitter/checkstyle.rs @@ -0,0 +1,148 @@ +use self::xml::XmlEscaped; +use super::*; +use crate::rustfmt_diff::{make_diff, DiffLine, Mismatch}; +use std::io::{self, Write}; +use std::path::Path; + +mod xml; + +#[derive(Debug, Default)] +pub(crate) struct CheckstyleEmitter; + +impl Emitter for CheckstyleEmitter { + fn emit_header(&self, output: &mut dyn Write) -> Result<(), io::Error> { + writeln!(output, r#""#)?; + write!(output, r#""#)?; + Ok(()) + } + + fn emit_footer(&self, output: &mut dyn Write) -> Result<(), io::Error> { + writeln!(output, "") + } + + fn emit_formatted_file( + &mut self, + output: &mut dyn Write, + FormattedFile { + filename, + original_text, + formatted_text, + }: FormattedFile<'_>, + ) -> Result { + const CONTEXT_SIZE: usize = 0; + let filename = ensure_real_path(filename); + let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE); + output_checkstyle_file(output, filename, diff)?; + Ok(EmitterResult::default()) + } +} + +pub(crate) fn output_checkstyle_file( + mut writer: T, + filename: &Path, + diff: Vec, +) -> Result<(), io::Error> +where + T: Write, +{ + write!(writer, r#""#, filename.display())?; + for mismatch in diff { + let begin_line = mismatch.line_number; + let mut current_line; + let mut line_counter = 0; + for line in mismatch.lines { + // Do nothing with `DiffLine::Context` and `DiffLine::Resulting`. + if let DiffLine::Expected(message) = line { + current_line = begin_line + line_counter; + line_counter += 1; + write!( + writer, + r#""#, + current_line, + XmlEscaped(&message) + )?; + } + } + } + write!(writer, "")?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn emits_empty_record_on_file_with_no_mismatches() { + let file_name = "src/well_formatted.rs"; + let mut writer = Vec::new(); + let _ = output_checkstyle_file(&mut writer, &PathBuf::from(file_name), vec![]); + assert_eq!( + &writer[..], + format!(r#""#, file_name).as_bytes() + ); + } + + // https://github.com/rust-lang/rustfmt/issues/1636 + #[test] + fn emits_single_xml_tree_containing_all_files() { + let bin_file = "src/bin.rs"; + let bin_original = vec!["fn main() {", "println!(\"Hello, world!\");", "}"]; + let bin_formatted = vec!["fn main() {", " println!(\"Hello, world!\");", "}"]; + let lib_file = "src/lib.rs"; + let lib_original = vec!["fn greet() {", "println!(\"Greetings!\");", "}"]; + let lib_formatted = vec!["fn greet() {", " println!(\"Greetings!\");", "}"]; + let mut writer = Vec::new(); + let mut emitter = CheckstyleEmitter::default(); + let _ = emitter.emit_header(&mut writer); + let _ = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from(bin_file)), + original_text: &bin_original.join("\n"), + formatted_text: &bin_formatted.join("\n"), + }, + ) + .unwrap(); + let _ = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from(lib_file)), + original_text: &lib_original.join("\n"), + formatted_text: &lib_formatted.join("\n"), + }, + ) + .unwrap(); + let _ = emitter.emit_footer(&mut writer); + let exp_bin_xml = vec![ + format!(r#""#, bin_file), + format!( + r#""#, + XmlEscaped(&r#" println!("Hello, world!");"#), + ), + String::from(""), + ]; + let exp_lib_xml = vec![ + format!(r#""#, lib_file), + format!( + r#""#, + XmlEscaped(&r#" println!("Greetings!");"#), + ), + String::from(""), + ]; + assert_eq!( + String::from_utf8(writer).unwrap(), + vec![ + r#""#, + "\n", + r#""#, + &format!("{}{}", exp_bin_xml.join(""), exp_lib_xml.join("")), + "\n", + ] + .join(""), + ); + } +} diff --git a/src/tools/rustfmt/src/emitter/checkstyle/xml.rs b/src/tools/rustfmt/src/emitter/checkstyle/xml.rs new file mode 100644 index 0000000000..f251aabe87 --- /dev/null +++ b/src/tools/rustfmt/src/emitter/checkstyle/xml.rs @@ -0,0 +1,52 @@ +use std::fmt::{self, Display}; + +/// Convert special characters into XML entities. +/// This is needed for checkstyle output. +pub(super) struct XmlEscaped<'a>(pub(super) &'a str); + +impl<'a> Display for XmlEscaped<'a> { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + for char in self.0.chars() { + match char { + '<' => write!(formatter, "<"), + '>' => write!(formatter, ">"), + '"' => write!(formatter, """), + '\'' => write!(formatter, "'"), + '&' => write!(formatter, "&"), + _ => write!(formatter, "{}", char), + }?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn special_characters_are_escaped() { + assert_eq!( + "<>"'&", + format!("{}", XmlEscaped(r#"<>"'&"#)), + ); + } + + #[test] + fn special_characters_are_escaped_in_string_with_other_characters() { + assert_eq!( + "The quick brown "🦊" jumps <over> the lazy 🐶", + format!( + "{}", + XmlEscaped(r#"The quick brown "🦊" jumps the lazy 🐶"#) + ), + ); + } + + #[test] + fn other_characters_are_not_escaped() { + let string = "The quick brown 🦊 jumps over the lazy 🐶"; + assert_eq!(string, format!("{}", XmlEscaped(string))); + } +} diff --git a/src/tools/rustfmt/src/emitter/diff.rs b/src/tools/rustfmt/src/emitter/diff.rs new file mode 100644 index 0000000000..9be4fb28f9 --- /dev/null +++ b/src/tools/rustfmt/src/emitter/diff.rs @@ -0,0 +1,138 @@ +use super::*; +use crate::config::Config; +use crate::rustfmt_diff::{make_diff, print_diff}; + +pub(crate) struct DiffEmitter { + config: Config, +} + +impl DiffEmitter { + pub(crate) fn new(config: Config) -> Self { + Self { config } + } +} + +impl Emitter for DiffEmitter { + fn emit_formatted_file( + &mut self, + output: &mut dyn Write, + FormattedFile { + filename, + original_text, + formatted_text, + }: FormattedFile<'_>, + ) -> Result { + const CONTEXT_SIZE: usize = 3; + let mismatch = make_diff(&original_text, formatted_text, CONTEXT_SIZE); + let has_diff = !mismatch.is_empty(); + + if has_diff { + if self.config.print_misformatted_file_names() { + writeln!(output, "{}", ensure_real_path(filename).display())?; + } else { + print_diff( + mismatch, + |line_num| format!("Diff in {} at line {}:", filename, line_num), + &self.config, + ); + } + } else if original_text != formatted_text { + // This occurs when the only difference between the original and formatted values + // is the newline style. This happens because The make_diff function compares the + // original and formatted values line by line, independent of line endings. + let file_path = ensure_real_path(filename); + writeln!(output, "Incorrect newline style in {}", file_path.display())?; + return Ok(EmitterResult { has_diff: true }); + } + + return Ok(EmitterResult { has_diff }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::Config; + use crate::FileName; + use std::path::PathBuf; + + #[test] + fn does_not_print_when_no_files_reformatted() { + let mut writer = Vec::new(); + let config = Config::default(); + let mut emitter = DiffEmitter::new(config); + let result = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from("src/lib.rs")), + original_text: "fn empty() {}\n", + formatted_text: "fn empty() {}\n", + }, + ) + .unwrap(); + assert_eq!(result.has_diff, false); + assert_eq!(writer.len(), 0); + } + + #[test] + fn prints_file_names_when_config_is_enabled() { + let bin_file = "src/bin.rs"; + let bin_original = "fn main() {\nprintln!(\"Hello, world!\");\n}"; + let bin_formatted = "fn main() {\n println!(\"Hello, world!\");\n}"; + let lib_file = "src/lib.rs"; + let lib_original = "fn greet() {\nprintln!(\"Greetings!\");\n}"; + let lib_formatted = "fn greet() {\n println!(\"Greetings!\");\n}"; + + let mut writer = Vec::new(); + let mut config = Config::default(); + config.set().print_misformatted_file_names(true); + let mut emitter = DiffEmitter::new(config); + let _ = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from(bin_file)), + original_text: bin_original, + formatted_text: bin_formatted, + }, + ) + .unwrap(); + let _ = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from(lib_file)), + original_text: lib_original, + formatted_text: lib_formatted, + }, + ) + .unwrap(); + + assert_eq!( + String::from_utf8(writer).unwrap(), + format!("{}\n{}\n", bin_file, lib_file), + ) + } + + #[test] + fn prints_newline_message_with_only_newline_style_diff() { + let mut writer = Vec::new(); + let config = Config::default(); + let mut emitter = DiffEmitter::new(config); + let _ = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from("src/lib.rs")), + original_text: "fn empty() {}\n", + formatted_text: "fn empty() {}\r\n", + }, + ) + .unwrap(); + assert_eq!( + String::from_utf8(writer).unwrap(), + String::from("Incorrect newline style in src/lib.rs\n") + ); + } +} diff --git a/src/tools/rustfmt/src/emitter/files.rs b/src/tools/rustfmt/src/emitter/files.rs new file mode 100644 index 0000000000..6360b73ee6 --- /dev/null +++ b/src/tools/rustfmt/src/emitter/files.rs @@ -0,0 +1,37 @@ +use super::*; +use std::fs; + +#[derive(Debug, Default)] +pub(crate) struct FilesEmitter { + print_misformatted_file_names: bool, +} + +impl FilesEmitter { + pub(crate) fn new(print_misformatted_file_names: bool) -> Self { + Self { + print_misformatted_file_names, + } + } +} + +impl Emitter for FilesEmitter { + fn emit_formatted_file( + &mut self, + output: &mut dyn Write, + FormattedFile { + filename, + original_text, + formatted_text, + }: FormattedFile<'_>, + ) -> Result { + // Write text directly over original file if there is a diff. + let filename = ensure_real_path(filename); + if original_text != formatted_text { + fs::write(filename, formatted_text)?; + if self.print_misformatted_file_names { + writeln!(output, "{}", filename.display())?; + } + } + Ok(EmitterResult::default()) + } +} diff --git a/src/tools/rustfmt/src/emitter/files_with_backup.rs b/src/tools/rustfmt/src/emitter/files_with_backup.rs new file mode 100644 index 0000000000..4c15f6fa5e --- /dev/null +++ b/src/tools/rustfmt/src/emitter/files_with_backup.rs @@ -0,0 +1,31 @@ +use super::*; +use std::fs; + +#[derive(Debug, Default)] +pub(crate) struct FilesWithBackupEmitter; + +impl Emitter for FilesWithBackupEmitter { + fn emit_formatted_file( + &mut self, + _output: &mut dyn Write, + FormattedFile { + filename, + original_text, + formatted_text, + }: FormattedFile<'_>, + ) -> Result { + let filename = ensure_real_path(filename); + if original_text != formatted_text { + // Do a little dance to make writing safer - write to a temp file + // rename the original to a .bk, then rename the temp file to the + // original. + let tmp_name = filename.with_extension("tmp"); + let bk_name = filename.with_extension("bk"); + + fs::write(&tmp_name, formatted_text)?; + fs::rename(filename, bk_name)?; + fs::rename(tmp_name, filename)?; + } + Ok(EmitterResult::default()) + } +} diff --git a/src/tools/rustfmt/src/emitter/json.rs b/src/tools/rustfmt/src/emitter/json.rs new file mode 100644 index 0000000000..269dd2d4da --- /dev/null +++ b/src/tools/rustfmt/src/emitter/json.rs @@ -0,0 +1,349 @@ +use super::*; +use crate::rustfmt_diff::{make_diff, DiffLine, Mismatch}; +use serde::Serialize; +use serde_json::to_string as to_json_string; +use std::io::{self, Write}; +use std::path::Path; + +#[derive(Debug, Default)] +pub(crate) struct JsonEmitter { + num_files: u32, +} + +#[derive(Debug, Default, Serialize)] +struct MismatchedBlock { + original_begin_line: u32, + original_end_line: u32, + expected_begin_line: u32, + expected_end_line: u32, + original: String, + expected: String, +} + +#[derive(Debug, Default, Serialize)] +struct MismatchedFile { + name: String, + mismatches: Vec, +} + +impl Emitter for JsonEmitter { + fn emit_header(&self, output: &mut dyn Write) -> Result<(), io::Error> { + write!(output, "[")?; + Ok(()) + } + + fn emit_footer(&self, output: &mut dyn Write) -> Result<(), io::Error> { + write!(output, "]")?; + Ok(()) + } + + fn emit_formatted_file( + &mut self, + output: &mut dyn Write, + FormattedFile { + filename, + original_text, + formatted_text, + }: FormattedFile<'_>, + ) -> Result { + const CONTEXT_SIZE: usize = 0; + let filename = ensure_real_path(filename); + let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE); + let has_diff = !diff.is_empty(); + + if has_diff { + output_json_file(output, filename, diff, self.num_files)?; + self.num_files += 1; + } + + Ok(EmitterResult { has_diff }) + } +} + +fn output_json_file( + mut writer: T, + filename: &Path, + diff: Vec, + num_emitted_files: u32, +) -> Result<(), io::Error> +where + T: Write, +{ + let mut mismatches = vec![]; + for mismatch in diff { + let original_begin_line = mismatch.line_number_orig; + let expected_begin_line = mismatch.line_number; + let mut original_end_line = original_begin_line; + let mut expected_end_line = expected_begin_line; + let mut original_line_counter = 0; + let mut expected_line_counter = 0; + let mut original_lines = vec![]; + let mut expected_lines = vec![]; + + for line in mismatch.lines { + match line { + DiffLine::Expected(msg) => { + expected_end_line = expected_begin_line + expected_line_counter; + expected_line_counter += 1; + expected_lines.push(msg) + } + DiffLine::Resulting(msg) => { + original_end_line = original_begin_line + original_line_counter; + original_line_counter += 1; + original_lines.push(msg) + } + DiffLine::Context(_) => continue, + } + } + + mismatches.push(MismatchedBlock { + original_begin_line, + original_end_line, + expected_begin_line, + expected_end_line, + original: original_lines.join("\n"), + expected: expected_lines.join("\n"), + }); + } + let json = to_json_string(&MismatchedFile { + name: String::from(filename.to_str().unwrap()), + mismatches, + })?; + let prefix = if num_emitted_files > 0 { "," } else { "" }; + write!(writer, "{}{}", prefix, &json)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::FileName; + use std::path::PathBuf; + + #[test] + fn expected_line_range_correct_when_single_line_split() { + let file = "foo/bar.rs"; + let mismatched_file = MismatchedFile { + name: String::from(file), + mismatches: vec![MismatchedBlock { + original_begin_line: 79, + original_end_line: 79, + expected_begin_line: 79, + expected_end_line: 82, + original: String::from("fn Foo() where T: Bar {"), + expected: String::from("fn Foo()\nwhere\n T: Bar,\n{"), + }], + }; + let mismatch = Mismatch { + line_number: 79, + line_number_orig: 79, + lines: vec![ + DiffLine::Resulting(String::from("fn Foo() where T: Bar {")), + DiffLine::Expected(String::from("fn Foo()")), + DiffLine::Expected(String::from("where")), + DiffLine::Expected(String::from(" T: Bar,")), + DiffLine::Expected(String::from("{")), + ], + }; + + let mut writer = Vec::new(); + let exp_json = to_json_string(&mismatched_file).unwrap(); + let _ = output_json_file(&mut writer, &PathBuf::from(file), vec![mismatch], 0); + assert_eq!(&writer[..], format!("{}", exp_json).as_bytes()); + } + + #[test] + fn context_lines_ignored() { + let file = "src/lib.rs"; + let mismatched_file = MismatchedFile { + name: String::from(file), + mismatches: vec![MismatchedBlock { + original_begin_line: 5, + original_end_line: 5, + expected_begin_line: 5, + expected_end_line: 5, + original: String::from( + "fn foo(_x: &u64) -> Option<&(dyn::std::error::Error + 'static)> {", + ), + expected: String::from( + "fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {", + ), + }], + }; + let mismatch = Mismatch { + line_number: 5, + line_number_orig: 5, + lines: vec![ + DiffLine::Context(String::new()), + DiffLine::Resulting(String::from( + "fn foo(_x: &u64) -> Option<&(dyn::std::error::Error + 'static)> {", + )), + DiffLine::Context(String::new()), + DiffLine::Expected(String::from( + "fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {", + )), + DiffLine::Context(String::new()), + ], + }; + + let mut writer = Vec::new(); + let exp_json = to_json_string(&mismatched_file).unwrap(); + let _ = output_json_file(&mut writer, &PathBuf::from(file), vec![mismatch], 0); + assert_eq!(&writer[..], format!("{}", exp_json).as_bytes()); + } + + #[test] + fn emits_empty_array_on_no_diffs() { + let mut writer = Vec::new(); + let mut emitter = JsonEmitter::default(); + let _ = emitter.emit_header(&mut writer); + let result = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from("src/lib.rs")), + original_text: "fn empty() {}\n", + formatted_text: "fn empty() {}\n", + }, + ) + .unwrap(); + let _ = emitter.emit_footer(&mut writer); + assert_eq!(result.has_diff, false); + assert_eq!(&writer[..], "[]".as_bytes()); + } + + #[test] + fn emits_array_with_files_with_diffs() { + let file_name = "src/bin.rs"; + let original = vec![ + "fn main() {", + "println!(\"Hello, world!\");", + "}", + "", + "#[cfg(test)]", + "mod tests {", + "#[test]", + "fn it_works() {", + " assert_eq!(2 + 2, 4);", + "}", + "}", + ]; + let formatted = vec![ + "fn main() {", + " println!(\"Hello, world!\");", + "}", + "", + "#[cfg(test)]", + "mod tests {", + " #[test]", + " fn it_works() {", + " assert_eq!(2 + 2, 4);", + " }", + "}", + ]; + let mut writer = Vec::new(); + let mut emitter = JsonEmitter::default(); + let _ = emitter.emit_header(&mut writer); + let result = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from(file_name)), + original_text: &original.join("\n"), + formatted_text: &formatted.join("\n"), + }, + ) + .unwrap(); + let _ = emitter.emit_footer(&mut writer); + let exp_json = to_json_string(&MismatchedFile { + name: String::from(file_name), + mismatches: vec![ + MismatchedBlock { + original_begin_line: 2, + original_end_line: 2, + expected_begin_line: 2, + expected_end_line: 2, + original: String::from("println!(\"Hello, world!\");"), + expected: String::from(" println!(\"Hello, world!\");"), + }, + MismatchedBlock { + original_begin_line: 7, + original_end_line: 10, + expected_begin_line: 7, + expected_end_line: 10, + original: String::from( + "#[test]\nfn it_works() {\n assert_eq!(2 + 2, 4);\n}", + ), + expected: String::from( + " #[test]\n fn it_works() {\n assert_eq!(2 + 2, 4);\n }", + ), + }, + ], + }) + .unwrap(); + assert_eq!(result.has_diff, true); + assert_eq!(&writer[..], format!("[{}]", exp_json).as_bytes()); + } + + #[test] + fn emits_valid_json_with_multiple_files() { + let bin_file = "src/bin.rs"; + let bin_original = vec!["fn main() {", "println!(\"Hello, world!\");", "}"]; + let bin_formatted = vec!["fn main() {", " println!(\"Hello, world!\");", "}"]; + let lib_file = "src/lib.rs"; + let lib_original = vec!["fn greet() {", "println!(\"Greetings!\");", "}"]; + let lib_formatted = vec!["fn greet() {", " println!(\"Greetings!\");", "}"]; + let mut writer = Vec::new(); + let mut emitter = JsonEmitter::default(); + let _ = emitter.emit_header(&mut writer); + let _ = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from(bin_file)), + original_text: &bin_original.join("\n"), + formatted_text: &bin_formatted.join("\n"), + }, + ) + .unwrap(); + let _ = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from(lib_file)), + original_text: &lib_original.join("\n"), + formatted_text: &lib_formatted.join("\n"), + }, + ) + .unwrap(); + let _ = emitter.emit_footer(&mut writer); + let exp_bin_json = to_json_string(&MismatchedFile { + name: String::from(bin_file), + mismatches: vec![MismatchedBlock { + original_begin_line: 2, + original_end_line: 2, + expected_begin_line: 2, + expected_end_line: 2, + original: String::from("println!(\"Hello, world!\");"), + expected: String::from(" println!(\"Hello, world!\");"), + }], + }) + .unwrap(); + let exp_lib_json = to_json_string(&MismatchedFile { + name: String::from(lib_file), + mismatches: vec![MismatchedBlock { + original_begin_line: 2, + original_end_line: 2, + expected_begin_line: 2, + expected_end_line: 2, + original: String::from("println!(\"Greetings!\");"), + expected: String::from(" println!(\"Greetings!\");"), + }], + }) + .unwrap(); + assert_eq!( + &writer[..], + format!("[{},{}]", exp_bin_json, exp_lib_json).as_bytes() + ); + } +} diff --git a/src/tools/rustfmt/src/emitter/modified_lines.rs b/src/tools/rustfmt/src/emitter/modified_lines.rs new file mode 100644 index 0000000000..94ff570a8a --- /dev/null +++ b/src/tools/rustfmt/src/emitter/modified_lines.rs @@ -0,0 +1,24 @@ +use super::*; +use crate::rustfmt_diff::{make_diff, ModifiedLines}; +use std::io::Write; + +#[derive(Debug, Default)] +pub(crate) struct ModifiedLinesEmitter; + +impl Emitter for ModifiedLinesEmitter { + fn emit_formatted_file( + &mut self, + output: &mut dyn Write, + FormattedFile { + original_text, + formatted_text, + .. + }: FormattedFile<'_>, + ) -> Result { + const CONTEXT_SIZE: usize = 0; + let mismatch = make_diff(original_text, formatted_text, CONTEXT_SIZE); + let has_diff = !mismatch.is_empty(); + write!(output, "{}", ModifiedLines::from(mismatch))?; + Ok(EmitterResult { has_diff }) + } +} diff --git a/src/tools/rustfmt/src/emitter/stdout.rs b/src/tools/rustfmt/src/emitter/stdout.rs new file mode 100644 index 0000000000..9fddd515e4 --- /dev/null +++ b/src/tools/rustfmt/src/emitter/stdout.rs @@ -0,0 +1,32 @@ +use super::*; +use crate::config::Verbosity; +use std::io::Write; + +#[derive(Debug)] +pub(crate) struct StdoutEmitter { + verbosity: Verbosity, +} + +impl StdoutEmitter { + pub(crate) fn new(verbosity: Verbosity) -> Self { + Self { verbosity } + } +} + +impl Emitter for StdoutEmitter { + fn emit_formatted_file( + &mut self, + output: &mut dyn Write, + FormattedFile { + filename, + formatted_text, + .. + }: FormattedFile<'_>, + ) -> Result { + if self.verbosity != Verbosity::Quiet { + writeln!(output, "{}:\n", filename)?; + } + write!(output, "{}", formatted_text)?; + Ok(EmitterResult::default()) + } +} diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs new file mode 100644 index 0000000000..33add30c7c --- /dev/null +++ b/src/tools/rustfmt/src/expr.rs @@ -0,0 +1,2068 @@ +use std::borrow::Cow; +use std::cmp::min; + +use itertools::Itertools; +use rustc_ast::token::{DelimToken, LitKind}; +use rustc_ast::{ast, ptr}; +use rustc_span::{BytePos, Span}; + +use crate::chains::rewrite_chain; +use crate::closures; +use crate::comment::{ + combine_strs_with_missing_comments, comment_style, contains_comment, recover_comment_removed, + rewrite_comment, rewrite_missing_comment, CharClasses, FindUncommented, +}; +use crate::config::lists::*; +use crate::config::{Config, ControlBraceStyle, IndentStyle, Version}; +use crate::lists::{ + definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape, + struct_lit_tactic, write_list, ListFormatting, Separator, +}; +use crate::macros::{rewrite_macro, MacroPosition}; +use crate::matches::rewrite_match; +use crate::overflow::{self, IntoOverflowableItem, OverflowableItem}; +use crate::pairs::{rewrite_all_pairs, rewrite_pair, PairParts}; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::{Indent, Shape}; +use crate::source_map::{LineRangeUtils, SpanUtils}; +use crate::spanned::Spanned; +use crate::string::{rewrite_string, StringFormat}; +use crate::types::{rewrite_path, PathContext}; +use crate::utils::{ + colon_spaces, contains_skip, count_newlines, first_line_ends_with, inner_attributes, + last_line_extendable, last_line_width, mk_sp, outer_attributes, semicolon_for_expr, + unicode_str_width, wrap_str, +}; +use crate::vertical::rewrite_with_alignment; +use crate::visitor::FmtVisitor; + +impl Rewrite for ast::Expr { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + format_expr(self, ExprType::SubExpression, context, shape) + } +} + +#[derive(Copy, Clone, PartialEq)] +pub(crate) enum ExprType { + Statement, + SubExpression, +} + +pub(crate) fn format_expr( + expr: &ast::Expr, + expr_type: ExprType, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + skip_out_of_file_lines_range!(context, expr.span); + + if contains_skip(&*expr.attrs) { + return Some(context.snippet(expr.span()).to_owned()); + } + let shape = if expr_type == ExprType::Statement && semicolon_for_expr(context, expr) { + shape.sub_width(1)? + } else { + shape + }; + + let expr_rw = match expr.kind { + ast::ExprKind::Array(ref expr_vec) => rewrite_array( + "", + expr_vec.iter(), + expr.span, + context, + shape, + choose_separator_tactic(context, expr.span), + None, + ), + ast::ExprKind::Lit(ref l) => { + if let Some(expr_rw) = rewrite_literal(context, l, shape) { + Some(expr_rw) + } else { + if let LitKind::StrRaw(_) = l.token.kind { + Some(context.snippet(l.span).trim().into()) + } else { + None + } + } + } + ast::ExprKind::Call(ref callee, ref args) => { + let inner_span = mk_sp(callee.span.hi(), expr.span.hi()); + let callee_str = callee.rewrite(context, shape)?; + rewrite_call(context, &callee_str, args, inner_span, shape) + } + ast::ExprKind::Paren(ref subexpr) => rewrite_paren(context, subexpr, shape, expr.span), + ast::ExprKind::Binary(op, ref lhs, ref rhs) => { + // FIXME: format comments between operands and operator + rewrite_all_pairs(expr, shape, context).or_else(|| { + rewrite_pair( + &**lhs, + &**rhs, + PairParts::infix(&format!(" {} ", context.snippet(op.span))), + context, + shape, + context.config.binop_separator(), + ) + }) + } + ast::ExprKind::Unary(op, ref subexpr) => rewrite_unary_op(context, op, subexpr, shape), + ast::ExprKind::Struct(ref path, ref fields, ref struct_rest) => rewrite_struct_lit( + context, + path, + fields, + struct_rest, + &expr.attrs, + expr.span, + shape, + ), + ast::ExprKind::Tup(ref items) => { + rewrite_tuple(context, items.iter(), expr.span, shape, items.len() == 1) + } + ast::ExprKind::Let(..) => None, + ast::ExprKind::If(..) + | ast::ExprKind::ForLoop(..) + | ast::ExprKind::Loop(..) + | ast::ExprKind::While(..) => to_control_flow(expr, expr_type) + .and_then(|control_flow| control_flow.rewrite(context, shape)), + ast::ExprKind::ConstBlock(ref anon_const) => { + Some(format!("const {}", anon_const.rewrite(context, shape)?)) + } + ast::ExprKind::Block(ref block, opt_label) => { + match expr_type { + ExprType::Statement => { + if is_unsafe_block(block) { + rewrite_block(block, Some(&expr.attrs), opt_label, context, shape) + } else if let rw @ Some(_) = + rewrite_empty_block(context, block, Some(&expr.attrs), opt_label, "", shape) + { + // Rewrite block without trying to put it in a single line. + rw + } else { + let prefix = block_prefix(context, block, shape)?; + + rewrite_block_with_visitor( + context, + &prefix, + block, + Some(&expr.attrs), + opt_label, + shape, + true, + ) + } + } + ExprType::SubExpression => { + rewrite_block(block, Some(&expr.attrs), opt_label, context, shape) + } + } + } + ast::ExprKind::Match(ref cond, ref arms) => { + rewrite_match(context, cond, arms, shape, expr.span, &expr.attrs) + } + ast::ExprKind::Path(ref qself, ref path) => { + rewrite_path(context, PathContext::Expr, qself.as_ref(), path, shape) + } + ast::ExprKind::Assign(ref lhs, ref rhs, _) => { + rewrite_assignment(context, lhs, rhs, None, shape) + } + ast::ExprKind::AssignOp(ref op, ref lhs, ref rhs) => { + rewrite_assignment(context, lhs, rhs, Some(op), shape) + } + ast::ExprKind::Continue(ref opt_label) => { + let id_str = match *opt_label { + Some(label) => format!(" {}", label.ident), + None => String::new(), + }; + Some(format!("continue{}", id_str)) + } + ast::ExprKind::Break(ref opt_label, ref opt_expr) => { + let id_str = match *opt_label { + Some(label) => format!(" {}", label.ident), + None => String::new(), + }; + + if let Some(ref expr) = *opt_expr { + rewrite_unary_prefix(context, &format!("break{} ", id_str), &**expr, shape) + } else { + Some(format!("break{}", id_str)) + } + } + ast::ExprKind::Yield(ref opt_expr) => { + if let Some(ref expr) = *opt_expr { + rewrite_unary_prefix(context, "yield ", &**expr, shape) + } else { + Some("yield".to_string()) + } + } + ast::ExprKind::Closure(capture, ref is_async, movability, ref fn_decl, ref body, _) => { + closures::rewrite_closure( + capture, is_async, movability, fn_decl, body, expr.span, context, shape, + ) + } + ast::ExprKind::Try(..) | ast::ExprKind::Field(..) | ast::ExprKind::MethodCall(..) => { + rewrite_chain(expr, context, shape) + } + ast::ExprKind::MacCall(ref mac) => { + rewrite_macro(mac, None, context, shape, MacroPosition::Expression).or_else(|| { + wrap_str( + context.snippet(expr.span).to_owned(), + context.config.max_width(), + shape, + ) + }) + } + ast::ExprKind::Ret(None) => Some("return".to_owned()), + ast::ExprKind::Ret(Some(ref expr)) => { + rewrite_unary_prefix(context, "return ", &**expr, shape) + } + ast::ExprKind::Box(ref expr) => rewrite_unary_prefix(context, "box ", &**expr, shape), + ast::ExprKind::AddrOf(borrow_kind, mutability, ref expr) => { + rewrite_expr_addrof(context, borrow_kind, mutability, expr, shape) + } + ast::ExprKind::Cast(ref expr, ref ty) => rewrite_pair( + &**expr, + &**ty, + PairParts::infix(" as "), + context, + shape, + SeparatorPlace::Front, + ), + ast::ExprKind::Type(ref expr, ref ty) => rewrite_pair( + &**expr, + &**ty, + PairParts::infix(": "), + context, + shape, + SeparatorPlace::Back, + ), + ast::ExprKind::Index(ref expr, ref index) => { + rewrite_index(&**expr, &**index, context, shape) + } + ast::ExprKind::Repeat(ref expr, ref repeats) => rewrite_pair( + &**expr, + &*repeats.value, + PairParts::new("[", "; ", "]"), + context, + shape, + SeparatorPlace::Back, + ), + ast::ExprKind::Range(ref lhs, ref rhs, limits) => { + let delim = match limits { + ast::RangeLimits::HalfOpen => "..", + ast::RangeLimits::Closed => "..=", + }; + + fn needs_space_before_range(context: &RewriteContext<'_>, lhs: &ast::Expr) -> bool { + match lhs.kind { + ast::ExprKind::Lit(ref lit) => match lit.kind { + ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) => { + context.snippet(lit.span).ends_with('.') + } + _ => false, + }, + ast::ExprKind::Unary(_, ref expr) => needs_space_before_range(context, &expr), + _ => false, + } + } + + fn needs_space_after_range(rhs: &ast::Expr) -> bool { + match rhs.kind { + // Don't format `.. ..` into `....`, which is invalid. + // + // This check is unnecessary for `lhs`, because a range + // starting from another range needs parentheses as `(x ..) ..` + // (`x .. ..` is a range from `x` to `..`). + ast::ExprKind::Range(None, _, _) => true, + _ => false, + } + } + + let default_sp_delim = |lhs: Option<&ast::Expr>, rhs: Option<&ast::Expr>| { + let space_if = |b: bool| if b { " " } else { "" }; + + format!( + "{}{}{}", + lhs.map_or("", |lhs| space_if(needs_space_before_range(context, lhs))), + delim, + rhs.map_or("", |rhs| space_if(needs_space_after_range(rhs))), + ) + }; + + match (lhs.as_ref().map(|x| &**x), rhs.as_ref().map(|x| &**x)) { + (Some(lhs), Some(rhs)) => { + let sp_delim = if context.config.spaces_around_ranges() { + format!(" {} ", delim) + } else { + default_sp_delim(Some(lhs), Some(rhs)) + }; + rewrite_pair( + &*lhs, + &*rhs, + PairParts::infix(&sp_delim), + context, + shape, + context.config.binop_separator(), + ) + } + (None, Some(rhs)) => { + let sp_delim = if context.config.spaces_around_ranges() { + format!("{} ", delim) + } else { + default_sp_delim(None, Some(rhs)) + }; + rewrite_unary_prefix(context, &sp_delim, &*rhs, shape) + } + (Some(lhs), None) => { + let sp_delim = if context.config.spaces_around_ranges() { + format!(" {}", delim) + } else { + default_sp_delim(Some(lhs), None) + }; + rewrite_unary_suffix(context, &sp_delim, &*lhs, shape) + } + (None, None) => Some(delim.to_owned()), + } + } + // We do not format these expressions yet, but they should still + // satisfy our width restrictions. + // Style Guide RFC for InlineAsm variant pending + // https://github.com/rust-dev-tools/fmt-rfcs/issues/152 + ast::ExprKind::LlvmInlineAsm(..) | ast::ExprKind::InlineAsm(..) => { + Some(context.snippet(expr.span).to_owned()) + } + ast::ExprKind::TryBlock(ref block) => { + if let rw @ Some(_) = + rewrite_single_line_block(context, "try ", block, Some(&expr.attrs), None, shape) + { + rw + } else { + // 9 = `try ` + let budget = shape.width.saturating_sub(9); + Some(format!( + "{}{}", + "try ", + rewrite_block( + block, + Some(&expr.attrs), + None, + context, + Shape::legacy(budget, shape.indent) + )? + )) + } + } + ast::ExprKind::Async(capture_by, _node_id, ref block) => { + let mover = if capture_by == ast::CaptureBy::Value { + "move " + } else { + "" + }; + if let rw @ Some(_) = rewrite_single_line_block( + context, + format!("{}{}", "async ", mover).as_str(), + block, + Some(&expr.attrs), + None, + shape, + ) { + rw + } else { + // 6 = `async ` + let budget = shape.width.saturating_sub(6); + Some(format!( + "{}{}{}", + "async ", + mover, + rewrite_block( + block, + Some(&expr.attrs), + None, + context, + Shape::legacy(budget, shape.indent) + )? + )) + } + } + ast::ExprKind::Await(_) => rewrite_chain(expr, context, shape), + ast::ExprKind::Underscore => Some("_".to_owned()), + ast::ExprKind::Err => None, + }; + + expr_rw + .and_then(|expr_str| recover_comment_removed(expr_str, expr.span, context)) + .and_then(|expr_str| { + let attrs = outer_attributes(&expr.attrs); + let attrs_str = attrs.rewrite(context, shape)?; + let span = mk_sp( + attrs.last().map_or(expr.span.lo(), |attr| attr.span.hi()), + expr.span.lo(), + ); + combine_strs_with_missing_comments(context, &attrs_str, &expr_str, span, shape, false) + }) +} + +pub(crate) fn rewrite_array<'a, T: 'a + IntoOverflowableItem<'a>>( + name: &'a str, + exprs: impl Iterator, + span: Span, + context: &'a RewriteContext<'_>, + shape: Shape, + force_separator_tactic: Option, + delim_token: Option, +) -> Option { + overflow::rewrite_with_square_brackets( + context, + name, + exprs, + shape, + span, + force_separator_tactic, + delim_token, + ) +} + +fn rewrite_empty_block( + context: &RewriteContext<'_>, + block: &ast::Block, + attrs: Option<&[ast::Attribute]>, + label: Option, + prefix: &str, + shape: Shape, +) -> Option { + if block_has_statements(&block) { + return None; + } + + let label_str = rewrite_label(label); + if attrs.map_or(false, |a| !inner_attributes(a).is_empty()) { + return None; + } + + if !block_contains_comment(context, block) && shape.width >= 2 { + return Some(format!("{}{}{{}}", prefix, label_str)); + } + + // If a block contains only a single-line comment, then leave it on one line. + let user_str = context.snippet(block.span); + let user_str = user_str.trim(); + if user_str.starts_with('{') && user_str.ends_with('}') { + let comment_str = user_str[1..user_str.len() - 1].trim(); + if block.stmts.is_empty() + && !comment_str.contains('\n') + && !comment_str.starts_with("//") + && comment_str.len() + 4 <= shape.width + { + return Some(format!("{}{}{{ {} }}", prefix, label_str, comment_str)); + } + } + + None +} + +fn block_prefix(context: &RewriteContext<'_>, block: &ast::Block, shape: Shape) -> Option { + Some(match block.rules { + ast::BlockCheckMode::Unsafe(..) => { + let snippet = context.snippet(block.span); + let open_pos = snippet.find_uncommented("{")?; + // Extract comment between unsafe and block start. + let trimmed = &snippet[6..open_pos].trim(); + + if !trimmed.is_empty() { + // 9 = "unsafe {".len(), 7 = "unsafe ".len() + let budget = shape.width.checked_sub(9)?; + format!( + "unsafe {} ", + rewrite_comment( + trimmed, + true, + Shape::legacy(budget, shape.indent + 7), + context.config, + )? + ) + } else { + "unsafe ".to_owned() + } + } + ast::BlockCheckMode::Default => String::new(), + }) +} + +fn rewrite_single_line_block( + context: &RewriteContext<'_>, + prefix: &str, + block: &ast::Block, + attrs: Option<&[ast::Attribute]>, + label: Option, + shape: Shape, +) -> Option { + if is_simple_block(context, block, attrs) { + let expr_shape = shape.offset_left(last_line_width(prefix))?; + let expr_str = block.stmts[0].rewrite(context, expr_shape)?; + let label_str = rewrite_label(label); + let result = format!("{}{}{{ {} }}", prefix, label_str, expr_str); + if result.len() <= shape.width && !result.contains('\n') { + return Some(result); + } + } + None +} + +pub(crate) fn rewrite_block_with_visitor( + context: &RewriteContext<'_>, + prefix: &str, + block: &ast::Block, + attrs: Option<&[ast::Attribute]>, + label: Option, + shape: Shape, + has_braces: bool, +) -> Option { + if let rw @ Some(_) = rewrite_empty_block(context, block, attrs, label, prefix, shape) { + return rw; + } + + let mut visitor = FmtVisitor::from_context(context); + visitor.block_indent = shape.indent; + visitor.is_if_else_block = context.is_if_else_block(); + match (block.rules, label) { + (ast::BlockCheckMode::Unsafe(..), _) | (ast::BlockCheckMode::Default, Some(_)) => { + let snippet = context.snippet(block.span); + let open_pos = snippet.find_uncommented("{")?; + visitor.last_pos = block.span.lo() + BytePos(open_pos as u32) + } + (ast::BlockCheckMode::Default, None) => visitor.last_pos = block.span.lo(), + } + + let inner_attrs = attrs.map(inner_attributes); + let label_str = rewrite_label(label); + visitor.visit_block(block, inner_attrs.as_ref().map(|a| &**a), has_braces); + let visitor_context = visitor.get_context(); + context + .skipped_range + .borrow_mut() + .append(&mut visitor_context.skipped_range.borrow_mut()); + Some(format!("{}{}{}", prefix, label_str, visitor.buffer)) +} + +impl Rewrite for ast::Block { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + rewrite_block(self, None, None, context, shape) + } +} + +fn rewrite_block( + block: &ast::Block, + attrs: Option<&[ast::Attribute]>, + label: Option, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let prefix = block_prefix(context, block, shape)?; + + // shape.width is used only for the single line case: either the empty block `{}`, + // or an unsafe expression `unsafe { e }`. + if let rw @ Some(_) = rewrite_empty_block(context, block, attrs, label, &prefix, shape) { + return rw; + } + + let result = rewrite_block_with_visitor(context, &prefix, block, attrs, label, shape, true); + if let Some(ref result_str) = result { + if result_str.lines().count() <= 3 { + if let rw @ Some(_) = + rewrite_single_line_block(context, &prefix, block, attrs, label, shape) + { + return rw; + } + } + } + + result +} + +// Rewrite condition if the given expression has one. +pub(crate) fn rewrite_cond( + context: &RewriteContext<'_>, + expr: &ast::Expr, + shape: Shape, +) -> Option { + match expr.kind { + ast::ExprKind::Match(ref cond, _) => { + // `match `cond` {` + let cond_shape = match context.config.indent_style() { + IndentStyle::Visual => shape.shrink_left(6).and_then(|s| s.sub_width(2))?, + IndentStyle::Block => shape.offset_left(8)?, + }; + cond.rewrite(context, cond_shape) + } + _ => to_control_flow(expr, ExprType::SubExpression).and_then(|control_flow| { + let alt_block_sep = + String::from("\n") + &shape.indent.block_only().to_string(context.config); + control_flow + .rewrite_cond(context, shape, &alt_block_sep) + .and_then(|rw| Some(rw.0)) + }), + } +} + +// Abstraction over control flow expressions +#[derive(Debug)] +struct ControlFlow<'a> { + cond: Option<&'a ast::Expr>, + block: &'a ast::Block, + else_block: Option<&'a ast::Expr>, + label: Option, + pat: Option<&'a ast::Pat>, + keyword: &'a str, + matcher: &'a str, + connector: &'a str, + allow_single_line: bool, + // HACK: `true` if this is an `if` expression in an `else if`. + nested_if: bool, + span: Span, +} + +fn extract_pats_and_cond(expr: &ast::Expr) -> (Option<&ast::Pat>, &ast::Expr) { + match expr.kind { + ast::ExprKind::Let(ref pat, ref cond) => (Some(pat), cond), + _ => (None, expr), + } +} + +// FIXME: Refactor this. +fn to_control_flow(expr: &ast::Expr, expr_type: ExprType) -> Option> { + match expr.kind { + ast::ExprKind::If(ref cond, ref if_block, ref else_block) => { + let (pat, cond) = extract_pats_and_cond(cond); + Some(ControlFlow::new_if( + cond, + pat, + if_block, + else_block.as_ref().map(|e| &**e), + expr_type == ExprType::SubExpression, + false, + expr.span, + )) + } + ast::ExprKind::ForLoop(ref pat, ref cond, ref block, label) => { + Some(ControlFlow::new_for(pat, cond, block, label, expr.span)) + } + ast::ExprKind::Loop(ref block, label) => { + Some(ControlFlow::new_loop(block, label, expr.span)) + } + ast::ExprKind::While(ref cond, ref block, label) => { + let (pat, cond) = extract_pats_and_cond(cond); + Some(ControlFlow::new_while(pat, cond, block, label, expr.span)) + } + _ => None, + } +} + +fn choose_matcher(pat: Option<&ast::Pat>) -> &'static str { + pat.map_or("", |_| "let") +} + +impl<'a> ControlFlow<'a> { + fn new_if( + cond: &'a ast::Expr, + pat: Option<&'a ast::Pat>, + block: &'a ast::Block, + else_block: Option<&'a ast::Expr>, + allow_single_line: bool, + nested_if: bool, + span: Span, + ) -> ControlFlow<'a> { + let matcher = choose_matcher(pat); + ControlFlow { + cond: Some(cond), + block, + else_block, + label: None, + pat, + keyword: "if", + matcher, + connector: " =", + allow_single_line, + nested_if, + span, + } + } + + fn new_loop(block: &'a ast::Block, label: Option, span: Span) -> ControlFlow<'a> { + ControlFlow { + cond: None, + block, + else_block: None, + label, + pat: None, + keyword: "loop", + matcher: "", + connector: "", + allow_single_line: false, + nested_if: false, + span, + } + } + + fn new_while( + pat: Option<&'a ast::Pat>, + cond: &'a ast::Expr, + block: &'a ast::Block, + label: Option, + span: Span, + ) -> ControlFlow<'a> { + let matcher = choose_matcher(pat); + ControlFlow { + cond: Some(cond), + block, + else_block: None, + label, + pat, + keyword: "while", + matcher, + connector: " =", + allow_single_line: false, + nested_if: false, + span, + } + } + + fn new_for( + pat: &'a ast::Pat, + cond: &'a ast::Expr, + block: &'a ast::Block, + label: Option, + span: Span, + ) -> ControlFlow<'a> { + ControlFlow { + cond: Some(cond), + block, + else_block: None, + label, + pat: Some(pat), + keyword: "for", + matcher: "", + connector: " in", + allow_single_line: false, + nested_if: false, + span, + } + } + + fn rewrite_single_line( + &self, + pat_expr_str: &str, + context: &RewriteContext<'_>, + width: usize, + ) -> Option { + assert!(self.allow_single_line); + let else_block = self.else_block?; + let fixed_cost = self.keyword.len() + " { } else { }".len(); + + if let ast::ExprKind::Block(ref else_node, _) = else_block.kind { + if !is_simple_block(context, self.block, None) + || !is_simple_block(context, else_node, None) + || pat_expr_str.contains('\n') + { + return None; + } + + let new_width = width.checked_sub(pat_expr_str.len() + fixed_cost)?; + let expr = &self.block.stmts[0]; + let if_str = expr.rewrite(context, Shape::legacy(new_width, Indent::empty()))?; + + let new_width = new_width.checked_sub(if_str.len())?; + let else_expr = &else_node.stmts[0]; + let else_str = else_expr.rewrite(context, Shape::legacy(new_width, Indent::empty()))?; + + if if_str.contains('\n') || else_str.contains('\n') { + return None; + } + + let result = format!( + "{} {} {{ {} }} else {{ {} }}", + self.keyword, pat_expr_str, if_str, else_str + ); + + if result.len() <= width { + return Some(result); + } + } + + None + } +} + +/// Returns `true` if the last line of pat_str has leading whitespace and it is wider than the +/// shape's indent. +fn last_line_offsetted(start_column: usize, pat_str: &str) -> bool { + let mut leading_whitespaces = 0; + for c in pat_str.chars().rev() { + match c { + '\n' => break, + _ if c.is_whitespace() => leading_whitespaces += 1, + _ => leading_whitespaces = 0, + } + } + leading_whitespaces > start_column +} + +impl<'a> ControlFlow<'a> { + fn rewrite_pat_expr( + &self, + context: &RewriteContext<'_>, + expr: &ast::Expr, + shape: Shape, + offset: usize, + ) -> Option { + debug!("rewrite_pat_expr {:?} {:?} {:?}", shape, self.pat, expr); + + let cond_shape = shape.offset_left(offset)?; + if let Some(pat) = self.pat { + let matcher = if self.matcher.is_empty() { + self.matcher.to_owned() + } else { + format!("{} ", self.matcher) + }; + let pat_shape = cond_shape + .offset_left(matcher.len())? + .sub_width(self.connector.len())?; + let pat_string = pat.rewrite(context, pat_shape)?; + let comments_lo = context + .snippet_provider + .span_after(self.span, self.connector.trim()); + let missing_comments = if let Some(comment) = + rewrite_missing_comment(mk_sp(comments_lo, expr.span.lo()), cond_shape, context) + { + if !self.connector.is_empty() && !comment.is_empty() { + if comment_style(&comment, false).is_line_comment() || comment.contains("\n") { + let newline = &pat_shape + .indent + .block_indent(context.config) + .to_string_with_newline(context.config); + // An extra space is added when the lhs and rhs are joined + // so we need to remove one space from the end to ensure + // the comment and rhs are aligned. + let mut suffix = newline.as_ref().to_string(); + if !suffix.is_empty() { + suffix.truncate(suffix.len() - 1); + } + format!("{}{}{}", newline, comment, suffix) + } else { + format!(" {}", comment) + } + } else { + comment + } + } else { + "".to_owned() + }; + + let result = format!( + "{}{}{}{}", + matcher, pat_string, self.connector, missing_comments + ); + return rewrite_assign_rhs(context, result, expr, cond_shape); + } + + let expr_rw = expr.rewrite(context, cond_shape); + // The expression may (partially) fit on the current line. + // We do not allow splitting between `if` and condition. + if self.keyword == "if" || expr_rw.is_some() { + return expr_rw; + } + + // The expression won't fit on the current line, jump to next. + let nested_shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + let nested_indent_str = nested_shape.indent.to_string_with_newline(context.config); + expr.rewrite(context, nested_shape) + .map(|expr_rw| format!("{}{}", nested_indent_str, expr_rw)) + } + + fn rewrite_cond( + &self, + context: &RewriteContext<'_>, + shape: Shape, + alt_block_sep: &str, + ) -> Option<(String, usize)> { + // Do not take the rhs overhead from the upper expressions into account + // when rewriting pattern. + let new_width = context.budget(shape.used_width()); + let fresh_shape = Shape { + width: new_width, + ..shape + }; + let constr_shape = if self.nested_if { + // We are part of an if-elseif-else chain. Our constraints are tightened. + // 7 = "} else " .len() + fresh_shape.offset_left(7)? + } else { + fresh_shape + }; + + let label_string = rewrite_label(self.label); + // 1 = space after keyword. + let offset = self.keyword.len() + label_string.len() + 1; + + let pat_expr_string = match self.cond { + Some(cond) => self.rewrite_pat_expr(context, cond, constr_shape, offset)?, + None => String::new(), + }; + + let brace_overhead = + if context.config.control_brace_style() != ControlBraceStyle::AlwaysNextLine { + // 2 = ` {` + 2 + } else { + 0 + }; + let one_line_budget = context + .config + .max_width() + .saturating_sub(constr_shape.used_width() + offset + brace_overhead); + let force_newline_brace = (pat_expr_string.contains('\n') + || pat_expr_string.len() > one_line_budget) + && (!last_line_extendable(&pat_expr_string) + || last_line_offsetted(shape.used_width(), &pat_expr_string)); + + // Try to format if-else on single line. + if self.allow_single_line + && context + .config + .width_heuristics() + .single_line_if_else_max_width + > 0 + { + let trial = self.rewrite_single_line(&pat_expr_string, context, shape.width); + + if let Some(cond_str) = trial { + if cond_str.len() + <= context + .config + .width_heuristics() + .single_line_if_else_max_width + { + return Some((cond_str, 0)); + } + } + } + + let cond_span = if let Some(cond) = self.cond { + cond.span + } else { + mk_sp(self.block.span.lo(), self.block.span.lo()) + }; + + // `for event in event` + // Do not include label in the span. + let lo = self + .label + .map_or(self.span.lo(), |label| label.ident.span.hi()); + let between_kwd_cond = mk_sp( + context + .snippet_provider + .span_after(mk_sp(lo, self.span.hi()), self.keyword.trim()), + if self.pat.is_none() { + cond_span.lo() + } else if self.matcher.is_empty() { + self.pat.unwrap().span.lo() + } else { + context + .snippet_provider + .span_before(self.span, self.matcher.trim()) + }, + ); + + let between_kwd_cond_comment = extract_comment(between_kwd_cond, context, shape); + + let after_cond_comment = + extract_comment(mk_sp(cond_span.hi(), self.block.span.lo()), context, shape); + + let block_sep = if self.cond.is_none() && between_kwd_cond_comment.is_some() { + "" + } else if context.config.control_brace_style() == ControlBraceStyle::AlwaysNextLine + || force_newline_brace + { + alt_block_sep + } else { + " " + }; + + let used_width = if pat_expr_string.contains('\n') { + last_line_width(&pat_expr_string) + } else { + // 2 = spaces after keyword and condition. + label_string.len() + self.keyword.len() + pat_expr_string.len() + 2 + }; + + Some(( + format!( + "{}{}{}{}{}", + label_string, + self.keyword, + between_kwd_cond_comment.as_ref().map_or( + if pat_expr_string.is_empty() || pat_expr_string.starts_with('\n') { + "" + } else { + " " + }, + |s| &**s, + ), + pat_expr_string, + after_cond_comment.as_ref().map_or(block_sep, |s| &**s) + ), + used_width, + )) + } +} + +impl<'a> Rewrite for ControlFlow<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + debug!("ControlFlow::rewrite {:?} {:?}", self, shape); + + let alt_block_sep = &shape.indent.to_string_with_newline(context.config); + let (cond_str, used_width) = self.rewrite_cond(context, shape, alt_block_sep)?; + // If `used_width` is 0, it indicates that whole control flow is written in a single line. + if used_width == 0 { + return Some(cond_str); + } + + let block_width = shape.width.saturating_sub(used_width); + // This is used only for the empty block case: `{}`. So, we use 1 if we know + // we should avoid the single line case. + let block_width = if self.else_block.is_some() || self.nested_if { + min(1, block_width) + } else { + block_width + }; + let block_shape = Shape { + width: block_width, + ..shape + }; + let block_str = { + let old_val = context.is_if_else_block.replace(self.else_block.is_some()); + let result = + rewrite_block_with_visitor(context, "", self.block, None, None, block_shape, true); + context.is_if_else_block.replace(old_val); + result? + }; + + let mut result = format!("{}{}", cond_str, block_str); + + if let Some(else_block) = self.else_block { + let shape = Shape::indented(shape.indent, context.config); + let mut last_in_chain = false; + let rewrite = match else_block.kind { + // If the else expression is another if-else expression, prevent it + // from being formatted on a single line. + // Note how we're passing the original shape, as the + // cost of "else" should not cascade. + ast::ExprKind::If(ref cond, ref if_block, ref next_else_block) => { + let (pats, cond) = extract_pats_and_cond(cond); + ControlFlow::new_if( + cond, + pats, + if_block, + next_else_block.as_ref().map(|e| &**e), + false, + true, + mk_sp(else_block.span.lo(), self.span.hi()), + ) + .rewrite(context, shape) + } + _ => { + last_in_chain = true; + // When rewriting a block, the width is only used for single line + // blocks, passing 1 lets us avoid that. + let else_shape = Shape { + width: min(1, shape.width), + ..shape + }; + format_expr(else_block, ExprType::Statement, context, else_shape) + } + }; + + let between_kwd_else_block = mk_sp( + self.block.span.hi(), + context + .snippet_provider + .span_before(mk_sp(self.block.span.hi(), else_block.span.lo()), "else"), + ); + let between_kwd_else_block_comment = + extract_comment(between_kwd_else_block, context, shape); + + let after_else = mk_sp( + context + .snippet_provider + .span_after(mk_sp(self.block.span.hi(), else_block.span.lo()), "else"), + else_block.span.lo(), + ); + let after_else_comment = extract_comment(after_else, context, shape); + + let between_sep = match context.config.control_brace_style() { + ControlBraceStyle::AlwaysNextLine | ControlBraceStyle::ClosingNextLine => { + &*alt_block_sep + } + ControlBraceStyle::AlwaysSameLine => " ", + }; + let after_sep = match context.config.control_brace_style() { + ControlBraceStyle::AlwaysNextLine if last_in_chain => &*alt_block_sep, + _ => " ", + }; + + result.push_str(&format!( + "{}else{}", + between_kwd_else_block_comment + .as_ref() + .map_or(between_sep, |s| &**s), + after_else_comment.as_ref().map_or(after_sep, |s| &**s), + )); + result.push_str(&rewrite?); + } + + Some(result) + } +} + +fn rewrite_label(opt_label: Option) -> Cow<'static, str> { + match opt_label { + Some(label) => Cow::from(format!("{}: ", label.ident)), + None => Cow::from(""), + } +} + +fn extract_comment(span: Span, context: &RewriteContext<'_>, shape: Shape) -> Option { + match rewrite_missing_comment(span, shape, context) { + Some(ref comment) if !comment.is_empty() => Some(format!( + "{indent}{}{indent}", + comment, + indent = shape.indent.to_string_with_newline(context.config) + )), + _ => None, + } +} + +pub(crate) fn block_contains_comment(context: &RewriteContext<'_>, block: &ast::Block) -> bool { + contains_comment(context.snippet(block.span)) +} + +// Checks that a block contains no statements, an expression and no comments or +// attributes. +// FIXME: incorrectly returns false when comment is contained completely within +// the expression. +pub(crate) fn is_simple_block( + context: &RewriteContext<'_>, + block: &ast::Block, + attrs: Option<&[ast::Attribute]>, +) -> bool { + block.stmts.len() == 1 + && stmt_is_expr(&block.stmts[0]) + && !block_contains_comment(context, block) + && attrs.map_or(true, |a| a.is_empty()) +} + +/// Checks whether a block contains at most one statement or expression, and no +/// comments or attributes. +pub(crate) fn is_simple_block_stmt( + context: &RewriteContext<'_>, + block: &ast::Block, + attrs: Option<&[ast::Attribute]>, +) -> bool { + block.stmts.len() <= 1 + && !block_contains_comment(context, block) + && attrs.map_or(true, |a| a.is_empty()) +} + +fn block_has_statements(block: &ast::Block) -> bool { + block + .stmts + .iter() + .any(|stmt| !matches!(stmt.kind, ast::StmtKind::Empty)) +} + +/// Checks whether a block contains no statements, expressions, comments, or +/// inner attributes. +pub(crate) fn is_empty_block( + context: &RewriteContext<'_>, + block: &ast::Block, + attrs: Option<&[ast::Attribute]>, +) -> bool { + !block_has_statements(&block) + && !block_contains_comment(context, block) + && attrs.map_or(true, |a| inner_attributes(a).is_empty()) +} + +pub(crate) fn stmt_is_expr(stmt: &ast::Stmt) -> bool { + match stmt.kind { + ast::StmtKind::Expr(..) => true, + _ => false, + } +} + +pub(crate) fn is_unsafe_block(block: &ast::Block) -> bool { + if let ast::BlockCheckMode::Unsafe(..) = block.rules { + true + } else { + false + } +} + +pub(crate) fn rewrite_literal( + context: &RewriteContext<'_>, + l: &ast::Lit, + shape: Shape, +) -> Option { + match l.kind { + ast::LitKind::Str(_, ast::StrStyle::Cooked) => rewrite_string_lit(context, l.span, shape), + _ => wrap_str( + context.snippet(l.span).to_owned(), + context.config.max_width(), + shape, + ), + } +} + +fn rewrite_string_lit(context: &RewriteContext<'_>, span: Span, shape: Shape) -> Option { + let string_lit = context.snippet(span); + + if !context.config.format_strings() { + if string_lit + .lines() + .dropping_back(1) + .all(|line| line.ends_with('\\')) + && context.config.version() == Version::Two + { + return Some(string_lit.to_owned()); + } else { + return wrap_str(string_lit.to_owned(), context.config.max_width(), shape); + } + } + + // Remove the quote characters. + let str_lit = &string_lit[1..string_lit.len() - 1]; + + rewrite_string( + str_lit, + &StringFormat::new(shape.visual_indent(0), context.config), + shape.width.saturating_sub(2), + ) +} + +fn choose_separator_tactic(context: &RewriteContext<'_>, span: Span) -> Option { + if context.inside_macro() { + if span_ends_with_comma(context, span) { + Some(SeparatorTactic::Always) + } else { + Some(SeparatorTactic::Never) + } + } else { + None + } +} + +pub(crate) fn rewrite_call( + context: &RewriteContext<'_>, + callee: &str, + args: &[ptr::P], + span: Span, + shape: Shape, +) -> Option { + overflow::rewrite_with_parens( + context, + callee, + args.iter(), + shape, + span, + context.config.width_heuristics().fn_call_width, + choose_separator_tactic(context, span), + ) +} + +pub(crate) fn is_simple_expr(expr: &ast::Expr) -> bool { + match expr.kind { + ast::ExprKind::Lit(..) => true, + ast::ExprKind::Path(ref qself, ref path) => qself.is_none() && path.segments.len() <= 1, + ast::ExprKind::AddrOf(_, _, ref expr) + | ast::ExprKind::Box(ref expr) + | ast::ExprKind::Cast(ref expr, _) + | ast::ExprKind::Field(ref expr, _) + | ast::ExprKind::Try(ref expr) + | ast::ExprKind::Unary(_, ref expr) => is_simple_expr(expr), + ast::ExprKind::Index(ref lhs, ref rhs) => is_simple_expr(lhs) && is_simple_expr(rhs), + ast::ExprKind::Repeat(ref lhs, ref rhs) => { + is_simple_expr(lhs) && is_simple_expr(&*rhs.value) + } + _ => false, + } +} + +pub(crate) fn is_every_expr_simple(lists: &[OverflowableItem<'_>]) -> bool { + lists.iter().all(OverflowableItem::is_simple) +} + +pub(crate) fn can_be_overflowed_expr( + context: &RewriteContext<'_>, + expr: &ast::Expr, + args_len: usize, +) -> bool { + match expr.kind { + _ if !expr.attrs.is_empty() => false, + ast::ExprKind::Match(..) => { + (context.use_block_indent() && args_len == 1) + || (context.config.indent_style() == IndentStyle::Visual && args_len > 1) + || context.config.overflow_delimited_expr() + } + ast::ExprKind::If(..) + | ast::ExprKind::ForLoop(..) + | ast::ExprKind::Loop(..) + | ast::ExprKind::While(..) => { + context.config.combine_control_expr() && context.use_block_indent() && args_len == 1 + } + + // Handle always block-like expressions + ast::ExprKind::Async(..) | ast::ExprKind::Block(..) | ast::ExprKind::Closure(..) => true, + + // Handle `[]` and `{}`-like expressions + ast::ExprKind::Array(..) | ast::ExprKind::Struct(..) => { + context.config.overflow_delimited_expr() + || (context.use_block_indent() && args_len == 1) + } + ast::ExprKind::MacCall(ref mac) => { + match ( + rustc_ast::ast::MacDelimiter::from_token(mac.args.delim()), + context.config.overflow_delimited_expr(), + ) { + (Some(ast::MacDelimiter::Bracket), true) + | (Some(ast::MacDelimiter::Brace), true) => true, + _ => context.use_block_indent() && args_len == 1, + } + } + + // Handle parenthetical expressions + ast::ExprKind::Call(..) | ast::ExprKind::MethodCall(..) | ast::ExprKind::Tup(..) => { + context.use_block_indent() && args_len == 1 + } + + // Handle unary-like expressions + ast::ExprKind::AddrOf(_, _, ref expr) + | ast::ExprKind::Box(ref expr) + | ast::ExprKind::Try(ref expr) + | ast::ExprKind::Unary(_, ref expr) + | ast::ExprKind::Cast(ref expr, _) => can_be_overflowed_expr(context, expr, args_len), + _ => false, + } +} + +pub(crate) fn is_nested_call(expr: &ast::Expr) -> bool { + match expr.kind { + ast::ExprKind::Call(..) | ast::ExprKind::MacCall(..) => true, + ast::ExprKind::AddrOf(_, _, ref expr) + | ast::ExprKind::Box(ref expr) + | ast::ExprKind::Try(ref expr) + | ast::ExprKind::Unary(_, ref expr) + | ast::ExprKind::Cast(ref expr, _) => is_nested_call(expr), + _ => false, + } +} + +/// Returns `true` if a function call or a method call represented by the given span ends with a +/// trailing comma. This function is used when rewriting macro, as adding or removing a trailing +/// comma from macro can potentially break the code. +pub(crate) fn span_ends_with_comma(context: &RewriteContext<'_>, span: Span) -> bool { + let mut result: bool = Default::default(); + let mut prev_char: char = Default::default(); + let closing_delimiters = &[')', '}', ']']; + + for (kind, c) in CharClasses::new(context.snippet(span).chars()) { + match c { + _ if kind.is_comment() || c.is_whitespace() => continue, + c if closing_delimiters.contains(&c) => { + result &= !closing_delimiters.contains(&prev_char); + } + ',' => result = true, + _ => result = false, + } + prev_char = c; + } + + result +} + +fn rewrite_paren( + context: &RewriteContext<'_>, + mut subexpr: &ast::Expr, + shape: Shape, + mut span: Span, +) -> Option { + debug!("rewrite_paren, shape: {:?}", shape); + + // Extract comments within parens. + let mut pre_span; + let mut post_span; + let mut pre_comment; + let mut post_comment; + let remove_nested_parens = context.config.remove_nested_parens(); + loop { + // 1 = "(" or ")" + pre_span = mk_sp(span.lo() + BytePos(1), subexpr.span.lo()); + post_span = mk_sp(subexpr.span.hi(), span.hi() - BytePos(1)); + pre_comment = rewrite_missing_comment(pre_span, shape, context)?; + post_comment = rewrite_missing_comment(post_span, shape, context)?; + + // Remove nested parens if there are no comments. + if let ast::ExprKind::Paren(ref subsubexpr) = subexpr.kind { + if remove_nested_parens && pre_comment.is_empty() && post_comment.is_empty() { + span = subexpr.span; + subexpr = subsubexpr; + continue; + } + } + + break; + } + + // 1 = `(` and `)` + let sub_shape = shape.offset_left(1)?.sub_width(1)?; + let subexpr_str = subexpr.rewrite(context, sub_shape)?; + let fits_single_line = !pre_comment.contains("//") && !post_comment.contains("//"); + if fits_single_line { + Some(format!("({}{}{})", pre_comment, subexpr_str, post_comment)) + } else { + rewrite_paren_in_multi_line(context, subexpr, shape, pre_span, post_span) + } +} + +fn rewrite_paren_in_multi_line( + context: &RewriteContext<'_>, + subexpr: &ast::Expr, + shape: Shape, + pre_span: Span, + post_span: Span, +) -> Option { + let nested_indent = shape.indent.block_indent(context.config); + let nested_shape = Shape::indented(nested_indent, context.config); + let pre_comment = rewrite_missing_comment(pre_span, nested_shape, context)?; + let post_comment = rewrite_missing_comment(post_span, nested_shape, context)?; + let subexpr_str = subexpr.rewrite(context, nested_shape)?; + + let mut result = String::with_capacity(subexpr_str.len() * 2); + result.push('('); + if !pre_comment.is_empty() { + result.push_str(&nested_indent.to_string_with_newline(context.config)); + result.push_str(&pre_comment); + } + result.push_str(&nested_indent.to_string_with_newline(context.config)); + result.push_str(&subexpr_str); + if !post_comment.is_empty() { + result.push_str(&nested_indent.to_string_with_newline(context.config)); + result.push_str(&post_comment); + } + result.push_str(&shape.indent.to_string_with_newline(context.config)); + result.push(')'); + + Some(result) +} + +fn rewrite_index( + expr: &ast::Expr, + index: &ast::Expr, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let expr_str = expr.rewrite(context, shape)?; + + let offset = last_line_width(&expr_str) + 1; + let rhs_overhead = shape.rhs_overhead(context.config); + let index_shape = if expr_str.contains('\n') { + Shape::legacy(context.config.max_width(), shape.indent) + .offset_left(offset) + .and_then(|shape| shape.sub_width(1 + rhs_overhead)) + } else { + match context.config.indent_style() { + IndentStyle::Block => shape + .offset_left(offset) + .and_then(|shape| shape.sub_width(1)), + IndentStyle::Visual => shape.visual_indent(offset).sub_width(offset + 1), + } + }; + let orig_index_rw = index_shape.and_then(|s| index.rewrite(context, s)); + + // Return if index fits in a single line. + match orig_index_rw { + Some(ref index_str) if !index_str.contains('\n') => { + return Some(format!("{}[{}]", expr_str, index_str)); + } + _ => (), + } + + // Try putting index on the next line and see if it fits in a single line. + let indent = shape.indent.block_indent(context.config); + let index_shape = Shape::indented(indent, context.config).offset_left(1)?; + let index_shape = index_shape.sub_width(1 + rhs_overhead)?; + let new_index_rw = index.rewrite(context, index_shape); + match (orig_index_rw, new_index_rw) { + (_, Some(ref new_index_str)) if !new_index_str.contains('\n') => Some(format!( + "{}{}[{}]", + expr_str, + indent.to_string_with_newline(context.config), + new_index_str, + )), + (None, Some(ref new_index_str)) => Some(format!( + "{}{}[{}]", + expr_str, + indent.to_string_with_newline(context.config), + new_index_str, + )), + (Some(ref index_str), _) => Some(format!("{}[{}]", expr_str, index_str)), + _ => None, + } +} + +fn struct_lit_can_be_aligned(fields: &[ast::Field], has_base: bool) -> bool { + !has_base && fields.iter().all(|field| !field.is_shorthand) +} + +fn rewrite_struct_lit<'a>( + context: &RewriteContext<'_>, + path: &ast::Path, + fields: &'a [ast::Field], + struct_rest: &ast::StructRest, + attrs: &[ast::Attribute], + span: Span, + shape: Shape, +) -> Option { + debug!("rewrite_struct_lit: shape {:?}", shape); + + enum StructLitField<'a> { + Regular(&'a ast::Field), + Base(&'a ast::Expr), + Rest(&'a Span), + } + + // 2 = " {".len() + let path_shape = shape.sub_width(2)?; + let path_str = rewrite_path(context, PathContext::Expr, None, path, path_shape)?; + + let has_base = match struct_rest { + ast::StructRest::None if fields.is_empty() => return Some(format!("{} {{}}", path_str)), + ast::StructRest::Rest(_) if fields.is_empty() => { + return Some(format!("{} {{ .. }}", path_str)); + } + ast::StructRest::Base(_) => true, + _ => false, + }; + + // Foo { a: Foo } - indent is +3, width is -5. + let (h_shape, v_shape) = struct_lit_shape(shape, context, path_str.len() + 3, 2)?; + + let one_line_width = h_shape.map_or(0, |shape| shape.width); + let body_lo = context.snippet_provider.span_after(span, "{"); + let fields_str = if struct_lit_can_be_aligned(fields, has_base) + && context.config.struct_field_align_threshold() > 0 + { + rewrite_with_alignment( + fields, + context, + v_shape, + mk_sp(body_lo, span.hi()), + one_line_width, + )? + } else { + let field_iter = fields.iter().map(StructLitField::Regular).chain( + match struct_rest { + ast::StructRest::Base(expr) => Some(StructLitField::Base(&**expr)), + ast::StructRest::Rest(span) => Some(StructLitField::Rest(span)), + ast::StructRest::None => None, + } + .into_iter(), + ); + + let span_lo = |item: &StructLitField<'_>| match *item { + StructLitField::Regular(field) => field.span().lo(), + StructLitField::Base(expr) => { + let last_field_hi = fields.last().map_or(span.lo(), |field| field.span.hi()); + let snippet = context.snippet(mk_sp(last_field_hi, expr.span.lo())); + let pos = snippet.find_uncommented("..").unwrap(); + last_field_hi + BytePos(pos as u32) + } + StructLitField::Rest(span) => span.lo(), + }; + let span_hi = |item: &StructLitField<'_>| match *item { + StructLitField::Regular(field) => field.span().hi(), + StructLitField::Base(expr) => expr.span.hi(), + StructLitField::Rest(span) => span.hi(), + }; + let rewrite = |item: &StructLitField<'_>| match *item { + StructLitField::Regular(field) => { + // The 1 taken from the v_budget is for the comma. + rewrite_field(context, field, v_shape.sub_width(1)?, 0) + } + StructLitField::Base(expr) => { + // 2 = .. + expr.rewrite(context, v_shape.offset_left(2)?) + .map(|s| format!("..{}", s)) + } + StructLitField::Rest(_) => Some("..".to_owned()), + }; + + let items = itemize_list( + context.snippet_provider, + field_iter, + "}", + ",", + span_lo, + span_hi, + rewrite, + body_lo, + span.hi(), + false, + ); + let item_vec = items.collect::>(); + + let tactic = struct_lit_tactic(h_shape, context, &item_vec); + let nested_shape = shape_for_tactic(tactic, h_shape, v_shape); + + let ends_with_comma = span_ends_with_comma(context, span); + let force_no_trailing_comma = context.inside_macro() && !ends_with_comma; + + let fmt = struct_lit_formatting( + nested_shape, + tactic, + context, + force_no_trailing_comma + || has_base + || !context.use_block_indent() + || matches!(struct_rest, ast::StructRest::Rest(_)), + ); + + write_list(&item_vec, &fmt)? + }; + + let fields_str = + wrap_struct_field(context, &attrs, &fields_str, shape, v_shape, one_line_width)?; + Some(format!("{} {{{}}}", path_str, fields_str)) + + // FIXME if context.config.indent_style() == Visual, but we run out + // of space, we should fall back to BlockIndent. +} + +pub(crate) fn wrap_struct_field( + context: &RewriteContext<'_>, + attrs: &[ast::Attribute], + fields_str: &str, + shape: Shape, + nested_shape: Shape, + one_line_width: usize, +) -> Option { + let should_vertical = context.config.indent_style() == IndentStyle::Block + && (fields_str.contains('\n') + || !context.config.struct_lit_single_line() + || fields_str.len() > one_line_width); + + let inner_attrs = &inner_attributes(attrs); + if inner_attrs.is_empty() { + if should_vertical { + Some(format!( + "{}{}{}", + nested_shape.indent.to_string_with_newline(context.config), + fields_str, + shape.indent.to_string_with_newline(context.config) + )) + } else { + // One liner or visual indent. + Some(format!(" {} ", fields_str)) + } + } else { + Some(format!( + "{}{}{}{}{}", + nested_shape.indent.to_string_with_newline(context.config), + inner_attrs.rewrite(context, shape)?, + nested_shape.indent.to_string_with_newline(context.config), + fields_str, + shape.indent.to_string_with_newline(context.config) + )) + } +} + +pub(crate) fn struct_lit_field_separator(config: &Config) -> &str { + colon_spaces(config) +} + +pub(crate) fn rewrite_field( + context: &RewriteContext<'_>, + field: &ast::Field, + shape: Shape, + prefix_max_width: usize, +) -> Option { + if contains_skip(&field.attrs) { + return Some(context.snippet(field.span()).to_owned()); + } + let mut attrs_str = field.attrs.rewrite(context, shape)?; + if !attrs_str.is_empty() { + attrs_str.push_str(&shape.indent.to_string_with_newline(context.config)); + }; + let name = context.snippet(field.ident.span); + if field.is_shorthand { + Some(attrs_str + name) + } else { + let mut separator = String::from(struct_lit_field_separator(context.config)); + for _ in 0..prefix_max_width.saturating_sub(name.len()) { + separator.push(' '); + } + let overhead = name.len() + separator.len(); + let expr_shape = shape.offset_left(overhead)?; + let expr = field.expr.rewrite(context, expr_shape); + + match expr { + Some(ref e) if e.as_str() == name && context.config.use_field_init_shorthand() => { + Some(attrs_str + name) + } + Some(e) => Some(format!("{}{}{}{}", attrs_str, name, separator, e)), + None => { + let expr_offset = shape.indent.block_indent(context.config); + let expr = field + .expr + .rewrite(context, Shape::indented(expr_offset, context.config)); + expr.map(|s| { + format!( + "{}{}:\n{}{}", + attrs_str, + name, + expr_offset.to_string(context.config), + s + ) + }) + } + } + } +} + +fn rewrite_tuple_in_visual_indent_style<'a, T: 'a + IntoOverflowableItem<'a>>( + context: &RewriteContext<'_>, + mut items: impl Iterator, + span: Span, + shape: Shape, + is_singleton_tuple: bool, +) -> Option { + // In case of length 1, need a trailing comma + debug!("rewrite_tuple_in_visual_indent_style {:?}", shape); + if is_singleton_tuple { + // 3 = "(" + ",)" + let nested_shape = shape.sub_width(3)?.visual_indent(1); + return items + .next() + .unwrap() + .rewrite(context, nested_shape) + .map(|s| format!("({},)", s)); + } + + let list_lo = context.snippet_provider.span_after(span, "("); + let nested_shape = shape.sub_width(2)?.visual_indent(1); + let items = itemize_list( + context.snippet_provider, + items, + ")", + ",", + |item| item.span().lo(), + |item| item.span().hi(), + |item| item.rewrite(context, nested_shape), + list_lo, + span.hi() - BytePos(1), + false, + ); + let item_vec: Vec<_> = items.collect(); + let tactic = definitive_tactic( + &item_vec, + ListTactic::HorizontalVertical, + Separator::Comma, + nested_shape.width, + ); + let fmt = ListFormatting::new(nested_shape, context.config) + .tactic(tactic) + .ends_with_newline(false); + let list_str = write_list(&item_vec, &fmt)?; + + Some(format!("({})", list_str)) +} + +pub(crate) fn rewrite_tuple<'a, T: 'a + IntoOverflowableItem<'a>>( + context: &'a RewriteContext<'_>, + items: impl Iterator, + span: Span, + shape: Shape, + is_singleton_tuple: bool, +) -> Option { + debug!("rewrite_tuple {:?}", shape); + if context.use_block_indent() { + // We use the same rule as function calls for rewriting tuples. + let force_tactic = if context.inside_macro() { + if span_ends_with_comma(context, span) { + Some(SeparatorTactic::Always) + } else { + Some(SeparatorTactic::Never) + } + } else if is_singleton_tuple { + Some(SeparatorTactic::Always) + } else { + None + }; + overflow::rewrite_with_parens( + context, + "", + items, + shape, + span, + context.config.width_heuristics().fn_call_width, + force_tactic, + ) + } else { + rewrite_tuple_in_visual_indent_style(context, items, span, shape, is_singleton_tuple) + } +} + +pub(crate) fn rewrite_unary_prefix( + context: &RewriteContext<'_>, + prefix: &str, + rewrite: &R, + shape: Shape, +) -> Option { + rewrite + .rewrite(context, shape.offset_left(prefix.len())?) + .map(|r| format!("{}{}", prefix, r)) +} + +// FIXME: this is probably not correct for multi-line Rewrites. we should +// subtract suffix.len() from the last line budget, not the first! +pub(crate) fn rewrite_unary_suffix( + context: &RewriteContext<'_>, + suffix: &str, + rewrite: &R, + shape: Shape, +) -> Option { + rewrite + .rewrite(context, shape.sub_width(suffix.len())?) + .map(|mut r| { + r.push_str(suffix); + r + }) +} + +fn rewrite_unary_op( + context: &RewriteContext<'_>, + op: ast::UnOp, + expr: &ast::Expr, + shape: Shape, +) -> Option { + // For some reason, an UnOp is not spanned like BinOp! + rewrite_unary_prefix(context, ast::UnOp::to_string(op), expr, shape) +} + +fn rewrite_assignment( + context: &RewriteContext<'_>, + lhs: &ast::Expr, + rhs: &ast::Expr, + op: Option<&ast::BinOp>, + shape: Shape, +) -> Option { + let operator_str = match op { + Some(op) => context.snippet(op.span), + None => "=", + }; + + // 1 = space between lhs and operator. + let lhs_shape = shape.sub_width(operator_str.len() + 1)?; + let lhs_str = format!("{} {}", lhs.rewrite(context, lhs_shape)?, operator_str); + + rewrite_assign_rhs(context, lhs_str, rhs, shape) +} + +/// Controls where to put the rhs. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) enum RhsTactics { + /// Use heuristics. + Default, + /// Put the rhs on the next line if it uses multiple line, without extra indentation. + ForceNextLineWithoutIndent, + /// Allow overflowing max width if neither `Default` nor `ForceNextLineWithoutIndent` + /// did not work. + AllowOverflow, +} + +// The left hand side must contain everything up to, and including, the +// assignment operator. +pub(crate) fn rewrite_assign_rhs, R: Rewrite>( + context: &RewriteContext<'_>, + lhs: S, + ex: &R, + shape: Shape, +) -> Option { + rewrite_assign_rhs_with(context, lhs, ex, shape, RhsTactics::Default) +} + +pub(crate) fn rewrite_assign_rhs_with, R: Rewrite>( + context: &RewriteContext<'_>, + lhs: S, + ex: &R, + shape: Shape, + rhs_tactics: RhsTactics, +) -> Option { + let lhs = lhs.into(); + let last_line_width = last_line_width(&lhs).saturating_sub(if lhs.contains('\n') { + shape.indent.width() + } else { + 0 + }); + // 1 = space between operator and rhs. + let orig_shape = shape.offset_left(last_line_width + 1).unwrap_or(Shape { + width: 0, + offset: shape.offset + last_line_width + 1, + ..shape + }); + let rhs = choose_rhs( + context, + ex, + orig_shape, + ex.rewrite(context, orig_shape), + rhs_tactics, + )?; + Some(lhs + &rhs) +} + +fn choose_rhs( + context: &RewriteContext<'_>, + expr: &R, + shape: Shape, + orig_rhs: Option, + rhs_tactics: RhsTactics, +) -> Option { + match orig_rhs { + Some(ref new_str) + if !new_str.contains('\n') && unicode_str_width(new_str) <= shape.width => + { + Some(format!(" {}", new_str)) + } + _ => { + // Expression did not fit on the same line as the identifier. + // Try splitting the line and see if that works better. + let new_shape = shape_from_rhs_tactic(context, shape, rhs_tactics)?; + let new_rhs = expr.rewrite(context, new_shape); + let new_indent_str = &shape + .indent + .block_indent(context.config) + .to_string_with_newline(context.config); + + match (orig_rhs, new_rhs) { + (Some(ref orig_rhs), Some(ref new_rhs)) + if wrap_str(new_rhs.clone(), context.config.max_width(), new_shape) + .is_none() => + { + Some(format!(" {}", orig_rhs)) + } + (Some(ref orig_rhs), Some(ref new_rhs)) + if prefer_next_line(orig_rhs, new_rhs, rhs_tactics) => + { + Some(format!("{}{}", new_indent_str, new_rhs)) + } + (None, Some(ref new_rhs)) => Some(format!("{}{}", new_indent_str, new_rhs)), + (None, None) if rhs_tactics == RhsTactics::AllowOverflow => { + let shape = shape.infinite_width(); + expr.rewrite(context, shape).map(|s| format!(" {}", s)) + } + (None, None) => None, + (Some(orig_rhs), _) => Some(format!(" {}", orig_rhs)), + } + } + } +} + +fn shape_from_rhs_tactic( + context: &RewriteContext<'_>, + shape: Shape, + rhs_tactic: RhsTactics, +) -> Option { + match rhs_tactic { + RhsTactics::ForceNextLineWithoutIndent => shape + .with_max_width(context.config) + .sub_width(shape.indent.width()), + RhsTactics::Default | RhsTactics::AllowOverflow => { + Shape::indented(shape.indent.block_indent(context.config), context.config) + .sub_width(shape.rhs_overhead(context.config)) + } + } +} + +/// Returns true if formatting next_line_rhs is better on a new line when compared to the +/// original's line formatting. +/// +/// It is considered better if: +/// 1. the tactic is ForceNextLineWithoutIndent +/// 2. next_line_rhs doesn't have newlines +/// 3. the original line has more newlines than next_line_rhs +/// 4. the original formatting of the first line ends with `(`, `{`, or `[` and next_line_rhs +/// doesn't +pub(crate) fn prefer_next_line( + orig_rhs: &str, + next_line_rhs: &str, + rhs_tactics: RhsTactics, +) -> bool { + rhs_tactics == RhsTactics::ForceNextLineWithoutIndent + || !next_line_rhs.contains('\n') + || count_newlines(orig_rhs) > count_newlines(next_line_rhs) + 1 + || first_line_ends_with(orig_rhs, '(') && !first_line_ends_with(next_line_rhs, '(') + || first_line_ends_with(orig_rhs, '{') && !first_line_ends_with(next_line_rhs, '{') + || first_line_ends_with(orig_rhs, '[') && !first_line_ends_with(next_line_rhs, '[') +} + +fn rewrite_expr_addrof( + context: &RewriteContext<'_>, + borrow_kind: ast::BorrowKind, + mutability: ast::Mutability, + expr: &ast::Expr, + shape: Shape, +) -> Option { + let operator_str = match (mutability, borrow_kind) { + (ast::Mutability::Not, ast::BorrowKind::Ref) => "&", + (ast::Mutability::Not, ast::BorrowKind::Raw) => "&raw const ", + (ast::Mutability::Mut, ast::BorrowKind::Ref) => "&mut ", + (ast::Mutability::Mut, ast::BorrowKind::Raw) => "&raw mut ", + }; + rewrite_unary_prefix(context, operator_str, expr, shape) +} + +pub(crate) fn is_method_call(expr: &ast::Expr) -> bool { + match expr.kind { + ast::ExprKind::MethodCall(..) => true, + ast::ExprKind::AddrOf(_, _, ref expr) + | ast::ExprKind::Box(ref expr) + | ast::ExprKind::Cast(ref expr, _) + | ast::ExprKind::Try(ref expr) + | ast::ExprKind::Unary(_, ref expr) => is_method_call(expr), + _ => false, + } +} + +#[cfg(test)] +mod test { + use super::last_line_offsetted; + + #[test] + fn test_last_line_offsetted() { + let lines = "one\n two"; + assert_eq!(last_line_offsetted(2, lines), true); + assert_eq!(last_line_offsetted(4, lines), false); + assert_eq!(last_line_offsetted(6, lines), false); + + let lines = "one two"; + assert_eq!(last_line_offsetted(2, lines), false); + assert_eq!(last_line_offsetted(0, lines), false); + + let lines = "\ntwo"; + assert_eq!(last_line_offsetted(2, lines), false); + assert_eq!(last_line_offsetted(0, lines), false); + + let lines = "one\n two three"; + assert_eq!(last_line_offsetted(2, lines), true); + let lines = "one\n two three"; + assert_eq!(last_line_offsetted(2, lines), false); + } +} diff --git a/src/tools/rustfmt/src/format-diff/main.rs b/src/tools/rustfmt/src/format-diff/main.rs new file mode 100644 index 0000000000..8a8bb9356c --- /dev/null +++ b/src/tools/rustfmt/src/format-diff/main.rs @@ -0,0 +1,281 @@ +// Inspired by Clang's clang-format-diff: +// +// https://github.com/llvm-mirror/clang/blob/master/tools/clang-format/clang-format-diff.py + +#![deny(warnings)] + +use env_logger; +#[macro_use] +extern crate log; +use regex; +use serde::{Deserialize, Serialize}; +use serde_json as json; +use thiserror::Error; + +use std::collections::HashSet; +use std::env; +use std::ffi::OsStr; +use std::io::{self, BufRead}; +use std::process; + +use regex::Regex; + +use structopt::clap::AppSettings; +use structopt::StructOpt; + +/// The default pattern of files to format. +/// +/// We only want to format rust files by default. +const DEFAULT_PATTERN: &str = r".*\.rs"; + +#[derive(Error, Debug)] +enum FormatDiffError { + #[error("{0}")] + IncorrectOptions(#[from] getopts::Fail), + #[error("{0}")] + IncorrectFilter(#[from] regex::Error), + #[error("{0}")] + IoError(#[from] io::Error), +} + +#[derive(StructOpt, Debug)] +#[structopt( + name = "rustfmt-format-diff", + setting = AppSettings::DisableVersion, + setting = AppSettings::NextLineHelp +)] +pub struct Opts { + /// Skip the smallest prefix containing NUMBER slashes + #[structopt( + short = "p", + long = "skip-prefix", + value_name = "NUMBER", + default_value = "0" + )] + skip_prefix: u32, + + /// Custom pattern selecting file paths to reformat + #[structopt( + short = "f", + long = "filter", + value_name = "PATTERN", + default_value = DEFAULT_PATTERN + )] + filter: String, +} + +fn main() { + env_logger::init(); + let opts = Opts::from_args(); + if let Err(e) = run(opts) { + println!("{}", e); + Opts::clap().print_help().expect("cannot write to stdout"); + process::exit(1); + } +} + +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +struct Range { + file: String, + range: [u32; 2], +} + +fn run(opts: Opts) -> Result<(), FormatDiffError> { + let (files, ranges) = scan_diff(io::stdin(), opts.skip_prefix, &opts.filter)?; + run_rustfmt(&files, &ranges) +} + +fn run_rustfmt(files: &HashSet, ranges: &[Range]) -> Result<(), FormatDiffError> { + if files.is_empty() || ranges.is_empty() { + debug!("No files to format found"); + return Ok(()); + } + + let ranges_as_json = json::to_string(ranges).unwrap(); + + debug!("Files: {:?}", files); + debug!("Ranges: {:?}", ranges); + + let rustfmt_var = env::var_os("RUSTFMT"); + let rustfmt = match &rustfmt_var { + Some(rustfmt) => rustfmt, + None => OsStr::new("rustfmt"), + }; + let exit_status = process::Command::new(rustfmt) + .args(files) + .arg("--file-lines") + .arg(ranges_as_json) + .status()?; + + if !exit_status.success() { + return Err(FormatDiffError::IoError(io::Error::new( + io::ErrorKind::Other, + format!("rustfmt failed with {}", exit_status), + ))); + } + Ok(()) +} + +/// Scans a diff from `from`, and returns the set of files found, and the ranges +/// in those files. +fn scan_diff( + from: R, + skip_prefix: u32, + file_filter: &str, +) -> Result<(HashSet, Vec), FormatDiffError> +where + R: io::Read, +{ + let diff_pattern = format!(r"^\+\+\+\s(?:.*?/){{{}}}(\S*)", skip_prefix); + let diff_pattern = Regex::new(&diff_pattern).unwrap(); + + let lines_pattern = Regex::new(r"^@@.*\+(\d+)(,(\d+))?").unwrap(); + + let file_filter = Regex::new(&format!("^{}$", file_filter))?; + + let mut current_file = None; + + let mut files = HashSet::new(); + let mut ranges = vec![]; + for line in io::BufReader::new(from).lines() { + let line = line.unwrap(); + + if let Some(captures) = diff_pattern.captures(&line) { + current_file = Some(captures.get(1).unwrap().as_str().to_owned()); + } + + let file = match current_file { + Some(ref f) => &**f, + None => continue, + }; + + // FIXME(emilio): We could avoid this most of the time if needed, but + // it's not clear it's worth it. + if !file_filter.is_match(file) { + continue; + } + + let lines_captures = match lines_pattern.captures(&line) { + Some(captures) => captures, + None => continue, + }; + + let start_line = lines_captures + .get(1) + .unwrap() + .as_str() + .parse::() + .unwrap(); + let line_count = match lines_captures.get(3) { + Some(line_count) => line_count.as_str().parse::().unwrap(), + None => 1, + }; + + if line_count == 0 { + continue; + } + + let end_line = start_line + line_count - 1; + files.insert(file.to_owned()); + ranges.push(Range { + file: file.to_owned(), + range: [start_line, end_line], + }); + } + + Ok((files, ranges)) +} + +#[test] +fn scan_simple_git_diff() { + const DIFF: &str = include_str!("test/bindgen.diff"); + let (files, ranges) = scan_diff(DIFF.as_bytes(), 1, r".*\.rs").expect("scan_diff failed?"); + + assert!( + files.contains("src/ir/traversal.rs"), + "Should've matched the filter" + ); + + assert!( + !files.contains("tests/headers/anon_enum.hpp"), + "Shouldn't have matched the filter" + ); + + assert_eq!( + &ranges, + &[ + Range { + file: "src/ir/item.rs".to_owned(), + range: [148, 158], + }, + Range { + file: "src/ir/item.rs".to_owned(), + range: [160, 170], + }, + Range { + file: "src/ir/traversal.rs".to_owned(), + range: [9, 16], + }, + Range { + file: "src/ir/traversal.rs".to_owned(), + range: [35, 43], + }, + ] + ); +} + +#[cfg(test)] +mod cmd_line_tests { + use super::*; + + #[test] + fn default_options() { + let empty: Vec = vec![]; + let o = Opts::from_iter(&empty); + assert_eq!(DEFAULT_PATTERN, o.filter); + assert_eq!(0, o.skip_prefix); + } + + #[test] + fn good_options() { + let o = Opts::from_iter(&["test", "-p", "10", "-f", r".*\.hs"]); + assert_eq!(r".*\.hs", o.filter); + assert_eq!(10, o.skip_prefix); + } + + #[test] + fn unexpected_option() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "unexpected"]) + .is_err() + ); + } + + #[test] + fn unexpected_flag() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "--flag"]) + .is_err() + ); + } + + #[test] + fn overridden_option() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "-p", "10", "-p", "20"]) + .is_err() + ); + } + + #[test] + fn negative_filter() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "-p", "-1"]) + .is_err() + ); + } +} diff --git a/src/tools/rustfmt/src/format-diff/test/bindgen.diff b/src/tools/rustfmt/src/format-diff/test/bindgen.diff new file mode 100644 index 0000000000..d2fd379f47 --- /dev/null +++ b/src/tools/rustfmt/src/format-diff/test/bindgen.diff @@ -0,0 +1,67 @@ +diff --git a/src/ir/item.rs b/src/ir/item.rs +index 7f3afefb..90d15e96 100644 +--- a/src/ir/item.rs ++++ b/src/ir/item.rs +@@ -148,7 +148,11 @@ impl<'a, 'b> Iterator for ItemAncestorsIter<'a, 'b> + impl AsTemplateParam for ItemId { + type Extra = (); + +- fn as_template_param(&self, ctx: &BindgenContext, _: &()) -> Option { ++ fn as_template_param( ++ &self, ++ ctx: &BindgenContext, ++ _: &(), ++ ) -> Option { + ctx.resolve_item(*self).as_template_param(ctx, &()) + } + } +@@ -156,7 +160,11 @@ impl AsTemplateParam for ItemId { + impl AsTemplateParam for Item { + type Extra = (); + +- fn as_template_param(&self, ctx: &BindgenContext, _: &()) -> Option { ++ fn as_template_param( ++ &self, ++ ctx: &BindgenContext, ++ _: &(), ++ ) -> Option { + self.kind.as_template_param(ctx, self) + } + } +diff --git a/src/ir/traversal.rs b/src/ir/traversal.rs +index 762a3e2d..b9c9dd4e 100644 +--- a/src/ir/traversal.rs ++++ b/src/ir/traversal.rs +@@ -9,6 +9,8 @@ use std::collections::{BTreeMap, VecDeque}; + /// + /// from --> to + /// ++/// Random content to generate a diff. ++/// + /// The `from` is left implicit: it is the concrete `Trace` implementer which + /// yielded this outgoing edge. + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +@@ -33,7 +35,9 @@ impl Into for Edge { + } + } + +-/// The kind of edge reference. This is useful when we wish to only consider ++/// The kind of edge reference. ++/// ++/// This is useful when we wish to only consider + /// certain kinds of edges for a particular traversal or analysis. + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum EdgeKind { +diff --git a/tests/headers/anon_enum.hpp b/tests/headers/anon_enum.hpp +index 1961fe6c..34759df3 100644 +--- a/tests/headers/anon_enum.hpp ++++ b/tests/headers/anon_enum.hpp +@@ -1,7 +1,7 @@ + struct Test { + int foo; + float bar; +- enum { T_NONE }; ++ enum { T_NONE, T_SOME }; + }; + + typedef enum { diff --git a/src/tools/rustfmt/src/format_report_formatter.rs b/src/tools/rustfmt/src/format_report_formatter.rs new file mode 100644 index 0000000000..2fc6c4e895 --- /dev/null +++ b/src/tools/rustfmt/src/format_report_formatter.rs @@ -0,0 +1,174 @@ +use crate::config::FileName; +use crate::formatting::FormattingError; +use crate::{ErrorKind, FormatReport}; +use annotate_snippets::display_list::DisplayList; +use annotate_snippets::formatter::DisplayListFormatter; +use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use std::fmt::{self, Display}; + +/// A builder for [`FormatReportFormatter`]. +pub struct FormatReportFormatterBuilder<'a> { + report: &'a FormatReport, + enable_colors: bool, +} + +impl<'a> FormatReportFormatterBuilder<'a> { + /// Creates a new [`FormatReportFormatterBuilder`]. + pub fn new(report: &'a FormatReport) -> Self { + Self { + report, + enable_colors: false, + } + } + + /// Enables colors and formatting in the output. + pub fn enable_colors(self, enable_colors: bool) -> Self { + Self { + enable_colors, + ..self + } + } + + /// Creates a new [`FormatReportFormatter`] from the settings in this builder. + pub fn build(self) -> FormatReportFormatter<'a> { + FormatReportFormatter { + report: self.report, + enable_colors: self.enable_colors, + } + } +} + +/// Formats the warnings/errors in a [`FormatReport`]. +/// +/// Can be created using a [`FormatReportFormatterBuilder`]. +pub struct FormatReportFormatter<'a> { + report: &'a FormatReport, + enable_colors: bool, +} + +impl<'a> Display for FormatReportFormatter<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let formatter = DisplayListFormatter::new(self.enable_colors, false); + let errors_by_file = &self.report.internal.borrow().0; + + for (file, errors) in errors_by_file { + for error in errors { + let snippet = formatting_error_to_snippet(file, error); + writeln!(f, "{}\n", formatter.format(&DisplayList::from(snippet)))?; + } + } + + if !errors_by_file.is_empty() { + let snippet = formatting_failure_snippet(self.report.warning_count()); + writeln!(f, "{}", formatter.format(&DisplayList::from(snippet)))?; + } + + Ok(()) + } +} + +fn formatting_failure_snippet(warning_count: usize) -> Snippet { + Snippet { + title: Some(Annotation { + id: None, + label: Some(format!( + "rustfmt has failed to format. See previous {} errors.", + warning_count + )), + annotation_type: AnnotationType::Warning, + }), + footer: Vec::new(), + slices: Vec::new(), + } +} + +fn formatting_error_to_snippet(file: &FileName, error: &FormattingError) -> Snippet { + let slices = vec![snippet_code_slice(file, error)]; + let title = Some(snippet_title(error)); + let footer = snippet_footer(error).into_iter().collect(); + + Snippet { + title, + footer, + slices, + } +} + +fn snippet_title(error: &FormattingError) -> Annotation { + let annotation_type = error_kind_to_snippet_annotation_type(&error.kind); + + Annotation { + id: title_annotation_id(error), + label: Some(error.kind.to_string()), + annotation_type, + } +} + +fn snippet_footer(error: &FormattingError) -> Option { + let message_suffix = error.msg_suffix(); + + if !message_suffix.is_empty() { + Some(Annotation { + id: None, + label: Some(message_suffix.to_string()), + annotation_type: AnnotationType::Note, + }) + } else { + None + } +} + +fn snippet_code_slice(file: &FileName, error: &FormattingError) -> Slice { + let annotations = slice_annotation(error).into_iter().collect(); + let origin = Some(format!("{}:{}", file, error.line)); + let source = error.line_buffer.clone(); + + Slice { + source, + line_start: error.line, + origin, + fold: false, + annotations, + } +} + +fn slice_annotation(error: &FormattingError) -> Option { + let (range_start, range_length) = error.format_len(); + let range_end = range_start + range_length; + + if range_length > 0 { + Some(SourceAnnotation { + annotation_type: AnnotationType::Error, + range: (range_start, range_end), + label: String::new(), + }) + } else { + None + } +} + +fn title_annotation_id(error: &FormattingError) -> Option { + const INTERNAL_ERROR_ID: &str = "internal"; + + if error.is_internal() { + Some(INTERNAL_ERROR_ID.to_string()) + } else { + None + } +} + +fn error_kind_to_snippet_annotation_type(error_kind: &ErrorKind) -> AnnotationType { + match error_kind { + ErrorKind::LineOverflow(..) + | ErrorKind::TrailingWhitespace + | ErrorKind::IoError(_) + | ErrorKind::ModuleResolutionError(_) + | ErrorKind::ParseError + | ErrorKind::LostComment + | ErrorKind::LicenseCheck + | ErrorKind::BadAttr + | ErrorKind::InvalidGlobPattern(_) + | ErrorKind::VersionMismatch => AnnotationType::Error, + ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => AnnotationType::Warning, + } +} diff --git a/src/tools/rustfmt/src/formatting.rs b/src/tools/rustfmt/src/formatting.rs new file mode 100644 index 0000000000..289e58cf69 --- /dev/null +++ b/src/tools/rustfmt/src/formatting.rs @@ -0,0 +1,606 @@ +// High level formatting functions. + +use std::collections::HashMap; +use std::io::{self, Write}; +use std::time::{Duration, Instant}; + +use rustc_ast::ast; +use rustc_span::Span; + +use self::newline_style::apply_newline_style; +use crate::comment::{CharClasses, FullCodeCharKind}; +use crate::config::{Config, FileName, Verbosity}; +use crate::issues::BadIssueSeeker; +use crate::modules::Module; +use crate::syntux::parser::{DirectoryOwnership, Parser, ParserError}; +use crate::syntux::session::ParseSess; +use crate::utils::count_newlines; +use crate::visitor::FmtVisitor; +use crate::{modules, source_file, ErrorKind, FormatReport, Input, Session}; + +mod newline_style; + +// A map of the files of a crate, with their new content +pub(crate) type SourceFile = Vec; +pub(crate) type FileRecord = (FileName, String); + +impl<'b, T: Write + 'b> Session<'b, T> { + pub(crate) fn format_input_inner( + &mut self, + input: Input, + is_macro_def: bool, + ) -> Result { + if !self.config.version_meets_requirement() { + return Err(ErrorKind::VersionMismatch); + } + + rustc_span::with_session_globals(self.config.edition().into(), || { + if self.config.disable_all_formatting() { + // When the input is from stdin, echo back the input. + if let Input::Text(ref buf) = input { + if let Err(e) = io::stdout().write_all(buf.as_bytes()) { + return Err(From::from(e)); + } + } + return Ok(FormatReport::new()); + } + + let config = &self.config.clone(); + let format_result = format_project(input, config, self, is_macro_def); + + format_result.map(|report| { + self.errors.add(&report.internal.borrow().1); + report + }) + }) + } +} + +// Format an entire crate (or subset of the module tree). +fn format_project( + input: Input, + config: &Config, + handler: &mut T, + is_macro_def: bool, +) -> Result { + let mut timer = Timer::start(); + + let main_file = input.file_name(); + let input_is_stdin = main_file == FileName::Stdin; + + let parse_session = ParseSess::new(config)?; + if config.skip_children() && parse_session.ignore_file(&main_file) { + return Ok(FormatReport::new()); + } + + // Parse the crate. + let mut report = FormatReport::new(); + let directory_ownership = input.to_directory_ownership(); + let krate = match Parser::parse_crate(config, input, directory_ownership, &parse_session) { + Ok(krate) => krate, + // Surface parse error via Session (errors are merged there from report) + Err(e) => { + let forbid_verbose = input_is_stdin || e != ParserError::ParsePanicError; + should_emit_verbose(forbid_verbose, config, || { + eprintln!("The Rust parser panicked"); + }); + report.add_parsing_error(); + return Ok(report); + } + }; + + let mut context = FormatContext::new(&krate, report, parse_session, config, handler); + let files = modules::ModResolver::new( + &context.parse_session, + directory_ownership.unwrap_or(DirectoryOwnership::UnownedViaMod), + !input_is_stdin && !config.skip_children(), + ) + .visit_crate(&krate)?; + + timer = timer.done_parsing(); + + // Suppress error output if we have to do any further parsing. + context.parse_session.set_silent_emitter(); + + for (path, module) in files { + let should_ignore = !input_is_stdin && context.ignore_file(&path); + if (config.skip_children() && path != main_file) || should_ignore { + continue; + } + should_emit_verbose(input_is_stdin, config, || println!("Formatting {}", path)); + context.format_file(path, &module, is_macro_def)?; + } + timer = timer.done_formatting(); + + should_emit_verbose(input_is_stdin, config, || { + println!( + "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase", + timer.get_parse_time(), + timer.get_format_time(), + ) + }); + + Ok(context.report) +} + +// Used for formatting files. +#[derive(new)] +struct FormatContext<'a, T: FormatHandler> { + krate: &'a ast::Crate, + report: FormatReport, + parse_session: ParseSess, + config: &'a Config, + handler: &'a mut T, +} + +impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> { + fn ignore_file(&self, path: &FileName) -> bool { + self.parse_session.ignore_file(path) + } + + // Formats a single file/module. + fn format_file( + &mut self, + path: FileName, + module: &Module<'_>, + is_macro_def: bool, + ) -> Result<(), ErrorKind> { + let snippet_provider = self.parse_session.snippet_provider(module.as_ref().inner); + let mut visitor = FmtVisitor::from_parse_sess( + &self.parse_session, + &self.config, + &snippet_provider, + self.report.clone(), + ); + visitor.skip_context.update_with_attrs(&self.krate.attrs); + visitor.is_macro_def = is_macro_def; + visitor.last_pos = snippet_provider.start_pos(); + visitor.skip_empty_lines(snippet_provider.end_pos()); + visitor.format_separate_mod(module, snippet_provider.end_pos()); + + debug_assert_eq!( + visitor.line_number, + count_newlines(&visitor.buffer), + "failed in format_file visitor.buffer:\n {:?}", + &visitor.buffer + ); + + // For some reason, the source_map does not include terminating + // newlines so we must add one on for each file. This is sad. + source_file::append_newline(&mut visitor.buffer); + + format_lines( + &mut visitor.buffer, + &path, + &visitor.skipped_range.borrow(), + &self.config, + &self.report, + ); + + apply_newline_style( + self.config.newline_style(), + &mut visitor.buffer, + snippet_provider.entire_snippet(), + ); + + if visitor.macro_rewrite_failure { + self.report.add_macro_format_failure(); + } + self.report + .add_non_formatted_ranges(visitor.skipped_range.borrow().clone()); + + self.handler.handle_formatted_file( + &self.parse_session, + path, + visitor.buffer.to_owned(), + &mut self.report, + ) + } +} + +// Handle the results of formatting. +trait FormatHandler { + fn handle_formatted_file( + &mut self, + parse_session: &ParseSess, + path: FileName, + result: String, + report: &mut FormatReport, + ) -> Result<(), ErrorKind>; +} + +impl<'b, T: Write + 'b> FormatHandler for Session<'b, T> { + // Called for each formatted file. + fn handle_formatted_file( + &mut self, + parse_session: &ParseSess, + path: FileName, + result: String, + report: &mut FormatReport, + ) -> Result<(), ErrorKind> { + if let Some(ref mut out) = self.out { + match source_file::write_file( + Some(parse_session), + &path, + &result, + out, + &mut *self.emitter, + self.config.newline_style(), + ) { + Ok(ref result) if result.has_diff => report.add_diff(), + Err(e) => { + // Create a new error with path_str to help users see which files failed + let err_msg = format!("{}: {}", path, e); + return Err(io::Error::new(e.kind(), err_msg).into()); + } + _ => {} + } + } + + self.source_file.push((path, result)); + Ok(()) + } +} + +pub(crate) struct FormattingError { + pub(crate) line: usize, + pub(crate) kind: ErrorKind, + is_comment: bool, + is_string: bool, + pub(crate) line_buffer: String, +} + +impl FormattingError { + pub(crate) fn from_span( + span: Span, + parse_sess: &ParseSess, + kind: ErrorKind, + ) -> FormattingError { + FormattingError { + line: parse_sess.line_of_byte_pos(span.lo()), + is_comment: kind.is_comment(), + kind, + is_string: false, + line_buffer: parse_sess.span_to_first_line_string(span), + } + } + + pub(crate) fn is_internal(&self) -> bool { + match self.kind { + ErrorKind::LineOverflow(..) + | ErrorKind::TrailingWhitespace + | ErrorKind::IoError(_) + | ErrorKind::ParseError + | ErrorKind::LostComment => true, + _ => false, + } + } + + pub(crate) fn msg_suffix(&self) -> &str { + if self.is_comment || self.is_string { + "set `error_on_unformatted = false` to suppress \ + the warning against comments or string literals\n" + } else { + "" + } + } + + // (space, target) + pub(crate) fn format_len(&self) -> (usize, usize) { + match self.kind { + ErrorKind::LineOverflow(found, max) => (max, found - max), + ErrorKind::TrailingWhitespace + | ErrorKind::DeprecatedAttr + | ErrorKind::BadIssue(_) + | ErrorKind::BadAttr + | ErrorKind::LostComment + | ErrorKind::LicenseCheck => { + let trailing_ws_start = self + .line_buffer + .rfind(|c: char| !c.is_whitespace()) + .map(|pos| pos + 1) + .unwrap_or(0); + ( + trailing_ws_start, + self.line_buffer.len() - trailing_ws_start, + ) + } + _ => unreachable!(), + } + } +} + +pub(crate) type FormatErrorMap = HashMap>; + +#[derive(Default, Debug, PartialEq)] +pub(crate) struct ReportedErrors { + // Encountered e.g., an IO error. + pub(crate) has_operational_errors: bool, + + // Failed to reformat code because of parsing errors. + pub(crate) has_parsing_errors: bool, + + // Code is valid, but it is impossible to format it properly. + pub(crate) has_formatting_errors: bool, + + // Code contains macro call that was unable to format. + pub(crate) has_macro_format_failure: bool, + + // Failed a check, such as the license check or other opt-in checking. + pub(crate) has_check_errors: bool, + + /// Formatted code differs from existing code (--check only). + pub(crate) has_diff: bool, +} + +impl ReportedErrors { + /// Combine two summaries together. + pub(crate) fn add(&mut self, other: &ReportedErrors) { + self.has_operational_errors |= other.has_operational_errors; + self.has_parsing_errors |= other.has_parsing_errors; + self.has_formatting_errors |= other.has_formatting_errors; + self.has_macro_format_failure |= other.has_macro_format_failure; + self.has_check_errors |= other.has_check_errors; + self.has_diff |= other.has_diff; + } +} + +#[derive(Clone, Copy, Debug)] +enum Timer { + Disabled, + Initialized(Instant), + DoneParsing(Instant, Instant), + DoneFormatting(Instant, Instant, Instant), +} + +impl Timer { + fn start() -> Timer { + if cfg!(target_arch = "wasm32") { + Timer::Disabled + } else { + Timer::Initialized(Instant::now()) + } + } + fn done_parsing(self) -> Self { + match self { + Timer::Disabled => Timer::Disabled, + Timer::Initialized(init_time) => Timer::DoneParsing(init_time, Instant::now()), + _ => panic!("Timer can only transition to DoneParsing from Initialized state"), + } + } + + fn done_formatting(self) -> Self { + match self { + Timer::Disabled => Timer::Disabled, + Timer::DoneParsing(init_time, parse_time) => { + Timer::DoneFormatting(init_time, parse_time, Instant::now()) + } + _ => panic!("Timer can only transition to DoneFormatting from DoneParsing state"), + } + } + + /// Returns the time it took to parse the source files in seconds. + fn get_parse_time(&self) -> f32 { + match *self { + Timer::Disabled => panic!("this platform cannot time execution"), + Timer::DoneParsing(init, parse_time) | Timer::DoneFormatting(init, parse_time, _) => { + // This should never underflow since `Instant::now()` guarantees monotonicity. + Self::duration_to_f32(parse_time.duration_since(init)) + } + Timer::Initialized(..) => unreachable!(), + } + } + + /// Returns the time it took to go from the parsed AST to the formatted output. Parsing time is + /// not included. + fn get_format_time(&self) -> f32 { + match *self { + Timer::Disabled => panic!("this platform cannot time execution"), + Timer::DoneFormatting(_init, parse_time, format_time) => { + Self::duration_to_f32(format_time.duration_since(parse_time)) + } + Timer::DoneParsing(..) | Timer::Initialized(..) => unreachable!(), + } + } + + fn duration_to_f32(d: Duration) -> f32 { + d.as_secs() as f32 + d.subsec_nanos() as f32 / 1_000_000_000f32 + } +} + +// Formatting done on a char by char or line by line basis. +// FIXME(#20): other stuff for parity with make tidy. +fn format_lines( + text: &mut String, + name: &FileName, + skipped_range: &[(usize, usize)], + config: &Config, + report: &FormatReport, +) { + let mut formatter = FormatLines::new(name, skipped_range, config); + formatter.check_license(text); + formatter.iterate(text); + + if formatter.newline_count > 1 { + debug!("track truncate: {} {}", text.len(), formatter.newline_count); + let line = text.len() - formatter.newline_count + 1; + text.truncate(line); + } + + report.append(name.clone(), formatter.errors); +} + +struct FormatLines<'a> { + name: &'a FileName, + skipped_range: &'a [(usize, usize)], + last_was_space: bool, + line_len: usize, + cur_line: usize, + newline_count: usize, + errors: Vec, + issue_seeker: BadIssueSeeker, + line_buffer: String, + current_line_contains_string_literal: bool, + format_line: bool, + allow_issue_seek: bool, + config: &'a Config, +} + +impl<'a> FormatLines<'a> { + fn new( + name: &'a FileName, + skipped_range: &'a [(usize, usize)], + config: &'a Config, + ) -> FormatLines<'a> { + let issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme()); + FormatLines { + name, + skipped_range, + last_was_space: false, + line_len: 0, + cur_line: 1, + newline_count: 0, + errors: vec![], + allow_issue_seek: !issue_seeker.is_disabled(), + issue_seeker, + line_buffer: String::with_capacity(config.max_width() * 2), + current_line_contains_string_literal: false, + format_line: config.file_lines().contains_line(name, 1), + config, + } + } + + fn check_license(&mut self, text: &mut String) { + if let Some(ref license_template) = self.config.license_template { + if !license_template.is_match(text) { + self.errors.push(FormattingError { + line: self.cur_line, + kind: ErrorKind::LicenseCheck, + is_comment: false, + is_string: false, + line_buffer: String::new(), + }); + } + } + } + + // Iterate over the chars in the file map. + fn iterate(&mut self, text: &mut String) { + for (kind, c) in CharClasses::new(text.chars()) { + if c == '\r' { + continue; + } + + if self.allow_issue_seek && self.format_line { + // Add warnings for bad todos/ fixmes + if let Some(issue) = self.issue_seeker.inspect(c) { + self.push_err(ErrorKind::BadIssue(issue), false, false); + } + } + + if c == '\n' { + self.new_line(kind); + } else { + self.char(c, kind); + } + } + } + + fn new_line(&mut self, kind: FullCodeCharKind) { + if self.format_line { + // Check for (and record) trailing whitespace. + if self.last_was_space { + if self.should_report_error(kind, &ErrorKind::TrailingWhitespace) + && !self.is_skipped_line() + { + self.push_err( + ErrorKind::TrailingWhitespace, + kind.is_comment(), + kind.is_string(), + ); + } + self.line_len -= 1; + } + + // Check for any line width errors we couldn't correct. + let error_kind = ErrorKind::LineOverflow(self.line_len, self.config.max_width()); + if self.line_len > self.config.max_width() + && !self.is_skipped_line() + && self.should_report_error(kind, &error_kind) + { + let is_string = self.current_line_contains_string_literal; + self.push_err(error_kind, kind.is_comment(), is_string); + } + } + + self.line_len = 0; + self.cur_line += 1; + self.format_line = self + .config + .file_lines() + .contains_line(self.name, self.cur_line); + self.newline_count += 1; + self.last_was_space = false; + self.line_buffer.clear(); + self.current_line_contains_string_literal = false; + } + + fn char(&mut self, c: char, kind: FullCodeCharKind) { + self.newline_count = 0; + self.line_len += if c == '\t' { + self.config.tab_spaces() + } else { + 1 + }; + self.last_was_space = c.is_whitespace(); + self.line_buffer.push(c); + if kind.is_string() { + self.current_line_contains_string_literal = true; + } + } + + fn push_err(&mut self, kind: ErrorKind, is_comment: bool, is_string: bool) { + self.errors.push(FormattingError { + line: self.cur_line, + kind, + is_comment, + is_string, + line_buffer: self.line_buffer.clone(), + }); + } + + fn should_report_error(&self, char_kind: FullCodeCharKind, error_kind: &ErrorKind) -> bool { + let allow_error_report = if char_kind.is_comment() + || self.current_line_contains_string_literal + || error_kind.is_comment() + { + self.config.error_on_unformatted() + } else { + true + }; + + match error_kind { + ErrorKind::LineOverflow(..) => { + self.config.error_on_line_overflow() && allow_error_report + } + ErrorKind::TrailingWhitespace | ErrorKind::LostComment => allow_error_report, + _ => true, + } + } + + /// Returns `true` if the line with the given line number was skipped by `#[rustfmt::skip]`. + fn is_skipped_line(&self) -> bool { + self.skipped_range + .iter() + .any(|&(lo, hi)| lo <= self.cur_line && self.cur_line <= hi) + } +} + +fn should_emit_verbose(forbid_verbose_output: bool, config: &Config, f: F) +where + F: Fn(), +{ + if config.verbose() == Verbosity::Verbose && !forbid_verbose_output { + f(); + } +} diff --git a/src/tools/rustfmt/src/formatting/newline_style.rs b/src/tools/rustfmt/src/formatting/newline_style.rs new file mode 100644 index 0000000000..ac62009490 --- /dev/null +++ b/src/tools/rustfmt/src/formatting/newline_style.rs @@ -0,0 +1,250 @@ +use crate::NewlineStyle; + +/// Apply this newline style to the formatted text. When the style is set +/// to `Auto`, the `raw_input_text` is used to detect the existing line +/// endings. +/// +/// If the style is set to `Auto` and `raw_input_text` contains no +/// newlines, the `Native` style will be used. +pub(crate) fn apply_newline_style( + newline_style: NewlineStyle, + formatted_text: &mut String, + raw_input_text: &str, +) { + *formatted_text = match effective_newline_style(newline_style, raw_input_text) { + EffectiveNewlineStyle::Windows => convert_to_windows_newlines(formatted_text), + EffectiveNewlineStyle::Unix => convert_to_unix_newlines(formatted_text), + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum EffectiveNewlineStyle { + Windows, + Unix, +} + +fn effective_newline_style( + newline_style: NewlineStyle, + raw_input_text: &str, +) -> EffectiveNewlineStyle { + match newline_style { + NewlineStyle::Auto => auto_detect_newline_style(raw_input_text), + NewlineStyle::Native => native_newline_style(), + NewlineStyle::Windows => EffectiveNewlineStyle::Windows, + NewlineStyle::Unix => EffectiveNewlineStyle::Unix, + } +} + +const LINE_FEED: char = '\n'; +const CARRIAGE_RETURN: char = '\r'; +const WINDOWS_NEWLINE: &str = "\r\n"; +const UNIX_NEWLINE: &str = "\n"; + +fn auto_detect_newline_style(raw_input_text: &str) -> EffectiveNewlineStyle { + let first_line_feed_pos = raw_input_text.chars().position(|ch| ch == LINE_FEED); + match first_line_feed_pos { + Some(first_line_feed_pos) => { + let char_before_line_feed_pos = first_line_feed_pos.saturating_sub(1); + let char_before_line_feed = raw_input_text.chars().nth(char_before_line_feed_pos); + match char_before_line_feed { + Some(CARRIAGE_RETURN) => EffectiveNewlineStyle::Windows, + _ => EffectiveNewlineStyle::Unix, + } + } + None => native_newline_style(), + } +} + +fn native_newline_style() -> EffectiveNewlineStyle { + if cfg!(windows) { + EffectiveNewlineStyle::Windows + } else { + EffectiveNewlineStyle::Unix + } +} + +fn convert_to_windows_newlines(formatted_text: &String) -> String { + let mut transformed = String::with_capacity(2 * formatted_text.capacity()); + let mut chars = formatted_text.chars().peekable(); + while let Some(current_char) = chars.next() { + let next_char = chars.peek(); + match current_char { + LINE_FEED => transformed.push_str(WINDOWS_NEWLINE), + CARRIAGE_RETURN if next_char == Some(&LINE_FEED) => {} + current_char => transformed.push(current_char), + } + } + transformed +} + +fn convert_to_unix_newlines(formatted_text: &String) -> String { + formatted_text.replace(WINDOWS_NEWLINE, UNIX_NEWLINE) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn auto_detects_unix_newlines() { + assert_eq!( + EffectiveNewlineStyle::Unix, + auto_detect_newline_style("One\nTwo\nThree") + ); + } + + #[test] + fn auto_detects_windows_newlines() { + assert_eq!( + EffectiveNewlineStyle::Windows, + auto_detect_newline_style("One\r\nTwo\r\nThree") + ); + } + + #[test] + fn auto_detects_windows_newlines_with_multibyte_char_on_first_line() { + assert_eq!( + EffectiveNewlineStyle::Windows, + auto_detect_newline_style("A 🎢 of a first line\r\nTwo\r\nThree") + ); + } + + #[test] + fn falls_back_to_native_newlines_if_no_newlines_are_found() { + let expected_newline_style = if cfg!(windows) { + EffectiveNewlineStyle::Windows + } else { + EffectiveNewlineStyle::Unix + }; + assert_eq!( + expected_newline_style, + auto_detect_newline_style("One Two Three") + ); + } + + #[test] + fn auto_detects_and_applies_unix_newlines() { + let formatted_text = "One\nTwo\nThree"; + let raw_input_text = "One\nTwo\nThree"; + + let mut out = String::from(formatted_text); + apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text); + assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'"); + } + + #[test] + fn auto_detects_and_applies_windows_newlines() { + let formatted_text = "One\nTwo\nThree"; + let raw_input_text = "One\r\nTwo\r\nThree"; + + let mut out = String::from(formatted_text); + apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text); + assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'"); + } + + #[test] + fn auto_detects_and_applies_native_newlines() { + let formatted_text = "One\nTwo\nThree"; + let raw_input_text = "One Two Three"; + + let mut out = String::from(formatted_text); + apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text); + + if cfg!(windows) { + assert_eq!( + "One\r\nTwo\r\nThree", &out, + "auto-native-windows should detect 'crlf'" + ); + } else { + assert_eq!( + "One\nTwo\nThree", &out, + "auto-native-unix should detect 'lf'" + ); + } + } + + #[test] + fn applies_unix_newlines() { + test_newlines_are_applied_correctly( + "One\r\nTwo\nThree", + "One\nTwo\nThree", + NewlineStyle::Unix, + ); + } + + #[test] + fn applying_unix_newlines_changes_nothing_for_unix_newlines() { + let formatted_text = "One\nTwo\nThree"; + test_newlines_are_applied_correctly(formatted_text, formatted_text, NewlineStyle::Unix); + } + + #[test] + fn applies_unix_newlines_to_string_with_unix_and_windows_newlines() { + test_newlines_are_applied_correctly( + "One\r\nTwo\r\nThree\nFour", + "One\nTwo\nThree\nFour", + NewlineStyle::Unix, + ); + } + + #[test] + fn applies_windows_newlines_to_string_with_unix_and_windows_newlines() { + test_newlines_are_applied_correctly( + "One\nTwo\nThree\r\nFour", + "One\r\nTwo\r\nThree\r\nFour", + NewlineStyle::Windows, + ); + } + + #[test] + fn applying_windows_newlines_changes_nothing_for_windows_newlines() { + let formatted_text = "One\r\nTwo\r\nThree"; + test_newlines_are_applied_correctly(formatted_text, formatted_text, NewlineStyle::Windows); + } + + #[test] + fn keeps_carriage_returns_when_applying_windows_newlines_to_str_with_unix_newlines() { + test_newlines_are_applied_correctly( + "One\nTwo\nThree\rDrei", + "One\r\nTwo\r\nThree\rDrei", + NewlineStyle::Windows, + ); + } + + #[test] + fn keeps_carriage_returns_when_applying_unix_newlines_to_str_with_unix_newlines() { + test_newlines_are_applied_correctly( + "One\nTwo\nThree\rDrei", + "One\nTwo\nThree\rDrei", + NewlineStyle::Unix, + ); + } + + #[test] + fn keeps_carriage_returns_when_applying_windows_newlines_to_str_with_windows_newlines() { + test_newlines_are_applied_correctly( + "One\r\nTwo\r\nThree\rDrei", + "One\r\nTwo\r\nThree\rDrei", + NewlineStyle::Windows, + ); + } + + #[test] + fn keeps_carriage_returns_when_applying_unix_newlines_to_str_with_windows_newlines() { + test_newlines_are_applied_correctly( + "One\r\nTwo\r\nThree\rDrei", + "One\nTwo\nThree\rDrei", + NewlineStyle::Unix, + ); + } + + fn test_newlines_are_applied_correctly( + input: &str, + expected: &str, + newline_style: NewlineStyle, + ) { + let mut out = String::from(input); + apply_newline_style(newline_style, &mut out, input); + assert_eq!(expected, &out); + } +} diff --git a/src/tools/rustfmt/src/git-rustfmt/main.rs b/src/tools/rustfmt/src/git-rustfmt/main.rs new file mode 100644 index 0000000000..c90eae3e64 --- /dev/null +++ b/src/tools/rustfmt/src/git-rustfmt/main.rs @@ -0,0 +1,196 @@ +#[macro_use] +extern crate log; + +use std::env; +use std::io::stdout; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::str::FromStr; + +use env_logger; +use getopts::{Matches, Options}; +use rustfmt_nightly as rustfmt; + +use crate::rustfmt::{load_config, CliOptions, FormatReportFormatterBuilder, Input, Session}; + +fn prune_files(files: Vec<&str>) -> Vec<&str> { + let prefixes: Vec<_> = files + .iter() + .filter(|f| f.ends_with("mod.rs") || f.ends_with("lib.rs")) + .map(|f| &f[..f.len() - 6]) + .collect(); + + let mut pruned_prefixes = vec![]; + for p1 in prefixes { + if p1.starts_with("src/bin/") || pruned_prefixes.iter().all(|p2| !p1.starts_with(p2)) { + pruned_prefixes.push(p1); + } + } + debug!("prefixes: {:?}", pruned_prefixes); + + files + .into_iter() + .filter(|f| { + if f.ends_with("mod.rs") || f.ends_with("lib.rs") || f.starts_with("src/bin/") { + return true; + } + pruned_prefixes.iter().all(|pp| !f.starts_with(pp)) + }) + .collect() +} + +fn git_diff(commits: &str) -> String { + let mut cmd = Command::new("git"); + cmd.arg("diff"); + if commits != "0" { + cmd.arg(format!("HEAD~{}", commits)); + } + let output = cmd.output().expect("Couldn't execute `git diff`"); + String::from_utf8_lossy(&output.stdout).into_owned() +} + +fn get_files(input: &str) -> Vec<&str> { + input + .lines() + .filter(|line| line.starts_with("+++ b/") && line.ends_with(".rs")) + .map(|line| &line[6..]) + .collect() +} + +fn fmt_files(files: &[&str]) -> i32 { + let (config, _) = + load_config::(Some(Path::new(".")), None).expect("couldn't load config"); + + let mut exit_code = 0; + let mut out = stdout(); + let mut session = Session::new(config, Some(&mut out)); + for file in files { + let report = session.format(Input::File(PathBuf::from(file))).unwrap(); + if report.has_warnings() { + eprintln!("{}", FormatReportFormatterBuilder::new(&report).build()); + } + if !session.has_no_errors() { + exit_code = 1; + } + } + exit_code +} + +struct NullOptions; + +impl CliOptions for NullOptions { + fn apply_to(self, _: &mut rustfmt::Config) { + unreachable!(); + } + fn config_path(&self) -> Option<&Path> { + unreachable!(); + } +} + +fn uncommitted_files() -> Vec { + let mut cmd = Command::new("git"); + cmd.arg("ls-files"); + cmd.arg("--others"); + cmd.arg("--modified"); + cmd.arg("--exclude-standard"); + let output = cmd.output().expect("Couldn't execute Git"); + let stdout = String::from_utf8_lossy(&output.stdout); + stdout + .lines() + .filter(|s| s.ends_with(".rs")) + .map(std::borrow::ToOwned::to_owned) + .collect() +} + +fn check_uncommitted() { + let uncommitted = uncommitted_files(); + debug!("uncommitted files: {:?}", uncommitted); + if !uncommitted.is_empty() { + println!("Found untracked changes:"); + for f in &uncommitted { + println!(" {}", f); + } + println!("Commit your work, or run with `-u`."); + println!("Exiting."); + std::process::exit(1); + } +} + +fn make_opts() -> Options { + let mut opts = Options::new(); + opts.optflag("h", "help", "show this message"); + opts.optflag("c", "check", "check only, don't format (unimplemented)"); + opts.optflag("u", "uncommitted", "format uncommitted files"); + opts +} + +struct Config { + commits: String, + uncommitted: bool, + check: bool, +} + +impl Config { + fn from_args(matches: &Matches, opts: &Options) -> Config { + // `--help` display help message and quit + if matches.opt_present("h") { + let message = format!( + "\nusage: {} [options]\n\n\ + commits: number of commits to format, default: 1", + env::args_os().next().unwrap().to_string_lossy() + ); + println!("{}", opts.usage(&message)); + std::process::exit(0); + } + + let mut config = Config { + commits: "1".to_owned(), + uncommitted: false, + check: false, + }; + + if matches.opt_present("c") { + config.check = true; + unimplemented!(); + } + + if matches.opt_present("u") { + config.uncommitted = true; + } + + if matches.free.len() > 1 { + panic!("unknown arguments, use `-h` for usage"); + } + if matches.free.len() == 1 { + let commits = matches.free[0].trim(); + if u32::from_str(commits).is_err() { + panic!("Couldn't parse number of commits"); + } + config.commits = commits.to_owned(); + } + + config + } +} + +fn main() { + env_logger::init(); + + let opts = make_opts(); + let matches = opts + .parse(env::args().skip(1)) + .expect("Couldn't parse command line"); + let config = Config::from_args(&matches, &opts); + + if !config.uncommitted { + check_uncommitted(); + } + + let stdout = git_diff(&config.commits); + let files = get_files(&stdout); + debug!("files: {:?}", files); + let files = prune_files(files); + debug!("pruned files: {:?}", files); + let exit_code = fmt_files(&files); + std::process::exit(exit_code); +} diff --git a/src/tools/rustfmt/src/ignore_path.rs b/src/tools/rustfmt/src/ignore_path.rs new file mode 100644 index 0000000000..d8974e12b8 --- /dev/null +++ b/src/tools/rustfmt/src/ignore_path.rs @@ -0,0 +1,57 @@ +use ignore::{self, gitignore}; + +use crate::config::{FileName, IgnoreList}; + +pub(crate) struct IgnorePathSet { + ignore_set: gitignore::Gitignore, +} + +impl IgnorePathSet { + pub(crate) fn from_ignore_list(ignore_list: &IgnoreList) -> Result { + let mut ignore_builder = gitignore::GitignoreBuilder::new(ignore_list.rustfmt_toml_path()); + + for ignore_path in ignore_list { + ignore_builder.add_line(None, ignore_path.to_str().unwrap())?; + } + + Ok(IgnorePathSet { + ignore_set: ignore_builder.build()?, + }) + } + + pub(crate) fn is_match(&self, file_name: &FileName) -> bool { + match file_name { + FileName::Stdin => false, + FileName::Real(p) => self + .ignore_set + .matched_path_or_any_parents(p, false) + .is_ignore(), + } + } +} + +#[cfg(test)] +mod test { + use std::path::{Path, PathBuf}; + + use crate::config::{Config, FileName}; + use crate::ignore_path::IgnorePathSet; + + #[test] + fn test_ignore_path_set() { + match option_env!("CFG_RELEASE_CHANNEL") { + // this test requires nightly + None | Some("nightly") => { + let config = + Config::from_toml(r#"ignore = ["foo.rs", "bar_dir/*"]"#, Path::new("")) + .unwrap(); + let ignore_path_set = IgnorePathSet::from_ignore_list(&config.ignore()).unwrap(); + + assert!(ignore_path_set.is_match(&FileName::Real(PathBuf::from("src/foo.rs")))); + assert!(ignore_path_set.is_match(&FileName::Real(PathBuf::from("bar_dir/baz.rs")))); + assert!(!ignore_path_set.is_match(&FileName::Real(PathBuf::from("src/bar.rs")))); + } + _ => (), + }; + } +} diff --git a/src/tools/rustfmt/src/imports.rs b/src/tools/rustfmt/src/imports.rs new file mode 100644 index 0000000000..0f635fe1cc --- /dev/null +++ b/src/tools/rustfmt/src/imports.rs @@ -0,0 +1,1210 @@ +use std::borrow::Cow; +use std::cmp::Ordering; +use std::fmt; + +use rustc_ast::ast::{self, UseTreeKind}; +use rustc_span::{ + symbol::{self, sym}, + BytePos, Span, DUMMY_SP, +}; + +use crate::comment::combine_strs_with_missing_comments; +use crate::config::lists::*; +use crate::config::{Edition, IndentStyle}; +use crate::lists::{ + definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator, +}; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::source_map::SpanUtils; +use crate::spanned::Spanned; +use crate::utils::{is_same_visibility, mk_sp, rewrite_ident}; +use crate::visitor::FmtVisitor; + +/// Returns a name imported by a `use` declaration. +/// E.g., returns `Ordering` for `std::cmp::Ordering` and `self` for `std::cmp::self`. +pub(crate) fn path_to_imported_ident(path: &ast::Path) -> symbol::Ident { + path.segments.last().unwrap().ident +} + +impl<'a> FmtVisitor<'a> { + pub(crate) fn format_import(&mut self, item: &ast::Item, tree: &ast::UseTree) { + let span = item.span(); + let shape = self.shape(); + let rw = UseTree::from_ast( + &self.get_context(), + tree, + None, + Some(item.vis.clone()), + Some(item.span.lo()), + Some(item.attrs.clone()), + ) + .rewrite_top_level(&self.get_context(), shape); + match rw { + Some(ref s) if s.is_empty() => { + // Format up to last newline + let prev_span = mk_sp(self.last_pos, source!(self, span).lo()); + let trimmed_snippet = self.snippet(prev_span).trim_end(); + let span_end = self.last_pos + BytePos(trimmed_snippet.len() as u32); + self.format_missing(span_end); + // We have an excessive newline from the removed import. + if self.buffer.ends_with('\n') { + self.buffer.pop(); + self.line_number -= 1; + } + self.last_pos = source!(self, span).hi(); + } + Some(ref s) => { + self.format_missing_with_indent(source!(self, span).lo()); + self.push_str(s); + self.last_pos = source!(self, span).hi(); + } + None => { + self.format_missing_with_indent(source!(self, span).lo()); + self.format_missing(source!(self, span).hi()); + } + } + } +} + +// Ordering of imports + +// We order imports by translating to our own representation and then sorting. +// The Rust AST data structures are really bad for this. Rustfmt applies a bunch +// of normalisations to imports and since we want to sort based on the result +// of these (and to maintain idempotence) we must apply the same normalisations +// to the data structures for sorting. +// +// We sort `self` and `super` before other imports, then identifier imports, +// then glob imports, then lists of imports. We do not take aliases into account +// when ordering unless the imports are identical except for the alias (rare in +// practice). + +// FIXME(#2531): we should unify the comparison code here with the formatting +// code elsewhere since we are essentially string-ifying twice. Furthermore, by +// parsing to our own format on comparison, we repeat a lot of work when +// sorting. + +// FIXME we do a lot of allocation to make our own representation. +#[derive(Clone, Eq, PartialEq)] +pub(crate) enum UseSegment { + Ident(String, Option), + Slf(Option), + Super(Option), + Crate(Option), + Glob, + List(Vec), +} + +#[derive(Clone)] +pub(crate) struct UseTree { + pub(crate) path: Vec, + pub(crate) span: Span, + // Comment information within nested use tree. + pub(crate) list_item: Option, + // Additional fields for top level use items. + // Should we have another struct for top-level use items rather than reusing this? + visibility: Option, + attrs: Option>, +} + +impl PartialEq for UseTree { + fn eq(&self, other: &UseTree) -> bool { + self.path == other.path + } +} +impl Eq for UseTree {} + +impl Spanned for UseTree { + fn span(&self) -> Span { + let lo = if let Some(ref attrs) = self.attrs { + attrs.iter().next().map_or(self.span.lo(), |a| a.span.lo()) + } else { + self.span.lo() + }; + mk_sp(lo, self.span.hi()) + } +} + +impl UseSegment { + // Clone a version of self with any top-level alias removed. + fn remove_alias(&self) -> UseSegment { + match *self { + UseSegment::Ident(ref s, _) => UseSegment::Ident(s.clone(), None), + UseSegment::Slf(_) => UseSegment::Slf(None), + UseSegment::Super(_) => UseSegment::Super(None), + UseSegment::Crate(_) => UseSegment::Crate(None), + _ => self.clone(), + } + } + + fn from_path_segment( + context: &RewriteContext<'_>, + path_seg: &ast::PathSegment, + modsep: bool, + ) -> Option { + let name = rewrite_ident(context, path_seg.ident); + if name.is_empty() || name == "{{root}}" { + return None; + } + Some(match name { + "self" => UseSegment::Slf(None), + "super" => UseSegment::Super(None), + "crate" => UseSegment::Crate(None), + _ => { + let mod_sep = if modsep { "::" } else { "" }; + UseSegment::Ident(format!("{}{}", mod_sep, name), None) + } + }) + } +} + +pub(crate) fn merge_use_trees(use_trees: Vec, merge_by: SharedPrefix) -> Vec { + let mut result = Vec::with_capacity(use_trees.len()); + for use_tree in use_trees { + if use_tree.has_comment() || use_tree.attrs.is_some() { + result.push(use_tree); + continue; + } + + for flattened in use_tree.flatten() { + if let Some(tree) = result + .iter_mut() + .find(|tree| tree.share_prefix(&flattened, merge_by)) + { + tree.merge(&flattened, merge_by); + } else { + result.push(flattened); + } + } + } + result +} + +pub(crate) fn flatten_use_trees(use_trees: Vec) -> Vec { + use_trees + .into_iter() + .flat_map(UseTree::flatten) + .map(|mut tree| { + // If a path ends in `::self`, rewrite it to `::{self}`. + if let Some(UseSegment::Slf(..)) = tree.path.last() { + let self_segment = tree.path.pop().unwrap(); + tree.path.push(UseSegment::List(vec![UseTree::from_path( + vec![self_segment], + DUMMY_SP, + )])); + } + tree + }) + .collect() +} + +impl fmt::Debug for UseTree { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Debug for UseSegment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for UseSegment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + UseSegment::Glob => write!(f, "*"), + UseSegment::Ident(ref s, _) => write!(f, "{}", s), + UseSegment::Slf(..) => write!(f, "self"), + UseSegment::Super(..) => write!(f, "super"), + UseSegment::Crate(..) => write!(f, "crate"), + UseSegment::List(ref list) => { + write!(f, "{{")?; + for (i, item) in list.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{}", item)?; + } + write!(f, "}}") + } + } + } +} +impl fmt::Display for UseTree { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (i, segment) in self.path.iter().enumerate() { + if i != 0 { + write!(f, "::")?; + } + write!(f, "{}", segment)?; + } + Ok(()) + } +} + +impl UseTree { + // Rewrite use tree with `use ` and a trailing `;`. + pub(crate) fn rewrite_top_level( + &self, + context: &RewriteContext<'_>, + shape: Shape, + ) -> Option { + let vis = self.visibility.as_ref().map_or(Cow::from(""), |vis| { + crate::utils::format_visibility(context, &vis) + }); + let use_str = self + .rewrite(context, shape.offset_left(vis.len())?) + .map(|s| { + if s.is_empty() { + s + } else { + format!("{}use {};", vis, s) + } + })?; + match self.attrs { + Some(ref attrs) if !attrs.is_empty() => { + let attr_str = attrs.rewrite(context, shape)?; + let lo = attrs.last().as_ref()?.span.hi(); + let hi = self.span.lo(); + let span = mk_sp(lo, hi); + + let allow_extend = if attrs.len() == 1 { + let line_len = attr_str.len() + 1 + use_str.len(); + !attrs.first().unwrap().is_doc_comment() + && context.config.inline_attribute_width() >= line_len + } else { + false + }; + + combine_strs_with_missing_comments( + context, + &attr_str, + &use_str, + span, + shape, + allow_extend, + ) + } + _ => Some(use_str), + } + } + + // FIXME: Use correct span? + // The given span is essentially incorrect, since we are reconstructing + // use-statements. This should not be a problem, though, since we have + // already tried to extract comment and observed that there are no comment + // around the given use item, and the span will not be used afterward. + fn from_path(path: Vec, span: Span) -> UseTree { + UseTree { + path, + span, + list_item: None, + visibility: None, + attrs: None, + } + } + + pub(crate) fn from_ast_with_normalization( + context: &RewriteContext<'_>, + item: &ast::Item, + ) -> Option { + match item.kind { + ast::ItemKind::Use(ref use_tree) => Some( + UseTree::from_ast( + context, + use_tree, + None, + Some(item.vis.clone()), + Some(item.span.lo()), + if item.attrs.is_empty() { + None + } else { + Some(item.attrs.clone()) + }, + ) + .normalize(), + ), + _ => None, + } + } + + fn from_ast( + context: &RewriteContext<'_>, + a: &ast::UseTree, + list_item: Option, + visibility: Option, + opt_lo: Option, + attrs: Option>, + ) -> UseTree { + let span = if let Some(lo) = opt_lo { + mk_sp(lo, a.span.hi()) + } else { + a.span + }; + let mut result = UseTree { + path: vec![], + span, + list_item, + visibility, + attrs, + }; + + let leading_modsep = + context.config.edition() >= Edition::Edition2018 && a.prefix.is_global(); + + let mut modsep = leading_modsep; + + for p in &a.prefix.segments { + if let Some(use_segment) = UseSegment::from_path_segment(context, p, modsep) { + result.path.push(use_segment); + modsep = false; + } + } + + match a.kind { + UseTreeKind::Glob => { + // in case of a global path and the glob starts at the root, e.g., "::*" + if a.prefix.segments.len() == 1 && leading_modsep { + result.path.push(UseSegment::Ident("".to_owned(), None)); + } + result.path.push(UseSegment::Glob); + } + UseTreeKind::Nested(ref list) => { + // Extract comments between nested use items. + // This needs to be done before sorting use items. + let items: Vec<_> = itemize_list( + context.snippet_provider, + list.iter().map(|(tree, _)| tree), + "}", + ",", + |tree| tree.span.lo(), + |tree| tree.span.hi(), + |_| Some("".to_owned()), // We only need comments for now. + context.snippet_provider.span_after(a.span, "{"), + a.span.hi(), + false, + ) + .collect(); + // in case of a global path and the nested list starts at the root, + // e.g., "::{foo, bar}" + if a.prefix.segments.len() == 1 && leading_modsep { + result.path.push(UseSegment::Ident("".to_owned(), None)); + } + result.path.push(UseSegment::List( + list.iter() + .zip(items.into_iter()) + .map(|(t, list_item)| { + Self::from_ast(context, &t.0, Some(list_item), None, None, None) + }) + .collect(), + )); + } + UseTreeKind::Simple(ref rename, ..) => { + // If the path has leading double colons and is composed of only 2 segments, then we + // bypass the call to path_to_imported_ident which would get only the ident and + // lose the path root, e.g., `that` in `::that`. + // The span of `a.prefix` contains the leading colons. + let name = if a.prefix.segments.len() == 2 && leading_modsep { + context.snippet(a.prefix.span).to_owned() + } else { + rewrite_ident(context, path_to_imported_ident(&a.prefix)).to_owned() + }; + let alias = rename.and_then(|ident| { + if ident.name == sym::underscore_imports { + // for impl-only-use + Some("_".to_owned()) + } else if ident == path_to_imported_ident(&a.prefix) { + None + } else { + Some(rewrite_ident(context, ident).to_owned()) + } + }); + let segment = match name.as_ref() { + "self" => UseSegment::Slf(alias), + "super" => UseSegment::Super(alias), + "crate" => UseSegment::Crate(alias), + _ => UseSegment::Ident(name, alias), + }; + + // `name` is already in result. + result.path.pop(); + result.path.push(segment); + } + } + result + } + + // Do the adjustments that rustfmt does elsewhere to use paths. + pub(crate) fn normalize(mut self) -> UseTree { + let mut last = self.path.pop().expect("Empty use tree?"); + // Hack around borrow checker. + let mut normalize_sole_list = false; + let mut aliased_self = false; + + // Remove foo::{} or self without attributes. + match last { + _ if self.attrs.is_some() => (), + UseSegment::List(ref list) if list.is_empty() => { + self.path = vec![]; + return self; + } + UseSegment::Slf(None) if self.path.is_empty() && self.visibility.is_some() => { + self.path = vec![]; + return self; + } + _ => (), + } + + // Normalise foo::self -> foo. + if let UseSegment::Slf(None) = last { + if !self.path.is_empty() { + return self; + } + } + + // Normalise foo::self as bar -> foo as bar. + if let UseSegment::Slf(_) = last { + match self.path.last() { + Some(UseSegment::Ident(_, None)) => { + aliased_self = true; + } + _ => {} + } + } + + let mut done = false; + if aliased_self { + match self.path.last_mut() { + Some(UseSegment::Ident(_, ref mut old_rename)) => { + assert!(old_rename.is_none()); + if let UseSegment::Slf(Some(rename)) = last.clone() { + *old_rename = Some(rename); + done = true; + } + } + _ => unreachable!(), + } + } + + if done { + return self; + } + + // Normalise foo::{bar} -> foo::bar + if let UseSegment::List(ref list) = last { + if list.len() == 1 && list[0].to_string() != "self" { + normalize_sole_list = true; + } + } + + if normalize_sole_list { + match last { + UseSegment::List(list) => { + for seg in &list[0].path { + self.path.push(seg.clone()); + } + return self.normalize(); + } + _ => unreachable!(), + } + } + + // Recursively normalize elements of a list use (including sorting the list). + if let UseSegment::List(list) = last { + let mut list = list.into_iter().map(UseTree::normalize).collect::>(); + list.sort(); + last = UseSegment::List(list); + } + + self.path.push(last); + self + } + + fn has_comment(&self) -> bool { + self.list_item.as_ref().map_or(false, ListItem::has_comment) + } + + fn same_visibility(&self, other: &UseTree) -> bool { + match (&self.visibility, &other.visibility) { + ( + Some(ast::Visibility { + kind: ast::VisibilityKind::Inherited, + .. + }), + None, + ) + | ( + None, + Some(ast::Visibility { + kind: ast::VisibilityKind::Inherited, + .. + }), + ) + | (None, None) => true, + (Some(ref a), Some(ref b)) => is_same_visibility(a, b), + _ => false, + } + } + + fn share_prefix(&self, other: &UseTree, shared_prefix: SharedPrefix) -> bool { + if self.path.is_empty() + || other.path.is_empty() + || self.attrs.is_some() + || !self.same_visibility(other) + { + false + } else { + match shared_prefix { + SharedPrefix::Crate => self.path[0] == other.path[0], + SharedPrefix::Module => { + self.path[..self.path.len() - 1] == other.path[..other.path.len() - 1] + } + } + } + } + + fn flatten(self) -> Vec { + if self.path.is_empty() { + return vec![self]; + } + match self.path.clone().last().unwrap() { + UseSegment::List(list) => { + if list.len() == 1 && list[0].path.len() == 1 { + match list[0].path[0] { + UseSegment::Slf(..) => return vec![self], + _ => (), + }; + } + let prefix = &self.path[..self.path.len() - 1]; + let mut result = vec![]; + for nested_use_tree in list { + for flattend in &mut nested_use_tree.clone().flatten() { + let mut new_path = prefix.to_vec(); + new_path.append(&mut flattend.path); + result.push(UseTree { + path: new_path, + span: self.span, + list_item: None, + visibility: self.visibility.clone(), + attrs: None, + }); + } + } + + result + } + _ => vec![self], + } + } + + fn merge(&mut self, other: &UseTree, merge_by: SharedPrefix) { + let mut prefix = 0; + for (a, b) in self.path.iter().zip(other.path.iter()) { + if *a == *b { + prefix += 1; + } else { + break; + } + } + if let Some(new_path) = merge_rest(&self.path, &other.path, prefix, merge_by) { + self.path = new_path; + self.span = self.span.to(other.span); + } + } +} + +fn merge_rest( + a: &[UseSegment], + b: &[UseSegment], + mut len: usize, + merge_by: SharedPrefix, +) -> Option> { + if a.len() == len && b.len() == len { + return None; + } + if a.len() != len && b.len() != len { + if let UseSegment::List(ref list) = a[len] { + let mut list = list.clone(); + merge_use_trees_inner( + &mut list, + UseTree::from_path(b[len..].to_vec(), DUMMY_SP), + merge_by, + ); + let mut new_path = b[..len].to_vec(); + new_path.push(UseSegment::List(list)); + return Some(new_path); + } + } else if len == 1 { + let rest = if a.len() == len { &b[1..] } else { &a[1..] }; + return Some(vec![ + b[0].clone(), + UseSegment::List(vec![ + UseTree::from_path(vec![UseSegment::Slf(None)], DUMMY_SP), + UseTree::from_path(rest.to_vec(), DUMMY_SP), + ]), + ]); + } else { + len -= 1; + } + let mut list = vec![ + UseTree::from_path(a[len..].to_vec(), DUMMY_SP), + UseTree::from_path(b[len..].to_vec(), DUMMY_SP), + ]; + list.sort(); + let mut new_path = b[..len].to_vec(); + new_path.push(UseSegment::List(list)); + Some(new_path) +} + +fn merge_use_trees_inner(trees: &mut Vec, use_tree: UseTree, merge_by: SharedPrefix) { + let similar_trees = trees + .iter_mut() + .filter(|tree| tree.share_prefix(&use_tree, merge_by)); + if use_tree.path.len() == 1 && merge_by == SharedPrefix::Crate { + if let Some(tree) = similar_trees.min_by_key(|tree| tree.path.len()) { + if tree.path.len() == 1 { + return; + } + } + } else if let Some(tree) = similar_trees.max_by_key(|tree| tree.path.len()) { + if tree.path.len() > 1 { + tree.merge(&use_tree, merge_by); + return; + } + } + trees.push(use_tree); + trees.sort(); +} + +impl PartialOrd for UseSegment { + fn partial_cmp(&self, other: &UseSegment) -> Option { + Some(self.cmp(other)) + } +} +impl PartialOrd for UseTree { + fn partial_cmp(&self, other: &UseTree) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for UseSegment { + fn cmp(&self, other: &UseSegment) -> Ordering { + use self::UseSegment::*; + + fn is_upper_snake_case(s: &str) -> bool { + s.chars() + .all(|c| c.is_uppercase() || c == '_' || c.is_numeric()) + } + + match (self, other) { + (&Slf(ref a), &Slf(ref b)) + | (&Super(ref a), &Super(ref b)) + | (&Crate(ref a), &Crate(ref b)) => a.cmp(b), + (&Glob, &Glob) => Ordering::Equal, + (&Ident(ref ia, ref aa), &Ident(ref ib, ref ab)) => { + // snake_case < CamelCase < UPPER_SNAKE_CASE + if ia.starts_with(char::is_uppercase) && ib.starts_with(char::is_lowercase) { + return Ordering::Greater; + } + if ia.starts_with(char::is_lowercase) && ib.starts_with(char::is_uppercase) { + return Ordering::Less; + } + if is_upper_snake_case(ia) && !is_upper_snake_case(ib) { + return Ordering::Greater; + } + if !is_upper_snake_case(ia) && is_upper_snake_case(ib) { + return Ordering::Less; + } + let ident_ord = ia.cmp(ib); + if ident_ord != Ordering::Equal { + return ident_ord; + } + if aa.is_none() && ab.is_some() { + return Ordering::Less; + } + if aa.is_some() && ab.is_none() { + return Ordering::Greater; + } + aa.cmp(ab) + } + (&List(ref a), &List(ref b)) => { + for (a, b) in a.iter().zip(b.iter()) { + let ord = a.cmp(b); + if ord != Ordering::Equal { + return ord; + } + } + + a.len().cmp(&b.len()) + } + (&Slf(_), _) => Ordering::Less, + (_, &Slf(_)) => Ordering::Greater, + (&Super(_), _) => Ordering::Less, + (_, &Super(_)) => Ordering::Greater, + (&Crate(_), _) => Ordering::Less, + (_, &Crate(_)) => Ordering::Greater, + (&Ident(..), _) => Ordering::Less, + (_, &Ident(..)) => Ordering::Greater, + (&Glob, _) => Ordering::Less, + (_, &Glob) => Ordering::Greater, + } + } +} +impl Ord for UseTree { + fn cmp(&self, other: &UseTree) -> Ordering { + for (a, b) in self.path.iter().zip(other.path.iter()) { + let ord = a.cmp(b); + // The comparison without aliases is a hack to avoid situations like + // comparing `a::b` to `a as c` - where the latter should be ordered + // first since it is shorter. + if ord != Ordering::Equal && a.remove_alias().cmp(&b.remove_alias()) != Ordering::Equal + { + return ord; + } + } + + self.path.len().cmp(&other.path.len()) + } +} + +fn rewrite_nested_use_tree( + context: &RewriteContext<'_>, + use_tree_list: &[UseTree], + shape: Shape, +) -> Option { + let mut list_items = Vec::with_capacity(use_tree_list.len()); + let nested_shape = match context.config.imports_indent() { + IndentStyle::Block => shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config) + .sub_width(1)?, + IndentStyle::Visual => shape.visual_indent(0), + }; + for use_tree in use_tree_list { + if let Some(mut list_item) = use_tree.list_item.clone() { + list_item.item = use_tree.rewrite(context, nested_shape); + list_items.push(list_item); + } else { + list_items.push(ListItem::from_str(use_tree.rewrite(context, nested_shape)?)); + } + } + let has_nested_list = use_tree_list.iter().any(|use_segment| { + use_segment + .path + .last() + .map_or(false, |last_segment| match last_segment { + UseSegment::List(..) => true, + _ => false, + }) + }); + + let remaining_width = if has_nested_list { + 0 + } else { + shape.width.saturating_sub(2) + }; + + let tactic = definitive_tactic( + &list_items, + context.config.imports_layout(), + Separator::Comma, + remaining_width, + ); + + let ends_with_newline = context.config.imports_indent() == IndentStyle::Block + && tactic != DefinitiveListTactic::Horizontal; + let trailing_separator = if ends_with_newline { + context.config.trailing_comma() + } else { + SeparatorTactic::Never + }; + let fmt = ListFormatting::new(nested_shape, context.config) + .tactic(tactic) + .trailing_separator(trailing_separator) + .ends_with_newline(ends_with_newline) + .preserve_newline(true) + .nested(has_nested_list); + + let list_str = write_list(&list_items, &fmt)?; + + let result = if (list_str.contains('\n') || list_str.len() > remaining_width) + && context.config.imports_indent() == IndentStyle::Block + { + format!( + "{{\n{}{}\n{}}}", + nested_shape.indent.to_string(context.config), + list_str, + shape.indent.to_string(context.config) + ) + } else { + format!("{{{}}}", list_str) + }; + + Some(result) +} + +impl Rewrite for UseSegment { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + Some(match self { + UseSegment::Ident(ref ident, Some(ref rename)) => format!("{} as {}", ident, rename), + UseSegment::Ident(ref ident, None) => ident.clone(), + UseSegment::Slf(Some(ref rename)) => format!("self as {}", rename), + UseSegment::Slf(None) => "self".to_owned(), + UseSegment::Super(Some(ref rename)) => format!("super as {}", rename), + UseSegment::Super(None) => "super".to_owned(), + UseSegment::Crate(Some(ref rename)) => format!("crate as {}", rename), + UseSegment::Crate(None) => "crate".to_owned(), + UseSegment::Glob => "*".to_owned(), + UseSegment::List(ref use_tree_list) => rewrite_nested_use_tree( + context, + use_tree_list, + // 1 = "{" and "}" + shape.offset_left(1)?.sub_width(1)?, + )?, + }) + } +} + +impl Rewrite for UseTree { + // This does NOT format attributes and visibility or add a trailing `;`. + fn rewrite(&self, context: &RewriteContext<'_>, mut shape: Shape) -> Option { + let mut result = String::with_capacity(256); + let mut iter = self.path.iter().peekable(); + while let Some(ref segment) = iter.next() { + let segment_str = segment.rewrite(context, shape)?; + result.push_str(&segment_str); + if iter.peek().is_some() { + result.push_str("::"); + // 2 = "::" + shape = shape.offset_left(2 + segment_str.len())?; + } + } + Some(result) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum SharedPrefix { + Crate, + Module, +} + +#[cfg(test)] +mod test { + use super::*; + use rustc_span::DUMMY_SP; + + // Parse the path part of an import. This parser is not robust and is only + // suitable for use in a test harness. + fn parse_use_tree(s: &str) -> UseTree { + use std::iter::Peekable; + use std::mem::swap; + use std::str::Chars; + + struct Parser<'a> { + input: Peekable>, + } + + impl<'a> Parser<'a> { + fn bump(&mut self) { + self.input.next().unwrap(); + } + + fn eat(&mut self, c: char) { + assert!(self.input.next().unwrap() == c); + } + + fn push_segment( + result: &mut Vec, + buf: &mut String, + alias_buf: &mut Option, + ) { + if !buf.is_empty() { + let mut alias = None; + swap(alias_buf, &mut alias); + + match buf.as_ref() { + "self" => { + result.push(UseSegment::Slf(alias)); + *buf = String::new(); + *alias_buf = None; + } + "super" => { + result.push(UseSegment::Super(alias)); + *buf = String::new(); + *alias_buf = None; + } + "crate" => { + result.push(UseSegment::Crate(alias)); + *buf = String::new(); + *alias_buf = None; + } + _ => { + let mut name = String::new(); + swap(buf, &mut name); + result.push(UseSegment::Ident(name, alias)); + } + } + } + } + + fn parse_in_list(&mut self) -> UseTree { + let mut result = vec![]; + let mut buf = String::new(); + let mut alias_buf = None; + while let Some(&c) = self.input.peek() { + match c { + '{' => { + assert!(buf.is_empty()); + self.bump(); + result.push(UseSegment::List(self.parse_list())); + self.eat('}'); + } + '*' => { + assert!(buf.is_empty()); + self.bump(); + result.push(UseSegment::Glob); + } + ':' => { + self.bump(); + self.eat(':'); + Self::push_segment(&mut result, &mut buf, &mut alias_buf); + } + '}' | ',' => { + Self::push_segment(&mut result, &mut buf, &mut alias_buf); + return UseTree { + path: result, + span: DUMMY_SP, + list_item: None, + visibility: None, + attrs: None, + }; + } + ' ' => { + self.bump(); + self.eat('a'); + self.eat('s'); + self.eat(' '); + alias_buf = Some(String::new()); + } + c => { + self.bump(); + if let Some(ref mut buf) = alias_buf { + buf.push(c); + } else { + buf.push(c); + } + } + } + } + Self::push_segment(&mut result, &mut buf, &mut alias_buf); + UseTree { + path: result, + span: DUMMY_SP, + list_item: None, + visibility: None, + attrs: None, + } + } + + fn parse_list(&mut self) -> Vec { + let mut result = vec![]; + loop { + match self.input.peek().unwrap() { + ',' | ' ' => self.bump(), + '}' => { + return result; + } + _ => result.push(self.parse_in_list()), + } + } + } + } + + let mut parser = Parser { + input: s.chars().peekable(), + }; + parser.parse_in_list() + } + + macro_rules! parse_use_trees { + ($($s:expr),* $(,)*) => { + vec![ + $(parse_use_tree($s),)* + ] + } + } + + macro_rules! test_merge { + ($by:ident, [$($input:expr),* $(,)*], [$($output:expr),* $(,)*]) => { + assert_eq!( + merge_use_trees(parse_use_trees!($($input,)*), SharedPrefix::$by), + parse_use_trees!($($output,)*), + ); + } + } + + #[test] + fn test_use_tree_merge_crate() { + test_merge!( + Crate, + ["a::b::{c, d}", "a::b::{e, f}"], + ["a::b::{c, d, e, f}"] + ); + test_merge!(Crate, ["a::b::c", "a::b"], ["a::{b, b::c}"]); + test_merge!(Crate, ["a::b", "a::b"], ["a::b"]); + test_merge!(Crate, ["a", "a::b", "a::b::c"], ["a::{self, b, b::c}"]); + test_merge!( + Crate, + ["a", "a::b", "a::b::c", "a::b::c::d"], + ["a::{self, b, b::{c, c::d}}"] + ); + test_merge!( + Crate, + ["a", "a::b", "a::b::c", "a::b"], + ["a::{self, b, b::c}"] + ); + test_merge!( + Crate, + ["a::{b::{self, c}, d::e}", "a::d::f"], + ["a::{b::{self, c}, d::{e, f}}"] + ); + test_merge!( + Crate, + ["a::d::f", "a::{b::{self, c}, d::e}"], + ["a::{b::{self, c}, d::{e, f}}"] + ); + test_merge!( + Crate, + ["a::{c, d, b}", "a::{d, e, b, a, f}", "a::{f, g, c}"], + ["a::{a, b, c, d, e, f, g}"] + ); + test_merge!( + Crate, + ["a::{self}", "b::{self as foo}"], + ["a::{self}", "b::{self as foo}"] + ); + } + + #[test] + fn test_use_tree_merge_module() { + test_merge!( + Module, + ["foo::b", "foo::{a, c, d::e}"], + ["foo::{a, b, c}", "foo::d::e"] + ); + + test_merge!( + Module, + ["foo::{a::b, a::c, d::e, d::f}"], + ["foo::a::{b, c}", "foo::d::{e, f}"] + ); + } + + #[test] + fn test_flatten_use_trees() { + assert_eq!( + flatten_use_trees(parse_use_trees!["foo::{a::{b, c}, d::e}"]), + parse_use_trees!["foo::a::b", "foo::a::c", "foo::d::e"] + ); + + assert_eq!( + flatten_use_trees(parse_use_trees!["foo::{self, a, b::{c, d}, e::*}"]), + parse_use_trees![ + "foo::{self}", + "foo::a", + "foo::b::c", + "foo::b::d", + "foo::e::*" + ] + ); + } + + #[test] + fn test_use_tree_flatten() { + assert_eq!( + parse_use_tree("a::b::{c, d, e, f}").flatten(), + parse_use_trees!("a::b::c", "a::b::d", "a::b::e", "a::b::f",) + ); + + assert_eq!( + parse_use_tree("a::b::{c::{d, e, f}, g, h::{i, j, k}}").flatten(), + parse_use_trees![ + "a::b::c::d", + "a::b::c::e", + "a::b::c::f", + "a::b::g", + "a::b::h::i", + "a::b::h::j", + "a::b::h::k", + ] + ); + } + + #[test] + fn test_use_tree_normalize() { + assert_eq!(parse_use_tree("a::self").normalize(), parse_use_tree("a")); + assert_eq!( + parse_use_tree("a::self as foo").normalize(), + parse_use_tree("a as foo") + ); + assert_eq!( + parse_use_tree("a::{self}").normalize(), + parse_use_tree("a::{self}") + ); + assert_eq!(parse_use_tree("a::{b}").normalize(), parse_use_tree("a::b")); + assert_eq!( + parse_use_tree("a::{b, c::self}").normalize(), + parse_use_tree("a::{b, c}") + ); + assert_eq!( + parse_use_tree("a::{b as bar, c::self}").normalize(), + parse_use_tree("a::{b as bar, c}") + ); + } + + #[test] + fn test_use_tree_ord() { + assert!(parse_use_tree("a").normalize() < parse_use_tree("aa").normalize()); + assert!(parse_use_tree("a").normalize() < parse_use_tree("a::a").normalize()); + assert!(parse_use_tree("a").normalize() < parse_use_tree("*").normalize()); + assert!(parse_use_tree("a").normalize() < parse_use_tree("{a, b}").normalize()); + assert!(parse_use_tree("*").normalize() < parse_use_tree("{a, b}").normalize()); + + assert!( + parse_use_tree("aaaaaaaaaaaaaaa::{bb, cc, dddddddd}").normalize() + < parse_use_tree("aaaaaaaaaaaaaaa::{bb, cc, ddddddddd}").normalize() + ); + assert!( + parse_use_tree("serde::de::{Deserialize}").normalize() + < parse_use_tree("serde_json").normalize() + ); + assert!(parse_use_tree("a::b::c").normalize() < parse_use_tree("a::b::*").normalize()); + assert!( + parse_use_tree("foo::{Bar, Baz}").normalize() + < parse_use_tree("{Bar, Baz}").normalize() + ); + + assert!( + parse_use_tree("foo::{qux as bar}").normalize() + < parse_use_tree("foo::{self as bar}").normalize() + ); + assert!( + parse_use_tree("foo::{qux as bar}").normalize() + < parse_use_tree("foo::{baz, qux as bar}").normalize() + ); + assert!( + parse_use_tree("foo::{self as bar, baz}").normalize() + < parse_use_tree("foo::{baz, qux as bar}").normalize() + ); + + assert!(parse_use_tree("foo").normalize() < parse_use_tree("Foo").normalize()); + assert!(parse_use_tree("foo").normalize() < parse_use_tree("foo::Bar").normalize()); + + assert!( + parse_use_tree("std::cmp::{d, c, b, a}").normalize() + < parse_use_tree("std::cmp::{b, e, g, f}").normalize() + ); + } +} diff --git a/src/tools/rustfmt/src/issues.rs b/src/tools/rustfmt/src/issues.rs new file mode 100644 index 0000000000..d369b75541 --- /dev/null +++ b/src/tools/rustfmt/src/issues.rs @@ -0,0 +1,317 @@ +// Objects for seeking through a char stream for occurrences of TODO and FIXME. +// Depending on the loaded configuration, may also check that these have an +// associated issue number. + +use std::fmt; + +use crate::config::ReportTactic; + +const TO_DO_CHARS: &[char] = &['t', 'o', 'd', 'o']; +const FIX_ME_CHARS: &[char] = &['f', 'i', 'x', 'm', 'e']; + +// Enabled implementation detail is here because it is +// irrelevant outside the issues module +fn is_enabled(report_tactic: ReportTactic) -> bool { + report_tactic != ReportTactic::Never +} + +#[derive(Clone, Copy)] +enum Seeking { + Issue { todo_idx: usize, fixme_idx: usize }, + Number { issue: Issue, part: NumberPart }, +} + +#[derive(Clone, Copy)] +enum NumberPart { + OpenParen, + Pound, + Number, + CloseParen, +} + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub struct Issue { + issue_type: IssueType, + // Indicates whether we're looking for issues with missing numbers, or + // all issues of this type. + missing_number: bool, +} + +impl fmt::Display for Issue { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let msg = match self.issue_type { + IssueType::Todo => "TODO", + IssueType::Fixme => "FIXME", + }; + let details = if self.missing_number { + " without issue number" + } else { + "" + }; + + write!(fmt, "{}{}", msg, details) + } +} + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +enum IssueType { + Todo, + Fixme, +} + +enum IssueClassification { + Good, + Bad(Issue), + None, +} + +pub(crate) struct BadIssueSeeker { + state: Seeking, + report_todo: ReportTactic, + report_fixme: ReportTactic, +} + +impl BadIssueSeeker { + pub(crate) fn new(report_todo: ReportTactic, report_fixme: ReportTactic) -> BadIssueSeeker { + BadIssueSeeker { + state: Seeking::Issue { + todo_idx: 0, + fixme_idx: 0, + }, + report_todo, + report_fixme, + } + } + + pub(crate) fn is_disabled(&self) -> bool { + !is_enabled(self.report_todo) && !is_enabled(self.report_fixme) + } + + // Check whether or not the current char is conclusive evidence for an + // unnumbered TO-DO or FIX-ME. + pub(crate) fn inspect(&mut self, c: char) -> Option { + match self.state { + Seeking::Issue { + todo_idx, + fixme_idx, + } => { + self.state = self.inspect_issue(c, todo_idx, fixme_idx); + } + Seeking::Number { issue, part } => { + let result = self.inspect_number(c, issue, part); + + if let IssueClassification::None = result { + return None; + } + + self.state = Seeking::Issue { + todo_idx: 0, + fixme_idx: 0, + }; + + if let IssueClassification::Bad(issue) = result { + return Some(issue); + } + } + } + + None + } + + fn inspect_issue(&mut self, c: char, mut todo_idx: usize, mut fixme_idx: usize) -> Seeking { + if let Some(lower_case_c) = c.to_lowercase().next() { + if is_enabled(self.report_todo) && lower_case_c == TO_DO_CHARS[todo_idx] { + todo_idx += 1; + if todo_idx == TO_DO_CHARS.len() { + return Seeking::Number { + issue: Issue { + issue_type: IssueType::Todo, + missing_number: if let ReportTactic::Unnumbered = self.report_todo { + true + } else { + false + }, + }, + part: NumberPart::OpenParen, + }; + } + fixme_idx = 0; + } else if is_enabled(self.report_fixme) && lower_case_c == FIX_ME_CHARS[fixme_idx] { + // Exploit the fact that the character sets of todo and fixme + // are disjoint by adding else. + fixme_idx += 1; + if fixme_idx == FIX_ME_CHARS.len() { + return Seeking::Number { + issue: Issue { + issue_type: IssueType::Fixme, + missing_number: if let ReportTactic::Unnumbered = self.report_fixme { + true + } else { + false + }, + }, + part: NumberPart::OpenParen, + }; + } + todo_idx = 0; + } else { + todo_idx = 0; + fixme_idx = 0; + } + } + + Seeking::Issue { + todo_idx, + fixme_idx, + } + } + + fn inspect_number( + &mut self, + c: char, + issue: Issue, + mut part: NumberPart, + ) -> IssueClassification { + if !issue.missing_number || c == '\n' { + return IssueClassification::Bad(issue); + } else if c == ')' { + return if let NumberPart::CloseParen = part { + IssueClassification::Good + } else { + IssueClassification::Bad(issue) + }; + } + + match part { + NumberPart::OpenParen => { + if c != '(' { + return IssueClassification::Bad(issue); + } else { + part = NumberPart::Pound; + } + } + NumberPart::Pound => { + if c == '#' { + part = NumberPart::Number; + } + } + NumberPart::Number => { + if c >= '0' && c <= '9' { + part = NumberPart::CloseParen; + } else { + return IssueClassification::Bad(issue); + } + } + NumberPart::CloseParen => {} + } + + self.state = Seeking::Number { part, issue }; + + IssueClassification::None + } +} + +#[test] +fn find_unnumbered_issue() { + fn check_fail(text: &str, failing_pos: usize) { + let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered); + assert_eq!( + Some(failing_pos), + text.find(|c| seeker.inspect(c).is_some()) + ); + } + + fn check_pass(text: &str) { + let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered); + assert_eq!(None, text.find(|c| seeker.inspect(c).is_some())); + } + + check_fail("TODO\n", 4); + check_pass(" TO FIX DOME\n"); + check_fail(" \n FIXME\n", 8); + check_fail("FIXME(\n", 6); + check_fail("FIXME(#\n", 7); + check_fail("FIXME(#1\n", 8); + check_fail("FIXME(#)1\n", 7); + check_pass("FIXME(#1222)\n"); + check_fail("FIXME(#12\n22)\n", 9); + check_pass("FIXME(@maintainer, #1222, hello)\n"); + check_fail("TODO(#22) FIXME\n", 15); +} + +#[test] +fn find_issue() { + fn is_bad_issue(text: &str, report_todo: ReportTactic, report_fixme: ReportTactic) -> bool { + let mut seeker = BadIssueSeeker::new(report_todo, report_fixme); + text.chars().any(|c| seeker.inspect(c).is_some()) + } + + assert!(is_bad_issue( + "TODO(@maintainer, #1222, hello)\n", + ReportTactic::Always, + ReportTactic::Never, + )); + + assert!(!is_bad_issue( + "TODO: no number\n", + ReportTactic::Never, + ReportTactic::Always, + )); + + assert!(!is_bad_issue( + "Todo: mixed case\n", + ReportTactic::Never, + ReportTactic::Always, + )); + + assert!(is_bad_issue( + "This is a FIXME(#1)\n", + ReportTactic::Never, + ReportTactic::Always, + )); + + assert!(is_bad_issue( + "This is a FixMe(#1) mixed case\n", + ReportTactic::Never, + ReportTactic::Always, + )); + + assert!(!is_bad_issue( + "bad FIXME\n", + ReportTactic::Always, + ReportTactic::Never, + )); +} + +#[test] +fn issue_type() { + let mut seeker = BadIssueSeeker::new(ReportTactic::Always, ReportTactic::Never); + let expected = Some(Issue { + issue_type: IssueType::Todo, + missing_number: false, + }); + + assert_eq!( + expected, + "TODO(#100): more awesomeness" + .chars() + .map(|c| seeker.inspect(c)) + .find(Option::is_some) + .unwrap() + ); + + let mut seeker = BadIssueSeeker::new(ReportTactic::Never, ReportTactic::Unnumbered); + let expected = Some(Issue { + issue_type: IssueType::Fixme, + missing_number: true, + }); + + assert_eq!( + expected, + "Test. FIXME: bad, bad, not good" + .chars() + .map(|c| seeker.inspect(c)) + .find(Option::is_some) + .unwrap() + ); +} diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs new file mode 100644 index 0000000000..10654a2a5a --- /dev/null +++ b/src/tools/rustfmt/src/items.rs @@ -0,0 +1,3282 @@ +// Formatting top-level items - functions, structs, enums, traits, impls. + +use std::borrow::Cow; +use std::cmp::{max, min, Ordering}; + +use regex::Regex; +use rustc_ast::visit; +use rustc_ast::{ast, ptr}; +use rustc_span::{symbol, BytePos, Span, DUMMY_SP}; + +use crate::attr::filter_inline_attrs; +use crate::comment::{ + combine_strs_with_missing_comments, contains_comment, is_last_comment_block, + recover_comment_removed, recover_missing_comment_in_span, rewrite_missing_comment, + FindUncommented, +}; +use crate::config::lists::*; +use crate::config::{BraceStyle, Config, IndentStyle, Version}; +use crate::expr::{ + is_empty_block, is_simple_block_stmt, rewrite_assign_rhs, rewrite_assign_rhs_with, RhsTactics, +}; +use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator}; +use crate::macros::{rewrite_macro, MacroPosition}; +use crate::overflow; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::{Indent, Shape}; +use crate::source_map::{LineRangeUtils, SpanUtils}; +use crate::spanned::Spanned; +use crate::stmt::Stmt; +use crate::utils::*; +use crate::vertical::rewrite_with_alignment; +use crate::visitor::FmtVisitor; + +const DEFAULT_VISIBILITY: ast::Visibility = ast::Visibility { + kind: ast::VisibilityKind::Inherited, + span: DUMMY_SP, + tokens: None, +}; + +fn type_annotation_separator(config: &Config) -> &str { + colon_spaces(config) +} + +// Statements of the form +// let pat: ty = init; +impl Rewrite for ast::Local { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + debug!( + "Local::rewrite {:?} {} {:?}", + self, shape.width, shape.indent + ); + + skip_out_of_file_lines_range!(context, self.span); + + if contains_skip(&self.attrs) { + return None; + } + + let attrs_str = self.attrs.rewrite(context, shape)?; + let mut result = if attrs_str.is_empty() { + "let ".to_owned() + } else { + combine_strs_with_missing_comments( + context, + &attrs_str, + "let ", + mk_sp( + self.attrs.last().map(|a| a.span.hi()).unwrap(), + self.span.lo(), + ), + shape, + false, + )? + }; + + // 4 = "let ".len() + let pat_shape = shape.offset_left(4)?; + // 1 = ; + let pat_shape = pat_shape.sub_width(1)?; + let pat_str = self.pat.rewrite(context, pat_shape)?; + result.push_str(&pat_str); + + // String that is placed within the assignment pattern and expression. + let infix = { + let mut infix = String::with_capacity(32); + + if let Some(ref ty) = self.ty { + let separator = type_annotation_separator(context.config); + let ty_shape = if pat_str.contains('\n') { + shape.with_max_width(context.config) + } else { + shape + } + .offset_left(last_line_width(&result) + separator.len())? + // 2 = ` =` + .sub_width(2)?; + + let rewrite = ty.rewrite(context, ty_shape)?; + + infix.push_str(separator); + infix.push_str(&rewrite); + } + + if self.init.is_some() { + infix.push_str(" ="); + } + + infix + }; + + result.push_str(&infix); + + if let Some(ref ex) = self.init { + // 1 = trailing semicolon; + let nested_shape = shape.sub_width(1)?; + + result = rewrite_assign_rhs(context, result, &**ex, nested_shape)?; + } + + result.push(';'); + Some(result) + } +} + +// FIXME convert to using rewrite style rather than visitor +// FIXME format modules in this style +#[allow(dead_code)] +#[derive(Debug)] +struct Item<'a> { + unsafety: ast::Unsafe, + abi: Cow<'static, str>, + vis: Option<&'a ast::Visibility>, + body: Vec>, + span: Span, +} + +impl<'a> Item<'a> { + fn from_foreign_mod(fm: &'a ast::ForeignMod, span: Span, config: &Config) -> Item<'a> { + Item { + unsafety: fm.unsafety, + abi: format_extern( + ast::Extern::from_abi(fm.abi), + config.force_explicit_abi(), + true, + ), + vis: None, + body: fm + .items + .iter() + .map(|i| BodyElement::ForeignItem(i)) + .collect(), + span, + } + } +} + +#[derive(Debug)] +enum BodyElement<'a> { + // Stmt(&'a ast::Stmt), + // Field(&'a ast::Field), + // Variant(&'a ast::Variant), + // Item(&'a ast::Item), + ForeignItem(&'a ast::ForeignItem), +} + +/// Represents a fn's signature. +pub(crate) struct FnSig<'a> { + decl: &'a ast::FnDecl, + generics: &'a ast::Generics, + ext: ast::Extern, + is_async: Cow<'a, ast::Async>, + constness: ast::Const, + defaultness: ast::Defaultness, + unsafety: ast::Unsafe, + visibility: ast::Visibility, +} + +impl<'a> FnSig<'a> { + pub(crate) fn from_method_sig( + method_sig: &'a ast::FnSig, + generics: &'a ast::Generics, + visibility: ast::Visibility, + ) -> FnSig<'a> { + FnSig { + unsafety: method_sig.header.unsafety, + is_async: Cow::Borrowed(&method_sig.header.asyncness), + constness: method_sig.header.constness, + defaultness: ast::Defaultness::Final, + ext: method_sig.header.ext, + decl: &*method_sig.decl, + generics, + visibility, + } + } + + pub(crate) fn from_fn_kind( + fn_kind: &'a visit::FnKind<'_>, + generics: &'a ast::Generics, + decl: &'a ast::FnDecl, + defaultness: ast::Defaultness, + ) -> FnSig<'a> { + match *fn_kind { + visit::FnKind::Fn(fn_ctxt, _, fn_sig, vis, _) => match fn_ctxt { + visit::FnCtxt::Assoc(..) => { + let mut fn_sig = FnSig::from_method_sig(fn_sig, generics, vis.clone()); + fn_sig.defaultness = defaultness; + fn_sig + } + _ => FnSig { + decl, + generics, + ext: fn_sig.header.ext, + constness: fn_sig.header.constness, + is_async: Cow::Borrowed(&fn_sig.header.asyncness), + defaultness, + unsafety: fn_sig.header.unsafety, + visibility: vis.clone(), + }, + }, + _ => unreachable!(), + } + } + + fn to_str(&self, context: &RewriteContext<'_>) -> String { + let mut result = String::with_capacity(128); + // Vis defaultness constness unsafety abi. + result.push_str(&*format_visibility(context, &self.visibility)); + result.push_str(format_defaultness(self.defaultness)); + result.push_str(format_constness(self.constness)); + result.push_str(format_async(&self.is_async)); + result.push_str(format_unsafety(self.unsafety)); + result.push_str(&format_extern( + self.ext, + context.config.force_explicit_abi(), + false, + )); + result + } +} + +impl<'a> FmtVisitor<'a> { + fn format_item(&mut self, item: &Item<'_>) { + self.buffer.push_str(format_unsafety(item.unsafety)); + self.buffer.push_str(&item.abi); + + let snippet = self.snippet(item.span); + let brace_pos = snippet.find_uncommented("{").unwrap(); + + self.push_str("{"); + if !item.body.is_empty() || contains_comment(&snippet[brace_pos..]) { + // FIXME: this skips comments between the extern keyword and the opening + // brace. + self.last_pos = item.span.lo() + BytePos(brace_pos as u32 + 1); + self.block_indent = self.block_indent.block_indent(self.config); + + if !item.body.is_empty() { + for item in &item.body { + self.format_body_element(item); + } + } + + self.format_missing_no_indent(item.span.hi() - BytePos(1)); + self.block_indent = self.block_indent.block_unindent(self.config); + let indent_str = self.block_indent.to_string(self.config); + self.push_str(&indent_str); + } + + self.push_str("}"); + self.last_pos = item.span.hi(); + } + + fn format_body_element(&mut self, element: &BodyElement<'_>) { + match *element { + BodyElement::ForeignItem(item) => self.format_foreign_item(item), + } + } + + pub(crate) fn format_foreign_mod(&mut self, fm: &ast::ForeignMod, span: Span) { + let item = Item::from_foreign_mod(fm, span, self.config); + self.format_item(&item); + } + + fn format_foreign_item(&mut self, item: &ast::ForeignItem) { + let rewrite = item.rewrite(&self.get_context(), self.shape()); + let hi = item.span.hi(); + let span = if item.attrs.is_empty() { + item.span + } else { + mk_sp(item.attrs[0].span.lo(), hi) + }; + self.push_rewrite(span, rewrite); + self.last_pos = hi; + } + + pub(crate) fn rewrite_fn_before_block( + &mut self, + indent: Indent, + ident: symbol::Ident, + fn_sig: &FnSig<'_>, + span: Span, + ) -> Option<(String, FnBraceStyle)> { + let context = self.get_context(); + + let mut fn_brace_style = newline_for_brace(self.config, &fn_sig.generics.where_clause); + let (result, _, force_newline_brace) = + rewrite_fn_base(&context, indent, ident, fn_sig, span, fn_brace_style)?; + + // 2 = ` {` + if self.config.brace_style() == BraceStyle::AlwaysNextLine + || force_newline_brace + || last_line_width(&result) + 2 > self.shape().width + { + fn_brace_style = FnBraceStyle::NextLine + } + + Some((result, fn_brace_style)) + } + + pub(crate) fn rewrite_required_fn( + &mut self, + indent: Indent, + ident: symbol::Ident, + sig: &ast::FnSig, + generics: &ast::Generics, + span: Span, + ) -> Option { + // Drop semicolon or it will be interpreted as comment. + let span = mk_sp(span.lo(), span.hi() - BytePos(1)); + let context = self.get_context(); + + let (mut result, ends_with_comment, _) = rewrite_fn_base( + &context, + indent, + ident, + &FnSig::from_method_sig(sig, generics, DEFAULT_VISIBILITY), + span, + FnBraceStyle::None, + )?; + + // If `result` ends with a comment, then remember to add a newline + if ends_with_comment { + result.push_str(&indent.to_string_with_newline(context.config)); + } + + // Re-attach semicolon + result.push(';'); + + Some(result) + } + + pub(crate) fn single_line_fn( + &self, + fn_str: &str, + block: &ast::Block, + inner_attrs: Option<&[ast::Attribute]>, + ) -> Option { + if fn_str.contains('\n') || inner_attrs.map_or(false, |a| !a.is_empty()) { + return None; + } + + let context = self.get_context(); + + if self.config.empty_item_single_line() + && is_empty_block(&context, block, None) + && self.block_indent.width() + fn_str.len() + 3 <= self.config.max_width() + && !last_line_contains_single_line_comment(fn_str) + { + return Some(format!("{} {{}}", fn_str)); + } + + if !self.config.fn_single_line() || !is_simple_block_stmt(&context, block, None) { + return None; + } + + let res = Stmt::from_ast_node(block.stmts.first()?, true) + .rewrite(&self.get_context(), self.shape())?; + + let width = self.block_indent.width() + fn_str.len() + res.len() + 5; + if !res.contains('\n') && width <= self.config.max_width() { + Some(format!("{} {{ {} }}", fn_str, res)) + } else { + None + } + } + + pub(crate) fn visit_static(&mut self, static_parts: &StaticParts<'_>) { + let rewrite = rewrite_static(&self.get_context(), static_parts, self.block_indent); + self.push_rewrite(static_parts.span, rewrite); + } + + pub(crate) fn visit_struct(&mut self, struct_parts: &StructParts<'_>) { + let is_tuple = match struct_parts.def { + ast::VariantData::Tuple(..) => true, + _ => false, + }; + let rewrite = format_struct(&self.get_context(), struct_parts, self.block_indent, None) + .map(|s| if is_tuple { s + ";" } else { s }); + self.push_rewrite(struct_parts.span, rewrite); + } + + pub(crate) fn visit_enum( + &mut self, + ident: symbol::Ident, + vis: &ast::Visibility, + enum_def: &ast::EnumDef, + generics: &ast::Generics, + span: Span, + ) { + let enum_header = + format_header(&self.get_context(), "enum ", ident, vis, self.block_indent); + self.push_str(&enum_header); + + let enum_snippet = self.snippet(span); + let brace_pos = enum_snippet.find_uncommented("{").unwrap(); + let body_start = span.lo() + BytePos(brace_pos as u32 + 1); + let generics_str = format_generics( + &self.get_context(), + generics, + self.config.brace_style(), + if enum_def.variants.is_empty() { + BracePos::ForceSameLine + } else { + BracePos::Auto + }, + self.block_indent, + // make a span that starts right after `enum Foo` + mk_sp(ident.span.hi(), body_start), + last_line_width(&enum_header), + ) + .unwrap(); + self.push_str(&generics_str); + + self.last_pos = body_start; + + match self.format_variant_list(enum_def, body_start, span.hi()) { + Some(ref s) if enum_def.variants.is_empty() => self.push_str(s), + rw => { + self.push_rewrite(mk_sp(body_start, span.hi()), rw); + self.block_indent = self.block_indent.block_unindent(self.config); + } + } + } + + // Format the body of an enum definition + fn format_variant_list( + &mut self, + enum_def: &ast::EnumDef, + body_lo: BytePos, + body_hi: BytePos, + ) -> Option { + if enum_def.variants.is_empty() { + let mut buffer = String::with_capacity(128); + // 1 = "}" + let span = mk_sp(body_lo, body_hi - BytePos(1)); + format_empty_struct_or_tuple( + &self.get_context(), + span, + self.block_indent, + &mut buffer, + "", + "}", + ); + return Some(buffer); + } + let mut result = String::with_capacity(1024); + let original_offset = self.block_indent; + self.block_indent = self.block_indent.block_indent(self.config); + + // If enum variants have discriminants, try to vertically align those, + // provided the discrims are not shifted too much to the right + let align_threshold: usize = self.config.enum_discrim_align_threshold(); + let discr_ident_lens: Vec = enum_def + .variants + .iter() + .filter(|var| var.disr_expr.is_some()) + .map(|var| rewrite_ident(&self.get_context(), var.ident).len()) + .collect(); + // cut the list at the point of longest discrim shorter than the threshold + // All of the discrims under the threshold will get padded, and all above - left as is. + let pad_discrim_ident_to = *discr_ident_lens + .iter() + .filter(|&l| *l <= align_threshold) + .max() + .unwrap_or(&0); + + let itemize_list_with = |one_line_width: usize| { + itemize_list( + self.snippet_provider, + enum_def.variants.iter(), + "}", + ",", + |f| { + if !f.attrs.is_empty() { + f.attrs[0].span.lo() + } else { + f.span.lo() + } + }, + |f| f.span.hi(), + |f| self.format_variant(f, one_line_width, pad_discrim_ident_to), + body_lo, + body_hi, + false, + ) + .collect() + }; + let mut items: Vec<_> = + itemize_list_with(self.config.width_heuristics().struct_variant_width); + // If one of the variants use multiple lines, use multi-lined formatting for all variants. + let has_multiline_variant = items.iter().any(|item| item.inner_as_ref().contains('\n')); + let has_single_line_variant = items.iter().any(|item| !item.inner_as_ref().contains('\n')); + if has_multiline_variant && has_single_line_variant { + items = itemize_list_with(0); + } + + let shape = self.shape().sub_width(2)?; + let fmt = ListFormatting::new(shape, self.config) + .trailing_separator(self.config.trailing_comma()) + .preserve_newline(true); + + let list = write_list(&items, &fmt)?; + result.push_str(&list); + result.push_str(&original_offset.to_string_with_newline(self.config)); + result.push('}'); + Some(result) + } + + // Variant of an enum. + fn format_variant( + &self, + field: &ast::Variant, + one_line_width: usize, + pad_discrim_ident_to: usize, + ) -> Option { + if contains_skip(&field.attrs) { + let lo = field.attrs[0].span.lo(); + let span = mk_sp(lo, field.span.hi()); + return Some(self.snippet(span).to_owned()); + } + + let context = self.get_context(); + // 1 = ',' + let shape = self.shape().sub_width(1)?; + let attrs_str = field.attrs.rewrite(&context, shape)?; + let lo = field + .attrs + .last() + .map_or(field.span.lo(), |attr| attr.span.hi()); + let span = mk_sp(lo, field.span.lo()); + + let variant_body = match field.data { + ast::VariantData::Tuple(..) | ast::VariantData::Struct(..) => format_struct( + &context, + &StructParts::from_variant(field), + self.block_indent, + Some(one_line_width), + )?, + ast::VariantData::Unit(..) => rewrite_ident(&context, field.ident).to_owned(), + }; + + let variant_body = if let Some(ref expr) = field.disr_expr { + let lhs = format!("{:1$} =", variant_body, pad_discrim_ident_to); + rewrite_assign_rhs_with( + &context, + lhs, + &*expr.value, + shape, + RhsTactics::AllowOverflow, + )? + } else { + variant_body + }; + + combine_strs_with_missing_comments(&context, &attrs_str, &variant_body, span, shape, false) + } + + fn visit_impl_items(&mut self, items: &[ptr::P]) { + if self.get_context().config.reorder_impl_items() { + // Create visitor for each items, then reorder them. + let mut buffer = vec![]; + for item in items { + self.visit_impl_item(item); + buffer.push((self.buffer.clone(), item.clone())); + self.buffer.clear(); + } + + fn is_type(ty: &Option>) -> bool { + if let Some(lty) = ty { + if let ast::TyKind::ImplTrait(..) = lty.kind { + return false; + } + } + true + } + + fn is_opaque(ty: &Option>) -> bool { + !is_type(ty) + } + + fn both_type( + a: &Option>, + b: &Option>, + ) -> bool { + is_type(a) && is_type(b) + } + + fn both_opaque( + a: &Option>, + b: &Option>, + ) -> bool { + is_opaque(a) && is_opaque(b) + } + + // In rustc-ap-v638 the `OpaqueTy` AssocItemKind variant was removed but + // we still need to differentiate to maintain sorting order. + + // type -> opaque -> const -> macro -> method + use crate::ast::AssocItemKind::*; + fn need_empty_line(a: &ast::AssocItemKind, b: &ast::AssocItemKind) -> bool { + match (a, b) { + (TyAlias(lty), TyAlias(rty)) + if both_type(<y.3, &rty.3) || both_opaque(<y.3, &rty.3) => + { + false + } + (Const(..), Const(..)) => false, + _ => true, + } + } + + buffer.sort_by(|(_, a), (_, b)| match (&a.kind, &b.kind) { + (TyAlias(lty), TyAlias(rty)) + if both_type(<y.3, &rty.3) || both_opaque(<y.3, &rty.3) => + { + a.ident.as_str().cmp(&b.ident.as_str()) + } + (Const(..), Const(..)) | (MacCall(..), MacCall(..)) => { + a.ident.as_str().cmp(&b.ident.as_str()) + } + (Fn(..), Fn(..)) => a.span.lo().cmp(&b.span.lo()), + (TyAlias(ty), _) if is_type(&ty.3) => Ordering::Less, + (_, TyAlias(ty)) if is_type(&ty.3) => Ordering::Greater, + (TyAlias(..), _) => Ordering::Less, + (_, TyAlias(..)) => Ordering::Greater, + (Const(..), _) => Ordering::Less, + (_, Const(..)) => Ordering::Greater, + (MacCall(..), _) => Ordering::Less, + (_, MacCall(..)) => Ordering::Greater, + }); + let mut prev_kind = None; + for (buf, item) in buffer { + // Make sure that there are at least a single empty line between + // different impl items. + if prev_kind + .as_ref() + .map_or(false, |prev_kind| need_empty_line(prev_kind, &item.kind)) + { + self.push_str("\n"); + } + let indent_str = self.block_indent.to_string_with_newline(self.config); + self.push_str(&indent_str); + self.push_str(buf.trim()); + prev_kind = Some(item.kind.clone()); + } + } else { + for item in items { + self.visit_impl_item(item); + } + } + } +} + +pub(crate) fn format_impl( + context: &RewriteContext<'_>, + item: &ast::Item, + offset: Indent, +) -> Option { + if let ast::ItemKind::Impl(impl_kind) = &item.kind { + let ast::ImplKind { + ref generics, + ref self_ty, + ref items, + .. + } = **impl_kind; + let mut result = String::with_capacity(128); + let ref_and_type = format_impl_ref_and_type(context, item, offset)?; + let sep = offset.to_string_with_newline(context.config); + result.push_str(&ref_and_type); + + let where_budget = if result.contains('\n') { + context.config.max_width() + } else { + context.budget(last_line_width(&result)) + }; + + let mut option = WhereClauseOption::snuggled(&ref_and_type); + let snippet = context.snippet(item.span); + let open_pos = snippet.find_uncommented("{")? + 1; + if !contains_comment(&snippet[open_pos..]) + && items.is_empty() + && generics.where_clause.predicates.len() == 1 + && !result.contains('\n') + { + option.suppress_comma(); + option.snuggle(); + option.allow_single_line(); + } + + let missing_span = mk_sp(self_ty.span.hi(), item.span.hi()); + let where_span_end = context.snippet_provider.opt_span_before(missing_span, "{"); + let where_clause_str = rewrite_where_clause( + context, + &generics.where_clause, + context.config.brace_style(), + Shape::legacy(where_budget, offset.block_only()), + false, + "{", + where_span_end, + self_ty.span.hi(), + option, + )?; + + // If there is no where-clause, we may have missing comments between the trait name and + // the opening brace. + if generics.where_clause.predicates.is_empty() { + if let Some(hi) = where_span_end { + match recover_missing_comment_in_span( + mk_sp(self_ty.span.hi(), hi), + Shape::indented(offset, context.config), + context, + last_line_width(&result), + ) { + Some(ref missing_comment) if !missing_comment.is_empty() => { + result.push_str(missing_comment); + } + _ => (), + } + } + } + + if is_impl_single_line(context, items.as_slice(), &result, &where_clause_str, item)? { + result.push_str(&where_clause_str); + if where_clause_str.contains('\n') || last_line_contains_single_line_comment(&result) { + // if the where_clause contains extra comments AND + // there is only one where-clause predicate + // recover the suppressed comma in single line where_clause formatting + if generics.where_clause.predicates.len() == 1 { + result.push_str(","); + } + result.push_str(&format!("{}{{{}}}", sep, sep)); + } else { + result.push_str(" {}"); + } + return Some(result); + } + + result.push_str(&where_clause_str); + + let need_newline = last_line_contains_single_line_comment(&result) || result.contains('\n'); + match context.config.brace_style() { + _ if need_newline => result.push_str(&sep), + BraceStyle::AlwaysNextLine => result.push_str(&sep), + BraceStyle::PreferSameLine => result.push(' '), + BraceStyle::SameLineWhere => { + if !where_clause_str.is_empty() { + result.push_str(&sep); + } else { + result.push(' '); + } + } + } + + result.push('{'); + // this is an impl body snippet(impl SampleImpl { /* here */ }) + let lo = max(self_ty.span.hi(), generics.where_clause.span.hi()); + let snippet = context.snippet(mk_sp(lo, item.span.hi())); + let open_pos = snippet.find_uncommented("{")? + 1; + + if !items.is_empty() || contains_comment(&snippet[open_pos..]) { + let mut visitor = FmtVisitor::from_context(context); + let item_indent = offset.block_only().block_indent(context.config); + visitor.block_indent = item_indent; + visitor.last_pos = lo + BytePos(open_pos as u32); + + visitor.visit_attrs(&item.attrs, ast::AttrStyle::Inner); + visitor.visit_impl_items(items); + + visitor.format_missing(item.span.hi() - BytePos(1)); + + let inner_indent_str = visitor.block_indent.to_string_with_newline(context.config); + let outer_indent_str = offset.block_only().to_string_with_newline(context.config); + + result.push_str(&inner_indent_str); + result.push_str(visitor.buffer.trim()); + result.push_str(&outer_indent_str); + } else if need_newline || !context.config.empty_item_single_line() { + result.push_str(&sep); + } + + result.push('}'); + + Some(result) + } else { + unreachable!(); + } +} + +fn is_impl_single_line( + context: &RewriteContext<'_>, + items: &[ptr::P], + result: &str, + where_clause_str: &str, + item: &ast::Item, +) -> Option { + let snippet = context.snippet(item.span); + let open_pos = snippet.find_uncommented("{")? + 1; + + Some( + context.config.empty_item_single_line() + && items.is_empty() + && !result.contains('\n') + && result.len() + where_clause_str.len() <= context.config.max_width() + && !contains_comment(&snippet[open_pos..]), + ) +} + +fn format_impl_ref_and_type( + context: &RewriteContext<'_>, + item: &ast::Item, + offset: Indent, +) -> Option { + if let ast::ItemKind::Impl(impl_kind) = &item.kind { + let ast::ImplKind { + unsafety, + polarity, + defaultness, + constness, + ref generics, + of_trait: ref trait_ref, + ref self_ty, + .. + } = **impl_kind; + let mut result = String::with_capacity(128); + + result.push_str(&format_visibility(context, &item.vis)); + result.push_str(format_defaultness(defaultness)); + result.push_str(format_unsafety(unsafety)); + + let shape = if context.config.version() == Version::Two { + Shape::indented(offset + last_line_width(&result), context.config) + } else { + generics_shape_from_config( + context.config, + Shape::indented(offset + last_line_width(&result), context.config), + 0, + )? + }; + let generics_str = rewrite_generics(context, "impl", generics, shape)?; + result.push_str(&generics_str); + result.push_str(format_constness_right(constness)); + + let polarity_str = match polarity { + ast::ImplPolarity::Negative(_) => "!", + ast::ImplPolarity::Positive => "", + }; + + let polarity_overhead; + let trait_ref_overhead; + if let Some(ref trait_ref) = *trait_ref { + let result_len = last_line_width(&result); + result.push_str(&rewrite_trait_ref( + context, + trait_ref, + offset, + polarity_str, + result_len, + )?); + polarity_overhead = 0; // already written + trait_ref_overhead = " for".len(); + } else { + polarity_overhead = polarity_str.len(); + trait_ref_overhead = 0; + } + + // Try to put the self type in a single line. + let curly_brace_overhead = if generics.where_clause.predicates.is_empty() { + // If there is no where-clause adapt budget for type formatting to take space and curly + // brace into account. + match context.config.brace_style() { + BraceStyle::AlwaysNextLine => 0, + _ => 2, + } + } else { + 0 + }; + let used_space = last_line_width(&result) + + polarity_overhead + + trait_ref_overhead + + curly_brace_overhead; + // 1 = space before the type. + let budget = context.budget(used_space + 1); + if let Some(self_ty_str) = self_ty.rewrite(context, Shape::legacy(budget, offset)) { + if !self_ty_str.contains('\n') { + if trait_ref.is_some() { + result.push_str(" for "); + } else { + result.push(' '); + result.push_str(polarity_str); + } + result.push_str(&self_ty_str); + return Some(result); + } + } + + // Couldn't fit the self type on a single line, put it on a new line. + result.push('\n'); + // Add indentation of one additional tab. + let new_line_offset = offset.block_indent(context.config); + result.push_str(&new_line_offset.to_string(context.config)); + if trait_ref.is_some() { + result.push_str("for "); + } else { + result.push_str(polarity_str); + } + let budget = context.budget(last_line_width(&result) + polarity_overhead); + let type_offset = match context.config.indent_style() { + IndentStyle::Visual => new_line_offset + trait_ref_overhead, + IndentStyle::Block => new_line_offset, + }; + result.push_str(&*self_ty.rewrite(context, Shape::legacy(budget, type_offset))?); + Some(result) + } else { + unreachable!(); + } +} + +fn rewrite_trait_ref( + context: &RewriteContext<'_>, + trait_ref: &ast::TraitRef, + offset: Indent, + polarity_str: &str, + result_len: usize, +) -> Option { + // 1 = space between generics and trait_ref + let used_space = 1 + polarity_str.len() + result_len; + let shape = Shape::indented(offset + used_space, context.config); + if let Some(trait_ref_str) = trait_ref.rewrite(context, shape) { + if !trait_ref_str.contains('\n') { + return Some(format!(" {}{}", polarity_str, trait_ref_str)); + } + } + // We could not make enough space for trait_ref, so put it on new line. + let offset = offset.block_indent(context.config); + let shape = Shape::indented(offset, context.config); + let trait_ref_str = trait_ref.rewrite(context, shape)?; + Some(format!( + "{}{}{}", + offset.to_string_with_newline(context.config), + polarity_str, + trait_ref_str + )) +} + +pub(crate) struct StructParts<'a> { + prefix: &'a str, + ident: symbol::Ident, + vis: &'a ast::Visibility, + def: &'a ast::VariantData, + generics: Option<&'a ast::Generics>, + span: Span, +} + +impl<'a> StructParts<'a> { + fn format_header(&self, context: &RewriteContext<'_>, offset: Indent) -> String { + format_header(context, self.prefix, self.ident, self.vis, offset) + } + + fn from_variant(variant: &'a ast::Variant) -> Self { + StructParts { + prefix: "", + ident: variant.ident, + vis: &DEFAULT_VISIBILITY, + def: &variant.data, + generics: None, + span: variant.span, + } + } + + pub(crate) fn from_item(item: &'a ast::Item) -> Self { + let (prefix, def, generics) = match item.kind { + ast::ItemKind::Struct(ref def, ref generics) => ("struct ", def, generics), + ast::ItemKind::Union(ref def, ref generics) => ("union ", def, generics), + _ => unreachable!(), + }; + StructParts { + prefix, + ident: item.ident, + vis: &item.vis, + def, + generics: Some(generics), + span: item.span, + } + } +} + +fn format_struct( + context: &RewriteContext<'_>, + struct_parts: &StructParts<'_>, + offset: Indent, + one_line_width: Option, +) -> Option { + match *struct_parts.def { + ast::VariantData::Unit(..) => format_unit_struct(context, struct_parts, offset), + ast::VariantData::Tuple(ref fields, _) => { + format_tuple_struct(context, struct_parts, fields, offset) + } + ast::VariantData::Struct(ref fields, _) => { + format_struct_struct(context, struct_parts, fields, offset, one_line_width) + } + } +} + +pub(crate) fn format_trait( + context: &RewriteContext<'_>, + item: &ast::Item, + offset: Indent, +) -> Option { + if let ast::ItemKind::Trait(trait_kind) = &item.kind { + let ast::TraitKind(is_auto, unsafety, ref generics, ref generic_bounds, ref trait_items) = + **trait_kind; + let mut result = String::with_capacity(128); + let header = format!( + "{}{}{}trait ", + format_visibility(context, &item.vis), + format_unsafety(unsafety), + format_auto(is_auto), + ); + result.push_str(&header); + + let body_lo = context.snippet_provider.span_after(item.span, "{"); + + let shape = Shape::indented(offset, context.config).offset_left(result.len())?; + let generics_str = + rewrite_generics(context, rewrite_ident(context, item.ident), generics, shape)?; + result.push_str(&generics_str); + + // FIXME(#2055): rustfmt fails to format when there are comments between trait bounds. + if !generic_bounds.is_empty() { + let ident_hi = context + .snippet_provider + .span_after(item.span, &item.ident.as_str()); + let bound_hi = generic_bounds.last().unwrap().span().hi(); + let snippet = context.snippet(mk_sp(ident_hi, bound_hi)); + if contains_comment(snippet) { + return None; + } + + result = rewrite_assign_rhs_with( + context, + result + ":", + generic_bounds, + shape, + RhsTactics::ForceNextLineWithoutIndent, + )?; + } + + // Rewrite where-clause. + if !generics.where_clause.predicates.is_empty() { + let where_on_new_line = context.config.indent_style() != IndentStyle::Block; + + let where_budget = context.budget(last_line_width(&result)); + let pos_before_where = if generic_bounds.is_empty() { + generics.where_clause.span.lo() + } else { + generic_bounds[generic_bounds.len() - 1].span().hi() + }; + let option = WhereClauseOption::snuggled(&generics_str); + let where_clause_str = rewrite_where_clause( + context, + &generics.where_clause, + context.config.brace_style(), + Shape::legacy(where_budget, offset.block_only()), + where_on_new_line, + "{", + None, + pos_before_where, + option, + )?; + // If the where-clause cannot fit on the same line, + // put the where-clause on a new line + if !where_clause_str.contains('\n') + && last_line_width(&result) + where_clause_str.len() + offset.width() + > context.config.comment_width() + { + let width = offset.block_indent + context.config.tab_spaces() - 1; + let where_indent = Indent::new(0, width); + result.push_str(&where_indent.to_string_with_newline(context.config)); + } + result.push_str(&where_clause_str); + } else { + let item_snippet = context.snippet(item.span); + if let Some(lo) = item_snippet.find('/') { + // 1 = `{` + let comment_hi = body_lo - BytePos(1); + let comment_lo = item.span.lo() + BytePos(lo as u32); + if comment_lo < comment_hi { + match recover_missing_comment_in_span( + mk_sp(comment_lo, comment_hi), + Shape::indented(offset, context.config), + context, + last_line_width(&result), + ) { + Some(ref missing_comment) if !missing_comment.is_empty() => { + result.push_str(missing_comment); + } + _ => (), + } + } + } + } + + match context.config.brace_style() { + _ if last_line_contains_single_line_comment(&result) + || last_line_width(&result) + 2 > context.budget(offset.width()) => + { + result.push_str(&offset.to_string_with_newline(context.config)); + } + BraceStyle::AlwaysNextLine => { + result.push_str(&offset.to_string_with_newline(context.config)); + } + BraceStyle::PreferSameLine => result.push(' '), + BraceStyle::SameLineWhere => { + if result.contains('\n') + || (!generics.where_clause.predicates.is_empty() && !trait_items.is_empty()) + { + result.push_str(&offset.to_string_with_newline(context.config)); + } else { + result.push(' '); + } + } + } + result.push('{'); + + let block_span = mk_sp(generics.where_clause.span.hi(), item.span.hi()); + let snippet = context.snippet(block_span); + let open_pos = snippet.find_uncommented("{")? + 1; + let outer_indent_str = offset.block_only().to_string_with_newline(context.config); + + if !trait_items.is_empty() || contains_comment(&snippet[open_pos..]) { + let mut visitor = FmtVisitor::from_context(context); + visitor.block_indent = offset.block_only().block_indent(context.config); + visitor.last_pos = block_span.lo() + BytePos(open_pos as u32); + + for item in trait_items { + visitor.visit_trait_item(item); + } + + visitor.format_missing(item.span.hi() - BytePos(1)); + + let inner_indent_str = visitor.block_indent.to_string_with_newline(context.config); + + result.push_str(&inner_indent_str); + result.push_str(visitor.buffer.trim()); + result.push_str(&outer_indent_str); + } else if result.contains('\n') { + result.push_str(&outer_indent_str); + } + + result.push('}'); + Some(result) + } else { + unreachable!(); + } +} + +struct OpaqueTypeBounds<'a> { + generic_bounds: &'a ast::GenericBounds, +} + +impl<'a> Rewrite for OpaqueTypeBounds<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + self.generic_bounds + .rewrite(context, shape) + .map(|s| format!("impl {}", s)) + } +} + +pub(crate) struct TraitAliasBounds<'a> { + generic_bounds: &'a ast::GenericBounds, + generics: &'a ast::Generics, +} + +impl<'a> Rewrite for TraitAliasBounds<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let generic_bounds_str = self.generic_bounds.rewrite(context, shape)?; + + let mut option = WhereClauseOption::new(true, WhereClauseSpace::None); + option.allow_single_line(); + + let where_str = rewrite_where_clause( + context, + &self.generics.where_clause, + context.config.brace_style(), + shape, + false, + ";", + None, + self.generics.where_clause.span.lo(), + option, + )?; + + let fits_single_line = !generic_bounds_str.contains('\n') + && !where_str.contains('\n') + && generic_bounds_str.len() + where_str.len() + 1 <= shape.width; + let space = if generic_bounds_str.is_empty() || where_str.is_empty() { + Cow::from("") + } else if fits_single_line { + Cow::from(" ") + } else { + shape.indent.to_string_with_newline(&context.config) + }; + + Some(format!("{}{}{}", generic_bounds_str, space, where_str)) + } +} + +pub(crate) fn format_trait_alias( + context: &RewriteContext<'_>, + ident: symbol::Ident, + vis: &ast::Visibility, + generics: &ast::Generics, + generic_bounds: &ast::GenericBounds, + shape: Shape, +) -> Option { + let alias = rewrite_ident(context, ident); + // 6 = "trait ", 2 = " =" + let g_shape = shape.offset_left(6)?.sub_width(2)?; + let generics_str = rewrite_generics(context, &alias, generics, g_shape)?; + let vis_str = format_visibility(context, vis); + let lhs = format!("{}trait {} =", vis_str, generics_str); + // 1 = ";" + let trait_alias_bounds = TraitAliasBounds { + generics, + generic_bounds, + }; + rewrite_assign_rhs(context, lhs, &trait_alias_bounds, shape.sub_width(1)?).map(|s| s + ";") +} + +fn format_unit_struct( + context: &RewriteContext<'_>, + p: &StructParts<'_>, + offset: Indent, +) -> Option { + let header_str = format_header(context, p.prefix, p.ident, p.vis, offset); + let generics_str = if let Some(generics) = p.generics { + let hi = context.snippet_provider.span_before(p.span, ";"); + format_generics( + context, + generics, + context.config.brace_style(), + BracePos::None, + offset, + // make a span that starts right after `struct Foo` + mk_sp(p.ident.span.hi(), hi), + last_line_width(&header_str), + )? + } else { + String::new() + }; + Some(format!("{}{};", header_str, generics_str)) +} + +pub(crate) fn format_struct_struct( + context: &RewriteContext<'_>, + struct_parts: &StructParts<'_>, + fields: &[ast::StructField], + offset: Indent, + one_line_width: Option, +) -> Option { + let mut result = String::with_capacity(1024); + let span = struct_parts.span; + + let header_str = struct_parts.format_header(context, offset); + result.push_str(&header_str); + + let header_hi = struct_parts.ident.span.hi(); + let body_lo = context.snippet_provider.span_after(span, "{"); + + let generics_str = match struct_parts.generics { + Some(g) => format_generics( + context, + g, + context.config.brace_style(), + if fields.is_empty() { + BracePos::ForceSameLine + } else { + BracePos::Auto + }, + offset, + // make a span that starts right after `struct Foo` + mk_sp(header_hi, body_lo), + last_line_width(&result), + )?, + None => { + // 3 = ` {}`, 2 = ` {`. + let overhead = if fields.is_empty() { 3 } else { 2 }; + if (context.config.brace_style() == BraceStyle::AlwaysNextLine && !fields.is_empty()) + || context.config.max_width() < overhead + result.len() + { + format!("\n{}{{", offset.block_only().to_string(context.config)) + } else { + " {".to_owned() + } + } + }; + // 1 = `}` + let overhead = if fields.is_empty() { 1 } else { 0 }; + let total_width = result.len() + generics_str.len() + overhead; + if !generics_str.is_empty() + && !generics_str.contains('\n') + && total_width > context.config.max_width() + { + result.push('\n'); + result.push_str(&offset.to_string(context.config)); + result.push_str(generics_str.trim_start()); + } else { + result.push_str(&generics_str); + } + + if fields.is_empty() { + let inner_span = mk_sp(body_lo, span.hi() - BytePos(1)); + format_empty_struct_or_tuple(context, inner_span, offset, &mut result, "", "}"); + return Some(result); + } + + // 3 = ` ` and ` }` + let one_line_budget = context.budget(result.len() + 3 + offset.width()); + let one_line_budget = + one_line_width.map_or(0, |one_line_width| min(one_line_width, one_line_budget)); + + let items_str = rewrite_with_alignment( + fields, + context, + Shape::indented(offset.block_indent(context.config), context.config).sub_width(1)?, + mk_sp(body_lo, span.hi()), + one_line_budget, + )?; + + if !items_str.contains('\n') + && !result.contains('\n') + && items_str.len() <= one_line_budget + && !last_line_contains_single_line_comment(&items_str) + { + Some(format!("{} {} }}", result, items_str)) + } else { + Some(format!( + "{}\n{}{}\n{}}}", + result, + offset + .block_indent(context.config) + .to_string(context.config), + items_str, + offset.to_string(context.config) + )) + } +} + +fn get_bytepos_after_visibility(vis: &ast::Visibility, default_span: Span) -> BytePos { + match vis.kind { + ast::VisibilityKind::Crate(..) | ast::VisibilityKind::Restricted { .. } => vis.span.hi(), + _ => default_span.lo(), + } +} + +// Format tuple or struct without any fields. We need to make sure that the comments +// inside the delimiters are preserved. +fn format_empty_struct_or_tuple( + context: &RewriteContext<'_>, + span: Span, + offset: Indent, + result: &mut String, + opener: &str, + closer: &str, +) { + // 3 = " {}" or "();" + let used_width = last_line_used_width(&result, offset.width()) + 3; + if used_width > context.config.max_width() { + result.push_str(&offset.to_string_with_newline(context.config)) + } + result.push_str(opener); + match rewrite_missing_comment(span, Shape::indented(offset, context.config), context) { + Some(ref s) if s.is_empty() => (), + Some(ref s) => { + if !is_single_line(s) || first_line_contains_single_line_comment(s) { + let nested_indent_str = offset + .block_indent(context.config) + .to_string_with_newline(context.config); + result.push_str(&nested_indent_str); + } + result.push_str(s); + if last_line_contains_single_line_comment(s) { + result.push_str(&offset.to_string_with_newline(context.config)); + } + } + None => result.push_str(context.snippet(span)), + } + result.push_str(closer); +} + +fn format_tuple_struct( + context: &RewriteContext<'_>, + struct_parts: &StructParts<'_>, + fields: &[ast::StructField], + offset: Indent, +) -> Option { + let mut result = String::with_capacity(1024); + let span = struct_parts.span; + + let header_str = struct_parts.format_header(context, offset); + result.push_str(&header_str); + + let body_lo = if fields.is_empty() { + let lo = get_bytepos_after_visibility(struct_parts.vis, span); + context + .snippet_provider + .span_after(mk_sp(lo, span.hi()), "(") + } else { + fields[0].span.lo() + }; + let body_hi = if fields.is_empty() { + context + .snippet_provider + .span_after(mk_sp(body_lo, span.hi()), ")") + } else { + // This is a dirty hack to work around a missing `)` from the span of the last field. + let last_arg_span = fields[fields.len() - 1].span; + context + .snippet_provider + .opt_span_after(mk_sp(last_arg_span.hi(), span.hi()), ")") + .unwrap_or_else(|| last_arg_span.hi()) + }; + + let where_clause_str = match struct_parts.generics { + Some(generics) => { + let budget = context.budget(last_line_width(&header_str)); + let shape = Shape::legacy(budget, offset); + let generics_str = rewrite_generics(context, "", generics, shape)?; + result.push_str(&generics_str); + + let where_budget = context.budget(last_line_width(&result)); + let option = WhereClauseOption::new(true, WhereClauseSpace::Newline); + rewrite_where_clause( + context, + &generics.where_clause, + context.config.brace_style(), + Shape::legacy(where_budget, offset.block_only()), + false, + ";", + None, + body_hi, + option, + )? + } + None => "".to_owned(), + }; + + if fields.is_empty() { + let body_hi = context + .snippet_provider + .span_before(mk_sp(body_lo, span.hi()), ")"); + let inner_span = mk_sp(body_lo, body_hi); + format_empty_struct_or_tuple(context, inner_span, offset, &mut result, "(", ")"); + } else { + let shape = Shape::indented(offset, context.config).sub_width(1)?; + result = overflow::rewrite_with_parens( + context, + &result, + fields.iter(), + shape, + span, + context.config.width_heuristics().fn_call_width, + None, + )?; + } + + if !where_clause_str.is_empty() + && !where_clause_str.contains('\n') + && (result.contains('\n') + || offset.block_indent + result.len() + where_clause_str.len() + 1 + > context.config.max_width()) + { + // We need to put the where-clause on a new line, but we didn't + // know that earlier, so the where-clause will not be indented properly. + result.push('\n'); + result.push_str( + &(offset.block_only() + (context.config.tab_spaces() - 1)).to_string(context.config), + ); + } + result.push_str(&where_clause_str); + + Some(result) +} + +fn rewrite_type( + context: &RewriteContext<'_>, + indent: Indent, + ident: symbol::Ident, + vis: &ast::Visibility, + generics: &ast::Generics, + generic_bounds_opt: Option<&ast::GenericBounds>, + rhs: Option<&R>, + span: Span, +) -> Option { + let mut result = String::with_capacity(128); + result.push_str(&format!("{}type ", format_visibility(context, vis))); + let ident_str = rewrite_ident(context, ident); + + if generics.params.is_empty() { + result.push_str(ident_str) + } else { + // 2 = `= ` + let g_shape = Shape::indented(indent, context.config) + .offset_left(result.len())? + .sub_width(2)?; + let generics_str = rewrite_generics(context, ident_str, generics, g_shape)?; + result.push_str(&generics_str); + } + + if let Some(bounds) = generic_bounds_opt { + if !bounds.is_empty() { + // 2 = `: ` + let shape = Shape::indented(indent, context.config).offset_left(result.len() + 2)?; + let type_bounds = bounds.rewrite(context, shape).map(|s| format!(": {}", s))?; + result.push_str(&type_bounds); + } + } + + let where_budget = context.budget(last_line_width(&result)); + let mut option = WhereClauseOption::snuggled(&result); + if rhs.is_none() { + option.suppress_comma(); + } + let where_clause_str = rewrite_where_clause( + context, + &generics.where_clause, + context.config.brace_style(), + Shape::legacy(where_budget, indent), + false, + "=", + None, + generics.span.hi(), + option, + )?; + result.push_str(&where_clause_str); + + if let Some(ty) = rhs { + // If there's a where clause, add a newline before the assignment. Otherwise just add a + // space. + let has_where = !generics.where_clause.predicates.is_empty(); + if has_where { + result.push_str(&indent.to_string_with_newline(context.config)); + } else { + result.push(' '); + } + + let comment_span = context + .snippet_provider + .opt_span_before(span, "=") + .map(|op_lo| mk_sp(generics.where_clause.span.hi(), op_lo)); + + let lhs = match comment_span { + Some(comment_span) + if contains_comment(context.snippet_provider.span_to_snippet(comment_span)?) => + { + let comment_shape = if has_where { + Shape::indented(indent, context.config) + } else { + Shape::indented(indent, context.config) + .block_left(context.config.tab_spaces())? + }; + + combine_strs_with_missing_comments( + context, + result.trim_end(), + "=", + comment_span, + comment_shape, + true, + )? + } + _ => format!("{}=", result), + }; + + // 1 = `;` + let shape = Shape::indented(indent, context.config).sub_width(1)?; + rewrite_assign_rhs(context, lhs, &*ty, shape).map(|s| s + ";") + } else { + Some(format!("{};", result)) + } +} + +pub(crate) fn rewrite_opaque_type( + context: &RewriteContext<'_>, + indent: Indent, + ident: symbol::Ident, + generic_bounds: &ast::GenericBounds, + generics: &ast::Generics, + vis: &ast::Visibility, + span: Span, +) -> Option { + let opaque_type_bounds = OpaqueTypeBounds { generic_bounds }; + rewrite_type( + context, + indent, + ident, + vis, + generics, + Some(generic_bounds), + Some(&opaque_type_bounds), + span, + ) +} + +fn type_annotation_spacing(config: &Config) -> (&str, &str) { + ( + if config.space_before_colon() { " " } else { "" }, + if config.space_after_colon() { " " } else { "" }, + ) +} + +pub(crate) fn rewrite_struct_field_prefix( + context: &RewriteContext<'_>, + field: &ast::StructField, +) -> Option { + let vis = format_visibility(context, &field.vis); + let type_annotation_spacing = type_annotation_spacing(context.config); + Some(match field.ident { + Some(name) => format!( + "{}{}{}:", + vis, + rewrite_ident(context, name), + type_annotation_spacing.0 + ), + None => vis.to_string(), + }) +} + +impl Rewrite for ast::StructField { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + rewrite_struct_field(context, self, shape, 0) + } +} + +pub(crate) fn rewrite_struct_field( + context: &RewriteContext<'_>, + field: &ast::StructField, + shape: Shape, + lhs_max_width: usize, +) -> Option { + if contains_skip(&field.attrs) { + return Some(context.snippet(field.span()).to_owned()); + } + + let type_annotation_spacing = type_annotation_spacing(context.config); + let prefix = rewrite_struct_field_prefix(context, field)?; + + let attrs_str = field.attrs.rewrite(context, shape)?; + let attrs_extendable = field.ident.is_none() && is_attributes_extendable(&attrs_str); + let missing_span = if field.attrs.is_empty() { + mk_sp(field.span.lo(), field.span.lo()) + } else { + mk_sp(field.attrs.last().unwrap().span.hi(), field.span.lo()) + }; + let mut spacing = String::from(if field.ident.is_some() { + type_annotation_spacing.1 + } else { + "" + }); + // Try to put everything on a single line. + let attr_prefix = combine_strs_with_missing_comments( + context, + &attrs_str, + &prefix, + missing_span, + shape, + attrs_extendable, + )?; + let overhead = trimmed_last_line_width(&attr_prefix); + let lhs_offset = lhs_max_width.saturating_sub(overhead); + for _ in 0..lhs_offset { + spacing.push(' '); + } + // In this extreme case we will be missing a space between an attribute and a field. + if prefix.is_empty() && !attrs_str.is_empty() && attrs_extendable && spacing.is_empty() { + spacing.push(' '); + } + let orig_ty = shape + .offset_left(overhead + spacing.len()) + .and_then(|ty_shape| field.ty.rewrite(context, ty_shape)); + if let Some(ref ty) = orig_ty { + if !ty.contains('\n') { + return Some(attr_prefix + &spacing + ty); + } + } + + let is_prefix_empty = prefix.is_empty(); + // We must use multiline. We are going to put attributes and a field on different lines. + let field_str = rewrite_assign_rhs(context, prefix, &*field.ty, shape)?; + // Remove a leading white-space from `rewrite_assign_rhs()` when rewriting a tuple struct. + let field_str = if is_prefix_empty { + field_str.trim_start() + } else { + &field_str + }; + combine_strs_with_missing_comments(context, &attrs_str, field_str, missing_span, shape, false) +} + +pub(crate) struct StaticParts<'a> { + prefix: &'a str, + vis: &'a ast::Visibility, + ident: symbol::Ident, + ty: &'a ast::Ty, + mutability: ast::Mutability, + expr_opt: Option<&'a ptr::P>, + defaultness: Option, + span: Span, +} + +impl<'a> StaticParts<'a> { + pub(crate) fn from_item(item: &'a ast::Item) -> Self { + let (defaultness, prefix, ty, mutability, expr) = match item.kind { + ast::ItemKind::Static(ref ty, mutability, ref expr) => { + (None, "static", ty, mutability, expr) + } + ast::ItemKind::Const(defaultness, ref ty, ref expr) => { + (Some(defaultness), "const", ty, ast::Mutability::Not, expr) + } + _ => unreachable!(), + }; + StaticParts { + prefix, + vis: &item.vis, + ident: item.ident, + ty, + mutability, + expr_opt: expr.as_ref(), + defaultness, + span: item.span, + } + } + + pub(crate) fn from_trait_item(ti: &'a ast::AssocItem) -> Self { + let (defaultness, ty, expr_opt) = match ti.kind { + ast::AssocItemKind::Const(defaultness, ref ty, ref expr_opt) => { + (defaultness, ty, expr_opt) + } + _ => unreachable!(), + }; + StaticParts { + prefix: "const", + vis: &DEFAULT_VISIBILITY, + ident: ti.ident, + ty, + mutability: ast::Mutability::Not, + expr_opt: expr_opt.as_ref(), + defaultness: Some(defaultness), + span: ti.span, + } + } + + pub(crate) fn from_impl_item(ii: &'a ast::AssocItem) -> Self { + let (defaultness, ty, expr) = match ii.kind { + ast::AssocItemKind::Const(defaultness, ref ty, ref expr) => (defaultness, ty, expr), + _ => unreachable!(), + }; + StaticParts { + prefix: "const", + vis: &ii.vis, + ident: ii.ident, + ty, + mutability: ast::Mutability::Not, + expr_opt: expr.as_ref(), + defaultness: Some(defaultness), + span: ii.span, + } + } +} + +fn rewrite_static( + context: &RewriteContext<'_>, + static_parts: &StaticParts<'_>, + offset: Indent, +) -> Option { + let colon = colon_spaces(context.config); + let mut prefix = format!( + "{}{}{} {}{}{}", + format_visibility(context, static_parts.vis), + static_parts.defaultness.map_or("", format_defaultness), + static_parts.prefix, + format_mutability(static_parts.mutability), + rewrite_ident(context, static_parts.ident), + colon, + ); + // 2 = " =".len() + let ty_shape = + Shape::indented(offset.block_only(), context.config).offset_left(prefix.len() + 2)?; + let ty_str = match static_parts.ty.rewrite(context, ty_shape) { + Some(ty_str) => ty_str, + None => { + if prefix.ends_with(' ') { + prefix.pop(); + } + let nested_indent = offset.block_indent(context.config); + let nested_shape = Shape::indented(nested_indent, context.config); + let ty_str = static_parts.ty.rewrite(context, nested_shape)?; + format!( + "{}{}", + nested_indent.to_string_with_newline(context.config), + ty_str + ) + } + }; + + if let Some(expr) = static_parts.expr_opt { + let lhs = format!("{}{} =", prefix, ty_str); + // 1 = ; + let remaining_width = context.budget(offset.block_indent + 1); + rewrite_assign_rhs( + context, + lhs, + &**expr, + Shape::legacy(remaining_width, offset.block_only()), + ) + .and_then(|res| recover_comment_removed(res, static_parts.span, context)) + .map(|s| if s.ends_with(';') { s } else { s + ";" }) + } else { + Some(format!("{}{};", prefix, ty_str)) + } +} + +pub(crate) fn rewrite_type_alias( + ident: symbol::Ident, + ty_opt: Option<&ptr::P>, + generics: &ast::Generics, + generic_bounds_opt: Option<&ast::GenericBounds>, + context: &RewriteContext<'_>, + indent: Indent, + vis: &ast::Visibility, + span: Span, +) -> Option { + rewrite_type( + context, + indent, + ident, + vis, + generics, + generic_bounds_opt, + ty_opt, + span, + ) +} + +struct OpaqueType<'a> { + bounds: &'a ast::GenericBounds, +} + +impl<'a> Rewrite for OpaqueType<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let shape = shape.offset_left(5)?; // `impl ` + self.bounds + .rewrite(context, shape) + .map(|s| format!("impl {}", s)) + } +} + +pub(crate) fn rewrite_opaque_impl_type( + context: &RewriteContext<'_>, + ident: symbol::Ident, + generics: &ast::Generics, + generic_bounds: &ast::GenericBounds, + indent: Indent, +) -> Option { + let ident_str = rewrite_ident(context, ident); + // 5 = "type " + let generics_shape = Shape::indented(indent, context.config).offset_left(5)?; + let generics_str = rewrite_generics(context, ident_str, generics, generics_shape)?; + let prefix = format!("type {} =", generics_str); + let rhs = OpaqueType { + bounds: generic_bounds, + }; + + rewrite_assign_rhs( + context, + &prefix, + &rhs, + Shape::indented(indent, context.config).sub_width(1)?, + ) + .map(|s| s + ";") +} + +pub(crate) fn rewrite_associated_impl_type( + ident: symbol::Ident, + vis: &ast::Visibility, + defaultness: ast::Defaultness, + ty_opt: Option<&ptr::P>, + generics: &ast::Generics, + context: &RewriteContext<'_>, + indent: Indent, + span: Span, +) -> Option { + let result = rewrite_type_alias(ident, ty_opt, generics, None, context, indent, vis, span)?; + + match defaultness { + ast::Defaultness::Default(..) => Some(format!("default {}", result)), + _ => Some(result), + } +} + +impl Rewrite for ast::FnRetTy { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match *self { + ast::FnRetTy::Default(_) => Some(String::new()), + ast::FnRetTy::Ty(ref ty) => { + if context.config.version() == Version::One + || context.config.indent_style() == IndentStyle::Visual + { + let inner_width = shape.width.checked_sub(3)?; + return ty + .rewrite(context, Shape::legacy(inner_width, shape.indent + 3)) + .map(|r| format!("-> {}", r)); + } + + ty.rewrite(context, shape.offset_left(3)?) + .map(|s| format!("-> {}", s)) + } + } + } +} + +fn is_empty_infer(ty: &ast::Ty, pat_span: Span) -> bool { + match ty.kind { + ast::TyKind::Infer => ty.span.hi() == pat_span.hi(), + _ => false, + } +} + +/// Recover any missing comments between the param and the type. +/// +/// # Returns +/// +/// A 2-len tuple with the comment before the colon in first position, and the comment after the +/// colon in second position. +fn get_missing_param_comments( + context: &RewriteContext<'_>, + pat_span: Span, + ty_span: Span, + shape: Shape, +) -> (String, String) { + let missing_comment_span = mk_sp(pat_span.hi(), ty_span.lo()); + + let span_before_colon = { + let missing_comment_span_hi = context + .snippet_provider + .span_before(missing_comment_span, ":"); + mk_sp(pat_span.hi(), missing_comment_span_hi) + }; + let span_after_colon = { + let missing_comment_span_lo = context + .snippet_provider + .span_after(missing_comment_span, ":"); + mk_sp(missing_comment_span_lo, ty_span.lo()) + }; + + let comment_before_colon = rewrite_missing_comment(span_before_colon, shape, context) + .filter(|comment| !comment.is_empty()) + .map_or(String::new(), |comment| format!(" {}", comment)); + let comment_after_colon = rewrite_missing_comment(span_after_colon, shape, context) + .filter(|comment| !comment.is_empty()) + .map_or(String::new(), |comment| format!("{} ", comment)); + (comment_before_colon, comment_after_colon) +} + +impl Rewrite for ast::Param { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let param_attrs_result = self + .attrs + .rewrite(context, Shape::legacy(shape.width, shape.indent))?; + let (span, has_multiple_attr_lines) = if !self.attrs.is_empty() { + let num_attrs = self.attrs.len(); + ( + mk_sp(self.attrs[num_attrs - 1].span.hi(), self.pat.span.lo()), + param_attrs_result.contains("\n"), + ) + } else { + (mk_sp(self.span.lo(), self.span.lo()), false) + }; + + if let Some(ref explicit_self) = self.to_self() { + rewrite_explicit_self( + context, + explicit_self, + ¶m_attrs_result, + span, + shape, + has_multiple_attr_lines, + ) + } else if is_named_param(self) { + let mut result = combine_strs_with_missing_comments( + context, + ¶m_attrs_result, + &self + .pat + .rewrite(context, Shape::legacy(shape.width, shape.indent))?, + span, + shape, + !has_multiple_attr_lines, + )?; + + if !is_empty_infer(&*self.ty, self.pat.span) { + let (before_comment, after_comment) = + get_missing_param_comments(context, self.pat.span, self.ty.span, shape); + result.push_str(&before_comment); + result.push_str(colon_spaces(context.config)); + result.push_str(&after_comment); + let overhead = last_line_width(&result); + let max_width = shape.width.checked_sub(overhead)?; + let ty_str = self + .ty + .rewrite(context, Shape::legacy(max_width, shape.indent))?; + result.push_str(&ty_str); + } + + Some(result) + } else { + self.ty.rewrite(context, shape) + } + } +} + +fn rewrite_explicit_self( + context: &RewriteContext<'_>, + explicit_self: &ast::ExplicitSelf, + param_attrs: &str, + span: Span, + shape: Shape, + has_multiple_attr_lines: bool, +) -> Option { + match explicit_self.node { + ast::SelfKind::Region(lt, m) => { + let mut_str = format_mutability(m); + match lt { + Some(ref l) => { + let lifetime_str = l.rewrite( + context, + Shape::legacy(context.config.max_width(), Indent::empty()), + )?; + Some(combine_strs_with_missing_comments( + context, + ¶m_attrs, + &format!("&{} {}self", lifetime_str, mut_str), + span, + shape, + !has_multiple_attr_lines, + )?) + } + None => Some(combine_strs_with_missing_comments( + context, + ¶m_attrs, + &format!("&{}self", mut_str), + span, + shape, + !has_multiple_attr_lines, + )?), + } + } + ast::SelfKind::Explicit(ref ty, mutability) => { + let type_str = ty.rewrite( + context, + Shape::legacy(context.config.max_width(), Indent::empty()), + )?; + + Some(combine_strs_with_missing_comments( + context, + ¶m_attrs, + &format!("{}self: {}", format_mutability(mutability), type_str), + span, + shape, + !has_multiple_attr_lines, + )?) + } + ast::SelfKind::Value(mutability) => Some(combine_strs_with_missing_comments( + context, + ¶m_attrs, + &format!("{}self", format_mutability(mutability)), + span, + shape, + !has_multiple_attr_lines, + )?), + } +} + +pub(crate) fn span_lo_for_param(param: &ast::Param) -> BytePos { + if param.attrs.is_empty() { + if is_named_param(param) { + param.pat.span.lo() + } else { + param.ty.span.lo() + } + } else { + param.attrs[0].span.lo() + } +} + +pub(crate) fn span_hi_for_param(context: &RewriteContext<'_>, param: &ast::Param) -> BytePos { + match param.ty.kind { + ast::TyKind::Infer if context.snippet(param.ty.span) == "_" => param.ty.span.hi(), + ast::TyKind::Infer if is_named_param(param) => param.pat.span.hi(), + _ => param.ty.span.hi(), + } +} + +pub(crate) fn is_named_param(param: &ast::Param) -> bool { + if let ast::PatKind::Ident(_, ident, _) = param.pat.kind { + ident.name != symbol::kw::Empty + } else { + true + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum FnBraceStyle { + SameLine, + NextLine, + None, +} + +// Return type is (result, force_new_line_for_brace) +fn rewrite_fn_base( + context: &RewriteContext<'_>, + indent: Indent, + ident: symbol::Ident, + fn_sig: &FnSig<'_>, + span: Span, + fn_brace_style: FnBraceStyle, +) -> Option<(String, bool, bool)> { + let mut force_new_line_for_brace = false; + + let where_clause = &fn_sig.generics.where_clause; + + let mut result = String::with_capacity(1024); + result.push_str(&fn_sig.to_str(context)); + + // fn foo + result.push_str("fn "); + + // Generics. + let overhead = if let FnBraceStyle::SameLine = fn_brace_style { + // 4 = `() {` + 4 + } else { + // 2 = `()` + 2 + }; + let used_width = last_line_used_width(&result, indent.width()); + let one_line_budget = context.budget(used_width + overhead); + let shape = Shape { + width: one_line_budget, + indent, + offset: used_width, + }; + let fd = fn_sig.decl; + let generics_str = rewrite_generics( + context, + rewrite_ident(context, ident), + fn_sig.generics, + shape, + )?; + result.push_str(&generics_str); + + let snuggle_angle_bracket = generics_str + .lines() + .last() + .map_or(false, |l| l.trim_start().len() == 1); + + // Note that the width and indent don't really matter, we'll re-layout the + // return type later anyway. + let ret_str = fd + .output + .rewrite(context, Shape::indented(indent, context.config))?; + + let multi_line_ret_str = ret_str.contains('\n'); + let ret_str_len = if multi_line_ret_str { 0 } else { ret_str.len() }; + + // Params. + let (one_line_budget, multi_line_budget, mut param_indent) = compute_budgets_for_params( + context, + &result, + indent, + ret_str_len, + fn_brace_style, + multi_line_ret_str, + )?; + + debug!( + "rewrite_fn_base: one_line_budget: {}, multi_line_budget: {}, param_indent: {:?}", + one_line_budget, multi_line_budget, param_indent + ); + + result.push('('); + // Check if vertical layout was forced. + if one_line_budget == 0 + && !snuggle_angle_bracket + && context.config.indent_style() == IndentStyle::Visual + { + result.push_str(¶m_indent.to_string_with_newline(context.config)); + } + + // Skip `pub(crate)`. + let lo_after_visibility = get_bytepos_after_visibility(&fn_sig.visibility, span); + // A conservative estimation, the goal is to be over all parens in generics + let params_start = fn_sig + .generics + .params + .last() + .map_or(lo_after_visibility, |param| param.span().hi()); + let params_end = if fd.inputs.is_empty() { + context + .snippet_provider + .span_after(mk_sp(params_start, span.hi()), ")") + } else { + let last_span = mk_sp(fd.inputs[fd.inputs.len() - 1].span().hi(), span.hi()); + context.snippet_provider.span_after(last_span, ")") + }; + let params_span = mk_sp( + context + .snippet_provider + .span_after(mk_sp(params_start, span.hi()), "("), + params_end, + ); + let param_str = rewrite_params( + context, + &fd.inputs, + one_line_budget, + multi_line_budget, + indent, + param_indent, + params_span, + fd.c_variadic(), + )?; + + let put_params_in_block = match context.config.indent_style() { + IndentStyle::Block => param_str.contains('\n') || param_str.len() > one_line_budget, + _ => false, + } && !fd.inputs.is_empty(); + + let mut params_last_line_contains_comment = false; + let mut no_params_and_over_max_width = false; + + if put_params_in_block { + param_indent = indent.block_indent(context.config); + result.push_str(¶m_indent.to_string_with_newline(context.config)); + result.push_str(¶m_str); + result.push_str(&indent.to_string_with_newline(context.config)); + result.push(')'); + } else { + result.push_str(¶m_str); + let used_width = last_line_used_width(&result, indent.width()) + first_line_width(&ret_str); + // Put the closing brace on the next line if it overflows the max width. + // 1 = `)` + let closing_paren_overflow_max_width = + fd.inputs.is_empty() && used_width + 1 > context.config.max_width(); + // If the last line of params contains comment, we cannot put the closing paren + // on the same line. + params_last_line_contains_comment = param_str + .lines() + .last() + .map_or(false, |last_line| last_line.contains("//")); + + if context.config.version() == Version::Two { + if closing_paren_overflow_max_width { + result.push(')'); + result.push_str(&indent.to_string_with_newline(context.config)); + no_params_and_over_max_width = true; + } else if params_last_line_contains_comment { + result.push_str(&indent.to_string_with_newline(context.config)); + result.push(')'); + no_params_and_over_max_width = true; + } else { + result.push(')'); + } + } else { + if closing_paren_overflow_max_width || params_last_line_contains_comment { + result.push_str(&indent.to_string_with_newline(context.config)); + } + result.push(')'); + } + } + + // Return type. + if let ast::FnRetTy::Ty(..) = fd.output { + let ret_should_indent = match context.config.indent_style() { + // If our params are block layout then we surely must have space. + IndentStyle::Block if put_params_in_block || fd.inputs.is_empty() => false, + _ if params_last_line_contains_comment => false, + _ if result.contains('\n') || multi_line_ret_str => true, + _ => { + // If the return type would push over the max width, then put the return type on + // a new line. With the +1 for the signature length an additional space between + // the closing parenthesis of the param and the arrow '->' is considered. + let mut sig_length = result.len() + indent.width() + ret_str_len + 1; + + // If there is no where-clause, take into account the space after the return type + // and the brace. + if where_clause.predicates.is_empty() { + sig_length += 2; + } + + sig_length > context.config.max_width() + } + }; + let ret_shape = if ret_should_indent { + if context.config.version() == Version::One + || context.config.indent_style() == IndentStyle::Visual + { + let indent = if param_str.is_empty() { + // Aligning with non-existent params looks silly. + force_new_line_for_brace = true; + indent + 4 + } else { + // FIXME: we might want to check that using the param indent + // doesn't blow our budget, and if it does, then fallback to + // the where-clause indent. + param_indent + }; + + result.push_str(&indent.to_string_with_newline(context.config)); + Shape::indented(indent, context.config) + } else { + let mut ret_shape = Shape::indented(indent, context.config); + if param_str.is_empty() { + // Aligning with non-existent params looks silly. + force_new_line_for_brace = true; + ret_shape = if context.use_block_indent() { + ret_shape.offset_left(4).unwrap_or(ret_shape) + } else { + ret_shape.indent = ret_shape.indent + 4; + ret_shape + }; + } + + result.push_str(&ret_shape.indent.to_string_with_newline(context.config)); + ret_shape + } + } else { + if context.config.version() == Version::Two { + if !param_str.is_empty() || !no_params_and_over_max_width { + result.push(' '); + } + } else { + result.push(' '); + } + + let ret_shape = Shape::indented(indent, context.config); + ret_shape + .offset_left(last_line_width(&result)) + .unwrap_or(ret_shape) + }; + + if multi_line_ret_str || ret_should_indent { + // Now that we know the proper indent and width, we need to + // re-layout the return type. + let ret_str = fd.output.rewrite(context, ret_shape)?; + result.push_str(&ret_str); + } else { + result.push_str(&ret_str); + } + + // Comment between return type and the end of the decl. + let snippet_lo = fd.output.span().hi(); + if where_clause.predicates.is_empty() { + let snippet_hi = span.hi(); + let snippet = context.snippet(mk_sp(snippet_lo, snippet_hi)); + // Try to preserve the layout of the original snippet. + let original_starts_with_newline = snippet + .find(|c| c != ' ') + .map_or(false, |i| starts_with_newline(&snippet[i..])); + let original_ends_with_newline = snippet + .rfind(|c| c != ' ') + .map_or(false, |i| snippet[i..].ends_with('\n')); + let snippet = snippet.trim(); + if !snippet.is_empty() { + result.push(if original_starts_with_newline { + '\n' + } else { + ' ' + }); + result.push_str(snippet); + if original_ends_with_newline { + force_new_line_for_brace = true; + } + } + } + } + + let pos_before_where = match fd.output { + ast::FnRetTy::Default(..) => params_span.hi(), + ast::FnRetTy::Ty(ref ty) => ty.span.hi(), + }; + + let is_params_multi_lined = param_str.contains('\n'); + + let space = if put_params_in_block && ret_str.is_empty() { + WhereClauseSpace::Space + } else { + WhereClauseSpace::Newline + }; + let mut option = WhereClauseOption::new(fn_brace_style == FnBraceStyle::None, space); + if is_params_multi_lined { + option.veto_single_line(); + } + let where_clause_str = rewrite_where_clause( + context, + where_clause, + context.config.brace_style(), + Shape::indented(indent, context.config), + true, + "{", + Some(span.hi()), + pos_before_where, + option, + )?; + // If there are neither where-clause nor return type, we may be missing comments between + // params and `{`. + if where_clause_str.is_empty() { + if let ast::FnRetTy::Default(ret_span) = fd.output { + match recover_missing_comment_in_span( + mk_sp(params_span.hi(), ret_span.hi()), + shape, + context, + last_line_width(&result), + ) { + Some(ref missing_comment) if !missing_comment.is_empty() => { + result.push_str(missing_comment); + force_new_line_for_brace = true; + } + _ => (), + } + } + } + + result.push_str(&where_clause_str); + + let ends_with_comment = last_line_contains_single_line_comment(&result); + force_new_line_for_brace |= ends_with_comment; + force_new_line_for_brace |= + is_params_multi_lined && context.config.where_single_line() && !where_clause_str.is_empty(); + Some((result, ends_with_comment, force_new_line_for_brace)) +} + +/// Kind of spaces to put before `where`. +#[derive(Copy, Clone)] +enum WhereClauseSpace { + /// A single space. + Space, + /// A new line. + Newline, + /// Nothing. + None, +} + +#[derive(Copy, Clone)] +struct WhereClauseOption { + suppress_comma: bool, // Force no trailing comma + snuggle: WhereClauseSpace, + allow_single_line: bool, // Try single line where-clause instead of vertical layout + veto_single_line: bool, // Disallow a single-line where-clause. +} + +impl WhereClauseOption { + fn new(suppress_comma: bool, snuggle: WhereClauseSpace) -> WhereClauseOption { + WhereClauseOption { + suppress_comma, + snuggle, + allow_single_line: false, + veto_single_line: false, + } + } + + fn snuggled(current: &str) -> WhereClauseOption { + WhereClauseOption { + suppress_comma: false, + snuggle: if last_line_width(current) == 1 { + WhereClauseSpace::Space + } else { + WhereClauseSpace::Newline + }, + allow_single_line: false, + veto_single_line: false, + } + } + + fn suppress_comma(&mut self) { + self.suppress_comma = true + } + + fn allow_single_line(&mut self) { + self.allow_single_line = true + } + + fn snuggle(&mut self) { + self.snuggle = WhereClauseSpace::Space + } + + fn veto_single_line(&mut self) { + self.veto_single_line = true; + } +} + +fn rewrite_params( + context: &RewriteContext<'_>, + params: &[ast::Param], + one_line_budget: usize, + multi_line_budget: usize, + indent: Indent, + param_indent: Indent, + span: Span, + variadic: bool, +) -> Option { + if params.is_empty() { + let comment = context + .snippet(mk_sp( + span.lo(), + // to remove ')' + span.hi() - BytePos(1), + )) + .trim(); + return Some(comment.to_owned()); + } + let param_items: Vec<_> = itemize_list( + context.snippet_provider, + params.iter(), + ")", + ",", + |param| span_lo_for_param(param), + |param| param.ty.span.hi(), + |param| { + param + .rewrite(context, Shape::legacy(multi_line_budget, param_indent)) + .or_else(|| Some(context.snippet(param.span()).to_owned())) + }, + span.lo(), + span.hi(), + false, + ) + .collect(); + + let tactic = definitive_tactic( + ¶m_items, + context + .config + .fn_args_layout() + .to_list_tactic(param_items.len()), + Separator::Comma, + one_line_budget, + ); + let budget = match tactic { + DefinitiveListTactic::Horizontal => one_line_budget, + _ => multi_line_budget, + }; + let indent = match context.config.indent_style() { + IndentStyle::Block => indent.block_indent(context.config), + IndentStyle::Visual => param_indent, + }; + let trailing_separator = if variadic { + SeparatorTactic::Never + } else { + match context.config.indent_style() { + IndentStyle::Block => context.config.trailing_comma(), + IndentStyle::Visual => SeparatorTactic::Never, + } + }; + let fmt = ListFormatting::new(Shape::legacy(budget, indent), context.config) + .tactic(tactic) + .trailing_separator(trailing_separator) + .ends_with_newline(tactic.ends_with_newline(context.config.indent_style())) + .preserve_newline(true); + write_list(¶m_items, &fmt) +} + +fn compute_budgets_for_params( + context: &RewriteContext<'_>, + result: &str, + indent: Indent, + ret_str_len: usize, + fn_brace_style: FnBraceStyle, + force_vertical_layout: bool, +) -> Option<(usize, usize, Indent)> { + debug!( + "compute_budgets_for_params {} {:?}, {}, {:?}", + result.len(), + indent, + ret_str_len, + fn_brace_style, + ); + // Try keeping everything on the same line. + if !result.contains('\n') && !force_vertical_layout { + // 2 = `()`, 3 = `() `, space is before ret_string. + let overhead = if ret_str_len == 0 { 2 } else { 3 }; + let mut used_space = indent.width() + result.len() + ret_str_len + overhead; + match fn_brace_style { + FnBraceStyle::None => used_space += 1, // 1 = `;` + FnBraceStyle::SameLine => used_space += 2, // 2 = `{}` + FnBraceStyle::NextLine => (), + } + let one_line_budget = context.budget(used_space); + + if one_line_budget > 0 { + // 4 = "() {".len() + let (indent, multi_line_budget) = match context.config.indent_style() { + IndentStyle::Block => { + let indent = indent.block_indent(context.config); + (indent, context.budget(indent.width() + 1)) + } + IndentStyle::Visual => { + let indent = indent + result.len() + 1; + let multi_line_overhead = match fn_brace_style { + FnBraceStyle::SameLine => 4, + _ => 2, + } + indent.width(); + (indent, context.budget(multi_line_overhead)) + } + }; + + return Some((one_line_budget, multi_line_budget, indent)); + } + } + + // Didn't work. we must force vertical layout and put params on a newline. + let new_indent = indent.block_indent(context.config); + let used_space = match context.config.indent_style() { + // 1 = `,` + IndentStyle::Block => new_indent.width() + 1, + // Account for `)` and possibly ` {`. + IndentStyle::Visual => new_indent.width() + if ret_str_len == 0 { 1 } else { 3 }, + }; + Some((0, context.budget(used_space), new_indent)) +} + +fn newline_for_brace(config: &Config, where_clause: &ast::WhereClause) -> FnBraceStyle { + let predicate_count = where_clause.predicates.len(); + + if config.where_single_line() && predicate_count == 1 { + return FnBraceStyle::SameLine; + } + let brace_style = config.brace_style(); + + let use_next_line = brace_style == BraceStyle::AlwaysNextLine + || (brace_style == BraceStyle::SameLineWhere && predicate_count > 0); + if use_next_line { + FnBraceStyle::NextLine + } else { + FnBraceStyle::SameLine + } +} + +fn rewrite_generics( + context: &RewriteContext<'_>, + ident: &str, + generics: &ast::Generics, + shape: Shape, +) -> Option { + // FIXME: convert bounds to where-clauses where they get too big or if + // there is a where-clause at all. + + if generics.params.is_empty() { + return Some(ident.to_owned()); + } + + let params = generics.params.iter(); + overflow::rewrite_with_angle_brackets(context, ident, params, shape, generics.span) +} + +fn generics_shape_from_config(config: &Config, shape: Shape, offset: usize) -> Option { + match config.indent_style() { + IndentStyle::Visual => shape.visual_indent(1 + offset).sub_width(offset + 2), + IndentStyle::Block => { + // 1 = "," + shape + .block() + .block_indent(config.tab_spaces()) + .with_max_width(config) + .sub_width(1) + } + } +} + +fn rewrite_where_clause_rfc_style( + context: &RewriteContext<'_>, + where_clause: &ast::WhereClause, + shape: Shape, + terminator: &str, + span_end: Option, + span_end_before_where: BytePos, + where_clause_option: WhereClauseOption, +) -> Option { + let (where_keyword, allow_single_line) = rewrite_where_keyword( + context, + where_clause, + shape, + span_end_before_where, + where_clause_option, + )?; + + // 1 = `,` + let clause_shape = shape + .block() + .with_max_width(context.config) + .block_left(context.config.tab_spaces())? + .sub_width(1)?; + let force_single_line = context.config.where_single_line() + && where_clause.predicates.len() == 1 + && !where_clause_option.veto_single_line; + + let preds_str = rewrite_bounds_on_where_clause( + context, + where_clause, + clause_shape, + terminator, + span_end, + where_clause_option, + force_single_line, + )?; + + // 6 = `where ` + let clause_sep = + if allow_single_line && !preds_str.contains('\n') && 6 + preds_str.len() <= shape.width + || force_single_line + { + Cow::from(" ") + } else { + clause_shape.indent.to_string_with_newline(context.config) + }; + + Some(format!("{}{}{}", where_keyword, clause_sep, preds_str)) +} + +/// Rewrite `where` and comment around it. +fn rewrite_where_keyword( + context: &RewriteContext<'_>, + where_clause: &ast::WhereClause, + shape: Shape, + span_end_before_where: BytePos, + where_clause_option: WhereClauseOption, +) -> Option<(String, bool)> { + let block_shape = shape.block().with_max_width(context.config); + // 1 = `,` + let clause_shape = block_shape + .block_left(context.config.tab_spaces())? + .sub_width(1)?; + + let comment_separator = |comment: &str, shape: Shape| { + if comment.is_empty() { + Cow::from("") + } else { + shape.indent.to_string_with_newline(context.config) + } + }; + + let (span_before, span_after) = + missing_span_before_after_where(span_end_before_where, where_clause); + let (comment_before, comment_after) = + rewrite_comments_before_after_where(context, span_before, span_after, shape)?; + + let starting_newline = match where_clause_option.snuggle { + WhereClauseSpace::Space if comment_before.is_empty() => Cow::from(" "), + WhereClauseSpace::None => Cow::from(""), + _ => block_shape.indent.to_string_with_newline(context.config), + }; + + let newline_before_where = comment_separator(&comment_before, shape); + let newline_after_where = comment_separator(&comment_after, clause_shape); + let result = format!( + "{}{}{}where{}{}", + starting_newline, comment_before, newline_before_where, newline_after_where, comment_after + ); + let allow_single_line = where_clause_option.allow_single_line + && comment_before.is_empty() + && comment_after.is_empty(); + + Some((result, allow_single_line)) +} + +/// Rewrite bounds on a where clause. +fn rewrite_bounds_on_where_clause( + context: &RewriteContext<'_>, + where_clause: &ast::WhereClause, + shape: Shape, + terminator: &str, + span_end: Option, + where_clause_option: WhereClauseOption, + force_single_line: bool, +) -> Option { + let span_start = where_clause.predicates[0].span().lo(); + // If we don't have the start of the next span, then use the end of the + // predicates, but that means we miss comments. + let len = where_clause.predicates.len(); + let end_of_preds = where_clause.predicates[len - 1].span().hi(); + let span_end = span_end.unwrap_or(end_of_preds); + let items = itemize_list( + context.snippet_provider, + where_clause.predicates.iter(), + terminator, + ",", + |pred| pred.span().lo(), + |pred| pred.span().hi(), + |pred| pred.rewrite(context, shape), + span_start, + span_end, + false, + ); + let comma_tactic = if where_clause_option.suppress_comma || force_single_line { + SeparatorTactic::Never + } else { + context.config.trailing_comma() + }; + + // shape should be vertical only and only if we have `force_single_line` option enabled + // and the number of items of the where-clause is equal to 1 + let shape_tactic = if force_single_line { + DefinitiveListTactic::Horizontal + } else { + DefinitiveListTactic::Vertical + }; + + let fmt = ListFormatting::new(shape, context.config) + .tactic(shape_tactic) + .trailing_separator(comma_tactic) + .preserve_newline(true); + write_list(&items.collect::>(), &fmt) +} + +fn rewrite_where_clause( + context: &RewriteContext<'_>, + where_clause: &ast::WhereClause, + brace_style: BraceStyle, + shape: Shape, + on_new_line: bool, + terminator: &str, + span_end: Option, + span_end_before_where: BytePos, + where_clause_option: WhereClauseOption, +) -> Option { + if where_clause.predicates.is_empty() { + return Some(String::new()); + } + + if context.config.indent_style() == IndentStyle::Block { + return rewrite_where_clause_rfc_style( + context, + where_clause, + shape, + terminator, + span_end, + span_end_before_where, + where_clause_option, + ); + } + + let extra_indent = Indent::new(context.config.tab_spaces(), 0); + + let offset = match context.config.indent_style() { + IndentStyle::Block => shape.indent + extra_indent.block_indent(context.config), + // 6 = "where ".len() + IndentStyle::Visual => shape.indent + extra_indent + 6, + }; + // FIXME: if indent_style != Visual, then the budgets below might + // be out by a char or two. + + let budget = context.config.max_width() - offset.width(); + let span_start = where_clause.predicates[0].span().lo(); + // If we don't have the start of the next span, then use the end of the + // predicates, but that means we miss comments. + let len = where_clause.predicates.len(); + let end_of_preds = where_clause.predicates[len - 1].span().hi(); + let span_end = span_end.unwrap_or(end_of_preds); + let items = itemize_list( + context.snippet_provider, + where_clause.predicates.iter(), + terminator, + ",", + |pred| pred.span().lo(), + |pred| pred.span().hi(), + |pred| pred.rewrite(context, Shape::legacy(budget, offset)), + span_start, + span_end, + false, + ); + let item_vec = items.collect::>(); + // FIXME: we don't need to collect here + let tactic = definitive_tactic(&item_vec, ListTactic::Vertical, Separator::Comma, budget); + + let mut comma_tactic = context.config.trailing_comma(); + // Kind of a hack because we don't usually have trailing commas in where-clauses. + if comma_tactic == SeparatorTactic::Vertical || where_clause_option.suppress_comma { + comma_tactic = SeparatorTactic::Never; + } + + let fmt = ListFormatting::new(Shape::legacy(budget, offset), context.config) + .tactic(tactic) + .trailing_separator(comma_tactic) + .ends_with_newline(tactic.ends_with_newline(context.config.indent_style())) + .preserve_newline(true); + let preds_str = write_list(&item_vec, &fmt)?; + + let end_length = if terminator == "{" { + // If the brace is on the next line we don't need to count it otherwise it needs two + // characters " {" + match brace_style { + BraceStyle::AlwaysNextLine | BraceStyle::SameLineWhere => 0, + BraceStyle::PreferSameLine => 2, + } + } else if terminator == "=" { + 2 + } else { + terminator.len() + }; + if on_new_line + || preds_str.contains('\n') + || shape.indent.width() + " where ".len() + preds_str.len() + end_length > shape.width + { + Some(format!( + "\n{}where {}", + (shape.indent + extra_indent).to_string(context.config), + preds_str + )) + } else { + Some(format!(" where {}", preds_str)) + } +} + +fn missing_span_before_after_where( + before_item_span_end: BytePos, + where_clause: &ast::WhereClause, +) -> (Span, Span) { + let missing_span_before = mk_sp(before_item_span_end, where_clause.span.lo()); + // 5 = `where` + let pos_after_where = where_clause.span.lo() + BytePos(5); + let missing_span_after = mk_sp(pos_after_where, where_clause.predicates[0].span().lo()); + (missing_span_before, missing_span_after) +} + +fn rewrite_comments_before_after_where( + context: &RewriteContext<'_>, + span_before_where: Span, + span_after_where: Span, + shape: Shape, +) -> Option<(String, String)> { + let before_comment = rewrite_missing_comment(span_before_where, shape, context)?; + let after_comment = rewrite_missing_comment( + span_after_where, + shape.block_indent(context.config.tab_spaces()), + context, + )?; + Some((before_comment, after_comment)) +} + +fn format_header( + context: &RewriteContext<'_>, + item_name: &str, + ident: symbol::Ident, + vis: &ast::Visibility, + offset: Indent, +) -> String { + let mut result = String::with_capacity(128); + let shape = Shape::indented(offset, context.config); + + result.push_str(&format_visibility(context, vis).trim()); + + // Check for a missing comment between the visibility and the item name. + let after_vis = vis.span.hi(); + if let Some(before_item_name) = context + .snippet_provider + .opt_span_before(mk_sp(vis.span.lo(), ident.span.hi()), item_name.trim()) + { + let missing_span = mk_sp(after_vis, before_item_name); + if let Some(result_with_comment) = combine_strs_with_missing_comments( + context, + &result, + item_name, + missing_span, + shape, + /* allow_extend */ true, + ) { + result = result_with_comment; + } + } + + result.push_str(&rewrite_ident(context, ident)); + + result +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum BracePos { + None, + Auto, + ForceSameLine, +} + +fn format_generics( + context: &RewriteContext<'_>, + generics: &ast::Generics, + brace_style: BraceStyle, + brace_pos: BracePos, + offset: Indent, + span: Span, + used_width: usize, +) -> Option { + let shape = Shape::legacy(context.budget(used_width + offset.width()), offset); + let mut result = rewrite_generics(context, "", generics, shape)?; + + // If the generics are not parameterized then generics.span.hi() == 0, + // so we use span.lo(), which is the position after `struct Foo`. + let span_end_before_where = if !generics.params.is_empty() { + generics.span.hi() + } else { + span.lo() + }; + let (same_line_brace, missed_comments) = if !generics.where_clause.predicates.is_empty() { + let budget = context.budget(last_line_used_width(&result, offset.width())); + let mut option = WhereClauseOption::snuggled(&result); + if brace_pos == BracePos::None { + option.suppress_comma = true; + } + let where_clause_str = rewrite_where_clause( + context, + &generics.where_clause, + brace_style, + Shape::legacy(budget, offset.block_only()), + true, + "{", + Some(span.hi()), + span_end_before_where, + option, + )?; + result.push_str(&where_clause_str); + ( + brace_pos == BracePos::ForceSameLine || brace_style == BraceStyle::PreferSameLine, + // missed comments are taken care of in #rewrite_where_clause + None, + ) + } else { + ( + brace_pos == BracePos::ForceSameLine + || (result.contains('\n') && brace_style == BraceStyle::PreferSameLine + || brace_style != BraceStyle::AlwaysNextLine) + || trimmed_last_line_width(&result) == 1, + rewrite_missing_comment( + mk_sp( + span_end_before_where, + if brace_pos == BracePos::None { + span.hi() + } else { + context.snippet_provider.span_before(span, "{") + }, + ), + shape, + context, + ), + ) + }; + // add missing comments + let missed_line_comments = missed_comments + .filter(|missed_comments| !missed_comments.is_empty()) + .map_or(false, |missed_comments| { + let is_block = is_last_comment_block(&missed_comments); + let sep = if is_block { " " } else { "\n" }; + result.push_str(sep); + result.push_str(&missed_comments); + !is_block + }); + if brace_pos == BracePos::None { + return Some(result); + } + let total_used_width = last_line_used_width(&result, used_width); + let remaining_budget = context.budget(total_used_width); + // If the same line brace if forced, it indicates that we are rewriting an item with empty body, + // and hence we take the closer into account as well for one line budget. + // We assume that the closer has the same length as the opener. + let overhead = if brace_pos == BracePos::ForceSameLine { + // 3 = ` {}` + 3 + } else { + // 2 = ` {` + 2 + }; + let forbid_same_line_brace = missed_line_comments || overhead > remaining_budget; + if !forbid_same_line_brace && same_line_brace { + result.push(' '); + } else { + result.push('\n'); + result.push_str(&offset.block_only().to_string(context.config)); + } + result.push('{'); + + Some(result) +} + +impl Rewrite for ast::ForeignItem { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let attrs_str = self.attrs.rewrite(context, shape)?; + // Drop semicolon or it will be interpreted as comment. + // FIXME: this may be a faulty span from libsyntax. + let span = mk_sp(self.span.lo(), self.span.hi() - BytePos(1)); + + let item_str = match self.kind { + ast::ForeignItemKind::Fn(ref fn_kind) => { + let ast::FnKind(defaultness, ref fn_sig, ref generics, ref block) = **fn_kind; + if let Some(ref body) = block { + let mut visitor = FmtVisitor::from_context(context); + visitor.block_indent = shape.indent; + visitor.last_pos = self.span.lo(); + let inner_attrs = inner_attributes(&self.attrs); + let fn_ctxt = visit::FnCtxt::Foreign; + visitor.visit_fn( + visit::FnKind::Fn(fn_ctxt, self.ident, &fn_sig, &self.vis, Some(body)), + generics, + &fn_sig.decl, + self.span, + defaultness, + Some(&inner_attrs), + ); + Some(visitor.buffer.to_owned()) + } else { + rewrite_fn_base( + context, + shape.indent, + self.ident, + &FnSig::from_method_sig(&fn_sig, generics, self.vis.clone()), + span, + FnBraceStyle::None, + ) + .map(|(s, _, _)| format!("{};", s)) + } + } + ast::ForeignItemKind::Static(ref ty, mutability, _) => { + // FIXME(#21): we're dropping potential comments in between the + // function kw here. + let vis = format_visibility(context, &self.vis); + let mut_str = format_mutability(mutability); + let prefix = format!( + "{}static {}{}:", + vis, + mut_str, + rewrite_ident(context, self.ident) + ); + // 1 = ; + rewrite_assign_rhs(context, prefix, &**ty, shape.sub_width(1)?).map(|s| s + ";") + } + ast::ForeignItemKind::TyAlias(ref ty_alias_kind) => { + let ast::TyAliasKind(_, ref generics, ref generic_bounds, ref type_default) = + **ty_alias_kind; + rewrite_type_alias( + self.ident, + type_default.as_ref(), + generics, + Some(generic_bounds), + &context, + shape.indent, + &self.vis, + self.span, + ) + } + ast::ForeignItemKind::MacCall(ref mac) => { + rewrite_macro(mac, None, context, shape, MacroPosition::Item) + } + }?; + + let missing_span = if self.attrs.is_empty() { + mk_sp(self.span.lo(), self.span.lo()) + } else { + mk_sp(self.attrs[self.attrs.len() - 1].span.hi(), self.span.lo()) + }; + combine_strs_with_missing_comments( + context, + &attrs_str, + &item_str, + missing_span, + shape, + false, + ) + } +} + +/// Rewrite the attributes of an item. +fn rewrite_attrs( + context: &RewriteContext<'_>, + item: &ast::Item, + item_str: &str, + shape: Shape, +) -> Option { + let attrs = filter_inline_attrs(&item.attrs, item.span()); + let attrs_str = attrs.rewrite(context, shape)?; + + let missed_span = if attrs.is_empty() { + mk_sp(item.span.lo(), item.span.lo()) + } else { + mk_sp(attrs[attrs.len() - 1].span.hi(), item.span.lo()) + }; + + let allow_extend = if attrs.len() == 1 { + let line_len = attrs_str.len() + 1 + item_str.len(); + !attrs.first().unwrap().is_doc_comment() + && context.config.inline_attribute_width() >= line_len + } else { + false + }; + + combine_strs_with_missing_comments( + context, + &attrs_str, + &item_str, + missed_span, + shape, + allow_extend, + ) +} + +/// Rewrite an inline mod. +/// The given shape is used to format the mod's attributes. +pub(crate) fn rewrite_mod( + context: &RewriteContext<'_>, + item: &ast::Item, + attrs_shape: Shape, +) -> Option { + let mut result = String::with_capacity(32); + result.push_str(&*format_visibility(context, &item.vis)); + result.push_str("mod "); + result.push_str(rewrite_ident(context, item.ident)); + result.push(';'); + rewrite_attrs(context, item, &result, attrs_shape) +} + +/// Rewrite `extern crate foo;`. +/// The given shape is used to format the extern crate's attributes. +pub(crate) fn rewrite_extern_crate( + context: &RewriteContext<'_>, + item: &ast::Item, + attrs_shape: Shape, +) -> Option { + assert!(is_extern_crate(item)); + let new_str = context.snippet(item.span); + let item_str = if contains_comment(new_str) { + new_str.to_owned() + } else { + let no_whitespace = &new_str.split_whitespace().collect::>().join(" "); + String::from(&*Regex::new(r"\s;").unwrap().replace(no_whitespace, ";")) + }; + rewrite_attrs(context, item, &item_str, attrs_shape) +} + +/// Returns `true` for `mod foo;`, false for `mod foo { .. }`. +pub(crate) fn is_mod_decl(item: &ast::Item) -> bool { + match item.kind { + ast::ItemKind::Mod(ref m) => m.inner.hi() != item.span.hi(), + _ => false, + } +} + +pub(crate) fn is_use_item(item: &ast::Item) -> bool { + match item.kind { + ast::ItemKind::Use(_) => true, + _ => false, + } +} + +pub(crate) fn is_extern_crate(item: &ast::Item) -> bool { + match item.kind { + ast::ItemKind::ExternCrate(..) => true, + _ => false, + } +} diff --git a/src/tools/rustfmt/src/lib.rs b/src/tools/rustfmt/src/lib.rs new file mode 100644 index 0000000000..370b7eb6c4 --- /dev/null +++ b/src/tools/rustfmt/src/lib.rs @@ -0,0 +1,642 @@ +#![deny(rust_2018_idioms)] +#![warn(unreachable_pub)] + +#[macro_use] +extern crate derive_new; +#[cfg(test)] +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate log; + +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt; +use std::io::{self, Write}; +use std::mem; +use std::panic; +use std::path::PathBuf; +use std::rc::Rc; + +use ignore; +use rustc_ast::ast; +use rustc_span::symbol; +use thiserror::Error; + +use crate::comment::LineClasses; +use crate::emitter::Emitter; +use crate::formatting::{FormatErrorMap, FormattingError, ReportedErrors, SourceFile}; +use crate::issues::Issue; +use crate::modules::ModuleResolutionError; +use crate::shape::Indent; +use crate::syntux::parser::DirectoryOwnership; +use crate::utils::indent_next_line; + +pub use crate::config::{ + load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, NewlineStyle, + Range, Verbosity, +}; + +pub use crate::format_report_formatter::{FormatReportFormatter, FormatReportFormatterBuilder}; + +pub use crate::rustfmt_diff::{ModifiedChunk, ModifiedLines}; + +#[macro_use] +mod utils; + +mod attr; +mod chains; +mod closures; +mod comment; +pub(crate) mod config; +mod coverage; +mod emitter; +mod expr; +mod format_report_formatter; +pub(crate) mod formatting; +mod ignore_path; +mod imports; +mod issues; +mod items; +mod lists; +mod macros; +mod matches; +mod missed_spans; +pub(crate) mod modules; +mod overflow; +mod pairs; +mod patterns; +mod release_channel; +mod reorder; +mod rewrite; +pub(crate) mod rustfmt_diff; +mod shape; +mod skip; +pub(crate) mod source_file; +pub(crate) mod source_map; +mod spanned; +mod stmt; +mod string; +mod syntux; +#[cfg(test)] +mod test; +mod types; +mod vertical; +pub(crate) mod visitor; + +/// The various errors that can occur during formatting. Note that not all of +/// these can currently be propagated to clients. +#[derive(Error, Debug)] +pub enum ErrorKind { + /// Line has exceeded character limit (found, maximum). + #[error( + "line formatted, but exceeded maximum width \ + (maximum: {1} (see `max_width` option), found: {0})" + )] + LineOverflow(usize, usize), + /// Line ends in whitespace. + #[error("left behind trailing whitespace")] + TrailingWhitespace, + /// TODO or FIXME item without an issue number. + #[error("found {0}")] + BadIssue(Issue), + /// License check has failed. + #[error("license check failed")] + LicenseCheck, + /// Used deprecated skip attribute. + #[error("`rustfmt_skip` is deprecated; use `rustfmt::skip`")] + DeprecatedAttr, + /// Used a rustfmt:: attribute other than skip or skip::macros. + #[error("invalid attribute")] + BadAttr, + /// An io error during reading or writing. + #[error("io error: {0}")] + IoError(io::Error), + /// Error during module resolution. + #[error("{0}")] + ModuleResolutionError(#[from] ModuleResolutionError), + /// Parse error occurred when parsing the input. + #[error("parse error")] + ParseError, + /// The user mandated a version and the current version of Rustfmt does not + /// satisfy that requirement. + #[error("version mismatch")] + VersionMismatch, + /// If we had formatted the given node, then we would have lost a comment. + #[error("not formatted because a comment would be lost")] + LostComment, + /// Invalid glob pattern in `ignore` configuration option. + #[error("Invalid glob pattern found in ignore list: {0}")] + InvalidGlobPattern(ignore::Error), +} + +impl ErrorKind { + fn is_comment(&self) -> bool { + match self { + ErrorKind::LostComment => true, + _ => false, + } + } +} + +impl From for ErrorKind { + fn from(e: io::Error) -> ErrorKind { + ErrorKind::IoError(e) + } +} + +/// Result of formatting a snippet of code along with ranges of lines that didn't get formatted, +/// i.e., that got returned as they were originally. +#[derive(Debug)] +struct FormattedSnippet { + snippet: String, + non_formatted_ranges: Vec<(usize, usize)>, +} + +impl FormattedSnippet { + /// In case the snippet needed to be wrapped in a function, this shifts down the ranges of + /// non-formatted code. + fn unwrap_code_block(&mut self) { + self.non_formatted_ranges + .iter_mut() + .for_each(|(low, high)| { + *low -= 1; + *high -= 1; + }); + } + + /// Returns `true` if the line n did not get formatted. + fn is_line_non_formatted(&self, n: usize) -> bool { + self.non_formatted_ranges + .iter() + .any(|(low, high)| *low <= n && n <= *high) + } +} + +/// Reports on any issues that occurred during a run of Rustfmt. +/// +/// Can be reported to the user using the `Display` impl on [`FormatReportFormatter`]. +#[derive(Clone)] +pub struct FormatReport { + // Maps stringified file paths to their associated formatting errors. + internal: Rc>, + non_formatted_ranges: Vec<(usize, usize)>, +} + +impl FormatReport { + fn new() -> FormatReport { + FormatReport { + internal: Rc::new(RefCell::new((HashMap::new(), ReportedErrors::default()))), + non_formatted_ranges: Vec::new(), + } + } + + fn add_non_formatted_ranges(&mut self, mut ranges: Vec<(usize, usize)>) { + self.non_formatted_ranges.append(&mut ranges); + } + + fn append(&self, f: FileName, mut v: Vec) { + self.track_errors(&v); + self.internal + .borrow_mut() + .0 + .entry(f) + .and_modify(|fe| fe.append(&mut v)) + .or_insert(v); + } + + fn track_errors(&self, new_errors: &[FormattingError]) { + let errs = &mut self.internal.borrow_mut().1; + if !new_errors.is_empty() { + errs.has_formatting_errors = true; + } + if errs.has_operational_errors && errs.has_check_errors { + return; + } + for err in new_errors { + match err.kind { + ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => { + errs.has_operational_errors = true; + } + ErrorKind::BadIssue(_) + | ErrorKind::LicenseCheck + | ErrorKind::DeprecatedAttr + | ErrorKind::BadAttr + | ErrorKind::VersionMismatch => { + errs.has_check_errors = true; + } + _ => {} + } + } + } + + fn add_diff(&mut self) { + self.internal.borrow_mut().1.has_diff = true; + } + + fn add_macro_format_failure(&mut self) { + self.internal.borrow_mut().1.has_macro_format_failure = true; + } + + fn add_parsing_error(&mut self) { + self.internal.borrow_mut().1.has_parsing_errors = true; + } + + fn warning_count(&self) -> usize { + self.internal + .borrow() + .0 + .iter() + .map(|(_, errors)| errors.len()) + .sum() + } + + /// Whether any warnings or errors are present in the report. + pub fn has_warnings(&self) -> bool { + self.internal.borrow().1.has_formatting_errors + } + + /// Print the report to a terminal using colours and potentially other + /// fancy output. + #[deprecated(note = "Use FormatReportFormatter with colors enabled instead")] + pub fn fancy_print( + &self, + mut t: Box>, + ) -> Result<(), term::Error> { + writeln!( + t, + "{}", + FormatReportFormatterBuilder::new(&self) + .enable_colors(true) + .build() + )?; + Ok(()) + } +} + +/// Deprecated - Use FormatReportFormatter instead +// https://github.com/rust-lang/rust/issues/78625 +// https://github.com/rust-lang/rust/issues/39935 +impl fmt::Display for FormatReport { + // Prints all the formatting errors. + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(fmt, "{}", FormatReportFormatterBuilder::new(&self).build())?; + Ok(()) + } +} + +/// Format the given snippet. The snippet is expected to be *complete* code. +/// When we cannot parse the given snippet, this function returns `None`. +fn format_snippet(snippet: &str, config: &Config, is_macro_def: bool) -> Option { + let mut config = config.clone(); + panic::catch_unwind(|| { + let mut out: Vec = Vec::with_capacity(snippet.len() * 2); + config.set().emit_mode(config::EmitMode::Stdout); + config.set().verbose(Verbosity::Quiet); + config.set().hide_parse_errors(true); + + let (formatting_error, result) = { + let input = Input::Text(snippet.into()); + let mut session = Session::new(config, Some(&mut out)); + let result = session.format_input_inner(input, is_macro_def); + ( + session.errors.has_macro_format_failure + || session.out.as_ref().unwrap().is_empty() && !snippet.is_empty() + || result.is_err(), + result, + ) + }; + if formatting_error { + None + } else { + String::from_utf8(out).ok().map(|snippet| FormattedSnippet { + snippet, + non_formatted_ranges: result.unwrap().non_formatted_ranges, + }) + } + }) + // Discard panics encountered while formatting the snippet + // The ? operator is needed to remove the extra Option + .ok()? +} + +/// Format the given code block. Mainly targeted for code block in comment. +/// The code block may be incomplete (i.e., parser may be unable to parse it). +/// To avoid panic in parser, we wrap the code block with a dummy function. +/// The returned code block does **not** end with newline. +fn format_code_block( + code_snippet: &str, + config: &Config, + is_macro_def: bool, +) -> Option { + const FN_MAIN_PREFIX: &str = "fn main() {\n"; + + fn enclose_in_main_block(s: &str, config: &Config) -> String { + let indent = Indent::from_width(config, config.tab_spaces()); + let mut result = String::with_capacity(s.len() * 2); + result.push_str(FN_MAIN_PREFIX); + let mut need_indent = true; + for (kind, line) in LineClasses::new(s) { + if need_indent { + result.push_str(&indent.to_string(config)); + } + result.push_str(&line); + result.push('\n'); + need_indent = indent_next_line(kind, &line, config); + } + result.push('}'); + result + } + + // Wrap the given code block with `fn main()` if it does not have one. + let snippet = enclose_in_main_block(code_snippet, config); + let mut result = String::with_capacity(snippet.len()); + let mut is_first = true; + + // While formatting the code, ignore the config's newline style setting and always use "\n" + // instead of "\r\n" for the newline characters. This is ok because the output here is + // not directly outputted by rustfmt command, but used by the comment formatter's input. + // We have output-file-wide "\n" ==> "\r\n" conversion process after here if it's necessary. + let mut config_with_unix_newline = config.clone(); + config_with_unix_newline + .set() + .newline_style(NewlineStyle::Unix); + let mut formatted = format_snippet(&snippet, &config_with_unix_newline, is_macro_def)?; + // Remove wrapping main block + formatted.unwrap_code_block(); + + // Trim "fn main() {" on the first line and "}" on the last line, + // then unindent the whole code block. + let block_len = formatted + .snippet + .rfind('}') + .unwrap_or_else(|| formatted.snippet.len()); + let mut is_indented = true; + let indent_str = Indent::from_width(config, config.tab_spaces()).to_string(config); + for (kind, ref line) in LineClasses::new(&formatted.snippet[FN_MAIN_PREFIX.len()..block_len]) { + if !is_first { + result.push('\n'); + } else { + is_first = false; + } + let trimmed_line = if !is_indented { + line + } else if line.len() > config.max_width() { + // If there are lines that are larger than max width, we cannot tell + // whether we have succeeded but have some comments or strings that + // are too long, or we have failed to format code block. We will be + // conservative and just return `None` in this case. + return None; + } else if line.len() > indent_str.len() { + // Make sure that the line has leading whitespaces. + if line.starts_with(indent_str.as_ref()) { + let offset = if config.hard_tabs() { + 1 + } else { + config.tab_spaces() + }; + &line[offset..] + } else { + line + } + } else { + line + }; + result.push_str(trimmed_line); + is_indented = indent_next_line(kind, line, config); + } + Some(FormattedSnippet { + snippet: result, + non_formatted_ranges: formatted.non_formatted_ranges, + }) +} + +/// A session is a run of rustfmt across a single or multiple inputs. +pub struct Session<'b, T: Write> { + pub config: Config, + pub out: Option<&'b mut T>, + pub(crate) errors: ReportedErrors, + source_file: SourceFile, + emitter: Box, +} + +impl<'b, T: Write + 'b> Session<'b, T> { + pub fn new(config: Config, mut out: Option<&'b mut T>) -> Session<'b, T> { + let emitter = create_emitter(&config); + + if let Some(ref mut out) = out { + let _ = emitter.emit_header(out); + } + + Session { + config, + out, + emitter, + errors: ReportedErrors::default(), + source_file: SourceFile::new(), + } + } + + /// The main entry point for Rustfmt. Formats the given input according to the + /// given config. `out` is only necessary if required by the configuration. + pub fn format(&mut self, input: Input) -> Result { + self.format_input_inner(input, false) + } + + pub fn override_config(&mut self, mut config: Config, f: F) -> U + where + F: FnOnce(&mut Session<'b, T>) -> U, + { + mem::swap(&mut config, &mut self.config); + let result = f(self); + mem::swap(&mut config, &mut self.config); + result + } + + pub fn add_operational_error(&mut self) { + self.errors.has_operational_errors = true; + } + + pub fn has_operational_errors(&self) -> bool { + self.errors.has_operational_errors + } + + pub fn has_parsing_errors(&self) -> bool { + self.errors.has_parsing_errors + } + + pub fn has_formatting_errors(&self) -> bool { + self.errors.has_formatting_errors + } + + pub fn has_check_errors(&self) -> bool { + self.errors.has_check_errors + } + + pub fn has_diff(&self) -> bool { + self.errors.has_diff + } + + pub fn has_no_errors(&self) -> bool { + !(self.has_operational_errors() + || self.has_parsing_errors() + || self.has_formatting_errors() + || self.has_check_errors() + || self.has_diff()) + || self.errors.has_macro_format_failure + } +} + +pub(crate) fn create_emitter<'a>(config: &Config) -> Box { + match config.emit_mode() { + EmitMode::Files if config.make_backup() => { + Box::new(emitter::FilesWithBackupEmitter::default()) + } + EmitMode::Files => Box::new(emitter::FilesEmitter::new( + config.print_misformatted_file_names(), + )), + EmitMode::Stdout | EmitMode::Coverage => { + Box::new(emitter::StdoutEmitter::new(config.verbose())) + } + EmitMode::Json => Box::new(emitter::JsonEmitter::default()), + EmitMode::ModifiedLines => Box::new(emitter::ModifiedLinesEmitter::default()), + EmitMode::Checkstyle => Box::new(emitter::CheckstyleEmitter::default()), + EmitMode::Diff => Box::new(emitter::DiffEmitter::new(config.clone())), + } +} + +impl<'b, T: Write + 'b> Drop for Session<'b, T> { + fn drop(&mut self) { + if let Some(ref mut out) = self.out { + let _ = self.emitter.emit_footer(out); + } + } +} + +#[derive(Debug)] +pub enum Input { + File(PathBuf), + Text(String), +} + +impl Input { + fn file_name(&self) -> FileName { + match *self { + Input::File(ref file) => FileName::Real(file.clone()), + Input::Text(..) => FileName::Stdin, + } + } + + fn to_directory_ownership(&self) -> Option { + match self { + Input::File(ref file) => { + // If there exists a directory with the same name as an input, + // then the input should be parsed as a sub module. + let file_stem = file.file_stem()?; + if file.parent()?.to_path_buf().join(file_stem).is_dir() { + Some(DirectoryOwnership::Owned { + relative: file_stem.to_str().map(symbol::Ident::from_str), + }) + } else { + None + } + } + _ => None, + } + } +} + +#[cfg(test)] +mod unit_tests { + use super::*; + + #[test] + fn test_no_panic_on_format_snippet_and_format_code_block() { + // `format_snippet()` and `format_code_block()` should not panic + // even when we cannot parse the given snippet. + let snippet = "let"; + assert!(format_snippet(snippet, &Config::default(), false).is_none()); + assert!(format_code_block(snippet, &Config::default(), false).is_none()); + } + + fn test_format_inner(formatter: F, input: &str, expected: &str) -> bool + where + F: Fn(&str, &Config, bool) -> Option, + { + let output = formatter(input, &Config::default(), false); + output.is_some() && output.unwrap().snippet == expected + } + + #[test] + fn test_format_snippet() { + let snippet = "fn main() { println!(\"hello, world\"); }"; + #[cfg(not(windows))] + let expected = "fn main() {\n \ + println!(\"hello, world\");\n\ + }\n"; + #[cfg(windows)] + let expected = "fn main() {\r\n \ + println!(\"hello, world\");\r\n\ + }\r\n"; + assert!(test_format_inner(format_snippet, snippet, expected)); + } + + #[test] + fn test_format_code_block_fail() { + #[rustfmt::skip] + let code_block = "this_line_is_100_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(x, y, z);"; + assert!(format_code_block(code_block, &Config::default(), false).is_none()); + } + + #[test] + fn test_format_code_block() { + // simple code block + let code_block = "let x=3;"; + let expected = "let x = 3;"; + assert!(test_format_inner(format_code_block, code_block, expected)); + + // more complex code block, taken from chains.rs. + let code_block = +"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) { +( +chain_indent(context, shape.add_offset(parent_rewrite.len())), +context.config.indent_style() == IndentStyle::Visual || is_small_parent, +) +} else if is_block_expr(context, &parent, &parent_rewrite) { +match context.config.indent_style() { +// Try to put the first child on the same line with parent's last line +IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true), +// The parent is a block, so align the rest of the chain with the closing +// brace. +IndentStyle::Visual => (parent_shape, false), +} +} else { +( +chain_indent(context, shape.add_offset(parent_rewrite.len())), +false, +) +}; +"; + let expected = +"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) { + ( + chain_indent(context, shape.add_offset(parent_rewrite.len())), + context.config.indent_style() == IndentStyle::Visual || is_small_parent, + ) +} else if is_block_expr(context, &parent, &parent_rewrite) { + match context.config.indent_style() { + // Try to put the first child on the same line with parent's last line + IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true), + // The parent is a block, so align the rest of the chain with the closing + // brace. + IndentStyle::Visual => (parent_shape, false), + } +} else { + ( + chain_indent(context, shape.add_offset(parent_rewrite.len())), + false, + ) +};"; + assert!(test_format_inner(format_code_block, code_block, expected)); + } +} diff --git a/src/tools/rustfmt/src/lists.rs b/src/tools/rustfmt/src/lists.rs new file mode 100644 index 0000000000..bc55ef6686 --- /dev/null +++ b/src/tools/rustfmt/src/lists.rs @@ -0,0 +1,930 @@ +//! Format list-like expressions and items. + +use std::cmp; +use std::iter::Peekable; + +use rustc_span::BytePos; + +use crate::comment::{find_comment_end, rewrite_comment, FindUncommented}; +use crate::config::lists::*; +use crate::config::{Config, IndentStyle}; +use crate::rewrite::RewriteContext; +use crate::shape::{Indent, Shape}; +use crate::utils::{ + count_newlines, first_line_width, last_line_width, mk_sp, starts_with_newline, + unicode_str_width, +}; +use crate::visitor::SnippetProvider; + +pub(crate) struct ListFormatting<'a> { + tactic: DefinitiveListTactic, + separator: &'a str, + trailing_separator: SeparatorTactic, + separator_place: SeparatorPlace, + shape: Shape, + // Non-expressions, e.g., items, will have a new line at the end of the list. + // Important for comment styles. + ends_with_newline: bool, + // Remove newlines between list elements for expressions. + preserve_newline: bool, + // Nested import lists get some special handling for the "Mixed" list type + nested: bool, + // Whether comments should be visually aligned. + align_comments: bool, + config: &'a Config, +} + +impl<'a> ListFormatting<'a> { + pub(crate) fn new(shape: Shape, config: &'a Config) -> Self { + ListFormatting { + tactic: DefinitiveListTactic::Vertical, + separator: ",", + trailing_separator: SeparatorTactic::Never, + separator_place: SeparatorPlace::Back, + shape, + ends_with_newline: true, + preserve_newline: false, + nested: false, + align_comments: true, + config, + } + } + + pub(crate) fn tactic(mut self, tactic: DefinitiveListTactic) -> Self { + self.tactic = tactic; + self + } + + pub(crate) fn separator(mut self, separator: &'a str) -> Self { + self.separator = separator; + self + } + + pub(crate) fn trailing_separator(mut self, trailing_separator: SeparatorTactic) -> Self { + self.trailing_separator = trailing_separator; + self + } + + pub(crate) fn separator_place(mut self, separator_place: SeparatorPlace) -> Self { + self.separator_place = separator_place; + self + } + + pub(crate) fn ends_with_newline(mut self, ends_with_newline: bool) -> Self { + self.ends_with_newline = ends_with_newline; + self + } + + pub(crate) fn preserve_newline(mut self, preserve_newline: bool) -> Self { + self.preserve_newline = preserve_newline; + self + } + + pub(crate) fn nested(mut self, nested: bool) -> Self { + self.nested = nested; + self + } + + pub(crate) fn align_comments(mut self, align_comments: bool) -> Self { + self.align_comments = align_comments; + self + } + + pub(crate) fn needs_trailing_separator(&self) -> bool { + match self.trailing_separator { + // We always put separator in front. + SeparatorTactic::Always => true, + SeparatorTactic::Vertical => self.tactic == DefinitiveListTactic::Vertical, + SeparatorTactic::Never => { + self.tactic == DefinitiveListTactic::Vertical && self.separator_place.is_front() + } + } + } +} + +impl AsRef for ListItem { + fn as_ref(&self) -> &ListItem { + self + } +} + +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +pub(crate) enum ListItemCommentStyle { + // Try to keep the comment on the same line with the item. + SameLine, + // Put the comment on the previous or the next line of the item. + DifferentLine, + // No comment available. + None, +} + +#[derive(Debug, Clone)] +pub(crate) struct ListItem { + // None for comments mean that they are not present. + pub(crate) pre_comment: Option, + pub(crate) pre_comment_style: ListItemCommentStyle, + // Item should include attributes and doc comments. None indicates a failed + // rewrite. + pub(crate) item: Option, + pub(crate) post_comment: Option, + // Whether there is extra whitespace before this item. + pub(crate) new_lines: bool, +} + +impl ListItem { + pub(crate) fn empty() -> ListItem { + ListItem { + pre_comment: None, + pre_comment_style: ListItemCommentStyle::None, + item: None, + post_comment: None, + new_lines: false, + } + } + + pub(crate) fn inner_as_ref(&self) -> &str { + self.item.as_ref().map_or("", |s| s) + } + + pub(crate) fn is_different_group(&self) -> bool { + self.inner_as_ref().contains('\n') + || self.pre_comment.is_some() + || self + .post_comment + .as_ref() + .map_or(false, |s| s.contains('\n')) + } + + pub(crate) fn is_multiline(&self) -> bool { + self.inner_as_ref().contains('\n') + || self + .pre_comment + .as_ref() + .map_or(false, |s| s.contains('\n')) + || self + .post_comment + .as_ref() + .map_or(false, |s| s.contains('\n')) + } + + pub(crate) fn has_single_line_comment(&self) -> bool { + self.pre_comment + .as_ref() + .map_or(false, |comment| comment.trim_start().starts_with("//")) + || self + .post_comment + .as_ref() + .map_or(false, |comment| comment.trim_start().starts_with("//")) + } + + pub(crate) fn has_comment(&self) -> bool { + self.pre_comment.is_some() || self.post_comment.is_some() + } + + pub(crate) fn from_str>(s: S) -> ListItem { + ListItem { + pre_comment: None, + pre_comment_style: ListItemCommentStyle::None, + item: Some(s.into()), + post_comment: None, + new_lines: false, + } + } + + // Returns `true` if the item causes something to be written. + fn is_substantial(&self) -> bool { + fn empty(s: &Option) -> bool { + match *s { + Some(ref s) if !s.is_empty() => false, + _ => true, + } + } + + !(empty(&self.pre_comment) && empty(&self.item) && empty(&self.post_comment)) + } +} + +/// The type of separator for lists. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub(crate) enum Separator { + Comma, + VerticalBar, +} + +impl Separator { + pub(crate) fn len(self) -> usize { + match self { + // 2 = `, ` + Separator::Comma => 2, + // 3 = ` | ` + Separator::VerticalBar => 3, + } + } +} + +pub(crate) fn definitive_tactic( + items: I, + tactic: ListTactic, + sep: Separator, + width: usize, +) -> DefinitiveListTactic +where + I: IntoIterator + Clone, + T: AsRef, +{ + let pre_line_comments = items + .clone() + .into_iter() + .any(|item| item.as_ref().has_single_line_comment()); + + let limit = match tactic { + _ if pre_line_comments => return DefinitiveListTactic::Vertical, + ListTactic::Horizontal => return DefinitiveListTactic::Horizontal, + ListTactic::Vertical => return DefinitiveListTactic::Vertical, + ListTactic::LimitedHorizontalVertical(limit) => ::std::cmp::min(width, limit), + ListTactic::Mixed | ListTactic::HorizontalVertical => width, + }; + + let (sep_count, total_width) = calculate_width(items.clone()); + let total_sep_len = sep.len() * sep_count.saturating_sub(1); + let real_total = total_width + total_sep_len; + + if real_total <= limit && !items.into_iter().any(|item| item.as_ref().is_multiline()) { + DefinitiveListTactic::Horizontal + } else { + match tactic { + ListTactic::Mixed => DefinitiveListTactic::Mixed, + _ => DefinitiveListTactic::Vertical, + } + } +} + +// Format a list of commented items into a string. +pub(crate) fn write_list(items: I, formatting: &ListFormatting<'_>) -> Option +where + I: IntoIterator + Clone, + T: AsRef, +{ + let tactic = formatting.tactic; + let sep_len = formatting.separator.len(); + + // Now that we know how we will layout, we can decide for sure if there + // will be a trailing separator. + let mut trailing_separator = formatting.needs_trailing_separator(); + let mut result = String::with_capacity(128); + let cloned_items = items.clone(); + let mut iter = items.into_iter().enumerate().peekable(); + let mut item_max_width: Option = None; + let sep_place = + SeparatorPlace::from_tactic(formatting.separator_place, tactic, formatting.separator); + let mut prev_item_had_post_comment = false; + let mut prev_item_is_nested_import = false; + + let mut line_len = 0; + let indent_str = &formatting.shape.indent.to_string(formatting.config); + while let Some((i, item)) = iter.next() { + let item = item.as_ref(); + let inner_item = item.item.as_ref()?; + let first = i == 0; + let last = iter.peek().is_none(); + let mut separate = match sep_place { + SeparatorPlace::Front => !first, + SeparatorPlace::Back => !last || trailing_separator, + }; + let item_sep_len = if separate { sep_len } else { 0 }; + + // Item string may be multi-line. Its length (used for block comment alignment) + // should be only the length of the last line. + let item_last_line = if item.is_multiline() { + inner_item.lines().last().unwrap_or("") + } else { + inner_item.as_ref() + }; + let mut item_last_line_width = item_last_line.len() + item_sep_len; + if item_last_line.starts_with(&**indent_str) { + item_last_line_width -= indent_str.len(); + } + + if !item.is_substantial() { + continue; + } + + match tactic { + DefinitiveListTactic::Horizontal if !first => { + result.push(' '); + } + DefinitiveListTactic::SpecialMacro(num_args_before) => { + if i == 0 { + // Nothing + } else if i < num_args_before { + result.push(' '); + } else if i <= num_args_before + 1 { + result.push('\n'); + result.push_str(indent_str); + } else { + result.push(' '); + } + } + DefinitiveListTactic::Vertical + if !first && !inner_item.is_empty() && !result.is_empty() => + { + result.push('\n'); + result.push_str(indent_str); + } + DefinitiveListTactic::Mixed => { + let total_width = total_item_width(item) + item_sep_len; + + // 1 is space between separator and item. + if (line_len > 0 && line_len + 1 + total_width > formatting.shape.width) + || prev_item_had_post_comment + || (formatting.nested + && (prev_item_is_nested_import || (!first && inner_item.contains("::")))) + { + result.push('\n'); + result.push_str(indent_str); + line_len = 0; + if formatting.ends_with_newline { + trailing_separator = true; + } + } else if line_len > 0 { + result.push(' '); + line_len += 1; + } + + if last && formatting.ends_with_newline { + separate = formatting.trailing_separator != SeparatorTactic::Never; + } + + line_len += total_width; + } + _ => {} + } + + // Pre-comments + if let Some(ref comment) = item.pre_comment { + // Block style in non-vertical mode. + let block_mode = tactic == DefinitiveListTactic::Horizontal; + // Width restriction is only relevant in vertical mode. + let comment = + rewrite_comment(comment, block_mode, formatting.shape, formatting.config)?; + result.push_str(&comment); + + if !inner_item.is_empty() { + if tactic == DefinitiveListTactic::Vertical || tactic == DefinitiveListTactic::Mixed + { + // We cannot keep pre-comments on the same line if the comment if normalized. + let keep_comment = if formatting.config.normalize_comments() + || item.pre_comment_style == ListItemCommentStyle::DifferentLine + { + false + } else { + // We will try to keep the comment on the same line with the item here. + // 1 = ` ` + let total_width = total_item_width(item) + item_sep_len + 1; + total_width <= formatting.shape.width + }; + if keep_comment { + result.push(' '); + } else { + result.push('\n'); + result.push_str(indent_str); + // This is the width of the item (without comments). + line_len = item.item.as_ref().map_or(0, |s| unicode_str_width(&s)); + } + } else { + result.push(' '); + } + } + item_max_width = None; + } + + if separate && sep_place.is_front() && !first { + result.push_str(formatting.separator.trim()); + result.push(' '); + } + result.push_str(inner_item); + + // Post-comments + if tactic == DefinitiveListTactic::Horizontal && item.post_comment.is_some() { + let comment = item.post_comment.as_ref().unwrap(); + let formatted_comment = rewrite_comment( + comment, + true, + Shape::legacy(formatting.shape.width, Indent::empty()), + formatting.config, + )?; + + result.push(' '); + result.push_str(&formatted_comment); + } + + if separate && sep_place.is_back() { + result.push_str(formatting.separator); + } + + if tactic != DefinitiveListTactic::Horizontal && item.post_comment.is_some() { + let comment = item.post_comment.as_ref().unwrap(); + let overhead = last_line_width(&result) + first_line_width(comment.trim()); + + let rewrite_post_comment = |item_max_width: &mut Option| { + if item_max_width.is_none() && !last && !inner_item.contains('\n') { + *item_max_width = Some(max_width_of_item_with_post_comment( + &cloned_items, + i, + overhead, + formatting.config.max_width(), + )); + } + let overhead = if starts_with_newline(comment) { + 0 + } else if let Some(max_width) = *item_max_width { + max_width + 2 + } else { + // 1 = space between item and comment. + item_last_line_width + 1 + }; + let width = formatting.shape.width.checked_sub(overhead).unwrap_or(1); + let offset = formatting.shape.indent + overhead; + let comment_shape = Shape::legacy(width, offset); + + // Use block-style only for the last item or multiline comments. + let block_style = !formatting.ends_with_newline && last + || comment.trim().contains('\n') + || comment.trim().len() > width; + + rewrite_comment( + comment.trim_start(), + block_style, + comment_shape, + formatting.config, + ) + }; + + let mut formatted_comment = rewrite_post_comment(&mut item_max_width)?; + + if !starts_with_newline(comment) { + if formatting.align_comments { + let mut comment_alignment = + post_comment_alignment(item_max_width, inner_item.len()); + if first_line_width(&formatted_comment) + + last_line_width(&result) + + comment_alignment + + 1 + > formatting.config.max_width() + { + item_max_width = None; + formatted_comment = rewrite_post_comment(&mut item_max_width)?; + comment_alignment = + post_comment_alignment(item_max_width, inner_item.len()); + } + for _ in 0..=comment_alignment { + result.push(' '); + } + } + // An additional space for the missing trailing separator (or + // if we skipped alignment above). + if !formatting.align_comments + || (last + && item_max_width.is_some() + && !separate + && !formatting.separator.is_empty()) + { + result.push(' '); + } + } else { + result.push('\n'); + result.push_str(indent_str); + } + if formatted_comment.contains('\n') { + item_max_width = None; + } + result.push_str(&formatted_comment); + } else { + item_max_width = None; + } + + if formatting.preserve_newline + && !last + && tactic == DefinitiveListTactic::Vertical + && item.new_lines + { + item_max_width = None; + result.push('\n'); + } + + prev_item_had_post_comment = item.post_comment.is_some(); + prev_item_is_nested_import = inner_item.contains("::"); + } + + Some(result) +} + +fn max_width_of_item_with_post_comment( + items: &I, + i: usize, + overhead: usize, + max_budget: usize, +) -> usize +where + I: IntoIterator + Clone, + T: AsRef, +{ + let mut max_width = 0; + let mut first = true; + for item in items.clone().into_iter().skip(i) { + let item = item.as_ref(); + let inner_item_width = item.inner_as_ref().len(); + if !first + && (item.is_different_group() + || item.post_comment.is_none() + || inner_item_width + overhead > max_budget) + { + return max_width; + } + if max_width < inner_item_width { + max_width = inner_item_width; + } + if item.new_lines { + return max_width; + } + first = false; + } + max_width +} + +fn post_comment_alignment(item_max_width: Option, inner_item_len: usize) -> usize { + item_max_width.unwrap_or(0).saturating_sub(inner_item_len) +} + +pub(crate) struct ListItems<'a, I, F1, F2, F3> +where + I: Iterator, +{ + snippet_provider: &'a SnippetProvider, + inner: Peekable, + get_lo: F1, + get_hi: F2, + get_item_string: F3, + prev_span_end: BytePos, + next_span_start: BytePos, + terminator: &'a str, + separator: &'a str, + leave_last: bool, +} + +pub(crate) fn extract_pre_comment(pre_snippet: &str) -> (Option, ListItemCommentStyle) { + let trimmed_pre_snippet = pre_snippet.trim(); + // Both start and end are checked to support keeping a block comment inline with + // the item, even if there are preceeding line comments, while still supporting + // a snippet that starts with a block comment but also contains one or more + // trailing single line comments. + // https://github.com/rust-lang/rustfmt/issues/3025 + // https://github.com/rust-lang/rustfmt/pull/3048 + // https://github.com/rust-lang/rustfmt/issues/3839 + let starts_with_block_comment = trimmed_pre_snippet.starts_with("/*"); + let ends_with_block_comment = trimmed_pre_snippet.ends_with("*/"); + let starts_with_single_line_comment = trimmed_pre_snippet.starts_with("//"); + if ends_with_block_comment { + let comment_end = pre_snippet.rfind(|c| c == '/').unwrap(); + if pre_snippet[comment_end..].contains('\n') { + ( + Some(trimmed_pre_snippet.to_owned()), + ListItemCommentStyle::DifferentLine, + ) + } else { + ( + Some(trimmed_pre_snippet.to_owned()), + ListItemCommentStyle::SameLine, + ) + } + } else if starts_with_single_line_comment || starts_with_block_comment { + ( + Some(trimmed_pre_snippet.to_owned()), + ListItemCommentStyle::DifferentLine, + ) + } else { + (None, ListItemCommentStyle::None) + } +} + +pub(crate) fn extract_post_comment( + post_snippet: &str, + comment_end: usize, + separator: &str, +) -> Option { + let white_space: &[_] = &[' ', '\t']; + + // Cleanup post-comment: strip separators and whitespace. + let post_snippet = post_snippet[..comment_end].trim(); + let post_snippet_trimmed = if post_snippet.starts_with(|c| c == ',' || c == ':') { + post_snippet[1..].trim_matches(white_space) + } else if post_snippet.starts_with(separator) { + post_snippet[separator.len()..].trim_matches(white_space) + } + // not comment or over two lines + else if post_snippet.ends_with(',') + && (!post_snippet.trim().starts_with("//") || post_snippet.trim().contains('\n')) + { + post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space) + } else { + post_snippet + }; + // FIXME(#3441): post_snippet includes 'const' now + // it should not include here + let removed_newline_snippet = post_snippet_trimmed.trim(); + if !post_snippet_trimmed.is_empty() + && (removed_newline_snippet.starts_with("//") || removed_newline_snippet.starts_with("/*")) + { + Some(post_snippet_trimmed.to_owned()) + } else { + None + } +} + +pub(crate) fn get_comment_end( + post_snippet: &str, + separator: &str, + terminator: &str, + is_last: bool, +) -> usize { + if is_last { + return post_snippet + .find_uncommented(terminator) + .unwrap_or_else(|| post_snippet.len()); + } + + let mut block_open_index = post_snippet.find("/*"); + // check if it really is a block comment (and not `//*` or a nested comment) + if let Some(i) = block_open_index { + match post_snippet.find('/') { + Some(j) if j < i => block_open_index = None, + _ if post_snippet[..i].ends_with('/') => block_open_index = None, + _ => (), + } + } + let newline_index = post_snippet.find('\n'); + if let Some(separator_index) = post_snippet.find_uncommented(separator) { + match (block_open_index, newline_index) { + // Separator before comment, with the next item on same line. + // Comment belongs to next item. + (Some(i), None) if i > separator_index => separator_index + 1, + // Block-style post-comment before the separator. + (Some(i), None) => cmp::max( + find_comment_end(&post_snippet[i..]).unwrap() + i, + separator_index + 1, + ), + // Block-style post-comment. Either before or after the separator. + (Some(i), Some(j)) if i < j => cmp::max( + find_comment_end(&post_snippet[i..]).unwrap() + i, + separator_index + 1, + ), + // Potential *single* line comment. + (_, Some(j)) if j > separator_index => j + 1, + _ => post_snippet.len(), + } + } else if let Some(newline_index) = newline_index { + // Match arms may not have trailing comma. In any case, for match arms, + // we will assume that the post comment belongs to the next arm if they + // do not end with trailing comma. + newline_index + 1 + } else { + 0 + } +} + +// Account for extra whitespace between items. This is fiddly +// because of the way we divide pre- and post- comments. +pub(crate) fn has_extra_newline(post_snippet: &str, comment_end: usize) -> bool { + if post_snippet.is_empty() || comment_end == 0 { + return false; + } + + let len_last = post_snippet[..comment_end] + .chars() + .last() + .unwrap() + .len_utf8(); + // Everything from the separator to the next item. + let test_snippet = &post_snippet[comment_end - len_last..]; + let first_newline = test_snippet + .find('\n') + .unwrap_or_else(|| test_snippet.len()); + // From the end of the first line of comments. + let test_snippet = &test_snippet[first_newline..]; + let first = test_snippet + .find(|c: char| !c.is_whitespace()) + .unwrap_or_else(|| test_snippet.len()); + // From the end of the first line of comments to the next non-whitespace char. + let test_snippet = &test_snippet[..first]; + + // There were multiple line breaks which got trimmed to nothing. + count_newlines(test_snippet) > 1 +} + +impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3> +where + I: Iterator, + F1: Fn(&T) -> BytePos, + F2: Fn(&T) -> BytePos, + F3: Fn(&T) -> Option, +{ + type Item = ListItem; + + fn next(&mut self) -> Option { + self.inner.next().map(|item| { + // Pre-comment + let pre_snippet = self + .snippet_provider + .span_to_snippet(mk_sp(self.prev_span_end, (self.get_lo)(&item))) + .unwrap_or(""); + let (pre_comment, pre_comment_style) = extract_pre_comment(pre_snippet); + + // Post-comment + let next_start = match self.inner.peek() { + Some(next_item) => (self.get_lo)(next_item), + None => self.next_span_start, + }; + let post_snippet = self + .snippet_provider + .span_to_snippet(mk_sp((self.get_hi)(&item), next_start)) + .unwrap_or(""); + let comment_end = get_comment_end( + post_snippet, + self.separator, + self.terminator, + self.inner.peek().is_none(), + ); + let new_lines = has_extra_newline(post_snippet, comment_end); + let post_comment = extract_post_comment(post_snippet, comment_end, self.separator); + + self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32); + + ListItem { + pre_comment, + pre_comment_style, + item: if self.inner.peek().is_none() && self.leave_last { + None + } else { + (self.get_item_string)(&item) + }, + post_comment, + new_lines, + } + }) + } +} + +#[allow(clippy::too_many_arguments)] +// Creates an iterator over a list's items with associated comments. +pub(crate) fn itemize_list<'a, T, I, F1, F2, F3>( + snippet_provider: &'a SnippetProvider, + inner: I, + terminator: &'a str, + separator: &'a str, + get_lo: F1, + get_hi: F2, + get_item_string: F3, + prev_span_end: BytePos, + next_span_start: BytePos, + leave_last: bool, +) -> ListItems<'a, I, F1, F2, F3> +where + I: Iterator, + F1: Fn(&T) -> BytePos, + F2: Fn(&T) -> BytePos, + F3: Fn(&T) -> Option, +{ + ListItems { + snippet_provider, + inner: inner.peekable(), + get_lo, + get_hi, + get_item_string, + prev_span_end, + next_span_start, + terminator, + separator, + leave_last, + } +} + +/// Returns the count and total width of the list items. +fn calculate_width(items: I) -> (usize, usize) +where + I: IntoIterator, + T: AsRef, +{ + items + .into_iter() + .map(|item| total_item_width(item.as_ref())) + .fold((0, 0), |acc, l| (acc.0 + 1, acc.1 + l)) +} + +pub(crate) fn total_item_width(item: &ListItem) -> usize { + comment_len(item.pre_comment.as_ref().map(|x| &(*x)[..])) + + comment_len(item.post_comment.as_ref().map(|x| &(*x)[..])) + + &item.item.as_ref().map_or(0, |s| unicode_str_width(&s)) +} + +fn comment_len(comment: Option<&str>) -> usize { + match comment { + Some(s) => { + let text_len = s.trim().len(); + if text_len > 0 { + // We'll put " /*" before and " */" after inline comments. + text_len + 6 + } else { + text_len + } + } + None => 0, + } +} + +// Compute horizontal and vertical shapes for a struct-lit-like thing. +pub(crate) fn struct_lit_shape( + shape: Shape, + context: &RewriteContext<'_>, + prefix_width: usize, + suffix_width: usize, +) -> Option<(Option, Shape)> { + let v_shape = match context.config.indent_style() { + IndentStyle::Visual => shape + .visual_indent(0) + .shrink_left(prefix_width)? + .sub_width(suffix_width)?, + IndentStyle::Block => { + let shape = shape.block_indent(context.config.tab_spaces()); + Shape { + width: context.budget(shape.indent.width()), + ..shape + } + } + }; + let shape_width = shape.width.checked_sub(prefix_width + suffix_width); + if let Some(w) = shape_width { + let shape_width = cmp::min(w, context.config.width_heuristics().struct_lit_width); + Some((Some(Shape::legacy(shape_width, shape.indent)), v_shape)) + } else { + Some((None, v_shape)) + } +} + +// Compute the tactic for the internals of a struct-lit-like thing. +pub(crate) fn struct_lit_tactic( + h_shape: Option, + context: &RewriteContext<'_>, + items: &[ListItem], +) -> DefinitiveListTactic { + if let Some(h_shape) = h_shape { + let prelim_tactic = match (context.config.indent_style(), items.len()) { + (IndentStyle::Visual, 1) => ListTactic::HorizontalVertical, + _ if context.config.struct_lit_single_line() => ListTactic::HorizontalVertical, + _ => ListTactic::Vertical, + }; + definitive_tactic(items, prelim_tactic, Separator::Comma, h_shape.width) + } else { + DefinitiveListTactic::Vertical + } +} + +// Given a tactic and possible shapes for horizontal and vertical layout, +// come up with the actual shape to use. +pub(crate) fn shape_for_tactic( + tactic: DefinitiveListTactic, + h_shape: Option, + v_shape: Shape, +) -> Shape { + match tactic { + DefinitiveListTactic::Horizontal => h_shape.unwrap(), + _ => v_shape, + } +} + +// Create a ListFormatting object for formatting the internals of a +// struct-lit-like thing, that is a series of fields. +pub(crate) fn struct_lit_formatting<'a>( + shape: Shape, + tactic: DefinitiveListTactic, + context: &'a RewriteContext<'_>, + force_no_trailing_comma: bool, +) -> ListFormatting<'a> { + let ends_with_newline = context.config.indent_style() != IndentStyle::Visual + && tactic == DefinitiveListTactic::Vertical; + ListFormatting { + tactic, + separator: ",", + trailing_separator: if force_no_trailing_comma { + SeparatorTactic::Never + } else { + context.config.trailing_comma() + }, + separator_place: SeparatorPlace::Back, + shape, + ends_with_newline, + preserve_newline: true, + nested: false, + align_comments: true, + config: context.config, + } +} diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs new file mode 100644 index 0000000000..1abb1cd9e1 --- /dev/null +++ b/src/tools/rustfmt/src/macros.rs @@ -0,0 +1,1612 @@ +// Format list-like macro invocations. These are invocations whose token trees +// can be interpreted as expressions and separated by commas. +// Note that these token trees do not actually have to be interpreted as +// expressions by the compiler. An example of an invocation we would reformat is +// foo!( x, y, z ). The token x may represent an identifier in the code, but we +// interpreted as an expression. +// Macro uses which are not-list like, such as bar!(key => val), will not be +// reformatted. +// List-like invocations with parentheses will be formatted as function calls, +// and those with brackets will be formatted as array literals. + +use std::collections::HashMap; +use std::panic::{catch_unwind, AssertUnwindSafe}; + +use rustc_ast::token::{BinOpToken, DelimToken, Token, TokenKind}; +use rustc_ast::tokenstream::{Cursor, LazyTokenStream, TokenStream, TokenTree}; +use rustc_ast::{ast, ptr}; +use rustc_ast_pretty::pprust; +use rustc_parse::parser::{ForceCollect, Parser}; +use rustc_parse::{stream_to_parser, MACRO_ARGUMENTS}; +use rustc_span::{ + symbol::{self, kw}, + BytePos, Span, Symbol, DUMMY_SP, +}; + +use crate::comment::{ + contains_comment, CharClasses, FindUncommented, FullCodeCharKind, LineClasses, +}; +use crate::config::lists::*; +use crate::expr::rewrite_array; +use crate::lists::{itemize_list, write_list, ListFormatting}; +use crate::overflow; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::{Indent, Shape}; +use crate::source_map::SpanUtils; +use crate::spanned::Spanned; +use crate::utils::{ + format_visibility, indent_next_line, is_empty_line, mk_sp, remove_trailing_white_spaces, + rewrite_ident, trim_left_preserve_layout, wrap_str, NodeIdExt, +}; +use crate::visitor::FmtVisitor; + +const FORCED_BRACKET_MACROS: &[&str] = &["vec!"]; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum MacroPosition { + Item, + Statement, + Expression, + Pat, +} + +#[derive(Debug)] +pub(crate) enum MacroArg { + Expr(ptr::P), + Ty(ptr::P), + Pat(ptr::P), + Item(ptr::P), + Keyword(symbol::Ident, Span), +} + +impl MacroArg { + fn is_item(&self) -> bool { + match self { + MacroArg::Item(..) => true, + _ => false, + } + } +} + +impl Rewrite for ast::Item { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let mut visitor = crate::visitor::FmtVisitor::from_context(context); + visitor.block_indent = shape.indent; + visitor.last_pos = self.span().lo(); + visitor.visit_item(self); + Some(visitor.buffer.to_owned()) + } +} + +impl Rewrite for MacroArg { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match *self { + MacroArg::Expr(ref expr) => expr.rewrite(context, shape), + MacroArg::Ty(ref ty) => ty.rewrite(context, shape), + MacroArg::Pat(ref pat) => pat.rewrite(context, shape), + MacroArg::Item(ref item) => item.rewrite(context, shape), + MacroArg::Keyword(ident, _) => Some(ident.name.to_string()), + } + } +} + +fn build_parser<'a>(context: &RewriteContext<'a>, cursor: Cursor) -> Parser<'a> { + stream_to_parser( + context.parse_sess.inner(), + cursor.collect(), + MACRO_ARGUMENTS, + ) +} + +fn parse_macro_arg<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option { + macro_rules! parse_macro_arg { + ($macro_arg:ident, $parser:expr, $f:expr) => { + let mut cloned_parser = (*parser).clone(); + match $parser(&mut cloned_parser) { + Ok(x) => { + if parser.sess.span_diagnostic.has_errors() { + parser.sess.span_diagnostic.reset_err_count(); + } else { + // Parsing succeeded. + *parser = cloned_parser; + return Some(MacroArg::$macro_arg($f(x)?)); + } + } + Err(mut e) => { + e.cancel(); + parser.sess.span_diagnostic.reset_err_count(); + } + } + }; + } + + parse_macro_arg!( + Expr, + |parser: &mut rustc_parse::parser::Parser<'b>| parser.parse_expr(), + |x: ptr::P| Some(x) + ); + parse_macro_arg!( + Ty, + |parser: &mut rustc_parse::parser::Parser<'b>| parser.parse_ty(), + |x: ptr::P| Some(x) + ); + parse_macro_arg!( + Pat, + |parser: &mut rustc_parse::parser::Parser<'b>| parser.parse_pat(None), + |x: ptr::P| Some(x) + ); + // `parse_item` returns `Option>`. + parse_macro_arg!( + Item, + |parser: &mut rustc_parse::parser::Parser<'b>| parser.parse_item(ForceCollect::No), + |x: Option>| x + ); + + None +} + +/// Rewrite macro name without using pretty-printer if possible. +fn rewrite_macro_name( + context: &RewriteContext<'_>, + path: &ast::Path, + extra_ident: Option, +) -> String { + let name = if path.segments.len() == 1 { + // Avoid using pretty-printer in the common case. + format!("{}!", rewrite_ident(context, path.segments[0].ident)) + } else { + format!("{}!", pprust::path_to_string(path)) + }; + match extra_ident { + Some(ident) if ident.name != kw::Empty => format!("{} {}", name, ident), + _ => name, + } +} + +// Use this on failing to format the macro call. +fn return_macro_parse_failure_fallback( + context: &RewriteContext<'_>, + indent: Indent, + span: Span, +) -> Option { + // Mark this as a failure however we format it + context.macro_rewrite_failure.replace(true); + + // Heuristically determine whether the last line of the macro uses "Block" style + // rather than using "Visual" style, or another indentation style. + let is_like_block_indent_style = context + .snippet(span) + .lines() + .last() + .map(|closing_line| { + closing_line.trim().chars().all(|ch| match ch { + '}' | ')' | ']' => true, + _ => false, + }) + }) + .unwrap_or(false); + if is_like_block_indent_style { + return trim_left_preserve_layout(context.snippet(span), indent, &context.config); + } + + context.skipped_range.borrow_mut().push(( + context.parse_sess.line_of_byte_pos(span.lo()), + context.parse_sess.line_of_byte_pos(span.hi()), + )); + + // Return the snippet unmodified if the macro is not block-like + Some(context.snippet(span).to_owned()) +} + +pub(crate) fn rewrite_macro( + mac: &ast::MacCall, + extra_ident: Option, + context: &RewriteContext<'_>, + shape: Shape, + position: MacroPosition, +) -> Option { + let should_skip = context + .skip_context + .skip_macro(&context.snippet(mac.path.span).to_owned()); + if should_skip { + None + } else { + let guard = context.enter_macro(); + let result = catch_unwind(AssertUnwindSafe(|| { + rewrite_macro_inner( + mac, + extra_ident, + context, + shape, + position, + guard.is_nested(), + ) + })); + match result { + Err(..) | Ok(None) => { + context.macro_rewrite_failure.replace(true); + None + } + Ok(rw) => rw, + } + } +} + +fn check_keyword<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option { + for &keyword in RUST_KW.iter() { + if parser.token.is_keyword(keyword) + && parser.look_ahead(1, |t| { + t.kind == TokenKind::Eof + || t.kind == TokenKind::Comma + || t.kind == TokenKind::CloseDelim(DelimToken::NoDelim) + }) + { + parser.bump(); + return Some(MacroArg::Keyword( + symbol::Ident::with_dummy_span(keyword), + parser.prev_token.span, + )); + } + } + None +} + +fn rewrite_macro_inner( + mac: &ast::MacCall, + extra_ident: Option, + context: &RewriteContext<'_>, + shape: Shape, + position: MacroPosition, + is_nested_macro: bool, +) -> Option { + if context.config.use_try_shorthand() { + if let Some(expr) = convert_try_mac(mac, context) { + context.leave_macro(); + return expr.rewrite(context, shape); + } + } + + let original_style = macro_style(mac, context); + + let macro_name = rewrite_macro_name(context, &mac.path, extra_ident); + + let style = if FORCED_BRACKET_MACROS.contains(&¯o_name[..]) && !is_nested_macro { + DelimToken::Bracket + } else { + original_style + }; + + let ts = mac.args.inner_tokens(); + let has_comment = contains_comment(context.snippet(mac.span())); + if ts.is_empty() && !has_comment { + return match style { + DelimToken::Paren if position == MacroPosition::Item => { + Some(format!("{}();", macro_name)) + } + DelimToken::Bracket if position == MacroPosition::Item => { + Some(format!("{}[];", macro_name)) + } + DelimToken::Paren => Some(format!("{}()", macro_name)), + DelimToken::Bracket => Some(format!("{}[]", macro_name)), + DelimToken::Brace => Some(format!("{} {{}}", macro_name)), + _ => unreachable!(), + }; + } + // Format well-known macros which cannot be parsed as a valid AST. + if macro_name == "lazy_static!" && !has_comment { + if let success @ Some(..) = format_lazy_static(context, shape, &ts) { + return success; + } + } + + let mut parser = build_parser(context, ts.trees()); + let mut arg_vec = Vec::new(); + let mut vec_with_semi = false; + let mut trailing_comma = false; + + if DelimToken::Brace != style { + loop { + if let Some(arg) = check_keyword(&mut parser) { + arg_vec.push(arg); + } else if let Some(arg) = parse_macro_arg(&mut parser) { + arg_vec.push(arg); + } else { + return return_macro_parse_failure_fallback(context, shape.indent, mac.span()); + } + + match parser.token.kind { + TokenKind::Eof => break, + TokenKind::Comma => (), + TokenKind::Semi => { + // Try to parse `vec![expr; expr]` + if FORCED_BRACKET_MACROS.contains(&¯o_name[..]) { + parser.bump(); + if parser.token.kind != TokenKind::Eof { + match parse_macro_arg(&mut parser) { + Some(arg) => { + arg_vec.push(arg); + parser.bump(); + if parser.token.kind == TokenKind::Eof && arg_vec.len() == 2 { + vec_with_semi = true; + break; + } + } + None => { + return return_macro_parse_failure_fallback( + context, + shape.indent, + mac.span(), + ); + } + } + } + } + return return_macro_parse_failure_fallback(context, shape.indent, mac.span()); + } + _ if arg_vec.last().map_or(false, MacroArg::is_item) => continue, + _ => return return_macro_parse_failure_fallback(context, shape.indent, mac.span()), + } + + parser.bump(); + + if parser.token.kind == TokenKind::Eof { + trailing_comma = true; + break; + } + } + } + + if !arg_vec.is_empty() && arg_vec.iter().all(MacroArg::is_item) { + return rewrite_macro_with_items( + context, + &arg_vec, + ¯o_name, + shape, + style, + position, + mac.span(), + ); + } + + match style { + DelimToken::Paren => { + // Handle special case: `vec!(expr; expr)` + if vec_with_semi { + handle_vec_semi(context, shape, arg_vec, macro_name, style) + } else { + // Format macro invocation as function call, preserve the trailing + // comma because not all macros support them. + overflow::rewrite_with_parens( + context, + ¯o_name, + arg_vec.iter(), + shape, + mac.span(), + context.config.width_heuristics().fn_call_width, + if trailing_comma { + Some(SeparatorTactic::Always) + } else { + Some(SeparatorTactic::Never) + }, + ) + .map(|rw| match position { + MacroPosition::Item => format!("{};", rw), + _ => rw, + }) + } + } + DelimToken::Bracket => { + // Handle special case: `vec![expr; expr]` + if vec_with_semi { + handle_vec_semi(context, shape, arg_vec, macro_name, style) + } else { + // If we are rewriting `vec!` macro or other special macros, + // then we can rewrite this as an usual array literal. + // Otherwise, we must preserve the original existence of trailing comma. + let macro_name = ¯o_name.as_str(); + let mut force_trailing_comma = if trailing_comma { + Some(SeparatorTactic::Always) + } else { + Some(SeparatorTactic::Never) + }; + if FORCED_BRACKET_MACROS.contains(macro_name) && !is_nested_macro { + context.leave_macro(); + if context.use_block_indent() { + force_trailing_comma = Some(SeparatorTactic::Vertical); + }; + } + let rewrite = rewrite_array( + macro_name, + arg_vec.iter(), + mac.span(), + context, + shape, + force_trailing_comma, + Some(original_style), + )?; + let comma = match position { + MacroPosition::Item => ";", + _ => "", + }; + + Some(format!("{}{}", rewrite, comma)) + } + } + DelimToken::Brace => { + // For macro invocations with braces, always put a space between + // the `macro_name!` and `{ /* macro_body */ }` but skip modifying + // anything in between the braces (for now). + let snippet = context.snippet(mac.span()).trim_start_matches(|c| c != '{'); + match trim_left_preserve_layout(snippet, shape.indent, &context.config) { + Some(macro_body) => Some(format!("{} {}", macro_name, macro_body)), + None => Some(format!("{} {}", macro_name, snippet)), + } + } + _ => unreachable!(), + } +} + +fn handle_vec_semi( + context: &RewriteContext<'_>, + shape: Shape, + arg_vec: Vec, + macro_name: String, + delim_token: DelimToken, +) -> Option { + let (left, right) = match delim_token { + DelimToken::Paren => ("(", ")"), + DelimToken::Bracket => ("[", "]"), + _ => unreachable!(), + }; + + let mac_shape = shape.offset_left(macro_name.len())?; + // 8 = `vec![]` + `; ` or `vec!()` + `; ` + let total_overhead = 8; + let nested_shape = mac_shape.block_indent(context.config.tab_spaces()); + let lhs = arg_vec[0].rewrite(context, nested_shape)?; + let rhs = arg_vec[1].rewrite(context, nested_shape)?; + if !lhs.contains('\n') + && !rhs.contains('\n') + && lhs.len() + rhs.len() + total_overhead <= shape.width + { + // macro_name(lhs; rhs) or macro_name[lhs; rhs] + Some(format!("{}{}{}; {}{}", macro_name, left, lhs, rhs, right)) + } else { + // macro_name(\nlhs;\nrhs\n) or macro_name[\nlhs;\nrhs\n] + Some(format!( + "{}{}{}{};{}{}{}{}", + macro_name, + left, + nested_shape.indent.to_string_with_newline(context.config), + lhs, + nested_shape.indent.to_string_with_newline(context.config), + rhs, + shape.indent.to_string_with_newline(context.config), + right + )) + } +} + +pub(crate) fn rewrite_macro_def( + context: &RewriteContext<'_>, + shape: Shape, + indent: Indent, + def: &ast::MacroDef, + ident: symbol::Ident, + vis: &ast::Visibility, + span: Span, +) -> Option { + let snippet = Some(remove_trailing_white_spaces(context.snippet(span))); + if snippet.as_ref().map_or(true, |s| s.ends_with(';')) { + return snippet; + } + + let ts = def.body.inner_tokens(); + let mut parser = MacroParser::new(ts.into_trees()); + let parsed_def = match parser.parse() { + Some(def) => def, + None => return snippet, + }; + + let mut result = if def.macro_rules { + String::from("macro_rules!") + } else { + format!("{}macro", format_visibility(context, vis)) + }; + + result += " "; + result += rewrite_ident(context, ident); + + let multi_branch_style = def.macro_rules || parsed_def.branches.len() != 1; + + let arm_shape = if multi_branch_style { + shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config) + } else { + shape + }; + + let branch_items = itemize_list( + context.snippet_provider, + parsed_def.branches.iter(), + "}", + ";", + |branch| branch.span.lo(), + |branch| branch.span.hi(), + |branch| match branch.rewrite(context, arm_shape, multi_branch_style) { + Some(v) => Some(v), + // if the rewrite returned None because a macro could not be rewritten, then return the + // original body + None if context.macro_rewrite_failure.get() => { + Some(context.snippet(branch.body).trim().to_string()) + } + None => None, + }, + context.snippet_provider.span_after(span, "{"), + span.hi(), + false, + ) + .collect::>(); + + let fmt = ListFormatting::new(arm_shape, context.config) + .separator(if def.macro_rules { ";" } else { "" }) + .trailing_separator(SeparatorTactic::Always) + .preserve_newline(true); + + if multi_branch_style { + result += " {"; + result += &arm_shape.indent.to_string_with_newline(context.config); + } + + match write_list(&branch_items, &fmt) { + Some(ref s) => result += s, + None => return snippet, + } + + if multi_branch_style { + result += &indent.to_string_with_newline(context.config); + result += "}"; + } + + Some(result) +} + +fn register_metavariable( + map: &mut HashMap, + result: &mut String, + name: &str, + dollar_count: usize, +) { + let mut new_name = "$".repeat(dollar_count - 1); + let mut old_name = "$".repeat(dollar_count); + + new_name.push('z'); + new_name.push_str(name); + old_name.push_str(name); + + result.push_str(&new_name); + map.insert(old_name, new_name); +} + +// Replaces `$foo` with `zfoo`. We must check for name overlap to ensure we +// aren't causing problems. +// This should also work for escaped `$` variables, where we leave earlier `$`s. +fn replace_names(input: &str) -> Option<(String, HashMap)> { + // Each substitution will require five or six extra bytes. + let mut result = String::with_capacity(input.len() + 64); + let mut substs = HashMap::new(); + let mut dollar_count = 0; + let mut cur_name = String::new(); + + for (kind, c) in CharClasses::new(input.chars()) { + if kind != FullCodeCharKind::Normal { + result.push(c); + } else if c == '$' { + dollar_count += 1; + } else if dollar_count == 0 { + result.push(c); + } else if !c.is_alphanumeric() && !cur_name.is_empty() { + // Terminates a name following one or more dollars. + register_metavariable(&mut substs, &mut result, &cur_name, dollar_count); + + result.push(c); + dollar_count = 0; + cur_name.clear(); + } else if c == '(' && cur_name.is_empty() { + // FIXME: Support macro def with repeat. + return None; + } else if c.is_alphanumeric() || c == '_' { + cur_name.push(c); + } + } + + if !cur_name.is_empty() { + register_metavariable(&mut substs, &mut result, &cur_name, dollar_count); + } + + debug!("replace_names `{}` {:?}", result, substs); + + Some((result, substs)) +} + +#[derive(Debug, Clone)] +enum MacroArgKind { + /// e.g., `$x: expr`. + MetaVariable(Symbol, String), + /// e.g., `$($foo: expr),*` + Repeat( + /// `()`, `[]` or `{}`. + DelimToken, + /// Inner arguments inside delimiters. + Vec, + /// Something after the closing delimiter and the repeat token, if available. + Option>, + /// The repeat token. This could be one of `*`, `+` or `?`. + Token, + ), + /// e.g., `[derive(Debug)]` + Delimited(DelimToken, Vec), + /// A possible separator. e.g., `,` or `;`. + Separator(String, String), + /// Other random stuff that does not fit to other kinds. + /// e.g., `== foo` in `($x: expr == foo)`. + Other(String, String), +} + +fn delim_token_to_str( + context: &RewriteContext<'_>, + delim_token: DelimToken, + shape: Shape, + use_multiple_lines: bool, + inner_is_empty: bool, +) -> (String, String) { + let (lhs, rhs) = match delim_token { + DelimToken::Paren => ("(", ")"), + DelimToken::Bracket => ("[", "]"), + DelimToken::Brace => { + if inner_is_empty || use_multiple_lines { + ("{", "}") + } else { + ("{ ", " }") + } + } + DelimToken::NoDelim => ("", ""), + }; + if use_multiple_lines { + let indent_str = shape.indent.to_string_with_newline(context.config); + let nested_indent_str = shape + .indent + .block_indent(context.config) + .to_string_with_newline(context.config); + ( + format!("{}{}", lhs, nested_indent_str), + format!("{}{}", indent_str, rhs), + ) + } else { + (lhs.to_owned(), rhs.to_owned()) + } +} + +impl MacroArgKind { + fn starts_with_brace(&self) -> bool { + match *self { + MacroArgKind::Repeat(DelimToken::Brace, _, _, _) + | MacroArgKind::Delimited(DelimToken::Brace, _) => true, + _ => false, + } + } + + fn starts_with_dollar(&self) -> bool { + match *self { + MacroArgKind::Repeat(..) | MacroArgKind::MetaVariable(..) => true, + _ => false, + } + } + + fn ends_with_space(&self) -> bool { + match *self { + MacroArgKind::Separator(..) => true, + _ => false, + } + } + + fn has_meta_var(&self) -> bool { + match *self { + MacroArgKind::MetaVariable(..) => true, + MacroArgKind::Repeat(_, ref args, _, _) => args.iter().any(|a| a.kind.has_meta_var()), + _ => false, + } + } + + fn rewrite( + &self, + context: &RewriteContext<'_>, + shape: Shape, + use_multiple_lines: bool, + ) -> Option { + let rewrite_delimited_inner = |delim_tok, args| -> Option<(String, String, String)> { + let inner = wrap_macro_args(context, args, shape)?; + let (lhs, rhs) = delim_token_to_str(context, delim_tok, shape, false, inner.is_empty()); + if lhs.len() + inner.len() + rhs.len() <= shape.width { + return Some((lhs, inner, rhs)); + } + + let (lhs, rhs) = delim_token_to_str(context, delim_tok, shape, true, false); + let nested_shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + let inner = wrap_macro_args(context, args, nested_shape)?; + Some((lhs, inner, rhs)) + }; + + match *self { + MacroArgKind::MetaVariable(ty, ref name) => Some(format!("${}:{}", name, ty)), + MacroArgKind::Repeat(delim_tok, ref args, ref another, ref tok) => { + let (lhs, inner, rhs) = rewrite_delimited_inner(delim_tok, args)?; + let another = another + .as_ref() + .and_then(|a| a.rewrite(context, shape, use_multiple_lines)) + .unwrap_or_else(|| "".to_owned()); + let repeat_tok = pprust::token_to_string(tok); + + Some(format!("${}{}{}{}{}", lhs, inner, rhs, another, repeat_tok)) + } + MacroArgKind::Delimited(delim_tok, ref args) => { + rewrite_delimited_inner(delim_tok, args) + .map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs)) + } + MacroArgKind::Separator(ref sep, ref prefix) => Some(format!("{}{} ", prefix, sep)), + MacroArgKind::Other(ref inner, ref prefix) => Some(format!("{}{}", prefix, inner)), + } + } +} + +#[derive(Debug, Clone)] +struct ParsedMacroArg { + kind: MacroArgKind, + span: Span, +} + +impl ParsedMacroArg { + fn rewrite( + &self, + context: &RewriteContext<'_>, + shape: Shape, + use_multiple_lines: bool, + ) -> Option { + self.kind.rewrite(context, shape, use_multiple_lines) + } +} + +/// Parses macro arguments on macro def. +struct MacroArgParser { + /// Either a name of the next metavariable, a separator, or junk. + buf: String, + /// The start position on the current buffer. + lo: BytePos, + /// The first token of the current buffer. + start_tok: Token, + /// `true` if we are parsing a metavariable or a repeat. + is_meta_var: bool, + /// The position of the last token. + hi: BytePos, + /// The last token parsed. + last_tok: Token, + /// Holds the parsed arguments. + result: Vec, +} + +fn last_tok(tt: &TokenTree) -> Token { + match *tt { + TokenTree::Token(ref t) => t.clone(), + TokenTree::Delimited(delim_span, delim, _) => Token { + kind: TokenKind::CloseDelim(delim), + span: delim_span.close, + }, + } +} + +impl MacroArgParser { + fn new() -> MacroArgParser { + MacroArgParser { + lo: BytePos(0), + hi: BytePos(0), + buf: String::new(), + is_meta_var: false, + last_tok: Token { + kind: TokenKind::Eof, + span: DUMMY_SP, + }, + start_tok: Token { + kind: TokenKind::Eof, + span: DUMMY_SP, + }, + result: vec![], + } + } + + fn set_last_tok(&mut self, tok: &TokenTree) { + self.hi = tok.span().hi(); + self.last_tok = last_tok(tok); + } + + fn add_separator(&mut self) { + let prefix = if self.need_space_prefix() { + " ".to_owned() + } else { + "".to_owned() + }; + self.result.push(ParsedMacroArg { + kind: MacroArgKind::Separator(self.buf.clone(), prefix), + span: mk_sp(self.lo, self.hi), + }); + self.buf.clear(); + } + + fn add_other(&mut self) { + let prefix = if self.need_space_prefix() { + " ".to_owned() + } else { + "".to_owned() + }; + self.result.push(ParsedMacroArg { + kind: MacroArgKind::Other(self.buf.clone(), prefix), + span: mk_sp(self.lo, self.hi), + }); + self.buf.clear(); + } + + fn add_meta_variable(&mut self, iter: &mut Cursor) -> Option<()> { + match iter.next() { + Some(TokenTree::Token(Token { + kind: TokenKind::Ident(name, _), + span, + })) => { + self.result.push(ParsedMacroArg { + kind: MacroArgKind::MetaVariable(name, self.buf.clone()), + span: mk_sp(self.lo, span.hi()), + }); + + self.buf.clear(); + self.is_meta_var = false; + Some(()) + } + _ => None, + } + } + + fn add_delimited(&mut self, inner: Vec, delim: DelimToken, span: Span) { + self.result.push(ParsedMacroArg { + kind: MacroArgKind::Delimited(delim, inner), + span, + }); + } + + // $($foo: expr),? + fn add_repeat( + &mut self, + inner: Vec, + delim: DelimToken, + iter: &mut Cursor, + span: Span, + ) -> Option<()> { + let mut buffer = String::new(); + let mut first = true; + let mut lo = span.lo(); + let mut hi = span.hi(); + + // Parse '*', '+' or '?. + for tok in iter { + self.set_last_tok(&tok); + if first { + first = false; + lo = tok.span().lo(); + } + + match tok { + TokenTree::Token(Token { + kind: TokenKind::BinOp(BinOpToken::Plus), + .. + }) + | TokenTree::Token(Token { + kind: TokenKind::Question, + .. + }) + | TokenTree::Token(Token { + kind: TokenKind::BinOp(BinOpToken::Star), + .. + }) => { + break; + } + TokenTree::Token(ref t) => { + buffer.push_str(&pprust::token_to_string(&t)); + hi = t.span.hi(); + } + _ => return None, + } + } + + // There could be some random stuff between ')' and '*', '+' or '?'. + let another = if buffer.trim().is_empty() { + None + } else { + Some(Box::new(ParsedMacroArg { + kind: MacroArgKind::Other(buffer, "".to_owned()), + span: mk_sp(lo, hi), + })) + }; + + self.result.push(ParsedMacroArg { + kind: MacroArgKind::Repeat(delim, inner, another, self.last_tok.clone()), + span: mk_sp(self.lo, self.hi), + }); + Some(()) + } + + fn update_buffer(&mut self, t: &Token) { + if self.buf.is_empty() { + self.lo = t.span.lo(); + self.start_tok = t.clone(); + } else { + let needs_space = match next_space(&self.last_tok.kind) { + SpaceState::Ident => ident_like(t), + SpaceState::Punctuation => !ident_like(t), + SpaceState::Always => true, + SpaceState::Never => false, + }; + if force_space_before(&t.kind) || needs_space { + self.buf.push(' '); + } + } + + self.buf.push_str(&pprust::token_to_string(t)); + } + + fn need_space_prefix(&self) -> bool { + if self.result.is_empty() { + return false; + } + + let last_arg = self.result.last().unwrap(); + if let MacroArgKind::MetaVariable(..) = last_arg.kind { + if ident_like(&self.start_tok) { + return true; + } + if self.start_tok.kind == TokenKind::Colon { + return true; + } + } + + if force_space_before(&self.start_tok.kind) { + return true; + } + + false + } + + /// Returns a collection of parsed macro def's arguments. + fn parse(mut self, tokens: TokenStream) -> Option> { + let mut iter = tokens.trees(); + + while let Some(tok) = iter.next() { + match tok { + TokenTree::Token(Token { + kind: TokenKind::Dollar, + span, + }) => { + // We always want to add a separator before meta variables. + if !self.buf.is_empty() { + self.add_separator(); + } + + // Start keeping the name of this metavariable in the buffer. + self.is_meta_var = true; + self.lo = span.lo(); + self.start_tok = Token { + kind: TokenKind::Dollar, + span, + }; + } + TokenTree::Token(Token { + kind: TokenKind::Colon, + .. + }) if self.is_meta_var => { + self.add_meta_variable(&mut iter)?; + } + TokenTree::Token(ref t) => self.update_buffer(t), + TokenTree::Delimited(delimited_span, delimited, ref tts) => { + if !self.buf.is_empty() { + if next_space(&self.last_tok.kind) == SpaceState::Always { + self.add_separator(); + } else { + self.add_other(); + } + } + + // Parse the stuff inside delimiters. + let mut parser = MacroArgParser::new(); + parser.lo = delimited_span.open.lo(); + let delimited_arg = parser.parse(tts.clone())?; + + let span = delimited_span.entire(); + if self.is_meta_var { + self.add_repeat(delimited_arg, delimited, &mut iter, span)?; + self.is_meta_var = false; + } else { + self.add_delimited(delimited_arg, delimited, span); + } + } + } + + self.set_last_tok(&tok); + } + + // We are left with some stuff in the buffer. Since there is nothing + // left to separate, add this as `Other`. + if !self.buf.is_empty() { + self.add_other(); + } + + Some(self.result) + } +} + +fn wrap_macro_args( + context: &RewriteContext<'_>, + args: &[ParsedMacroArg], + shape: Shape, +) -> Option { + wrap_macro_args_inner(context, args, shape, false) + .or_else(|| wrap_macro_args_inner(context, args, shape, true)) +} + +fn wrap_macro_args_inner( + context: &RewriteContext<'_>, + args: &[ParsedMacroArg], + shape: Shape, + use_multiple_lines: bool, +) -> Option { + let mut result = String::with_capacity(128); + let mut iter = args.iter().peekable(); + let indent_str = shape.indent.to_string_with_newline(context.config); + + while let Some(ref arg) = iter.next() { + result.push_str(&arg.rewrite(context, shape, use_multiple_lines)?); + + if use_multiple_lines + && (arg.kind.ends_with_space() || iter.peek().map_or(false, |a| a.kind.has_meta_var())) + { + if arg.kind.ends_with_space() { + result.pop(); + } + result.push_str(&indent_str); + } else if let Some(ref next_arg) = iter.peek() { + let space_before_dollar = + !arg.kind.ends_with_space() && next_arg.kind.starts_with_dollar(); + let space_before_brace = next_arg.kind.starts_with_brace(); + if space_before_dollar || space_before_brace { + result.push(' '); + } + } + } + + if !use_multiple_lines && result.len() >= shape.width { + None + } else { + Some(result) + } +} + +// This is a bit sketchy. The token rules probably need tweaking, but it works +// for some common cases. I hope the basic logic is sufficient. Note that the +// meaning of some tokens is a bit different here from usual Rust, e.g., `*` +// and `(`/`)` have special meaning. +// +// We always try and format on one line. +// FIXME: Use multi-line when every thing does not fit on one line. +fn format_macro_args( + context: &RewriteContext<'_>, + token_stream: TokenStream, + shape: Shape, +) -> Option { + if !context.config.format_macro_matchers() { + let span = span_for_token_stream(&token_stream); + return Some(match span { + Some(span) => context.snippet(span).to_owned(), + None => String::new(), + }); + } + let parsed_args = MacroArgParser::new().parse(token_stream)?; + wrap_macro_args(context, &parsed_args, shape) +} + +fn span_for_token_stream(token_stream: &TokenStream) -> Option { + token_stream.trees().next().map(|tt| tt.span()) +} + +// We should insert a space if the next token is a: +#[derive(Copy, Clone, PartialEq)] +enum SpaceState { + Never, + Punctuation, + Ident, // Or ident/literal-like thing. + Always, +} + +fn force_space_before(tok: &TokenKind) -> bool { + debug!("tok: force_space_before {:?}", tok); + + match tok { + TokenKind::Eq + | TokenKind::Lt + | TokenKind::Le + | TokenKind::EqEq + | TokenKind::Ne + | TokenKind::Ge + | TokenKind::Gt + | TokenKind::AndAnd + | TokenKind::OrOr + | TokenKind::Not + | TokenKind::Tilde + | TokenKind::BinOpEq(_) + | TokenKind::At + | TokenKind::RArrow + | TokenKind::LArrow + | TokenKind::FatArrow + | TokenKind::BinOp(_) + | TokenKind::Pound + | TokenKind::Dollar => true, + _ => false, + } +} + +fn ident_like(tok: &Token) -> bool { + match tok.kind { + TokenKind::Ident(..) | TokenKind::Literal(..) | TokenKind::Lifetime(_) => true, + _ => false, + } +} + +fn next_space(tok: &TokenKind) -> SpaceState { + debug!("next_space: {:?}", tok); + + match tok { + TokenKind::Not + | TokenKind::BinOp(BinOpToken::And) + | TokenKind::Tilde + | TokenKind::At + | TokenKind::Comma + | TokenKind::Dot + | TokenKind::DotDot + | TokenKind::DotDotDot + | TokenKind::DotDotEq + | TokenKind::Question => SpaceState::Punctuation, + + TokenKind::ModSep + | TokenKind::Pound + | TokenKind::Dollar + | TokenKind::OpenDelim(_) + | TokenKind::CloseDelim(_) => SpaceState::Never, + + TokenKind::Literal(..) | TokenKind::Ident(..) | TokenKind::Lifetime(_) => SpaceState::Ident, + + _ => SpaceState::Always, + } +} + +/// Tries to convert a macro use into a short hand try expression. Returns `None` +/// when the macro is not an instance of `try!` (or parsing the inner expression +/// failed). +pub(crate) fn convert_try_mac( + mac: &ast::MacCall, + context: &RewriteContext<'_>, +) -> Option { + let path = &pprust::path_to_string(&mac.path); + if path == "try" || path == "r#try" { + let ts = mac.args.inner_tokens(); + let mut parser = build_parser(context, ts.trees()); + + Some(ast::Expr { + id: ast::NodeId::root(), // dummy value + kind: ast::ExprKind::Try(parser.parse_expr().ok()?), + span: mac.span(), // incorrect span, but shouldn't matter too much + attrs: ast::AttrVec::new(), + tokens: Some(LazyTokenStream::new(ts)), + }) + } else { + None + } +} + +pub(crate) fn macro_style(mac: &ast::MacCall, context: &RewriteContext<'_>) -> DelimToken { + let snippet = context.snippet(mac.span()); + let paren_pos = snippet.find_uncommented("(").unwrap_or(usize::max_value()); + let bracket_pos = snippet.find_uncommented("[").unwrap_or(usize::max_value()); + let brace_pos = snippet.find_uncommented("{").unwrap_or(usize::max_value()); + + if paren_pos < bracket_pos && paren_pos < brace_pos { + DelimToken::Paren + } else if bracket_pos < brace_pos { + DelimToken::Bracket + } else { + DelimToken::Brace + } +} + +// A very simple parser that just parses a macros 2.0 definition into its branches. +// Currently we do not attempt to parse any further than that. +#[derive(new)] +struct MacroParser { + toks: Cursor, +} + +impl MacroParser { + // (`(` ... `)` `=>` `{` ... `}`)* + fn parse(&mut self) -> Option { + let mut branches = vec![]; + while self.toks.look_ahead(1).is_some() { + branches.push(self.parse_branch()?); + } + + Some(Macro { branches }) + } + + // `(` ... `)` `=>` `{` ... `}` + fn parse_branch(&mut self) -> Option { + let tok = self.toks.next()?; + let (lo, args_paren_kind) = match tok { + TokenTree::Token(..) => return None, + TokenTree::Delimited(delimited_span, d, _) => (delimited_span.open.lo(), d), + }; + let args = tok.joint(); + match self.toks.next()? { + TokenTree::Token(Token { + kind: TokenKind::FatArrow, + .. + }) => {} + _ => return None, + } + let (mut hi, body, whole_body) = match self.toks.next()? { + TokenTree::Token(..) => return None, + TokenTree::Delimited(delimited_span, ..) => { + let data = delimited_span.entire().data(); + ( + data.hi, + Span::new(data.lo + BytePos(1), data.hi - BytePos(1), data.ctxt), + delimited_span.entire(), + ) + } + }; + if let Some(TokenTree::Token(Token { + kind: TokenKind::Semi, + span, + })) = self.toks.look_ahead(0) + { + hi = span.hi(); + self.toks.next(); + } + Some(MacroBranch { + span: mk_sp(lo, hi), + args_paren_kind, + args, + body, + whole_body, + }) + } +} + +// A parsed macros 2.0 macro definition. +struct Macro { + branches: Vec, +} + +// FIXME: it would be more efficient to use references to the token streams +// rather than clone them, if we can make the borrowing work out. +struct MacroBranch { + span: Span, + args_paren_kind: DelimToken, + args: TokenStream, + body: Span, + whole_body: Span, +} + +impl MacroBranch { + fn rewrite( + &self, + context: &RewriteContext<'_>, + shape: Shape, + multi_branch_style: bool, + ) -> Option { + // Only attempt to format function-like macros. + if self.args_paren_kind != DelimToken::Paren { + // FIXME(#1539): implement for non-sugared macros. + return None; + } + + // 5 = " => {" + let mut result = format_macro_args(context, self.args.clone(), shape.sub_width(5)?)?; + + if multi_branch_style { + result += " =>"; + } + + if !context.config.format_macro_bodies() { + result += " "; + result += context.snippet(self.whole_body); + return Some(result); + } + + // The macro body is the most interesting part. It might end up as various + // AST nodes, but also has special variables (e.g, `$foo`) which can't be + // parsed as regular Rust code (and note that these can be escaped using + // `$$`). We'll try and format like an AST node, but we'll substitute + // variables for new names with the same length first. + + let old_body = context.snippet(self.body).trim(); + let (body_str, substs) = replace_names(old_body)?; + let has_block_body = old_body.starts_with('{'); + + let mut config = context.config.clone(); + config.set().hide_parse_errors(true); + + result += " {"; + + let body_indent = if has_block_body { + shape.indent + } else { + shape.indent.block_indent(&config) + }; + let new_width = config.max_width() - body_indent.width(); + config.set().max_width(new_width); + + // First try to format as items, then as statements. + let new_body_snippet = match crate::format_snippet(&body_str, &config, true) { + Some(new_body) => new_body, + None => { + let new_width = new_width + config.tab_spaces(); + config.set().max_width(new_width); + match crate::format_code_block(&body_str, &config, true) { + Some(new_body) => new_body, + None => return None, + } + } + }; + let new_body = wrap_str( + new_body_snippet.snippet.to_string(), + config.max_width(), + shape, + )?; + + // Indent the body since it is in a block. + let indent_str = body_indent.to_string(&config); + let mut new_body = LineClasses::new(new_body.trim_end()) + .enumerate() + .fold( + (String::new(), true), + |(mut s, need_indent), (i, (kind, ref l))| { + if !is_empty_line(l) + && need_indent + && !new_body_snippet.is_line_non_formatted(i + 1) + { + s += &indent_str; + } + (s + l + "\n", indent_next_line(kind, &l, &config)) + }, + ) + .0; + + // Undo our replacement of macro variables. + // FIXME: this could be *much* more efficient. + for (old, new) in &substs { + if old_body.find(new).is_some() { + debug!("rewrite_macro_def: bailing matching variable: `{}`", new); + return None; + } + new_body = new_body.replace(new, old); + } + + if has_block_body { + result += new_body.trim(); + } else if !new_body.is_empty() { + result += "\n"; + result += &new_body; + result += &shape.indent.to_string(&config); + } + + result += "}"; + + Some(result) + } +} + +/// Format `lazy_static!` from https://crates.io/crates/lazy_static. +/// +/// # Expected syntax +/// +/// ```text +/// lazy_static! { +/// [pub] static ref NAME_1: TYPE_1 = EXPR_1; +/// [pub] static ref NAME_2: TYPE_2 = EXPR_2; +/// ... +/// [pub] static ref NAME_N: TYPE_N = EXPR_N; +/// } +/// ``` +fn format_lazy_static( + context: &RewriteContext<'_>, + shape: Shape, + ts: &TokenStream, +) -> Option { + let mut result = String::with_capacity(1024); + let mut parser = build_parser(context, ts.trees()); + let nested_shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + + result.push_str("lazy_static! {"); + result.push_str(&nested_shape.indent.to_string_with_newline(context.config)); + + macro_rules! parse_or { + ($method:ident $(,)* $($arg:expr),* $(,)*) => { + match parser.$method($($arg,)*) { + Ok(val) => { + if parser.sess.span_diagnostic.has_errors() { + parser.sess.span_diagnostic.reset_err_count(); + return None; + } else { + val + } + } + Err(mut err) => { + err.cancel(); + parser.sess.span_diagnostic.reset_err_count(); + return None; + } + } + } + } + + while parser.token.kind != TokenKind::Eof { + // Parse a `lazy_static!` item. + let vis = crate::utils::format_visibility( + context, + &parse_or!(parse_visibility, rustc_parse::parser::FollowedByType::No), + ); + parser.eat_keyword(kw::Static); + parser.eat_keyword(kw::Ref); + let id = parse_or!(parse_ident); + parser.eat(&TokenKind::Colon); + let ty = parse_or!(parse_ty); + parser.eat(&TokenKind::Eq); + let expr = parse_or!(parse_expr); + parser.eat(&TokenKind::Semi); + + // Rewrite as a static item. + let mut stmt = String::with_capacity(128); + stmt.push_str(&format!( + "{}static ref {}: {} =", + vis, + id, + ty.rewrite(context, nested_shape)? + )); + result.push_str(&crate::expr::rewrite_assign_rhs( + context, + stmt, + &*expr, + nested_shape.sub_width(1)?, + )?); + result.push(';'); + if parser.token.kind != TokenKind::Eof { + result.push_str(&nested_shape.indent.to_string_with_newline(context.config)); + } + } + + result.push_str(&shape.indent.to_string_with_newline(context.config)); + result.push('}'); + + Some(result) +} + +fn rewrite_macro_with_items( + context: &RewriteContext<'_>, + items: &[MacroArg], + macro_name: &str, + shape: Shape, + style: DelimToken, + position: MacroPosition, + span: Span, +) -> Option { + let (opener, closer) = match style { + DelimToken::Paren => ("(", ")"), + DelimToken::Bracket => ("[", "]"), + DelimToken::Brace => (" {", "}"), + _ => return None, + }; + let trailing_semicolon = match style { + DelimToken::Paren | DelimToken::Bracket if position == MacroPosition::Item => ";", + _ => "", + }; + + let mut visitor = FmtVisitor::from_context(context); + visitor.block_indent = shape.indent.block_indent(context.config); + visitor.last_pos = context.snippet_provider.span_after(span, opener.trim()); + for item in items { + let item = match item { + MacroArg::Item(item) => item, + _ => return None, + }; + visitor.visit_item(&item); + } + + let mut result = String::with_capacity(256); + result.push_str(¯o_name); + result.push_str(opener); + result.push_str(&visitor.block_indent.to_string_with_newline(context.config)); + result.push_str(visitor.buffer.trim()); + result.push_str(&shape.indent.to_string_with_newline(context.config)); + result.push_str(closer); + result.push_str(trailing_semicolon); + Some(result) +} + +const RUST_KW: [Symbol; 59] = [ + kw::PathRoot, + kw::DollarCrate, + kw::Underscore, + kw::As, + kw::Box, + kw::Break, + kw::Const, + kw::Continue, + kw::Crate, + kw::Else, + kw::Enum, + kw::Extern, + kw::False, + kw::Fn, + kw::For, + kw::If, + kw::Impl, + kw::In, + kw::Let, + kw::Loop, + kw::Match, + kw::Mod, + kw::Move, + kw::Mut, + kw::Pub, + kw::Ref, + kw::Return, + kw::SelfLower, + kw::SelfUpper, + kw::Static, + kw::Struct, + kw::Super, + kw::Trait, + kw::True, + kw::Type, + kw::Unsafe, + kw::Use, + kw::Where, + kw::While, + kw::Abstract, + kw::Become, + kw::Do, + kw::Final, + kw::Macro, + kw::Override, + kw::Priv, + kw::Typeof, + kw::Unsized, + kw::Virtual, + kw::Yield, + kw::Dyn, + kw::Async, + kw::Try, + kw::UnderscoreLifetime, + kw::StaticLifetime, + kw::Auto, + kw::Catch, + kw::Default, + kw::Union, +]; diff --git a/src/tools/rustfmt/src/matches.rs b/src/tools/rustfmt/src/matches.rs new file mode 100644 index 0000000000..a43aed09ef --- /dev/null +++ b/src/tools/rustfmt/src/matches.rs @@ -0,0 +1,599 @@ +//! Format match expression. + +use std::iter::repeat; + +use rustc_ast::{ast, ptr}; +use rustc_span::{BytePos, Span}; + +use crate::comment::{combine_strs_with_missing_comments, rewrite_comment}; +use crate::config::lists::*; +use crate::config::{Config, ControlBraceStyle, IndentStyle, MatchArmLeadingPipe, Version}; +use crate::expr::{ + format_expr, is_empty_block, is_simple_block, is_unsafe_block, prefer_next_line, rewrite_cond, + ExprType, RhsTactics, +}; +use crate::lists::{itemize_list, write_list, ListFormatting}; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::source_map::SpanUtils; +use crate::spanned::Spanned; +use crate::utils::{ + contains_skip, extra_offset, first_line_width, inner_attributes, last_line_extendable, mk_sp, + semicolon_for_expr, trimmed_last_line_width, unicode_str_width, +}; + +/// A simple wrapper type against `ast::Arm`. Used inside `write_list()`. +struct ArmWrapper<'a> { + arm: &'a ast::Arm, + /// `true` if the arm is the last one in match expression. Used to decide on whether we should + /// add trailing comma to the match arm when `config.trailing_comma() == Never`. + is_last: bool, + /// Holds a byte position of `|` at the beginning of the arm pattern, if available. + beginning_vert: Option, +} + +impl<'a> ArmWrapper<'a> { + fn new(arm: &'a ast::Arm, is_last: bool, beginning_vert: Option) -> ArmWrapper<'a> { + ArmWrapper { + arm, + is_last, + beginning_vert, + } + } +} + +impl<'a> Spanned for ArmWrapper<'a> { + fn span(&self) -> Span { + if let Some(lo) = self.beginning_vert { + let lo = std::cmp::min(lo, self.arm.span().lo()); + mk_sp(lo, self.arm.span().hi()) + } else { + self.arm.span() + } + } +} + +impl<'a> Rewrite for ArmWrapper<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + rewrite_match_arm( + context, + self.arm, + shape, + self.is_last, + self.beginning_vert.is_some(), + ) + } +} + +pub(crate) fn rewrite_match( + context: &RewriteContext<'_>, + cond: &ast::Expr, + arms: &[ast::Arm], + shape: Shape, + span: Span, + attrs: &[ast::Attribute], +) -> Option { + // Do not take the rhs overhead from the upper expressions into account + // when rewriting match condition. + let cond_shape = Shape { + width: context.budget(shape.used_width()), + ..shape + }; + // 6 = `match ` + let cond_shape = match context.config.indent_style() { + IndentStyle::Visual => cond_shape.shrink_left(6)?, + IndentStyle::Block => cond_shape.offset_left(6)?, + }; + let cond_str = cond.rewrite(context, cond_shape)?; + let alt_block_sep = &shape.indent.to_string_with_newline(context.config); + let block_sep = match context.config.control_brace_style() { + ControlBraceStyle::AlwaysNextLine => alt_block_sep, + _ if last_line_extendable(&cond_str) => " ", + // 2 = ` {` + _ if cond_str.contains('\n') || cond_str.len() + 2 > cond_shape.width => alt_block_sep, + _ => " ", + }; + + let nested_indent_str = shape + .indent + .block_indent(context.config) + .to_string(context.config); + // Inner attributes. + let inner_attrs = &inner_attributes(attrs); + let inner_attrs_str = if inner_attrs.is_empty() { + String::new() + } else { + inner_attrs + .rewrite(context, shape) + .map(|s| format!("{}{}\n", nested_indent_str, s))? + }; + + let open_brace_pos = if inner_attrs.is_empty() { + let hi = if arms.is_empty() { + span.hi() + } else { + arms[0].span().lo() + }; + context + .snippet_provider + .span_after(mk_sp(cond.span.hi(), hi), "{") + } else { + inner_attrs[inner_attrs.len() - 1].span.hi() + }; + + if arms.is_empty() { + let snippet = context.snippet(mk_sp(open_brace_pos, span.hi() - BytePos(1))); + if snippet.trim().is_empty() { + Some(format!("match {} {{}}", cond_str)) + } else { + // Empty match with comments or inner attributes? We are not going to bother, sorry ;) + Some(context.snippet(span).to_owned()) + } + } else { + let span_after_cond = mk_sp(cond.span.hi(), span.hi()); + Some(format!( + "match {}{}{{\n{}{}{}\n{}}}", + cond_str, + block_sep, + inner_attrs_str, + nested_indent_str, + rewrite_match_arms(context, arms, shape, span_after_cond, open_brace_pos)?, + shape.indent.to_string(context.config), + )) + } +} + +fn arm_comma(config: &Config, body: &ast::Expr, is_last: bool) -> &'static str { + if is_last && config.trailing_comma() == SeparatorTactic::Never { + "" + } else if config.match_block_trailing_comma() { + "," + } else if let ast::ExprKind::Block(ref block, _) = body.kind { + if let ast::BlockCheckMode::Default = block.rules { + "" + } else { + "," + } + } else { + "," + } +} + +/// Collect a byte position of the beginning `|` for each arm, if available. +fn collect_beginning_verts( + context: &RewriteContext<'_>, + arms: &[ast::Arm], + span: Span, +) -> Vec> { + let mut beginning_verts = Vec::with_capacity(arms.len()); + let mut lo = context.snippet_provider.span_after(span, "{"); + for arm in arms { + let hi = arm.pat.span.lo(); + let missing_span = mk_sp(lo, hi); + beginning_verts.push(context.snippet_provider.opt_span_before(missing_span, "|")); + lo = arm.span().hi(); + } + beginning_verts +} + +fn rewrite_match_arms( + context: &RewriteContext<'_>, + arms: &[ast::Arm], + shape: Shape, + span: Span, + open_brace_pos: BytePos, +) -> Option { + let arm_shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + + let arm_len = arms.len(); + let is_last_iter = repeat(false) + .take(arm_len.saturating_sub(1)) + .chain(repeat(true)); + let beginning_verts = collect_beginning_verts(context, arms, span); + let items = itemize_list( + context.snippet_provider, + arms.iter() + .zip(is_last_iter) + .zip(beginning_verts.into_iter()) + .map(|((arm, is_last), beginning_vert)| ArmWrapper::new(arm, is_last, beginning_vert)), + "}", + "|", + |arm| arm.span().lo(), + |arm| arm.span().hi(), + |arm| arm.rewrite(context, arm_shape), + open_brace_pos, + span.hi(), + false, + ); + let arms_vec: Vec<_> = items.collect(); + // We will add/remove commas inside `arm.rewrite()`, and hence no separator here. + let fmt = ListFormatting::new(arm_shape, context.config) + .separator("") + .preserve_newline(true); + + write_list(&arms_vec, &fmt) +} + +fn rewrite_match_arm( + context: &RewriteContext<'_>, + arm: &ast::Arm, + shape: Shape, + is_last: bool, + has_leading_pipe: bool, +) -> Option { + let (missing_span, attrs_str) = if !arm.attrs.is_empty() { + if contains_skip(&arm.attrs) { + let (_, body) = flatten_arm_body(context, &arm.body, None); + // `arm.span()` does not include trailing comma, add it manually. + return Some(format!( + "{}{}", + context.snippet(arm.span()), + arm_comma(context.config, body, is_last), + )); + } + let missing_span = mk_sp(arm.attrs[arm.attrs.len() - 1].span.hi(), arm.pat.span.lo()); + (missing_span, arm.attrs.rewrite(context, shape)?) + } else { + (mk_sp(arm.span().lo(), arm.span().lo()), String::new()) + }; + + // Leading pipe offset + // 2 = `| ` + let (pipe_offset, pipe_str) = match context.config.match_arm_leading_pipes() { + MatchArmLeadingPipe::Never => (0, ""), + MatchArmLeadingPipe::Preserve if !has_leading_pipe => (0, ""), + MatchArmLeadingPipe::Preserve | MatchArmLeadingPipe::Always => (2, "| "), + }; + + // Patterns + // 5 = ` => {` + let pat_shape = shape.sub_width(5)?.offset_left(pipe_offset)?; + let pats_str = arm.pat.rewrite(context, pat_shape)?; + + // Guard + let block_like_pat = trimmed_last_line_width(&pats_str) <= context.config.tab_spaces(); + let new_line_guard = pats_str.contains('\n') && !block_like_pat; + let guard_str = rewrite_guard( + context, + &arm.guard, + shape, + trimmed_last_line_width(&pats_str), + new_line_guard, + )?; + + let lhs_str = combine_strs_with_missing_comments( + context, + &attrs_str, + &format!("{}{}{}", pipe_str, pats_str, guard_str), + missing_span, + shape, + false, + )?; + + let arrow_span = mk_sp(arm.pat.span.hi(), arm.body.span().lo()); + rewrite_match_body( + context, + &arm.body, + &lhs_str, + shape, + guard_str.contains('\n'), + arrow_span, + is_last, + ) +} + +fn stmt_is_expr_mac(stmt: &ast::Stmt) -> bool { + if let ast::StmtKind::Expr(expr) = &stmt.kind { + if let ast::ExprKind::MacCall(_) = &expr.kind { + return true; + } + } + false +} + +fn block_can_be_flattened<'a>( + context: &RewriteContext<'_>, + expr: &'a ast::Expr, +) -> Option<&'a ast::Block> { + match expr.kind { + ast::ExprKind::Block(ref block, _) + if !is_unsafe_block(block) + && !context.inside_macro() + && is_simple_block(context, block, Some(&expr.attrs)) + && !stmt_is_expr_mac(&block.stmts[0]) => + { + Some(&*block) + } + _ => None, + } +} + +// (extend, body) +// @extend: true if the arm body can be put next to `=>` +// @body: flattened body, if the body is block with a single expression +fn flatten_arm_body<'a>( + context: &'a RewriteContext<'_>, + body: &'a ast::Expr, + opt_shape: Option, +) -> (bool, &'a ast::Expr) { + let can_extend = + |expr| !context.config.force_multiline_blocks() && can_flatten_block_around_this(expr); + + if let Some(ref block) = block_can_be_flattened(context, body) { + if let ast::StmtKind::Expr(ref expr) = block.stmts[0].kind { + if let ast::ExprKind::Block(..) = expr.kind { + flatten_arm_body(context, expr, None) + } else { + let cond_becomes_muti_line = opt_shape + .and_then(|shape| rewrite_cond(context, expr, shape)) + .map_or(false, |cond| cond.contains('\n')); + if cond_becomes_muti_line { + (false, &*body) + } else { + (can_extend(expr), &*expr) + } + } + } else { + (false, &*body) + } + } else { + (can_extend(body), &*body) + } +} + +fn rewrite_match_body( + context: &RewriteContext<'_>, + body: &ptr::P, + pats_str: &str, + shape: Shape, + has_guard: bool, + arrow_span: Span, + is_last: bool, +) -> Option { + let (extend, body) = flatten_arm_body( + context, + body, + shape.offset_left(extra_offset(pats_str, shape) + 4), + ); + let (is_block, is_empty_block) = if let ast::ExprKind::Block(ref block, _) = body.kind { + (true, is_empty_block(context, block, Some(&body.attrs))) + } else { + (false, false) + }; + + let comma = arm_comma(context.config, body, is_last); + let alt_block_sep = &shape.indent.to_string_with_newline(context.config); + + let combine_orig_body = |body_str: &str| { + let block_sep = match context.config.control_brace_style() { + ControlBraceStyle::AlwaysNextLine if is_block => alt_block_sep, + _ => " ", + }; + + Some(format!("{} =>{}{}{}", pats_str, block_sep, body_str, comma)) + }; + + let next_line_indent = if !is_block || is_empty_block { + shape.indent.block_indent(context.config) + } else { + shape.indent + }; + + let forbid_same_line = + (has_guard && pats_str.contains('\n') && !is_empty_block) || !body.attrs.is_empty(); + + // Look for comments between `=>` and the start of the body. + let arrow_comment = { + let arrow_snippet = context.snippet(arrow_span).trim(); + // search for the arrow starting from the end of the snippet since there may be a match + // expression within the guard + let arrow_index = arrow_snippet.rfind("=>").unwrap(); + // 2 = `=>` + let comment_str = arrow_snippet[arrow_index + 2..].trim(); + if comment_str.is_empty() { + String::new() + } else { + rewrite_comment(comment_str, false, shape, &context.config)? + } + }; + + let combine_next_line_body = |body_str: &str| { + let nested_indent_str = next_line_indent.to_string_with_newline(context.config); + + if is_block { + let mut result = pats_str.to_owned(); + result.push_str(" =>"); + if !arrow_comment.is_empty() { + result.push_str(&nested_indent_str); + result.push_str(&arrow_comment); + } + result.push_str(&nested_indent_str); + result.push_str(&body_str); + return Some(result); + } + + let indent_str = shape.indent.to_string_with_newline(context.config); + let (body_prefix, body_suffix) = + if context.config.match_arm_blocks() && !context.inside_macro() { + let comma = if context.config.match_block_trailing_comma() { + "," + } else { + "" + }; + let semicolon = if context.config.version() == Version::One { + "" + } else { + if semicolon_for_expr(context, body) { + ";" + } else { + "" + } + }; + ("{", format!("{}{}}}{}", semicolon, indent_str, comma)) + } else { + ("", String::from(",")) + }; + + let block_sep = match context.config.control_brace_style() { + ControlBraceStyle::AlwaysNextLine => format!("{}{}", alt_block_sep, body_prefix), + _ if body_prefix.is_empty() => "".to_owned(), + _ if forbid_same_line || !arrow_comment.is_empty() => { + format!("{}{}", alt_block_sep, body_prefix) + } + _ => format!(" {}", body_prefix), + } + &nested_indent_str; + + let mut result = pats_str.to_owned(); + result.push_str(" =>"); + if !arrow_comment.is_empty() { + result.push_str(&indent_str); + result.push_str(&arrow_comment); + } + result.push_str(&block_sep); + result.push_str(&body_str); + result.push_str(&body_suffix); + Some(result) + }; + + // Let's try and get the arm body on the same line as the condition. + // 4 = ` => `.len() + let orig_body_shape = shape + .offset_left(extra_offset(pats_str, shape) + 4) + .and_then(|shape| shape.sub_width(comma.len())); + let orig_body = if forbid_same_line || !arrow_comment.is_empty() { + None + } else if let Some(body_shape) = orig_body_shape { + let rewrite = nop_block_collapse( + format_expr(body, ExprType::Statement, context, body_shape), + body_shape.width, + ); + + match rewrite { + Some(ref body_str) + if is_block + || (!body_str.contains('\n') + && unicode_str_width(body_str) <= body_shape.width) => + { + return combine_orig_body(body_str); + } + _ => rewrite, + } + } else { + None + }; + let orig_budget = orig_body_shape.map_or(0, |shape| shape.width); + + // Try putting body on the next line and see if it looks better. + let next_line_body_shape = Shape::indented(next_line_indent, context.config); + let next_line_body = nop_block_collapse( + format_expr(body, ExprType::Statement, context, next_line_body_shape), + next_line_body_shape.width, + ); + match (orig_body, next_line_body) { + (Some(ref orig_str), Some(ref next_line_str)) + if prefer_next_line(orig_str, next_line_str, RhsTactics::Default) => + { + combine_next_line_body(next_line_str) + } + (Some(ref orig_str), _) if extend && first_line_width(orig_str) <= orig_budget => { + combine_orig_body(orig_str) + } + (Some(ref orig_str), Some(ref next_line_str)) if orig_str.contains('\n') => { + combine_next_line_body(next_line_str) + } + (None, Some(ref next_line_str)) => combine_next_line_body(next_line_str), + (None, None) => None, + (Some(ref orig_str), _) => combine_orig_body(orig_str), + } +} + +// The `if ...` guard on a match arm. +fn rewrite_guard( + context: &RewriteContext<'_>, + guard: &Option>, + shape: Shape, + // The amount of space used up on this line for the pattern in + // the arm (excludes offset). + pattern_width: usize, + multiline_pattern: bool, +) -> Option { + if let Some(ref guard) = *guard { + // First try to fit the guard string on the same line as the pattern. + // 4 = ` if `, 5 = ` => {` + let cond_shape = shape + .offset_left(pattern_width + 4) + .and_then(|s| s.sub_width(5)); + if !multiline_pattern { + if let Some(cond_shape) = cond_shape { + if let Some(cond_str) = guard.rewrite(context, cond_shape) { + if !cond_str.contains('\n') || pattern_width <= context.config.tab_spaces() { + return Some(format!(" if {}", cond_str)); + } + } + } + } + + // Not enough space to put the guard after the pattern, try a newline. + // 3 = `if `, 5 = ` => {` + let cond_shape = Shape::indented(shape.indent.block_indent(context.config), context.config) + .offset_left(3) + .and_then(|s| s.sub_width(5)); + if let Some(cond_shape) = cond_shape { + if let Some(cond_str) = guard.rewrite(context, cond_shape) { + return Some(format!( + "{}if {}", + cond_shape.indent.to_string_with_newline(context.config), + cond_str + )); + } + } + + None + } else { + Some(String::new()) + } +} + +fn nop_block_collapse(block_str: Option, budget: usize) -> Option { + debug!("nop_block_collapse {:?} {}", block_str, budget); + block_str.map(|block_str| { + if block_str.starts_with('{') + && budget >= 2 + && (block_str[1..].find(|c: char| !c.is_whitespace()).unwrap() == block_str.len() - 2) + { + String::from("{}") + } else { + block_str + } + }) +} + +fn can_flatten_block_around_this(body: &ast::Expr) -> bool { + match body.kind { + // We do not allow `if` to stay on the same line, since we could easily mistake + // `pat => if cond { ... }` and `pat if cond => { ... }`. + ast::ExprKind::If(..) => false, + // We do not allow collapsing a block around expression with condition + // to avoid it being cluttered with match arm. + ast::ExprKind::ForLoop(..) | ast::ExprKind::While(..) => false, + ast::ExprKind::Loop(..) + | ast::ExprKind::Match(..) + | ast::ExprKind::Block(..) + | ast::ExprKind::Closure(..) + | ast::ExprKind::Array(..) + | ast::ExprKind::Call(..) + | ast::ExprKind::MethodCall(..) + | ast::ExprKind::MacCall(..) + | ast::ExprKind::Struct(..) + | ast::ExprKind::Tup(..) => true, + ast::ExprKind::AddrOf(_, _, ref expr) + | ast::ExprKind::Box(ref expr) + | ast::ExprKind::Try(ref expr) + | ast::ExprKind::Unary(_, ref expr) + | ast::ExprKind::Index(ref expr, _) + | ast::ExprKind::Cast(ref expr, _) => can_flatten_block_around_this(expr), + _ => false, + } +} diff --git a/src/tools/rustfmt/src/missed_spans.rs b/src/tools/rustfmt/src/missed_spans.rs new file mode 100644 index 0000000000..17b11ed6cf --- /dev/null +++ b/src/tools/rustfmt/src/missed_spans.rs @@ -0,0 +1,361 @@ +use rustc_span::{BytePos, Pos, Span}; + +use crate::comment::{is_last_comment_block, rewrite_comment, CodeCharKind, CommentCodeSlices}; +use crate::config::file_lines::FileLines; +use crate::config::FileName; +use crate::config::Version; +use crate::coverage::transform_missing_snippet; +use crate::shape::{Indent, Shape}; +use crate::source_map::LineRangeUtils; +use crate::utils::{count_lf_crlf, count_newlines, last_line_width, mk_sp}; +use crate::visitor::FmtVisitor; + +struct SnippetStatus { + /// An offset to the current line from the beginning of the original snippet. + line_start: usize, + /// A length of trailing whitespaces on the current line. + last_wspace: Option, + /// The current line number. + cur_line: usize, +} + +impl SnippetStatus { + fn new(cur_line: usize) -> Self { + SnippetStatus { + line_start: 0, + last_wspace: None, + cur_line, + } + } +} + +impl<'a> FmtVisitor<'a> { + fn output_at_start(&self) -> bool { + self.buffer.is_empty() + } + + pub(crate) fn format_missing(&mut self, end: BytePos) { + // HACK(topecongiro): we use `format_missing()` to extract a missing comment between + // a macro (or similar) and a trailing semicolon. Here we just try to avoid calling + // `format_missing_inner` in the common case where there is no such comment. + // This is a hack, ideally we should fix a possible bug in `format_missing_inner` + // or refactor `visit_mac` and `rewrite_macro`, but this should suffice to fix the + // issue (#2727). + let missing_snippet = self.snippet(mk_sp(self.last_pos, end)); + if missing_snippet.trim() == ";" { + self.push_str(";"); + self.last_pos = end; + return; + } + self.format_missing_inner(end, |this, last_snippet, _| this.push_str(last_snippet)) + } + + pub(crate) fn format_missing_with_indent(&mut self, end: BytePos) { + let config = self.config; + self.format_missing_inner(end, |this, last_snippet, snippet| { + this.push_str(last_snippet.trim_end()); + if last_snippet == snippet && !this.output_at_start() { + // No new lines in the snippet. + this.push_str("\n"); + } + let indent = this.block_indent.to_string(config); + this.push_str(&indent); + }) + } + + pub(crate) fn format_missing_no_indent(&mut self, end: BytePos) { + self.format_missing_inner(end, |this, last_snippet, _| { + this.push_str(last_snippet.trim_end()); + }) + } + + fn format_missing_inner, &str, &str)>( + &mut self, + end: BytePos, + process_last_snippet: F, + ) { + let start = self.last_pos; + + if start == end { + // Do nothing if this is the beginning of the file. + if !self.output_at_start() { + process_last_snippet(self, "", ""); + } + return; + } + + assert!( + start < end, + "Request to format inverted span: {}", + self.parse_sess.span_to_debug_info(mk_sp(start, end)), + ); + + self.last_pos = end; + let span = mk_sp(start, end); + let snippet = self.snippet(span); + + // Do nothing for spaces in the beginning of the file + if start == BytePos(0) && end.0 as usize == snippet.len() && snippet.trim().is_empty() { + return; + } + + if snippet.trim().is_empty() && !out_of_file_lines_range!(self, span) { + // Keep vertical spaces within range. + self.push_vertical_spaces(count_newlines(snippet)); + process_last_snippet(self, "", snippet); + } else { + self.write_snippet(span, &process_last_snippet); + } + } + + fn push_vertical_spaces(&mut self, mut newline_count: usize) { + let offset = self.buffer.chars().rev().take_while(|c| *c == '\n').count(); + let newline_upper_bound = self.config.blank_lines_upper_bound() + 1; + let newline_lower_bound = self.config.blank_lines_lower_bound() + 1; + + if newline_count + offset > newline_upper_bound { + if offset >= newline_upper_bound { + newline_count = 0; + } else { + newline_count = newline_upper_bound - offset; + } + } else if newline_count + offset < newline_lower_bound { + if offset >= newline_lower_bound { + newline_count = 0; + } else { + newline_count = newline_lower_bound - offset; + } + } + + let blank_lines = "\n".repeat(newline_count); + self.push_str(&blank_lines); + } + + fn write_snippet(&mut self, span: Span, process_last_snippet: F) + where + F: Fn(&mut FmtVisitor<'_>, &str, &str), + { + // Get a snippet from the file start to the span's hi without allocating. + // We need it to determine what precedes the current comment. If the comment + // follows code on the same line, we won't touch it. + let big_span_lo = self.snippet_provider.start_pos(); + let big_snippet = self.snippet_provider.entire_snippet(); + let big_diff = (span.lo() - big_span_lo).to_usize(); + + let snippet = self.snippet(span); + + debug!("write_snippet `{}`", snippet); + + self.write_snippet_inner(big_snippet, snippet, big_diff, span, process_last_snippet); + } + + fn write_snippet_inner( + &mut self, + big_snippet: &str, + old_snippet: &str, + big_diff: usize, + span: Span, + process_last_snippet: F, + ) where + F: Fn(&mut FmtVisitor<'_>, &str, &str), + { + // Trim whitespace from the right hand side of each line. + // Annoyingly, the library functions for splitting by lines etc. are not + // quite right, so we must do it ourselves. + let line = self.parse_sess.line_of_byte_pos(span.lo()); + let file_name = &self.parse_sess.span_to_filename(span); + let mut status = SnippetStatus::new(line); + + let snippet = &*transform_missing_snippet(self.config, old_snippet); + + let slice_within_file_lines_range = + |file_lines: FileLines, cur_line, s| -> (usize, usize, bool) { + let (lf_count, crlf_count) = count_lf_crlf(s); + let newline_count = lf_count + crlf_count; + let within_file_lines_range = file_lines.contains_range( + file_name, + cur_line, + // if a newline character is at the end of the slice, then the number of + // newlines needs to be decreased by 1 so that the range checked against + // the file_lines is the visual range one would expect. + cur_line + newline_count - if s.ends_with('\n') { 1 } else { 0 }, + ); + (lf_count, crlf_count, within_file_lines_range) + }; + for (kind, offset, subslice) in CommentCodeSlices::new(snippet) { + debug!("{:?}: {:?}", kind, subslice); + + let (lf_count, crlf_count, within_file_lines_range) = + slice_within_file_lines_range(self.config.file_lines(), status.cur_line, subslice); + let newline_count = lf_count + crlf_count; + if CodeCharKind::Comment == kind && within_file_lines_range { + // 1: comment. + self.process_comment( + &mut status, + snippet, + &big_snippet[..(offset + big_diff)], + offset, + subslice, + ); + } else if subslice.trim().is_empty() && newline_count > 0 && within_file_lines_range { + // 2: blank lines. + self.push_vertical_spaces(newline_count); + status.cur_line += newline_count; + status.line_start = offset + lf_count + crlf_count * 2; + } else { + // 3: code which we failed to format or which is not within file-lines range. + self.process_missing_code(&mut status, snippet, subslice, offset, file_name); + } + } + + let last_snippet = &snippet[status.line_start..]; + let (_, _, within_file_lines_range) = + slice_within_file_lines_range(self.config.file_lines(), status.cur_line, last_snippet); + if within_file_lines_range { + process_last_snippet(self, last_snippet, snippet); + } else { + // just append what's left + self.push_str(last_snippet); + } + } + + fn process_comment( + &mut self, + status: &mut SnippetStatus, + snippet: &str, + big_snippet: &str, + offset: usize, + subslice: &str, + ) { + let last_char = big_snippet + .chars() + .rev() + .skip_while(|rev_c| [' ', '\t'].contains(rev_c)) + .next(); + + let fix_indent = last_char.map_or(true, |rev_c| ['{', '\n'].contains(&rev_c)); + let mut on_same_line = false; + + let comment_indent = if fix_indent { + if let Some('{') = last_char { + self.push_str("\n"); + } + let indent_str = self.block_indent.to_string(self.config); + self.push_str(&indent_str); + self.block_indent + } else if self.config.version() == Version::Two && !snippet.starts_with('\n') { + // The comment appears on the same line as the previous formatted code. + // Assuming that comment is logically associated with that code, we want to keep it on + // the same level and avoid mixing it with possible other comment. + on_same_line = true; + self.push_str(" "); + self.block_indent + } else { + self.push_str(" "); + Indent::from_width(self.config, last_line_width(&self.buffer)) + }; + + let comment_width = ::std::cmp::min( + self.config.comment_width(), + self.config.max_width() - self.block_indent.width(), + ); + let comment_shape = Shape::legacy(comment_width, comment_indent); + + if on_same_line { + match subslice.find("\n") { + None => { + self.push_str(subslice); + } + Some(offset) if offset + 1 == subslice.len() => { + self.push_str(&subslice[..offset]); + } + Some(offset) => { + // keep first line as is: if it were too long and wrapped, it may get mixed + // with the other lines. + let first_line = &subslice[..offset]; + self.push_str(first_line); + self.push_str(&comment_indent.to_string_with_newline(self.config)); + + let other_lines = &subslice[offset + 1..]; + let comment_str = + rewrite_comment(other_lines, false, comment_shape, self.config) + .unwrap_or_else(|| String::from(other_lines)); + self.push_str(&comment_str); + } + } + } else { + let comment_str = rewrite_comment(subslice, false, comment_shape, self.config) + .unwrap_or_else(|| String::from(subslice)); + self.push_str(&comment_str); + } + + status.last_wspace = None; + status.line_start = offset + subslice.len(); + + // Add a newline: + // - if there isn't one already + // - otherwise, only if the last line is a line comment + if status.line_start <= snippet.len() { + match snippet[status.line_start..] + .chars() + // skip trailing whitespaces + .skip_while(|c| *c == ' ' || *c == '\t') + .next() + { + Some('\n') | Some('\r') => { + if !is_last_comment_block(subslice) { + self.push_str("\n"); + } + } + _ => self.push_str("\n"), + } + } + + status.cur_line += count_newlines(subslice); + } + + fn process_missing_code( + &mut self, + status: &mut SnippetStatus, + snippet: &str, + subslice: &str, + offset: usize, + file_name: &FileName, + ) { + for (mut i, c) in subslice.char_indices() { + i += offset; + + if c == '\n' { + let skip_this_line = !self + .config + .file_lines() + .contains_line(file_name, status.cur_line); + if skip_this_line { + status.last_wspace = None; + } + + if let Some(lw) = status.last_wspace { + self.push_str(&snippet[status.line_start..lw]); + self.push_str("\n"); + status.last_wspace = None; + } else { + self.push_str(&snippet[status.line_start..=i]); + } + + status.cur_line += 1; + status.line_start = i + 1; + } else if c.is_whitespace() && status.last_wspace.is_none() { + status.last_wspace = Some(i); + } else { + status.last_wspace = None; + } + } + + let remaining = snippet[status.line_start..subslice.len() + offset].trim(); + if !remaining.is_empty() { + self.push_str(&self.block_indent.to_string(self.config)); + self.push_str(remaining); + status.line_start = subslice.len() + offset; + } + } +} diff --git a/src/tools/rustfmt/src/modules.rs b/src/tools/rustfmt/src/modules.rs new file mode 100644 index 0000000000..cadcd30a3d --- /dev/null +++ b/src/tools/rustfmt/src/modules.rs @@ -0,0 +1,487 @@ +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +use rustc_ast::ast; +use rustc_ast::attr::HasAttrs; +use rustc_ast::visit::Visitor; +use rustc_span::symbol::{self, sym, Symbol}; +use thiserror::Error; + +use crate::attr::MetaVisitor; +use crate::config::FileName; +use crate::items::is_mod_decl; +use crate::syntux::parser::{ + Directory, DirectoryOwnership, ModulePathSuccess, Parser, ParserError, +}; +use crate::syntux::session::ParseSess; +use crate::utils::contains_skip; + +mod visitor; + +type FileModMap<'ast> = BTreeMap>; + +/// Represents module with its inner attributes. +#[derive(Debug, Clone)] +pub(crate) struct Module<'a> { + ast_mod: Cow<'a, ast::Mod>, + inner_attr: Vec, +} + +impl<'a> Module<'a> { + pub(crate) fn new(ast_mod: Cow<'a, ast::Mod>, attrs: &[ast::Attribute]) -> Self { + let inner_attr = attrs + .iter() + .filter(|attr| attr.style == ast::AttrStyle::Inner) + .cloned() + .collect(); + Module { + ast_mod, + inner_attr, + } + } +} + +impl<'a> HasAttrs for Module<'a> { + fn attrs(&self) -> &[ast::Attribute] { + &self.inner_attr + } + fn visit_attrs(&mut self, f: impl FnOnce(&mut Vec)) { + f(&mut self.inner_attr) + } +} + +impl<'a> AsRef for Module<'a> { + fn as_ref(&self) -> &ast::Mod { + &self.ast_mod + } +} + +/// Maps each module to the corresponding file. +pub(crate) struct ModResolver<'ast, 'sess> { + parse_sess: &'sess ParseSess, + directory: Directory, + file_map: FileModMap<'ast>, + recursive: bool, +} + +/// Represents errors while trying to resolve modules. +#[error("failed to resolve mod `{module}`: {kind}")] +#[derive(Debug, Error)] +pub struct ModuleResolutionError { + pub(crate) module: String, + pub(crate) kind: ModuleResolutionErrorKind, +} + +#[derive(Debug, Error)] +pub(crate) enum ModuleResolutionErrorKind { + /// Find a file that cannot be parsed. + #[error("cannot parse {file}")] + ParseError { file: PathBuf }, + /// File cannot be found. + #[error("{file} does not exist")] + NotFound { file: PathBuf }, +} + +#[derive(Clone)] +enum SubModKind<'a, 'ast> { + /// `mod foo;` + External(PathBuf, DirectoryOwnership, Module<'ast>), + /// `mod foo;` with multiple sources. + MultiExternal(Vec<(PathBuf, DirectoryOwnership, Module<'ast>)>), + /// `mod foo {}` + Internal(&'a ast::Item), +} + +impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { + /// Creates a new `ModResolver`. + pub(crate) fn new( + parse_sess: &'sess ParseSess, + directory_ownership: DirectoryOwnership, + recursive: bool, + ) -> Self { + ModResolver { + directory: Directory { + path: PathBuf::new(), + ownership: directory_ownership, + }, + file_map: BTreeMap::new(), + parse_sess, + recursive, + } + } + + /// Creates a map that maps a file name to the module in AST. + pub(crate) fn visit_crate( + mut self, + krate: &'ast ast::Crate, + ) -> Result, ModuleResolutionError> { + let root_filename = self.parse_sess.span_to_filename(krate.span); + self.directory.path = match root_filename { + FileName::Real(ref p) => p.parent().unwrap_or(Path::new("")).to_path_buf(), + _ => PathBuf::new(), + }; + + // Skip visiting sub modules when the input is from stdin. + if self.recursive { + self.visit_mod_from_ast(&krate.module)?; + } + + self.file_map.insert( + root_filename, + Module::new(Cow::Borrowed(&krate.module), &krate.attrs), + ); + Ok(self.file_map) + } + + /// Visit `cfg_if` macro and look for module declarations. + fn visit_cfg_if(&mut self, item: Cow<'ast, ast::Item>) -> Result<(), ModuleResolutionError> { + let mut visitor = visitor::CfgIfVisitor::new(self.parse_sess); + visitor.visit_item(&item); + for module_item in visitor.mods() { + if let ast::ItemKind::Mod(ref sub_mod) = module_item.item.kind { + self.visit_sub_mod( + &module_item.item, + Module::new(Cow::Owned(sub_mod.clone()), &module_item.item.attrs), + )?; + } + } + Ok(()) + } + + /// Visit modules defined inside macro calls. + fn visit_mod_outside_ast(&mut self, module: ast::Mod) -> Result<(), ModuleResolutionError> { + for item in module.items { + if is_cfg_if(&item) { + self.visit_cfg_if(Cow::Owned(item.into_inner()))?; + continue; + } + + if let ast::ItemKind::Mod(ref sub_mod) = item.kind { + self.visit_sub_mod(&item, Module::new(Cow::Owned(sub_mod.clone()), &item.attrs))?; + } + } + Ok(()) + } + + /// Visit modules from AST. + fn visit_mod_from_ast(&mut self, module: &'ast ast::Mod) -> Result<(), ModuleResolutionError> { + for item in &module.items { + if is_cfg_if(item) { + self.visit_cfg_if(Cow::Borrowed(item))?; + } + + if let ast::ItemKind::Mod(ref sub_mod) = item.kind { + self.visit_sub_mod(item, Module::new(Cow::Borrowed(sub_mod), &item.attrs))?; + } + } + Ok(()) + } + + fn visit_sub_mod( + &mut self, + item: &'c ast::Item, + sub_mod: Module<'ast>, + ) -> Result<(), ModuleResolutionError> { + let old_directory = self.directory.clone(); + let sub_mod_kind = self.peek_sub_mod(item, &sub_mod)?; + if let Some(sub_mod_kind) = sub_mod_kind { + self.insert_sub_mod(sub_mod_kind.clone())?; + self.visit_sub_mod_inner(sub_mod, sub_mod_kind)?; + } + self.directory = old_directory; + Ok(()) + } + + /// Inspect the given sub-module which we are about to visit and returns its kind. + fn peek_sub_mod( + &self, + item: &'c ast::Item, + sub_mod: &Module<'ast>, + ) -> Result>, ModuleResolutionError> { + if contains_skip(&item.attrs) { + return Ok(None); + } + + if is_mod_decl(item) { + // mod foo; + // Look for an extern file. + self.find_external_module(item.ident, &item.attrs, sub_mod) + } else { + // An internal module (`mod foo { /* ... */ }`); + Ok(Some(SubModKind::Internal(item))) + } + } + + fn insert_sub_mod( + &mut self, + sub_mod_kind: SubModKind<'c, 'ast>, + ) -> Result<(), ModuleResolutionError> { + match sub_mod_kind { + SubModKind::External(mod_path, _, sub_mod) => { + self.file_map + .entry(FileName::Real(mod_path)) + .or_insert(sub_mod); + } + SubModKind::MultiExternal(mods) => { + for (mod_path, _, sub_mod) in mods { + self.file_map + .entry(FileName::Real(mod_path)) + .or_insert(sub_mod); + } + } + _ => (), + } + Ok(()) + } + + fn visit_sub_mod_inner( + &mut self, + sub_mod: Module<'ast>, + sub_mod_kind: SubModKind<'c, 'ast>, + ) -> Result<(), ModuleResolutionError> { + match sub_mod_kind { + SubModKind::External(mod_path, directory_ownership, sub_mod) => { + let directory = Directory { + path: mod_path.parent().unwrap().to_path_buf(), + ownership: directory_ownership, + }; + self.visit_sub_mod_after_directory_update(sub_mod, Some(directory)) + } + SubModKind::Internal(ref item) => { + self.push_inline_mod_directory(item.ident, &item.attrs); + self.visit_sub_mod_after_directory_update(sub_mod, None) + } + SubModKind::MultiExternal(mods) => { + for (mod_path, directory_ownership, sub_mod) in mods { + let directory = Directory { + path: mod_path.parent().unwrap().to_path_buf(), + ownership: directory_ownership, + }; + self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))?; + } + Ok(()) + } + } + } + + fn visit_sub_mod_after_directory_update( + &mut self, + sub_mod: Module<'ast>, + directory: Option, + ) -> Result<(), ModuleResolutionError> { + if let Some(directory) = directory { + self.directory = directory; + } + match sub_mod.ast_mod { + Cow::Borrowed(sub_mod) => self.visit_mod_from_ast(sub_mod), + Cow::Owned(sub_mod) => self.visit_mod_outside_ast(sub_mod), + } + } + + /// Find a file path in the filesystem which corresponds to the given module. + fn find_external_module( + &self, + mod_name: symbol::Ident, + attrs: &[ast::Attribute], + sub_mod: &Module<'ast>, + ) -> Result>, ModuleResolutionError> { + let relative = match self.directory.ownership { + DirectoryOwnership::Owned { relative } => relative, + DirectoryOwnership::UnownedViaBlock | DirectoryOwnership::UnownedViaMod => None, + }; + if let Some(path) = Parser::submod_path_from_attr(attrs, &self.directory.path) { + if self.parse_sess.is_file_parsed(&path) { + return Ok(None); + } + return match Parser::parse_file_as_module(self.parse_sess, &path, sub_mod.ast_mod.inner) + { + Ok((_, ref attrs)) if contains_skip(attrs) => Ok(None), + Ok(m) => Ok(Some(SubModKind::External( + path, + DirectoryOwnership::Owned { relative: None }, + Module::new(Cow::Owned(m.0), &m.1), + ))), + Err(ParserError::ParseError) => Err(ModuleResolutionError { + module: mod_name.to_string(), + kind: ModuleResolutionErrorKind::ParseError { file: path }, + }), + Err(..) => Err(ModuleResolutionError { + module: mod_name.to_string(), + kind: ModuleResolutionErrorKind::NotFound { file: path }, + }), + }; + } + + // Look for nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`. + let mut mods_outside_ast = self.find_mods_outside_of_ast(attrs, sub_mod); + + match self + .parse_sess + .default_submod_path(mod_name, relative, &self.directory.path) + .result + { + Ok(ModulePathSuccess { + path, ownership, .. + }) => { + let outside_mods_empty = mods_outside_ast.is_empty(); + let should_insert = !mods_outside_ast + .iter() + .any(|(outside_path, _, _)| outside_path == &path); + if self.parse_sess.is_file_parsed(&path) { + if outside_mods_empty { + return Ok(None); + } else { + if should_insert { + mods_outside_ast.push((path, ownership, sub_mod.clone())); + } + return Ok(Some(SubModKind::MultiExternal(mods_outside_ast))); + } + } + match Parser::parse_file_as_module(self.parse_sess, &path, sub_mod.ast_mod.inner) { + Ok((_, ref attrs)) if contains_skip(attrs) => Ok(None), + Ok(m) if outside_mods_empty => Ok(Some(SubModKind::External( + path, + ownership, + Module::new(Cow::Owned(m.0), &m.1), + ))), + Ok(m) => { + mods_outside_ast.push(( + path.clone(), + ownership, + Module::new(Cow::Owned(m.0), &m.1), + )); + if should_insert { + mods_outside_ast.push((path, ownership, sub_mod.clone())); + } + Ok(Some(SubModKind::MultiExternal(mods_outside_ast))) + } + Err(ParserError::ParseError) => Err(ModuleResolutionError { + module: mod_name.to_string(), + kind: ModuleResolutionErrorKind::ParseError { file: path }, + }), + Err(..) if outside_mods_empty => Err(ModuleResolutionError { + module: mod_name.to_string(), + kind: ModuleResolutionErrorKind::NotFound { file: path }, + }), + Err(..) => { + if should_insert { + mods_outside_ast.push((path, ownership, sub_mod.clone())); + } + Ok(Some(SubModKind::MultiExternal(mods_outside_ast))) + } + } + } + Err(mut e) if !mods_outside_ast.is_empty() => { + e.cancel(); + Ok(Some(SubModKind::MultiExternal(mods_outside_ast))) + } + Err(mut e) => { + e.cancel(); + Err(ModuleResolutionError { + module: mod_name.to_string(), + kind: ModuleResolutionErrorKind::NotFound { + file: self.directory.path.clone(), + }, + }) + } + } + } + + fn push_inline_mod_directory(&mut self, id: symbol::Ident, attrs: &[ast::Attribute]) { + if let Some(path) = find_path_value(attrs) { + self.directory.path.push(&*path.as_str()); + self.directory.ownership = DirectoryOwnership::Owned { relative: None }; + } else { + // We have to push on the current module name in the case of relative + // paths in order to ensure that any additional module paths from inline + // `mod x { ... }` come after the relative extension. + // + // For example, a `mod z { ... }` inside `x/y.rs` should set the current + // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`. + if let DirectoryOwnership::Owned { relative } = &mut self.directory.ownership { + if let Some(ident) = relative.take() { + // remove the relative offset + self.directory.path.push(&*ident.as_str()); + } + } + self.directory.path.push(&*id.as_str()); + } + } + + fn find_mods_outside_of_ast( + &self, + attrs: &[ast::Attribute], + sub_mod: &Module<'ast>, + ) -> Vec<(PathBuf, DirectoryOwnership, Module<'ast>)> { + // Filter nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`. + let mut path_visitor = visitor::PathVisitor::default(); + for attr in attrs.iter() { + if let Some(meta) = attr.meta() { + path_visitor.visit_meta_item(&meta) + } + } + let mut result = vec![]; + for path in path_visitor.paths() { + let mut actual_path = self.directory.path.clone(); + actual_path.push(&path); + if !actual_path.exists() { + continue; + } + if self.parse_sess.is_file_parsed(&actual_path) { + // If the specified file is already parsed, then we just use that. + result.push(( + actual_path, + DirectoryOwnership::Owned { relative: None }, + sub_mod.clone(), + )); + continue; + } + let m = match Parser::parse_file_as_module( + self.parse_sess, + &actual_path, + sub_mod.ast_mod.inner, + ) { + Ok((_, ref attrs)) if contains_skip(attrs) => continue, + Ok(m) => m, + Err(..) => continue, + }; + + result.push(( + actual_path, + DirectoryOwnership::Owned { relative: None }, + Module::new(Cow::Owned(m.0), &m.1), + )) + } + result + } +} + +fn path_value(attr: &ast::Attribute) -> Option { + if attr.has_name(sym::path) { + attr.value_str() + } else { + None + } +} + +// N.B., even when there are multiple `#[path = ...]` attributes, we just need to +// examine the first one, since rustc ignores the second and the subsequent ones +// as unused attributes. +fn find_path_value(attrs: &[ast::Attribute]) -> Option { + attrs.iter().flat_map(path_value).next() +} + +fn is_cfg_if(item: &ast::Item) -> bool { + match item.kind { + ast::ItemKind::MacCall(ref mac) => { + if let Some(first_segment) = mac.path.segments.first() { + if first_segment.ident.name == Symbol::intern("cfg_if") { + return true; + } + } + false + } + _ => false, + } +} diff --git a/src/tools/rustfmt/src/modules/visitor.rs b/src/tools/rustfmt/src/modules/visitor.rs new file mode 100644 index 0000000000..d5acf3f1cb --- /dev/null +++ b/src/tools/rustfmt/src/modules/visitor.rs @@ -0,0 +1,108 @@ +use rustc_ast::ast; +use rustc_ast::visit::Visitor; +use rustc_span::Symbol; + +use crate::attr::MetaVisitor; +use crate::syntux::parser::Parser; +use crate::syntux::session::ParseSess; + +pub(crate) struct ModItem { + pub(crate) item: ast::Item, +} + +/// Traverse `cfg_if!` macro and fetch modules. +pub(crate) struct CfgIfVisitor<'a> { + parse_sess: &'a ParseSess, + mods: Vec, +} + +impl<'a> CfgIfVisitor<'a> { + pub(crate) fn new(parse_sess: &'a ParseSess) -> CfgIfVisitor<'a> { + CfgIfVisitor { + mods: vec![], + parse_sess, + } + } + + pub(crate) fn mods(self) -> Vec { + self.mods + } +} + +impl<'a, 'ast: 'a> Visitor<'ast> for CfgIfVisitor<'a> { + fn visit_mac_call(&mut self, mac: &'ast ast::MacCall) { + match self.visit_mac_inner(mac) { + Ok(()) => (), + Err(e) => debug!("{}", e), + } + } +} + +impl<'a, 'ast: 'a> CfgIfVisitor<'a> { + fn visit_mac_inner(&mut self, mac: &'ast ast::MacCall) -> Result<(), &'static str> { + // Support both: + // ``` + // extern crate cfg_if; + // cfg_if::cfg_if! {..} + // ``` + // And: + // ``` + // #[macro_use] + // extern crate cfg_if; + // cfg_if! {..} + // ``` + match mac.path.segments.first() { + Some(first_segment) => { + if first_segment.ident.name != Symbol::intern("cfg_if") { + return Err("Expected cfg_if"); + } + } + None => { + return Err("Expected cfg_if"); + } + }; + + let items = Parser::parse_cfg_if(self.parse_sess, mac)?; + self.mods + .append(&mut items.into_iter().map(|item| ModItem { item }).collect()); + + Ok(()) + } +} + +/// Extracts `path = "foo.rs"` from attributes. +#[derive(Default)] +pub(crate) struct PathVisitor { + /// A list of path defined in attributes. + paths: Vec, +} + +impl PathVisitor { + pub(crate) fn paths(self) -> Vec { + self.paths + } +} + +impl<'ast> MetaVisitor<'ast> for PathVisitor { + fn visit_meta_name_value(&mut self, meta_item: &'ast ast::MetaItem, lit: &'ast ast::Lit) { + if meta_item.has_name(Symbol::intern("path")) && lit.kind.is_str() { + self.paths.push(lit_to_str(lit)); + } + } +} + +#[cfg(not(windows))] +fn lit_to_str(lit: &ast::Lit) -> String { + match lit.kind { + ast::LitKind::Str(symbol, ..) => symbol.to_string(), + _ => unreachable!(), + } +} + +#[cfg(windows)] +fn lit_to_str(lit: &ast::Lit) -> String { + match lit.kind { + ast::LitKind::Str(symbol, ..) => symbol.as_str().replace("/", "\\"), + _ => unreachable!(), + } +} diff --git a/src/tools/rustfmt/src/overflow.rs b/src/tools/rustfmt/src/overflow.rs new file mode 100644 index 0000000000..566f0e3663 --- /dev/null +++ b/src/tools/rustfmt/src/overflow.rs @@ -0,0 +1,786 @@ +//! Rewrite a list some items with overflow. + +use std::cmp::min; + +use itertools::Itertools; +use rustc_ast::token::DelimToken; +use rustc_ast::{ast, ptr}; +use rustc_span::Span; + +use crate::closures; +use crate::config::lists::*; +use crate::config::Version; +use crate::expr::{ + can_be_overflowed_expr, is_every_expr_simple, is_method_call, is_nested_call, is_simple_expr, + rewrite_cond, +}; +use crate::lists::{ + definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator, +}; +use crate::macros::MacroArg; +use crate::patterns::{can_be_overflowed_pat, TuplePatField}; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::source_map::SpanUtils; +use crate::spanned::Spanned; +use crate::types::{can_be_overflowed_type, SegmentParam}; +use crate::utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp}; + +const SHORT_ITEM_THRESHOLD: usize = 10; + +/// A list of `format!`-like macros, that take a long format string and a list of arguments to +/// format. +/// +/// Organized as a list of `(&str, usize)` tuples, giving the name of the macro and the number of +/// arguments before the format string (none for `format!("format", ...)`, one for `assert!(result, +/// "format", ...)`, two for `assert_eq!(left, right, "format", ...)`). +const SPECIAL_MACRO_WHITELIST: &[(&str, usize)] = &[ + // format! like macros + // From the Rust Standard Library. + ("eprint!", 0), + ("eprintln!", 0), + ("format!", 0), + ("format_args!", 0), + ("print!", 0), + ("println!", 0), + ("panic!", 0), + ("unreachable!", 0), + // From the `log` crate. + ("debug!", 0), + ("error!", 0), + ("info!", 0), + ("warn!", 0), + // write! like macros + ("assert!", 1), + ("debug_assert!", 1), + ("write!", 1), + ("writeln!", 1), + // assert_eq! like macros + ("assert_eq!", 2), + ("assert_ne!", 2), + ("debug_assert_eq!", 2), + ("debug_assert_ne!", 2), +]; + +const SPECIAL_ATTR_WHITELIST: &[(&str, usize)] = &[ + // From the `failure` crate. + ("fail", 0), +]; + +#[derive(Debug)] +pub(crate) enum OverflowableItem<'a> { + Expr(&'a ast::Expr), + GenericParam(&'a ast::GenericParam), + MacroArg(&'a MacroArg), + NestedMetaItem(&'a ast::NestedMetaItem), + SegmentParam(&'a SegmentParam<'a>), + StructField(&'a ast::StructField), + TuplePatField(&'a TuplePatField<'a>), + Ty(&'a ast::Ty), +} + +impl<'a> Rewrite for OverflowableItem<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + self.map(|item| item.rewrite(context, shape)) + } +} + +impl<'a> Spanned for OverflowableItem<'a> { + fn span(&self) -> Span { + self.map(|item| item.span()) + } +} + +impl<'a> OverflowableItem<'a> { + fn has_attrs(&self) -> bool { + match self { + OverflowableItem::Expr(ast::Expr { attrs, .. }) + | OverflowableItem::GenericParam(ast::GenericParam { attrs, .. }) => !attrs.is_empty(), + OverflowableItem::StructField(ast::StructField { attrs, .. }) => !attrs.is_empty(), + OverflowableItem::MacroArg(MacroArg::Expr(expr)) => !expr.attrs.is_empty(), + OverflowableItem::MacroArg(MacroArg::Item(item)) => !item.attrs.is_empty(), + _ => false, + } + } + + pub(crate) fn map(&self, f: F) -> T + where + F: Fn(&dyn IntoOverflowableItem<'a>) -> T, + { + match self { + OverflowableItem::Expr(expr) => f(*expr), + OverflowableItem::GenericParam(gp) => f(*gp), + OverflowableItem::MacroArg(macro_arg) => f(*macro_arg), + OverflowableItem::NestedMetaItem(nmi) => f(*nmi), + OverflowableItem::SegmentParam(sp) => f(*sp), + OverflowableItem::StructField(sf) => f(*sf), + OverflowableItem::TuplePatField(pat) => f(*pat), + OverflowableItem::Ty(ty) => f(*ty), + } + } + + pub(crate) fn is_simple(&self) -> bool { + match self { + OverflowableItem::Expr(expr) => is_simple_expr(expr), + OverflowableItem::MacroArg(MacroArg::Keyword(..)) => true, + OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_simple_expr(expr), + OverflowableItem::NestedMetaItem(nested_meta_item) => match nested_meta_item { + ast::NestedMetaItem::Literal(..) => true, + ast::NestedMetaItem::MetaItem(ref meta_item) => match meta_item.kind { + ast::MetaItemKind::Word => true, + _ => false, + }, + }, + _ => false, + } + } + + pub(crate) fn is_expr(&self) -> bool { + match self { + OverflowableItem::Expr(..) => true, + OverflowableItem::MacroArg(MacroArg::Expr(..)) => true, + _ => false, + } + } + + pub(crate) fn is_nested_call(&self) -> bool { + match self { + OverflowableItem::Expr(expr) => is_nested_call(expr), + OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_nested_call(expr), + _ => false, + } + } + + pub(crate) fn to_expr(&self) -> Option<&'a ast::Expr> { + match self { + OverflowableItem::Expr(expr) => Some(expr), + OverflowableItem::MacroArg(macro_arg) => match macro_arg { + MacroArg::Expr(ref expr) => Some(expr), + _ => None, + }, + _ => None, + } + } + + pub(crate) fn can_be_overflowed(&self, context: &RewriteContext<'_>, len: usize) -> bool { + match self { + OverflowableItem::Expr(expr) => can_be_overflowed_expr(context, expr, len), + OverflowableItem::MacroArg(macro_arg) => match macro_arg { + MacroArg::Expr(ref expr) => can_be_overflowed_expr(context, expr, len), + MacroArg::Ty(ref ty) => can_be_overflowed_type(context, ty, len), + MacroArg::Pat(..) => false, + MacroArg::Item(..) => len == 1, + MacroArg::Keyword(..) => false, + }, + OverflowableItem::NestedMetaItem(nested_meta_item) if len == 1 => { + match nested_meta_item { + ast::NestedMetaItem::Literal(..) => false, + ast::NestedMetaItem::MetaItem(..) => true, + } + } + OverflowableItem::SegmentParam(seg) => match seg { + SegmentParam::Type(ty) => can_be_overflowed_type(context, ty, len), + _ => false, + }, + OverflowableItem::TuplePatField(pat) => can_be_overflowed_pat(context, pat, len), + OverflowableItem::Ty(ty) => can_be_overflowed_type(context, ty, len), + _ => false, + } + } + + fn whitelist(&self) -> &'static [(&'static str, usize)] { + match self { + OverflowableItem::MacroArg(..) => SPECIAL_MACRO_WHITELIST, + OverflowableItem::NestedMetaItem(..) => SPECIAL_ATTR_WHITELIST, + _ => &[], + } + } +} + +pub(crate) trait IntoOverflowableItem<'a>: Rewrite + Spanned { + fn into_overflowable_item(&'a self) -> OverflowableItem<'a>; +} + +impl<'a, T: 'a + IntoOverflowableItem<'a>> IntoOverflowableItem<'a> for ptr::P { + fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { + (**self).into_overflowable_item() + } +} + +macro_rules! impl_into_overflowable_item_for_ast_node { + ($($ast_node:ident),*) => { + $( + impl<'a> IntoOverflowableItem<'a> for ast::$ast_node { + fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { + OverflowableItem::$ast_node(self) + } + } + )* + } +} + +macro_rules! impl_into_overflowable_item_for_rustfmt_types { + ([$($ty:ident),*], [$($ty_with_lifetime:ident),*]) => { + $( + impl<'a> IntoOverflowableItem<'a> for $ty { + fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { + OverflowableItem::$ty(self) + } + } + )* + $( + impl<'a> IntoOverflowableItem<'a> for $ty_with_lifetime<'a> { + fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { + OverflowableItem::$ty_with_lifetime(self) + } + } + )* + } +} + +impl_into_overflowable_item_for_ast_node!(Expr, GenericParam, NestedMetaItem, StructField, Ty); +impl_into_overflowable_item_for_rustfmt_types!([MacroArg], [SegmentParam, TuplePatField]); + +pub(crate) fn into_overflowable_list<'a, T>( + iter: impl Iterator, +) -> impl Iterator> +where + T: 'a + IntoOverflowableItem<'a>, +{ + iter.map(|x| IntoOverflowableItem::into_overflowable_item(x)) +} + +pub(crate) fn rewrite_with_parens<'a, T: 'a + IntoOverflowableItem<'a>>( + context: &'a RewriteContext<'_>, + ident: &'a str, + items: impl Iterator, + shape: Shape, + span: Span, + item_max_width: usize, + force_separator_tactic: Option, +) -> Option { + Context::new( + context, + items, + ident, + shape, + span, + "(", + ")", + item_max_width, + force_separator_tactic, + None, + ) + .rewrite(shape) +} + +pub(crate) fn rewrite_with_angle_brackets<'a, T: 'a + IntoOverflowableItem<'a>>( + context: &'a RewriteContext<'_>, + ident: &'a str, + items: impl Iterator, + shape: Shape, + span: Span, +) -> Option { + Context::new( + context, + items, + ident, + shape, + span, + "<", + ">", + context.config.max_width(), + None, + None, + ) + .rewrite(shape) +} + +pub(crate) fn rewrite_with_square_brackets<'a, T: 'a + IntoOverflowableItem<'a>>( + context: &'a RewriteContext<'_>, + name: &'a str, + items: impl Iterator, + shape: Shape, + span: Span, + force_separator_tactic: Option, + delim_token: Option, +) -> Option { + let (lhs, rhs) = match delim_token { + Some(DelimToken::Paren) => ("(", ")"), + Some(DelimToken::Brace) => ("{", "}"), + _ => ("[", "]"), + }; + Context::new( + context, + items, + name, + shape, + span, + lhs, + rhs, + context.config.width_heuristics().array_width, + force_separator_tactic, + Some(("[", "]")), + ) + .rewrite(shape) +} + +struct Context<'a> { + context: &'a RewriteContext<'a>, + items: Vec>, + ident: &'a str, + prefix: &'static str, + suffix: &'static str, + one_line_shape: Shape, + nested_shape: Shape, + span: Span, + item_max_width: usize, + one_line_width: usize, + force_separator_tactic: Option, + custom_delims: Option<(&'a str, &'a str)>, +} + +impl<'a> Context<'a> { + fn new>( + context: &'a RewriteContext<'_>, + items: impl Iterator, + ident: &'a str, + shape: Shape, + span: Span, + prefix: &'static str, + suffix: &'static str, + item_max_width: usize, + force_separator_tactic: Option, + custom_delims: Option<(&'a str, &'a str)>, + ) -> Context<'a> { + let used_width = extra_offset(ident, shape); + // 1 = `()` + let one_line_width = shape.width.saturating_sub(used_width + 2); + + // 1 = "(" or ")" + let one_line_shape = shape + .offset_left(last_line_width(ident) + 1) + .and_then(|shape| shape.sub_width(1)) + .unwrap_or(Shape { width: 0, ..shape }); + let nested_shape = shape_from_indent_style(context, shape, used_width + 2, used_width + 1); + Context { + context, + items: into_overflowable_list(items).collect(), + ident, + one_line_shape, + nested_shape, + span, + prefix, + suffix, + item_max_width, + one_line_width, + force_separator_tactic, + custom_delims, + } + } + + fn last_item(&self) -> Option<&OverflowableItem<'_>> { + self.items.last() + } + + fn items_span(&self) -> Span { + let span_lo = self + .context + .snippet_provider + .span_after(self.span, self.prefix); + mk_sp(span_lo, self.span.hi()) + } + + fn rewrite_last_item_with_overflow( + &self, + last_list_item: &mut ListItem, + shape: Shape, + ) -> Option { + let last_item = self.last_item()?; + let rewrite = match last_item { + OverflowableItem::Expr(ref expr) => { + match expr.kind { + // When overflowing the closure which consists of a single control flow + // expression, force to use block if its condition uses multi line. + ast::ExprKind::Closure(..) => { + // If the argument consists of multiple closures, we do not overflow + // the last closure. + if closures::args_have_many_closure(&self.items) { + None + } else { + closures::rewrite_last_closure(self.context, expr, shape) + } + } + + // When overflowing the expressions which consists of a control flow + // expression, avoid condition to use multi line. + ast::ExprKind::If(..) + | ast::ExprKind::ForLoop(..) + | ast::ExprKind::Loop(..) + | ast::ExprKind::While(..) + | ast::ExprKind::Match(..) => { + let multi_line = rewrite_cond(self.context, expr, shape) + .map_or(false, |cond| cond.contains('\n')); + + if multi_line { + None + } else { + expr.rewrite(self.context, shape) + } + } + + _ => expr.rewrite(self.context, shape), + } + } + item => item.rewrite(self.context, shape), + }; + + if let Some(rewrite) = rewrite { + // splitn(2, *).next().unwrap() is always safe. + let rewrite_first_line = Some(rewrite.splitn(2, '\n').next().unwrap().to_owned()); + last_list_item.item = rewrite_first_line; + Some(rewrite) + } else { + None + } + } + + fn default_tactic(&self, list_items: &[ListItem]) -> DefinitiveListTactic { + definitive_tactic( + list_items, + ListTactic::LimitedHorizontalVertical(self.item_max_width), + Separator::Comma, + self.one_line_width, + ) + } + + fn try_overflow_last_item(&self, list_items: &mut Vec) -> DefinitiveListTactic { + // 1 = "(" + let combine_arg_with_callee = self.items.len() == 1 + && self.items[0].is_expr() + && !self.items[0].has_attrs() + && self.ident.len() < self.context.config.tab_spaces(); + let overflow_last = combine_arg_with_callee || can_be_overflowed(self.context, &self.items); + + // Replace the last item with its first line to see if it fits with + // first arguments. + let placeholder = if overflow_last { + let old_value = self.context.force_one_line_chain.get(); + match self.last_item() { + Some(OverflowableItem::Expr(expr)) + if !combine_arg_with_callee && is_method_call(expr) => + { + self.context.force_one_line_chain.replace(true); + } + Some(OverflowableItem::MacroArg(MacroArg::Expr(expr))) + if !combine_arg_with_callee + && is_method_call(expr) + && self.context.config.version() == Version::Two => + { + self.context.force_one_line_chain.replace(true); + } + _ => (), + } + let result = last_item_shape( + &self.items, + list_items, + self.one_line_shape, + self.item_max_width, + ) + .and_then(|arg_shape| { + self.rewrite_last_item_with_overflow( + &mut list_items[self.items.len() - 1], + arg_shape, + ) + }); + self.context.force_one_line_chain.replace(old_value); + result + } else { + None + }; + + let mut tactic = definitive_tactic( + &*list_items, + ListTactic::LimitedHorizontalVertical(self.item_max_width), + Separator::Comma, + self.one_line_width, + ); + + // Replace the stub with the full overflowing last argument if the rewrite + // succeeded and its first line fits with the other arguments. + match (overflow_last, tactic, placeholder) { + (true, DefinitiveListTactic::Horizontal, Some(ref overflowed)) + if self.items.len() == 1 => + { + // When we are rewriting a nested function call, we restrict the + // budget for the inner function to avoid them being deeply nested. + // However, when the inner function has a prefix or a suffix + // (e.g., `foo() as u32`), this budget reduction may produce poorly + // formatted code, where a prefix or a suffix being left on its own + // line. Here we explicitlly check those cases. + if count_newlines(overflowed) == 1 { + let rw = self + .items + .last() + .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape)); + let no_newline = rw.as_ref().map_or(false, |s| !s.contains('\n')); + if no_newline { + list_items[self.items.len() - 1].item = rw; + } else { + list_items[self.items.len() - 1].item = Some(overflowed.to_owned()); + } + } else { + list_items[self.items.len() - 1].item = Some(overflowed.to_owned()); + } + } + (true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => { + list_items[self.items.len() - 1].item = placeholder; + } + _ if !self.items.is_empty() => { + list_items[self.items.len() - 1].item = self + .items + .last() + .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape)); + + // Use horizontal layout for a function with a single argument as long as + // everything fits in a single line. + // `self.one_line_width == 0` means vertical layout is forced. + if self.items.len() == 1 + && self.one_line_width != 0 + && !list_items[0].has_comment() + && !list_items[0].inner_as_ref().contains('\n') + && crate::lists::total_item_width(&list_items[0]) <= self.one_line_width + { + tactic = DefinitiveListTactic::Horizontal; + } else { + tactic = self.default_tactic(list_items); + + if tactic == DefinitiveListTactic::Vertical { + if let Some((all_simple, num_args_before)) = + maybe_get_args_offset(self.ident, &self.items) + { + let one_line = all_simple + && definitive_tactic( + &list_items[..num_args_before], + ListTactic::HorizontalVertical, + Separator::Comma, + self.nested_shape.width, + ) == DefinitiveListTactic::Horizontal + && definitive_tactic( + &list_items[num_args_before + 1..], + ListTactic::HorizontalVertical, + Separator::Comma, + self.nested_shape.width, + ) == DefinitiveListTactic::Horizontal; + + if one_line { + tactic = DefinitiveListTactic::SpecialMacro(num_args_before); + }; + } else if is_every_expr_simple(&self.items) && no_long_items(list_items) { + tactic = DefinitiveListTactic::Mixed; + } + } + } + } + _ => (), + } + + tactic + } + + fn rewrite_items(&self) -> Option<(bool, String)> { + let span = self.items_span(); + let items = itemize_list( + self.context.snippet_provider, + self.items.iter(), + self.suffix, + ",", + |item| item.span().lo(), + |item| item.span().hi(), + |item| item.rewrite(self.context, self.nested_shape), + span.lo(), + span.hi(), + true, + ); + let mut list_items: Vec<_> = items.collect(); + + // Try letting the last argument overflow to the next line with block + // indentation. If its first line fits on one line with the other arguments, + // we format the function arguments horizontally. + let tactic = self.try_overflow_last_item(&mut list_items); + let trailing_separator = if let Some(tactic) = self.force_separator_tactic { + tactic + } else if !self.context.use_block_indent() { + SeparatorTactic::Never + } else { + self.context.config.trailing_comma() + }; + let ends_with_newline = match tactic { + DefinitiveListTactic::Vertical | DefinitiveListTactic::Mixed => { + self.context.use_block_indent() + } + _ => false, + }; + + let fmt = ListFormatting::new(self.nested_shape, self.context.config) + .tactic(tactic) + .trailing_separator(trailing_separator) + .ends_with_newline(ends_with_newline); + + write_list(&list_items, &fmt) + .map(|items_str| (tactic == DefinitiveListTactic::Horizontal, items_str)) + } + + fn wrap_items(&self, items_str: &str, shape: Shape, is_extendable: bool) -> String { + let shape = Shape { + width: shape.width.saturating_sub(last_line_width(self.ident)), + ..shape + }; + + let (prefix, suffix) = match self.custom_delims { + Some((lhs, rhs)) => (lhs, rhs), + _ => (self.prefix, self.suffix), + }; + + let extend_width = if items_str.is_empty() { + 2 + } else { + first_line_width(items_str) + 1 + }; + let nested_indent_str = self + .nested_shape + .indent + .to_string_with_newline(self.context.config); + let indent_str = shape + .block() + .indent + .to_string_with_newline(self.context.config); + let mut result = String::with_capacity( + self.ident.len() + items_str.len() + 2 + indent_str.len() + nested_indent_str.len(), + ); + result.push_str(self.ident); + result.push_str(prefix); + let force_single_line = if self.context.config.version() == Version::Two { + !self.context.use_block_indent() || (is_extendable && extend_width <= shape.width) + } else { + // 2 = `()` + let fits_one_line = items_str.len() + 2 <= shape.width; + !self.context.use_block_indent() + || (self.context.inside_macro() && !items_str.contains('\n') && fits_one_line) + || (is_extendable && extend_width <= shape.width) + }; + if force_single_line { + result.push_str(items_str); + } else { + if !items_str.is_empty() { + result.push_str(&nested_indent_str); + result.push_str(items_str); + } + result.push_str(&indent_str); + } + result.push_str(suffix); + result + } + + fn rewrite(&self, shape: Shape) -> Option { + let (extendable, items_str) = self.rewrite_items()?; + + // If we are using visual indent style and failed to format, retry with block indent. + if !self.context.use_block_indent() + && need_block_indent(&items_str, self.nested_shape) + && !extendable + { + self.context.use_block.replace(true); + let result = self.rewrite(shape); + self.context.use_block.replace(false); + return result; + } + + Some(self.wrap_items(&items_str, shape, extendable)) + } +} + +fn need_block_indent(s: &str, shape: Shape) -> bool { + s.lines().skip(1).any(|s| { + s.find(|c| !char::is_whitespace(c)) + .map_or(false, |w| w + 1 < shape.indent.width()) + }) +} + +fn can_be_overflowed(context: &RewriteContext<'_>, items: &[OverflowableItem<'_>]) -> bool { + items + .last() + .map_or(false, |x| x.can_be_overflowed(context, items.len())) +} + +/// Returns a shape for the last argument which is going to be overflowed. +fn last_item_shape( + lists: &[OverflowableItem<'_>], + items: &[ListItem], + shape: Shape, + args_max_width: usize, +) -> Option { + if items.len() == 1 && !lists.get(0)?.is_nested_call() { + return Some(shape); + } + let offset = items + .iter() + .dropping_back(1) + .map(|i| { + // 2 = ", " + 2 + i.inner_as_ref().len() + }) + .sum(); + Shape { + width: min(args_max_width, shape.width), + ..shape + } + .offset_left(offset) +} + +fn shape_from_indent_style( + context: &RewriteContext<'_>, + shape: Shape, + overhead: usize, + offset: usize, +) -> Shape { + let (shape, overhead) = if context.use_block_indent() { + let shape = shape + .block() + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + (shape, 1) // 1 = "," + } else { + (shape.visual_indent(offset), overhead) + }; + Shape { + width: shape.width.saturating_sub(overhead), + ..shape + } +} + +fn no_long_items(list: &[ListItem]) -> bool { + list.iter() + .all(|item| item.inner_as_ref().len() <= SHORT_ITEM_THRESHOLD) +} + +/// In case special-case style is required, returns an offset from which we start horizontal layout. +pub(crate) fn maybe_get_args_offset( + callee_str: &str, + args: &[OverflowableItem<'_>], +) -> Option<(bool, usize)> { + if let Some(&(_, num_args_before)) = args + .get(0)? + .whitelist() + .iter() + .find(|&&(s, _)| s == callee_str) + { + let all_simple = args.len() > num_args_before + && is_every_expr_simple(&args[0..num_args_before]) + && is_every_expr_simple(&args[num_args_before + 1..]); + + Some((all_simple, num_args_before)) + } else { + None + } +} diff --git a/src/tools/rustfmt/src/pairs.rs b/src/tools/rustfmt/src/pairs.rs new file mode 100644 index 0000000000..0f3d5e8f87 --- /dev/null +++ b/src/tools/rustfmt/src/pairs.rs @@ -0,0 +1,318 @@ +use rustc_ast::ast; + +use crate::config::lists::*; +use crate::config::IndentStyle; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::utils::{ + first_line_width, is_single_line, last_line_width, trimmed_last_line_width, wrap_str, +}; + +/// Sigils that decorate a binop pair. +#[derive(new, Clone, Copy)] +pub(crate) struct PairParts<'a> { + prefix: &'a str, + infix: &'a str, + suffix: &'a str, +} + +impl<'a> PairParts<'a> { + pub(crate) fn infix(infix: &'a str) -> PairParts<'a> { + PairParts { + prefix: "", + infix, + suffix: "", + } + } +} + +// Flattens a tree of pairs into a list and tries to rewrite them all at once. +// FIXME would be nice to reuse the lists API for this, but because each separator +// can be different, we can't. +pub(crate) fn rewrite_all_pairs( + expr: &ast::Expr, + shape: Shape, + context: &RewriteContext<'_>, +) -> Option { + expr.flatten(context, shape).and_then(|list| { + // First we try formatting on one line. + rewrite_pairs_one_line(&list, shape, context) + .or_else(|| rewrite_pairs_multiline(&list, shape, context)) + }) +} + +// This may return a multi-line result since we allow the last expression to go +// multiline in a 'single line' formatting. +fn rewrite_pairs_one_line( + list: &PairList<'_, '_, T>, + shape: Shape, + context: &RewriteContext<'_>, +) -> Option { + assert!(list.list.len() >= 2, "Not a pair?"); + + let mut result = String::new(); + let base_shape = shape.block(); + + for ((_, rewrite), s) in list.list.iter().zip(list.separators.iter()) { + if let Some(rewrite) = rewrite { + if !is_single_line(&rewrite) || result.len() > shape.width { + return None; + } + + result.push_str(&rewrite); + result.push(' '); + result.push_str(s); + result.push(' '); + } else { + return None; + } + } + + let prefix_len = result.len(); + let last = list.list.last()?.0; + let cur_shape = base_shape.offset_left(last_line_width(&result))?; + let last_rewrite = last.rewrite(context, cur_shape)?; + result.push_str(&last_rewrite); + + if first_line_width(&result) > shape.width { + return None; + } + + // Check the last expression in the list. We sometimes let this expression + // go over multiple lines, but we check for some ugly conditions. + if !(is_single_line(&result) || last_rewrite.starts_with('{')) + && (last_rewrite.starts_with('(') || prefix_len > context.config.tab_spaces()) + { + return None; + } + + wrap_str(result, context.config.max_width(), shape) +} + +fn rewrite_pairs_multiline( + list: &PairList<'_, '_, T>, + shape: Shape, + context: &RewriteContext<'_>, +) -> Option { + let rhs_offset = shape.rhs_overhead(&context.config); + let nested_shape = (match context.config.indent_style() { + IndentStyle::Visual => shape.visual_indent(0), + IndentStyle::Block => shape.block_indent(context.config.tab_spaces()), + }) + .with_max_width(&context.config) + .sub_width(rhs_offset)?; + + let indent_str = nested_shape.indent.to_string_with_newline(context.config); + let mut result = String::new(); + + result.push_str(&list.list[0].1.as_ref()?); + + for ((e, default_rw), s) in list.list[1..].iter().zip(list.separators.iter()) { + // The following test checks if we should keep two subexprs on the same + // line. We do this if not doing so would create an orphan and there is + // enough space to do so. + let offset = if result.contains('\n') { + 0 + } else { + shape.used_width() + }; + if last_line_width(&result) + offset <= nested_shape.used_width() { + // We must snuggle the next line onto the previous line to avoid an orphan. + if let Some(line_shape) = + shape.offset_left(s.len() + 2 + trimmed_last_line_width(&result)) + { + if let Some(rewrite) = e.rewrite(context, line_shape) { + result.push(' '); + result.push_str(s); + result.push(' '); + result.push_str(&rewrite); + continue; + } + } + } + + match context.config.binop_separator() { + SeparatorPlace::Back => { + result.push(' '); + result.push_str(s); + result.push_str(&indent_str); + } + SeparatorPlace::Front => { + result.push_str(&indent_str); + result.push_str(s); + result.push(' '); + } + } + + result.push_str(&default_rw.as_ref()?); + } + Some(result) +} + +// Rewrites a single pair. +pub(crate) fn rewrite_pair( + lhs: &LHS, + rhs: &RHS, + pp: PairParts<'_>, + context: &RewriteContext<'_>, + shape: Shape, + separator_place: SeparatorPlace, +) -> Option +where + LHS: Rewrite, + RHS: Rewrite, +{ + let tab_spaces = context.config.tab_spaces(); + let lhs_overhead = match separator_place { + SeparatorPlace::Back => shape.used_width() + pp.prefix.len() + pp.infix.trim_end().len(), + SeparatorPlace::Front => shape.used_width(), + }; + let lhs_shape = Shape { + width: context.budget(lhs_overhead), + ..shape + }; + let lhs_result = lhs + .rewrite(context, lhs_shape) + .map(|lhs_str| format!("{}{}", pp.prefix, lhs_str))?; + + // Try to put both lhs and rhs on the same line. + let rhs_orig_result = shape + .offset_left(last_line_width(&lhs_result) + pp.infix.len()) + .and_then(|s| s.sub_width(pp.suffix.len())) + .and_then(|rhs_shape| rhs.rewrite(context, rhs_shape)); + if let Some(ref rhs_result) = rhs_orig_result { + // If the length of the lhs is equal to or shorter than the tab width or + // the rhs looks like block expression, we put the rhs on the same + // line with the lhs even if the rhs is multi-lined. + let allow_same_line = lhs_result.len() <= tab_spaces + || rhs_result + .lines() + .next() + .map(|first_line| first_line.ends_with('{')) + .unwrap_or(false); + if !rhs_result.contains('\n') || allow_same_line { + let one_line_width = last_line_width(&lhs_result) + + pp.infix.len() + + first_line_width(rhs_result) + + pp.suffix.len(); + if one_line_width <= shape.width { + return Some(format!( + "{}{}{}{}", + lhs_result, pp.infix, rhs_result, pp.suffix + )); + } + } + } + + // We have to use multiple lines. + // Re-evaluate the rhs because we have more space now: + let mut rhs_shape = match context.config.indent_style() { + IndentStyle::Visual => shape + .sub_width(pp.suffix.len() + pp.prefix.len())? + .visual_indent(pp.prefix.len()), + IndentStyle::Block => { + // Try to calculate the initial constraint on the right hand side. + let rhs_overhead = shape.rhs_overhead(context.config); + Shape::indented(shape.indent.block_indent(context.config), context.config) + .sub_width(rhs_overhead)? + } + }; + let infix = match separator_place { + SeparatorPlace::Back => pp.infix.trim_end(), + SeparatorPlace::Front => pp.infix.trim_start(), + }; + if separator_place == SeparatorPlace::Front { + rhs_shape = rhs_shape.offset_left(infix.len())?; + } + let rhs_result = rhs.rewrite(context, rhs_shape)?; + let indent_str = rhs_shape.indent.to_string_with_newline(context.config); + let infix_with_sep = match separator_place { + SeparatorPlace::Back => format!("{}{}", infix, indent_str), + SeparatorPlace::Front => format!("{}{}", indent_str, infix), + }; + Some(format!( + "{}{}{}{}", + lhs_result, infix_with_sep, rhs_result, pp.suffix + )) +} + +// A pair which forms a tree and can be flattened (e.g., binops). +trait FlattenPair: Rewrite + Sized { + fn flatten(&self, _: &RewriteContext<'_>, _: Shape) -> Option> { + None + } +} + +struct PairList<'a, 'b, T: Rewrite> { + list: Vec<(&'b T, Option)>, + separators: Vec<&'a str>, +} + +impl FlattenPair for ast::Expr { + fn flatten( + &self, + context: &RewriteContext<'_>, + shape: Shape, + ) -> Option> { + let top_op = match self.kind { + ast::ExprKind::Binary(op, _, _) => op.node, + _ => return None, + }; + + let default_rewrite = |node: &ast::Expr, sep: usize, is_first: bool| { + if is_first { + return node.rewrite(context, shape); + } + let nested_overhead = sep + 1; + let rhs_offset = shape.rhs_overhead(&context.config); + let nested_shape = (match context.config.indent_style() { + IndentStyle::Visual => shape.visual_indent(0), + IndentStyle::Block => shape.block_indent(context.config.tab_spaces()), + }) + .with_max_width(&context.config) + .sub_width(rhs_offset)?; + let default_shape = match context.config.binop_separator() { + SeparatorPlace::Back => nested_shape.sub_width(nested_overhead)?, + SeparatorPlace::Front => nested_shape.offset_left(nested_overhead)?, + }; + node.rewrite(context, default_shape) + }; + + // Turn a tree of binop expressions into a list using a depth-first, + // in-order traversal. + let mut stack = vec![]; + let mut list = vec![]; + let mut separators = vec![]; + let mut node = self; + loop { + match node.kind { + ast::ExprKind::Binary(op, ref lhs, _) if op.node == top_op => { + stack.push(node); + node = lhs; + } + _ => { + let op_len = separators.last().map_or(0, |s: &&str| s.len()); + let rw = default_rewrite(node, op_len, list.is_empty()); + list.push((node, rw)); + if let Some(pop) = stack.pop() { + match pop.kind { + ast::ExprKind::Binary(op, _, ref rhs) => { + separators.push(op.node.to_string()); + node = rhs; + } + _ => unreachable!(), + } + } else { + break; + } + } + } + } + + assert_eq!(list.len() - 1, separators.len()); + Some(PairList { list, separators }) + } +} + +impl FlattenPair for ast::Ty {} +impl FlattenPair for ast::Pat {} diff --git a/src/tools/rustfmt/src/patterns.rs b/src/tools/rustfmt/src/patterns.rs new file mode 100644 index 0000000000..5847bd08d8 --- /dev/null +++ b/src/tools/rustfmt/src/patterns.rs @@ -0,0 +1,525 @@ +use rustc_ast::ast::{self, BindingMode, FieldPat, Pat, PatKind, RangeEnd, RangeSyntax}; +use rustc_ast::ptr; +use rustc_span::{BytePos, Span}; + +use crate::comment::{combine_strs_with_missing_comments, FindUncommented}; +use crate::config::lists::*; +use crate::expr::{can_be_overflowed_expr, rewrite_unary_prefix, wrap_struct_field}; +use crate::lists::{ + definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape, + struct_lit_tactic, write_list, ListFormatting, ListItem, Separator, +}; +use crate::macros::{rewrite_macro, MacroPosition}; +use crate::overflow; +use crate::pairs::{rewrite_pair, PairParts}; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::source_map::SpanUtils; +use crate::spanned::Spanned; +use crate::types::{rewrite_path, PathContext}; +use crate::utils::{format_mutability, mk_sp, rewrite_ident}; + +/// Returns `true` if the given pattern is "short". +/// A short pattern is defined by the following grammar: +/// +/// [small, ntp]: +/// - single token +/// - `&[single-line, ntp]` +/// +/// [small]: +/// - `[small, ntp]` +/// - unary tuple constructor `([small, ntp])` +/// - `&[small]` +pub(crate) fn is_short_pattern(pat: &ast::Pat, pat_str: &str) -> bool { + // We also require that the pattern is reasonably 'small' with its literal width. + pat_str.len() <= 20 && !pat_str.contains('\n') && is_short_pattern_inner(pat) +} + +fn is_short_pattern_inner(pat: &ast::Pat) -> bool { + match pat.kind { + ast::PatKind::Rest | ast::PatKind::Wild | ast::PatKind::Lit(_) => true, + ast::PatKind::Ident(_, _, ref pat) => pat.is_none(), + ast::PatKind::Struct(..) + | ast::PatKind::MacCall(..) + | ast::PatKind::Slice(..) + | ast::PatKind::Path(..) + | ast::PatKind::Range(..) => false, + ast::PatKind::Tuple(ref subpats) => subpats.len() <= 1, + ast::PatKind::TupleStruct(ref path, ref subpats) => { + path.segments.len() <= 1 && subpats.len() <= 1 + } + ast::PatKind::Box(ref p) | ast::PatKind::Ref(ref p, _) | ast::PatKind::Paren(ref p) => { + is_short_pattern_inner(&*p) + } + PatKind::Or(ref pats) => pats.iter().all(|p| is_short_pattern_inner(p)), + } +} + +struct RangeOperand<'a>(&'a Option>); + +impl<'a> Rewrite for RangeOperand<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match &self.0 { + None => Some("".to_owned()), + Some(ref exp) => exp.rewrite(context, shape), + } + } +} + +impl Rewrite for Pat { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match self.kind { + PatKind::Or(ref pats) => { + let pat_strs = pats + .iter() + .map(|p| p.rewrite(context, shape)) + .collect::>>()?; + + let use_mixed_layout = pats + .iter() + .zip(pat_strs.iter()) + .all(|(pat, pat_str)| is_short_pattern(pat, pat_str)); + let items: Vec<_> = pat_strs.into_iter().map(ListItem::from_str).collect(); + let tactic = if use_mixed_layout { + DefinitiveListTactic::Mixed + } else { + definitive_tactic( + &items, + ListTactic::HorizontalVertical, + Separator::VerticalBar, + shape.width, + ) + }; + let fmt = ListFormatting::new(shape, context.config) + .tactic(tactic) + .separator(" |") + .separator_place(context.config.binop_separator()) + .ends_with_newline(false); + write_list(&items, &fmt) + } + PatKind::Box(ref pat) => rewrite_unary_prefix(context, "box ", &**pat, shape), + PatKind::Ident(binding_mode, ident, ref sub_pat) => { + let (prefix, mutability) = match binding_mode { + BindingMode::ByRef(mutability) => ("ref", mutability), + BindingMode::ByValue(mutability) => ("", mutability), + }; + let mut_infix = format_mutability(mutability).trim(); + let id_str = rewrite_ident(context, ident); + let sub_pat = match *sub_pat { + Some(ref p) => { + // 2 - `@ `. + let width = shape + .width + .checked_sub(prefix.len() + mut_infix.len() + id_str.len() + 2)?; + let lo = context.snippet_provider.span_after(self.span, "@"); + combine_strs_with_missing_comments( + context, + "@", + &p.rewrite(context, Shape::legacy(width, shape.indent))?, + mk_sp(lo, p.span.lo()), + shape, + true, + )? + } + None => "".to_owned(), + }; + + // combine prefix and mut + let (first_lo, first) = if !prefix.is_empty() && !mut_infix.is_empty() { + let hi = context.snippet_provider.span_before(self.span, "mut"); + let lo = context.snippet_provider.span_after(self.span, "ref"); + ( + context.snippet_provider.span_after(self.span, "mut"), + combine_strs_with_missing_comments( + context, + prefix, + mut_infix, + mk_sp(lo, hi), + shape, + true, + )?, + ) + } else if !prefix.is_empty() { + ( + context.snippet_provider.span_after(self.span, "ref"), + prefix.to_owned(), + ) + } else if !mut_infix.is_empty() { + ( + context.snippet_provider.span_after(self.span, "mut"), + mut_infix.to_owned(), + ) + } else { + (self.span.lo(), "".to_owned()) + }; + + let next = if !sub_pat.is_empty() { + let hi = context.snippet_provider.span_before(self.span, "@"); + combine_strs_with_missing_comments( + context, + id_str, + &sub_pat, + mk_sp(ident.span.hi(), hi), + shape, + true, + )? + } else { + id_str.to_owned() + }; + + combine_strs_with_missing_comments( + context, + &first, + &next, + mk_sp(first_lo, ident.span.lo()), + shape, + true, + ) + } + PatKind::Wild => { + if 1 <= shape.width { + Some("_".to_owned()) + } else { + None + } + } + PatKind::Rest => { + if 1 <= shape.width { + Some("..".to_owned()) + } else { + None + } + } + PatKind::Range(ref lhs, ref rhs, ref end_kind) => { + let infix = match end_kind.node { + RangeEnd::Included(RangeSyntax::DotDotDot) => "...", + RangeEnd::Included(RangeSyntax::DotDotEq) => "..=", + RangeEnd::Excluded => "..", + }; + let infix = if context.config.spaces_around_ranges() { + let lhs_spacing = match lhs { + None => "", + Some(_) => " ", + }; + let rhs_spacing = match rhs { + None => "", + Some(_) => " ", + }; + format!("{}{}{}", lhs_spacing, infix, rhs_spacing) + } else { + infix.to_owned() + }; + rewrite_pair( + &RangeOperand(lhs), + &RangeOperand(rhs), + PairParts::infix(&infix), + context, + shape, + SeparatorPlace::Front, + ) + } + PatKind::Ref(ref pat, mutability) => { + let prefix = format!("&{}", format_mutability(mutability)); + rewrite_unary_prefix(context, &prefix, &**pat, shape) + } + PatKind::Tuple(ref items) => rewrite_tuple_pat(items, None, self.span, context, shape), + PatKind::Path(ref q_self, ref path) => { + rewrite_path(context, PathContext::Expr, q_self.as_ref(), path, shape) + } + PatKind::TupleStruct(ref path, ref pat_vec) => { + let path_str = rewrite_path(context, PathContext::Expr, None, path, shape)?; + rewrite_tuple_pat(pat_vec, Some(path_str), self.span, context, shape) + } + PatKind::Lit(ref expr) => expr.rewrite(context, shape), + PatKind::Slice(ref slice_pat) => { + let rw: Vec = slice_pat + .iter() + .map(|p| { + if let Some(rw) = p.rewrite(context, shape) { + rw + } else { + format!("{}", context.snippet(p.span)) + } + }) + .collect(); + Some(format!("[{}]", rw.join(", "))) + } + PatKind::Struct(ref path, ref fields, ellipsis) => { + rewrite_struct_pat(path, fields, ellipsis, self.span, context, shape) + } + PatKind::MacCall(ref mac) => { + rewrite_macro(mac, None, context, shape, MacroPosition::Pat) + } + PatKind::Paren(ref pat) => pat + .rewrite(context, shape.offset_left(1)?.sub_width(1)?) + .map(|inner_pat| format!("({})", inner_pat)), + } + } +} + +fn rewrite_struct_pat( + path: &ast::Path, + fields: &[ast::FieldPat], + ellipsis: bool, + span: Span, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + // 2 = ` {` + let path_shape = shape.sub_width(2)?; + let path_str = rewrite_path(context, PathContext::Expr, None, path, path_shape)?; + + if fields.is_empty() && !ellipsis { + return Some(format!("{} {{}}", path_str)); + } + + let (ellipsis_str, terminator) = if ellipsis { (", ..", "..") } else { ("", "}") }; + + // 3 = ` { `, 2 = ` }`. + let (h_shape, v_shape) = + struct_lit_shape(shape, context, path_str.len() + 3, ellipsis_str.len() + 2)?; + + let items = itemize_list( + context.snippet_provider, + fields.iter(), + terminator, + ",", + |f| { + if f.attrs.is_empty() { + f.span.lo() + } else { + f.attrs.first().unwrap().span.lo() + } + }, + |f| f.span.hi(), + |f| f.rewrite(context, v_shape), + context.snippet_provider.span_after(span, "{"), + span.hi(), + false, + ); + let item_vec = items.collect::>(); + + let tactic = struct_lit_tactic(h_shape, context, &item_vec); + let nested_shape = shape_for_tactic(tactic, h_shape, v_shape); + let fmt = struct_lit_formatting(nested_shape, tactic, context, false); + + let mut fields_str = write_list(&item_vec, &fmt)?; + let one_line_width = h_shape.map_or(0, |shape| shape.width); + + if ellipsis { + if fields_str.contains('\n') || fields_str.len() > one_line_width { + // Add a missing trailing comma. + if context.config.trailing_comma() == SeparatorTactic::Never { + fields_str.push_str(","); + } + fields_str.push_str("\n"); + fields_str.push_str(&nested_shape.indent.to_string(context.config)); + fields_str.push_str(".."); + } else { + if !fields_str.is_empty() { + // there are preceding struct fields being matched on + if tactic == DefinitiveListTactic::Vertical { + // if the tactic is Vertical, write_list already added a trailing , + fields_str.push_str(" "); + } else { + fields_str.push_str(", "); + } + } + fields_str.push_str(".."); + } + } + + // ast::Pat doesn't have attrs so use &[] + let fields_str = wrap_struct_field(context, &[], &fields_str, shape, v_shape, one_line_width)?; + Some(format!("{} {{{}}}", path_str, fields_str)) +} + +impl Rewrite for FieldPat { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let hi_pos = if let Some(last) = self.attrs.last() { + last.span.hi() + } else { + self.pat.span.lo() + }; + + let attrs_str = if self.attrs.is_empty() { + String::from("") + } else { + self.attrs.rewrite(context, shape)? + }; + + let pat_str = self.pat.rewrite(context, shape)?; + if self.is_shorthand { + combine_strs_with_missing_comments( + context, + &attrs_str, + &pat_str, + mk_sp(hi_pos, self.pat.span.lo()), + shape, + false, + ) + } else { + let nested_shape = shape.block_indent(context.config.tab_spaces()); + let id_str = rewrite_ident(context, self.ident); + let one_line_width = id_str.len() + 2 + pat_str.len(); + let pat_and_id_str = if one_line_width <= shape.width { + format!("{}: {}", id_str, pat_str) + } else { + format!( + "{}:\n{}{}", + id_str, + nested_shape.indent.to_string(context.config), + self.pat.rewrite(context, nested_shape)? + ) + }; + combine_strs_with_missing_comments( + context, + &attrs_str, + &pat_and_id_str, + mk_sp(hi_pos, self.pat.span.lo()), + nested_shape, + false, + ) + } + } +} + +#[derive(Debug)] +pub(crate) enum TuplePatField<'a> { + Pat(&'a ptr::P), + Dotdot(Span), +} + +impl<'a> Rewrite for TuplePatField<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match *self { + TuplePatField::Pat(p) => p.rewrite(context, shape), + TuplePatField::Dotdot(_) => Some("..".to_string()), + } + } +} + +impl<'a> Spanned for TuplePatField<'a> { + fn span(&self) -> Span { + match *self { + TuplePatField::Pat(p) => p.span(), + TuplePatField::Dotdot(span) => span, + } + } +} + +impl<'a> TuplePatField<'a> { + fn is_dotdot(&self) -> bool { + match self { + TuplePatField::Pat(pat) => match pat.kind { + ast::PatKind::Rest => true, + _ => false, + }, + TuplePatField::Dotdot(_) => true, + } + } +} + +pub(crate) fn can_be_overflowed_pat( + context: &RewriteContext<'_>, + pat: &TuplePatField<'_>, + len: usize, +) -> bool { + match *pat { + TuplePatField::Pat(pat) => match pat.kind { + ast::PatKind::Path(..) + | ast::PatKind::Tuple(..) + | ast::PatKind::Struct(..) + | ast::PatKind::TupleStruct(..) => context.use_block_indent() && len == 1, + ast::PatKind::Ref(ref p, _) | ast::PatKind::Box(ref p) => { + can_be_overflowed_pat(context, &TuplePatField::Pat(p), len) + } + ast::PatKind::Lit(ref expr) => can_be_overflowed_expr(context, expr, len), + _ => false, + }, + TuplePatField::Dotdot(..) => false, + } +} + +fn rewrite_tuple_pat( + pats: &[ptr::P], + path_str: Option, + span: Span, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let mut pat_vec: Vec<_> = pats.iter().map(|x| TuplePatField::Pat(x)).collect(); + + if pat_vec.is_empty() { + return Some(format!("{}()", path_str.unwrap_or_default())); + } + let wildcard_suffix_len = count_wildcard_suffix_len(context, &pat_vec, span, shape); + let (pat_vec, span) = if context.config.condense_wildcard_suffixes() && wildcard_suffix_len >= 2 + { + let new_item_count = 1 + pat_vec.len() - wildcard_suffix_len; + let sp = pat_vec[new_item_count - 1].span(); + let snippet = context.snippet(sp); + let lo = sp.lo() + BytePos(snippet.find_uncommented("_").unwrap() as u32); + pat_vec[new_item_count - 1] = TuplePatField::Dotdot(mk_sp(lo, lo + BytePos(1))); + ( + &pat_vec[..new_item_count], + mk_sp(span.lo(), lo + BytePos(1)), + ) + } else { + (&pat_vec[..], span) + }; + + let is_last_pat_dotdot = pat_vec.last().map_or(false, |p| p.is_dotdot()); + let add_comma = path_str.is_none() && pat_vec.len() == 1 && !is_last_pat_dotdot; + let path_str = path_str.unwrap_or_default(); + + overflow::rewrite_with_parens( + &context, + &path_str, + pat_vec.iter(), + shape, + span, + context.config.max_width(), + if add_comma { + Some(SeparatorTactic::Always) + } else { + None + }, + ) +} + +fn count_wildcard_suffix_len( + context: &RewriteContext<'_>, + patterns: &[TuplePatField<'_>], + span: Span, + shape: Shape, +) -> usize { + let mut suffix_len = 0; + + let items: Vec<_> = itemize_list( + context.snippet_provider, + patterns.iter(), + ")", + ",", + |item| item.span().lo(), + |item| item.span().hi(), + |item| item.rewrite(context, shape), + context.snippet_provider.span_after(span, "("), + span.hi() - BytePos(1), + false, + ) + .collect(); + + for item in items.iter().rev().take_while(|i| match i.item { + Some(ref internal_string) if internal_string == "_" => true, + _ => false, + }) { + suffix_len += 1; + + if item.has_comment() { + break; + } + } + + suffix_len +} diff --git a/src/tools/rustfmt/src/release_channel.rs b/src/tools/rustfmt/src/release_channel.rs new file mode 100644 index 0000000000..948247b3c9 --- /dev/null +++ b/src/tools/rustfmt/src/release_channel.rs @@ -0,0 +1,16 @@ +/// Checks if we're in a nightly build. +/// +/// The environment variable `CFG_RELEASE_CHANNEL` is set during the rustc bootstrap +/// to "stable", "beta", or "nightly" depending on what toolchain is being built. +/// If we are being built as part of the stable or beta toolchains, we want +/// to disable unstable configuration options. +/// +/// If we're being built by cargo (e.g., `cargo +nightly install rustfmt-nightly`), +/// `CFG_RELEASE_CHANNEL` is not set. As we only support being built against the +/// nightly compiler when installed from crates.io, default to nightly mode. +#[macro_export] +macro_rules! is_nightly_channel { + () => { + option_env!("CFG_RELEASE_CHANNEL").map_or(true, |c| c == "nightly" || c == "dev") + }; +} diff --git a/src/tools/rustfmt/src/reorder.rs b/src/tools/rustfmt/src/reorder.rs new file mode 100644 index 0000000000..ac65ff2c10 --- /dev/null +++ b/src/tools/rustfmt/src/reorder.rs @@ -0,0 +1,332 @@ +//! Reorder items. +//! +//! `mod`, `extern crate` and `use` declarations are reordered in alphabetical +//! order. Trait items are reordered in pre-determined order (associated types +//! and constants comes before methods). + +// FIXME(#2455): Reorder trait items. + +use std::cmp::{Ord, Ordering}; + +use rustc_ast::ast; +use rustc_span::{symbol::sym, Span}; + +use crate::config::{Config, GroupImportsTactic, ImportGranularity}; +use crate::imports::{flatten_use_trees, merge_use_trees, SharedPrefix, UseSegment, UseTree}; +use crate::items::{is_mod_decl, rewrite_extern_crate, rewrite_mod}; +use crate::lists::{itemize_list, write_list, ListFormatting, ListItem}; +use crate::rewrite::RewriteContext; +use crate::shape::Shape; +use crate::source_map::LineRangeUtils; +use crate::spanned::Spanned; +use crate::utils::{contains_skip, mk_sp}; +use crate::visitor::FmtVisitor; + +/// Choose the ordering between the given two items. +fn compare_items(a: &ast::Item, b: &ast::Item) -> Ordering { + match (&a.kind, &b.kind) { + (&ast::ItemKind::Mod(..), &ast::ItemKind::Mod(..)) => { + a.ident.as_str().cmp(&b.ident.as_str()) + } + (&ast::ItemKind::ExternCrate(ref a_name), &ast::ItemKind::ExternCrate(ref b_name)) => { + // `extern crate foo as bar;` + // ^^^ Comparing this. + let a_orig_name = a_name.map_or_else(|| a.ident.as_str(), rustc_span::Symbol::as_str); + let b_orig_name = b_name.map_or_else(|| b.ident.as_str(), rustc_span::Symbol::as_str); + let result = a_orig_name.cmp(&b_orig_name); + if result != Ordering::Equal { + return result; + } + + // `extern crate foo as bar;` + // ^^^ Comparing this. + match (a_name, b_name) { + (Some(..), None) => Ordering::Greater, + (None, Some(..)) => Ordering::Less, + (None, None) => Ordering::Equal, + (Some(..), Some(..)) => a.ident.as_str().cmp(&b.ident.as_str()), + } + } + _ => unreachable!(), + } +} + +fn wrap_reorderable_items( + context: &RewriteContext<'_>, + list_items: &[ListItem], + shape: Shape, +) -> Option { + let fmt = ListFormatting::new(shape, context.config) + .separator("") + .align_comments(false); + write_list(list_items, &fmt) +} + +fn rewrite_reorderable_item( + context: &RewriteContext<'_>, + item: &ast::Item, + shape: Shape, +) -> Option { + match item.kind { + ast::ItemKind::ExternCrate(..) => rewrite_extern_crate(context, item, shape), + ast::ItemKind::Mod(..) => rewrite_mod(context, item, shape), + _ => None, + } +} + +/// Rewrite a list of items with reordering and/or regrouping. Every item +/// in `items` must have the same `ast::ItemKind`. Whether reordering, regrouping, +/// or both are done is determined from the `context`. +fn rewrite_reorderable_or_regroupable_items( + context: &RewriteContext<'_>, + reorderable_items: &[&ast::Item], + shape: Shape, + span: Span, +) -> Option { + match reorderable_items[0].kind { + // FIXME: Remove duplicated code. + ast::ItemKind::Use(..) => { + let mut normalized_items: Vec<_> = reorderable_items + .iter() + .filter_map(|item| UseTree::from_ast_with_normalization(context, item)) + .collect(); + let cloned = normalized_items.clone(); + // Add comments before merging. + let list_items = itemize_list( + context.snippet_provider, + cloned.iter(), + "", + ";", + |item| item.span().lo(), + |item| item.span().hi(), + |_item| Some("".to_owned()), + span.lo(), + span.hi(), + false, + ); + for (item, list_item) in normalized_items.iter_mut().zip(list_items) { + item.list_item = Some(list_item.clone()); + } + normalized_items = match context.config.imports_granularity() { + ImportGranularity::Crate => merge_use_trees(normalized_items, SharedPrefix::Crate), + ImportGranularity::Module => { + merge_use_trees(normalized_items, SharedPrefix::Module) + } + ImportGranularity::Item => flatten_use_trees(normalized_items), + ImportGranularity::Preserve => normalized_items, + }; + + let mut regrouped_items = match context.config.group_imports() { + GroupImportsTactic::Preserve => vec![normalized_items], + GroupImportsTactic::StdExternalCrate => group_imports(normalized_items), + }; + + if context.config.reorder_imports() { + regrouped_items.iter_mut().for_each(|items| items.sort()) + } + + // 4 = "use ", 1 = ";" + let nested_shape = shape.offset_left(4)?.sub_width(1)?; + let item_vec: Vec<_> = regrouped_items + .into_iter() + .filter(|use_group| !use_group.is_empty()) + .map(|use_group| { + let item_vec: Vec<_> = use_group + .into_iter() + .map(|use_tree| ListItem { + item: use_tree.rewrite_top_level(context, nested_shape), + ..use_tree.list_item.unwrap_or_else(ListItem::empty) + }) + .collect(); + wrap_reorderable_items(context, &item_vec, nested_shape) + }) + .collect::>>()?; + + let join_string = format!("\n\n{}", shape.indent.to_string(context.config)); + Some(item_vec.join(&join_string)) + } + _ => { + let list_items = itemize_list( + context.snippet_provider, + reorderable_items.iter(), + "", + ";", + |item| item.span().lo(), + |item| item.span().hi(), + |item| rewrite_reorderable_item(context, item, shape), + span.lo(), + span.hi(), + false, + ); + + let mut item_pair_vec: Vec<_> = list_items.zip(reorderable_items.iter()).collect(); + item_pair_vec.sort_by(|a, b| compare_items(a.1, b.1)); + let item_vec: Vec<_> = item_pair_vec.into_iter().map(|pair| pair.0).collect(); + + wrap_reorderable_items(context, &item_vec, shape) + } + } +} + +fn contains_macro_use_attr(item: &ast::Item) -> bool { + crate::attr::contains_name(&item.attrs, sym::macro_use) +} + +/// Divides imports into three groups, corresponding to standard, external +/// and local imports. Sorts each subgroup. +fn group_imports(uts: Vec) -> Vec> { + let mut std_imports = Vec::new(); + let mut external_imports = Vec::new(); + let mut local_imports = Vec::new(); + + for ut in uts.into_iter() { + if ut.path.is_empty() { + external_imports.push(ut); + continue; + } + match &ut.path[0] { + UseSegment::Ident(id, _) => match id.as_ref() { + "std" | "alloc" | "core" => std_imports.push(ut), + _ => external_imports.push(ut), + }, + UseSegment::Slf(_) | UseSegment::Super(_) | UseSegment::Crate(_) => { + local_imports.push(ut) + } + // These are probably illegal here + UseSegment::Glob | UseSegment::List(_) => external_imports.push(ut), + } + } + + vec![std_imports, external_imports, local_imports] +} + +/// A simplified version of `ast::ItemKind`. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +enum ReorderableItemKind { + ExternCrate, + Mod, + Use, + /// An item that cannot be reordered. Either has an unreorderable item kind + /// or an `macro_use` attribute. + Other, +} + +impl ReorderableItemKind { + fn from(item: &ast::Item) -> Self { + match item.kind { + _ if contains_macro_use_attr(item) | contains_skip(&item.attrs) => { + ReorderableItemKind::Other + } + ast::ItemKind::ExternCrate(..) => ReorderableItemKind::ExternCrate, + ast::ItemKind::Mod(..) if is_mod_decl(item) => ReorderableItemKind::Mod, + ast::ItemKind::Use(..) => ReorderableItemKind::Use, + _ => ReorderableItemKind::Other, + } + } + + fn is_same_item_kind(self, item: &ast::Item) -> bool { + ReorderableItemKind::from(item) == self + } + + fn is_reorderable(self, config: &Config) -> bool { + match self { + ReorderableItemKind::ExternCrate => config.reorder_imports(), + ReorderableItemKind::Mod => config.reorder_modules(), + ReorderableItemKind::Use => config.reorder_imports(), + ReorderableItemKind::Other => false, + } + } + + fn is_regroupable(self, config: &Config) -> bool { + match self { + ReorderableItemKind::ExternCrate + | ReorderableItemKind::Mod + | ReorderableItemKind::Other => false, + ReorderableItemKind::Use => config.group_imports() != GroupImportsTactic::Preserve, + } + } + + fn in_group(self, config: &Config) -> bool { + match self { + ReorderableItemKind::ExternCrate | ReorderableItemKind::Mod => true, + ReorderableItemKind::Use => config.group_imports() == GroupImportsTactic::Preserve, + ReorderableItemKind::Other => false, + } + } +} + +impl<'b, 'a: 'b> FmtVisitor<'a> { + /// Format items with the same item kind and reorder them, regroup them, or + /// both. If `in_group` is `true`, then the items separated by an empty line + /// will not be reordered together. + fn walk_reorderable_or_regroupable_items( + &mut self, + items: &[&ast::Item], + item_kind: ReorderableItemKind, + in_group: bool, + ) -> usize { + let mut last = self.parse_sess.lookup_line_range(items[0].span()); + let item_length = items + .iter() + .take_while(|ppi| { + item_kind.is_same_item_kind(&***ppi) + && (!in_group || { + let current = self.parse_sess.lookup_line_range(ppi.span()); + let in_same_group = current.lo < last.hi + 2; + last = current; + in_same_group + }) + }) + .count(); + let items = &items[..item_length]; + + let at_least_one_in_file_lines = items + .iter() + .any(|item| !out_of_file_lines_range!(self, item.span)); + + if at_least_one_in_file_lines && !items.is_empty() { + let lo = items.first().unwrap().span().lo(); + let hi = items.last().unwrap().span().hi(); + let span = mk_sp(lo, hi); + let rw = rewrite_reorderable_or_regroupable_items( + &self.get_context(), + items, + self.shape(), + span, + ); + self.push_rewrite(span, rw); + } else { + for item in items { + self.push_rewrite(item.span, None); + } + } + + item_length + } + + /// Visits and format the given items. Items are reordered If they are + /// consecutive and reorderable. + pub(crate) fn visit_items_with_reordering(&mut self, mut items: &[&ast::Item]) { + while !items.is_empty() { + // If the next item is a `use`, `extern crate` or `mod`, then extract it and any + // subsequent items that have the same item kind to be reordered within + // `walk_reorderable_items`. Otherwise, just format the next item for output. + let item_kind = ReorderableItemKind::from(items[0]); + if item_kind.is_reorderable(self.config) || item_kind.is_regroupable(self.config) { + let visited_items_num = self.walk_reorderable_or_regroupable_items( + items, + item_kind, + item_kind.in_group(self.config), + ); + let (_, rest) = items.split_at(visited_items_num); + items = rest; + } else { + // Reaching here means items were not reordered. There must be at least + // one item left in `items`, so calling `unwrap()` here is safe. + let (item, rest) = items.split_first().unwrap(); + self.visit_item(item); + items = rest; + } + } + } +} diff --git a/src/tools/rustfmt/src/rewrite.rs b/src/tools/rustfmt/src/rewrite.rs new file mode 100644 index 0000000000..c8abe70141 --- /dev/null +++ b/src/tools/rustfmt/src/rewrite.rs @@ -0,0 +1,98 @@ +// A generic trait to abstract the rewriting of an element (of the AST). + +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +use rustc_ast::ptr; +use rustc_span::Span; + +use crate::config::{Config, IndentStyle}; +use crate::shape::Shape; +use crate::skip::SkipContext; +use crate::syntux::session::ParseSess; +use crate::visitor::SnippetProvider; +use crate::FormatReport; + +pub(crate) trait Rewrite { + /// Rewrite self into shape. + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option; +} + +impl Rewrite for ptr::P { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + (**self).rewrite(context, shape) + } +} + +#[derive(Clone)] +pub(crate) struct RewriteContext<'a> { + pub(crate) parse_sess: &'a ParseSess, + pub(crate) config: &'a Config, + pub(crate) inside_macro: Rc>, + // Force block indent style even if we are using visual indent style. + pub(crate) use_block: Cell, + // When `is_if_else_block` is true, unindent the comment on top + // of the `else` or `else if`. + pub(crate) is_if_else_block: Cell, + // When rewriting chain, veto going multi line except the last element + pub(crate) force_one_line_chain: Cell, + pub(crate) snippet_provider: &'a SnippetProvider, + // Used for `format_snippet` + pub(crate) macro_rewrite_failure: Cell, + pub(crate) is_macro_def: bool, + pub(crate) report: FormatReport, + pub(crate) skip_context: SkipContext, + pub(crate) skipped_range: Rc>>, +} + +pub(crate) struct InsideMacroGuard { + is_nested_macro_context: bool, + inside_macro_ref: Rc>, +} + +impl InsideMacroGuard { + pub(crate) fn is_nested(&self) -> bool { + self.is_nested_macro_context + } +} + +impl Drop for InsideMacroGuard { + fn drop(&mut self) { + self.inside_macro_ref.replace(self.is_nested_macro_context); + } +} + +impl<'a> RewriteContext<'a> { + pub(crate) fn snippet(&self, span: Span) -> &str { + self.snippet_provider.span_to_snippet(span).unwrap() + } + + /// Returns `true` if we should use block indent style for rewriting function call. + pub(crate) fn use_block_indent(&self) -> bool { + self.config.indent_style() == IndentStyle::Block || self.use_block.get() + } + + pub(crate) fn budget(&self, used_width: usize) -> usize { + self.config.max_width().saturating_sub(used_width) + } + + pub(crate) fn inside_macro(&self) -> bool { + self.inside_macro.get() + } + + pub(crate) fn enter_macro(&self) -> InsideMacroGuard { + let is_nested_macro_context = self.inside_macro.replace(true); + InsideMacroGuard { + is_nested_macro_context, + inside_macro_ref: self.inside_macro.clone(), + } + } + + pub(crate) fn leave_macro(&self) { + self.inside_macro.replace(false); + } + + pub(crate) fn is_if_else_block(&self) -> bool { + self.is_if_else_block.get() + } +} diff --git a/src/tools/rustfmt/src/rustfmt_diff.rs b/src/tools/rustfmt/src/rustfmt_diff.rs new file mode 100644 index 0000000000..fc2c7d06e2 --- /dev/null +++ b/src/tools/rustfmt/src/rustfmt_diff.rs @@ -0,0 +1,403 @@ +use std::collections::VecDeque; +use std::fmt; +use std::io; +use std::io::Write; + +use crate::config::{Color, Config, Verbosity}; + +#[derive(Debug, PartialEq)] +pub enum DiffLine { + Context(String), + Expected(String), + Resulting(String), +} + +#[derive(Debug, PartialEq)] +pub struct Mismatch { + /// The line number in the formatted version. + pub line_number: u32, + /// The line number in the original version. + pub line_number_orig: u32, + /// The set of lines (context and old/new) in the mismatch. + pub lines: Vec, +} + +impl Mismatch { + fn new(line_number: u32, line_number_orig: u32) -> Mismatch { + Mismatch { + line_number, + line_number_orig, + lines: Vec::new(), + } + } +} + +/// A single span of changed lines, with 0 or more removed lines +/// and a vector of 0 or more inserted lines. +#[derive(Debug, PartialEq, Eq)] +pub struct ModifiedChunk { + /// The first to be removed from the original text + pub line_number_orig: u32, + /// The number of lines which have been replaced + pub lines_removed: u32, + /// The new lines + pub lines: Vec, +} + +/// Set of changed sections of a file. +#[derive(Debug, PartialEq, Eq)] +pub struct ModifiedLines { + /// The set of changed chunks. + pub chunks: Vec, +} + +impl From> for ModifiedLines { + fn from(mismatches: Vec) -> ModifiedLines { + let chunks = mismatches.into_iter().map(|mismatch| { + let lines = mismatch.lines.iter(); + let num_removed = lines + .filter(|line| match line { + DiffLine::Resulting(_) => true, + _ => false, + }) + .count(); + + let new_lines = mismatch.lines.into_iter().filter_map(|line| match line { + DiffLine::Context(_) | DiffLine::Resulting(_) => None, + DiffLine::Expected(str) => Some(str), + }); + + ModifiedChunk { + line_number_orig: mismatch.line_number_orig, + lines_removed: num_removed as u32, + lines: new_lines.collect(), + } + }); + + ModifiedLines { + chunks: chunks.collect(), + } + } +} + +// Converts a `Mismatch` into a serialized form, which just includes +// enough information to modify the original file. +// Each section starts with a line with three integers, space separated: +// lineno num_removed num_added +// followed by (`num_added`) lines of added text. The line numbers are +// relative to the original file. +impl fmt::Display for ModifiedLines { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for chunk in &self.chunks { + writeln!( + f, + "{} {} {}", + chunk.line_number_orig, + chunk.lines_removed, + chunk.lines.iter().count() + )?; + + for line in &chunk.lines { + writeln!(f, "{}", line)?; + } + } + + Ok(()) + } +} + +// Allows to convert `Display`ed `ModifiedLines` back to the structural data. +impl std::str::FromStr for ModifiedLines { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut chunks = vec![]; + + let mut lines = s.lines(); + while let Some(header) = lines.next() { + let mut header = header.split_whitespace(); + let (orig, rem, new_lines) = match (header.next(), header.next(), header.next()) { + (Some(orig), Some(removed), Some(added)) => (orig, removed, added), + _ => return Err(()), + }; + let (orig, rem, new_lines): (u32, u32, usize) = + match (orig.parse(), rem.parse(), new_lines.parse()) { + (Ok(a), Ok(b), Ok(c)) => (a, b, c), + _ => return Err(()), + }; + let lines = lines.by_ref().take(new_lines); + let lines: Vec<_> = lines.map(ToOwned::to_owned).collect(); + if lines.len() != new_lines { + return Err(()); + } + + chunks.push(ModifiedChunk { + line_number_orig: orig, + lines_removed: rem, + lines, + }); + } + + Ok(ModifiedLines { chunks }) + } +} + +// This struct handles writing output to stdout and abstracts away the logic +// of printing in color, if it's possible in the executing environment. +pub(crate) struct OutputWriter { + terminal: Option>>, +} + +impl OutputWriter { + // Create a new OutputWriter instance based on the caller's preference + // for colorized output and the capabilities of the terminal. + pub(crate) fn new(color: Color) -> Self { + if let Some(t) = term::stdout() { + if color.use_colored_tty() && t.supports_color() { + return OutputWriter { terminal: Some(t) }; + } + } + OutputWriter { terminal: None } + } + + // Write output in the optionally specified color. The output is written + // in the specified color if this OutputWriter instance contains a + // Terminal in its `terminal` field. + pub(crate) fn writeln(&mut self, msg: &str, color: Option) { + match &mut self.terminal { + Some(ref mut t) => { + if let Some(color) = color { + t.fg(color).unwrap(); + } + writeln!(t, "{}", msg).unwrap(); + if color.is_some() { + t.reset().unwrap(); + } + } + None => println!("{}", msg), + } + } +} + +// Produces a diff between the expected output and actual output of rustfmt. +pub(crate) fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec { + let mut line_number = 1; + let mut line_number_orig = 1; + let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size); + let mut lines_since_mismatch = context_size + 1; + let mut results = Vec::new(); + let mut mismatch = Mismatch::new(0, 0); + + for result in diff::lines(expected, actual) { + match result { + diff::Result::Left(str) => { + if lines_since_mismatch >= context_size && lines_since_mismatch > 0 { + results.push(mismatch); + mismatch = Mismatch::new( + line_number - context_queue.len() as u32, + line_number_orig - context_queue.len() as u32, + ); + } + + while let Some(line) = context_queue.pop_front() { + mismatch.lines.push(DiffLine::Context(line.to_owned())); + } + + mismatch.lines.push(DiffLine::Resulting(str.to_owned())); + line_number_orig += 1; + lines_since_mismatch = 0; + } + diff::Result::Right(str) => { + if lines_since_mismatch >= context_size && lines_since_mismatch > 0 { + results.push(mismatch); + mismatch = Mismatch::new( + line_number - context_queue.len() as u32, + line_number_orig - context_queue.len() as u32, + ); + } + + while let Some(line) = context_queue.pop_front() { + mismatch.lines.push(DiffLine::Context(line.to_owned())); + } + + mismatch.lines.push(DiffLine::Expected(str.to_owned())); + line_number += 1; + lines_since_mismatch = 0; + } + diff::Result::Both(str, _) => { + if context_queue.len() >= context_size { + let _ = context_queue.pop_front(); + } + + if lines_since_mismatch < context_size { + mismatch.lines.push(DiffLine::Context(str.to_owned())); + } else if context_size > 0 { + context_queue.push_back(str); + } + + line_number += 1; + line_number_orig += 1; + lines_since_mismatch += 1; + } + } + } + + results.push(mismatch); + results.remove(0); + + results +} + +pub(crate) fn print_diff(diff: Vec, get_section_title: F, config: &Config) +where + F: Fn(u32) -> String, +{ + let color = config.color(); + let line_terminator = if config.verbose() == Verbosity::Verbose { + "⏎" + } else { + "" + }; + + let mut writer = OutputWriter::new(color); + + for mismatch in diff { + let title = get_section_title(mismatch.line_number_orig); + writer.writeln(&title, None); + + for line in mismatch.lines { + match line { + DiffLine::Context(ref str) => { + writer.writeln(&format!(" {}{}", str, line_terminator), None) + } + DiffLine::Expected(ref str) => writer.writeln( + &format!("+{}{}", str, line_terminator), + Some(term::color::GREEN), + ), + DiffLine::Resulting(ref str) => writer.writeln( + &format!("-{}{}", str, line_terminator), + Some(term::color::RED), + ), + } + } + } +} + +#[cfg(test)] +mod test { + use super::DiffLine::*; + use super::{make_diff, Mismatch}; + use super::{ModifiedChunk, ModifiedLines}; + + #[test] + fn diff_simple() { + let src = "one\ntwo\nthree\nfour\nfive\n"; + let dest = "one\ntwo\ntrois\nfour\nfive\n"; + let diff = make_diff(src, dest, 1); + assert_eq!( + diff, + vec![Mismatch { + line_number: 2, + line_number_orig: 2, + lines: vec![ + Context("two".to_owned()), + Resulting("three".to_owned()), + Expected("trois".to_owned()), + Context("four".to_owned()), + ], + }] + ); + } + + #[test] + fn diff_simple2() { + let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n"; + let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n"; + let diff = make_diff(src, dest, 1); + assert_eq!( + diff, + vec![ + Mismatch { + line_number: 2, + line_number_orig: 2, + lines: vec![ + Context("two".to_owned()), + Resulting("three".to_owned()), + Expected("trois".to_owned()), + Context("four".to_owned()), + ], + }, + Mismatch { + line_number: 5, + line_number_orig: 5, + lines: vec![ + Resulting("five".to_owned()), + Expected("cinq".to_owned()), + Context("six".to_owned()), + ], + }, + ] + ); + } + + #[test] + fn diff_zerocontext() { + let src = "one\ntwo\nthree\nfour\nfive\n"; + let dest = "one\ntwo\ntrois\nfour\nfive\n"; + let diff = make_diff(src, dest, 0); + assert_eq!( + diff, + vec![Mismatch { + line_number: 3, + line_number_orig: 3, + lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())], + }] + ); + } + + #[test] + fn diff_trailing_newline() { + let src = "one\ntwo\nthree\nfour\nfive"; + let dest = "one\ntwo\nthree\nfour\nfive\n"; + let diff = make_diff(src, dest, 1); + assert_eq!( + diff, + vec![Mismatch { + line_number: 5, + line_number_orig: 5, + lines: vec![Context("five".to_owned()), Expected("".to_owned())], + }] + ); + } + + #[test] + fn modified_lines_from_str() { + use std::str::FromStr; + + let src = "1 6 2\nfn some() {}\nfn main() {}\n25 3 1\n struct Test {}"; + let lines = ModifiedLines::from_str(src).unwrap(); + assert_eq!( + lines, + ModifiedLines { + chunks: vec![ + ModifiedChunk { + line_number_orig: 1, + lines_removed: 6, + lines: vec!["fn some() {}".to_owned(), "fn main() {}".to_owned(),] + }, + ModifiedChunk { + line_number_orig: 25, + lines_removed: 3, + lines: vec![" struct Test {}".to_owned()] + } + ] + } + ); + + let src = "1 5 3"; + assert_eq!(ModifiedLines::from_str(src), Err(())); + + let src = "1 5 3\na\nb"; + assert_eq!(ModifiedLines::from_str(src), Err(())); + } +} diff --git a/src/tools/rustfmt/src/shape.rs b/src/tools/rustfmt/src/shape.rs new file mode 100644 index 0000000000..4376fd12b5 --- /dev/null +++ b/src/tools/rustfmt/src/shape.rs @@ -0,0 +1,373 @@ +use std::borrow::Cow; +use std::cmp::min; +use std::ops::{Add, Sub}; + +use crate::Config; + +#[derive(Copy, Clone, Debug)] +pub(crate) struct Indent { + // Width of the block indent, in characters. Must be a multiple of + // Config::tab_spaces. + pub(crate) block_indent: usize, + // Alignment in characters. + pub(crate) alignment: usize, +} + +// INDENT_BUFFER.len() = 81 +const INDENT_BUFFER_LEN: usize = 80; +const INDENT_BUFFER: &str = + "\n "; + +impl Indent { + pub(crate) fn new(block_indent: usize, alignment: usize) -> Indent { + Indent { + block_indent, + alignment, + } + } + + pub(crate) fn from_width(config: &Config, width: usize) -> Indent { + if config.hard_tabs() { + let tab_num = width / config.tab_spaces(); + let alignment = width % config.tab_spaces(); + Indent::new(config.tab_spaces() * tab_num, alignment) + } else { + Indent::new(width, 0) + } + } + + pub(crate) fn empty() -> Indent { + Indent::new(0, 0) + } + + pub(crate) fn block_only(&self) -> Indent { + Indent { + block_indent: self.block_indent, + alignment: 0, + } + } + + pub(crate) fn block_indent(mut self, config: &Config) -> Indent { + self.block_indent += config.tab_spaces(); + self + } + + pub(crate) fn block_unindent(mut self, config: &Config) -> Indent { + if self.block_indent < config.tab_spaces() { + Indent::new(self.block_indent, 0) + } else { + self.block_indent -= config.tab_spaces(); + self + } + } + + pub(crate) fn width(&self) -> usize { + self.block_indent + self.alignment + } + + pub(crate) fn to_string(&self, config: &Config) -> Cow<'static, str> { + self.to_string_inner(config, 1) + } + + pub(crate) fn to_string_with_newline(&self, config: &Config) -> Cow<'static, str> { + self.to_string_inner(config, 0) + } + + fn to_string_inner(&self, config: &Config, offset: usize) -> Cow<'static, str> { + let (num_tabs, num_spaces) = if config.hard_tabs() { + (self.block_indent / config.tab_spaces(), self.alignment) + } else { + (0, self.width()) + }; + let num_chars = num_tabs + num_spaces; + if num_tabs == 0 && num_chars + offset <= INDENT_BUFFER_LEN { + Cow::from(&INDENT_BUFFER[offset..=num_chars]) + } else { + let mut indent = String::with_capacity(num_chars + if offset == 0 { 1 } else { 0 }); + if offset == 0 { + indent.push('\n'); + } + for _ in 0..num_tabs { + indent.push('\t') + } + for _ in 0..num_spaces { + indent.push(' ') + } + Cow::from(indent) + } + } +} + +impl Add for Indent { + type Output = Indent; + + fn add(self, rhs: Indent) -> Indent { + Indent { + block_indent: self.block_indent + rhs.block_indent, + alignment: self.alignment + rhs.alignment, + } + } +} + +impl Sub for Indent { + type Output = Indent; + + fn sub(self, rhs: Indent) -> Indent { + Indent::new( + self.block_indent - rhs.block_indent, + self.alignment - rhs.alignment, + ) + } +} + +impl Add for Indent { + type Output = Indent; + + fn add(self, rhs: usize) -> Indent { + Indent::new(self.block_indent, self.alignment + rhs) + } +} + +impl Sub for Indent { + type Output = Indent; + + fn sub(self, rhs: usize) -> Indent { + Indent::new(self.block_indent, self.alignment - rhs) + } +} + +// 8096 is close enough to infinite for rustfmt. +const INFINITE_SHAPE_WIDTH: usize = 8096; + +#[derive(Copy, Clone, Debug)] +pub(crate) struct Shape { + pub(crate) width: usize, + // The current indentation of code. + pub(crate) indent: Indent, + // Indentation + any already emitted text on the first line of the current + // statement. + pub(crate) offset: usize, +} + +impl Shape { + /// `indent` is the indentation of the first line. The next lines + /// should begin with at least `indent` spaces (except backwards + /// indentation). The first line should not begin with indentation. + /// `width` is the maximum number of characters on the last line + /// (excluding `indent`). The width of other lines is not limited by + /// `width`. + /// Note that in reality, we sometimes use width for lines other than the + /// last (i.e., we are conservative). + // .......*-------* + // | | + // | *-* + // *-----| + // |<------------>| max width + // |<---->| indent + // |<--->| width + pub(crate) fn legacy(width: usize, indent: Indent) -> Shape { + Shape { + width, + indent, + offset: indent.alignment, + } + } + + pub(crate) fn indented(indent: Indent, config: &Config) -> Shape { + Shape { + width: config.max_width().saturating_sub(indent.width()), + indent, + offset: indent.alignment, + } + } + + pub(crate) fn with_max_width(&self, config: &Config) -> Shape { + Shape { + width: config.max_width().saturating_sub(self.indent.width()), + ..*self + } + } + + pub(crate) fn visual_indent(&self, extra_width: usize) -> Shape { + let alignment = self.offset + extra_width; + Shape { + width: self.width, + indent: Indent::new(self.indent.block_indent, alignment), + offset: alignment, + } + } + + pub(crate) fn block_indent(&self, extra_width: usize) -> Shape { + if self.indent.alignment == 0 { + Shape { + width: self.width, + indent: Indent::new(self.indent.block_indent + extra_width, 0), + offset: 0, + } + } else { + Shape { + width: self.width, + indent: self.indent + extra_width, + offset: self.indent.alignment + extra_width, + } + } + } + + pub(crate) fn block_left(&self, width: usize) -> Option { + self.block_indent(width).sub_width(width) + } + + pub(crate) fn add_offset(&self, extra_width: usize) -> Shape { + Shape { + offset: self.offset + extra_width, + ..*self + } + } + + pub(crate) fn block(&self) -> Shape { + Shape { + indent: self.indent.block_only(), + ..*self + } + } + + pub(crate) fn saturating_sub_width(&self, width: usize) -> Shape { + self.sub_width(width).unwrap_or(Shape { width: 0, ..*self }) + } + + pub(crate) fn sub_width(&self, width: usize) -> Option { + Some(Shape { + width: self.width.checked_sub(width)?, + ..*self + }) + } + + pub(crate) fn shrink_left(&self, width: usize) -> Option { + Some(Shape { + width: self.width.checked_sub(width)?, + indent: self.indent + width, + offset: self.offset + width, + }) + } + + pub(crate) fn offset_left(&self, width: usize) -> Option { + self.add_offset(width).sub_width(width) + } + + pub(crate) fn used_width(&self) -> usize { + self.indent.block_indent + self.offset + } + + pub(crate) fn rhs_overhead(&self, config: &Config) -> usize { + config + .max_width() + .saturating_sub(self.used_width() + self.width) + } + + pub(crate) fn comment(&self, config: &Config) -> Shape { + let width = min( + self.width, + config.comment_width().saturating_sub(self.indent.width()), + ); + Shape { width, ..*self } + } + + pub(crate) fn to_string_with_newline(&self, config: &Config) -> Cow<'static, str> { + let mut offset_indent = self.indent; + offset_indent.alignment = self.offset; + offset_indent.to_string_inner(config, 0) + } + + /// Creates a `Shape` with a virtually infinite width. + pub(crate) fn infinite_width(&self) -> Shape { + Shape { + width: INFINITE_SHAPE_WIDTH, + ..*self + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn indent_add_sub() { + let indent = Indent::new(4, 8) + Indent::new(8, 12); + assert_eq!(12, indent.block_indent); + assert_eq!(20, indent.alignment); + + let indent = indent - Indent::new(4, 4); + assert_eq!(8, indent.block_indent); + assert_eq!(16, indent.alignment); + } + + #[test] + fn indent_add_sub_alignment() { + let indent = Indent::new(4, 8) + 4; + assert_eq!(4, indent.block_indent); + assert_eq!(12, indent.alignment); + + let indent = indent - 4; + assert_eq!(4, indent.block_indent); + assert_eq!(8, indent.alignment); + } + + #[test] + fn indent_to_string_spaces() { + let config = Config::default(); + let indent = Indent::new(4, 8); + + // 12 spaces + assert_eq!(" ", indent.to_string(&config)); + } + + #[test] + fn indent_to_string_hard_tabs() { + let mut config = Config::default(); + config.set().hard_tabs(true); + let indent = Indent::new(8, 4); + + // 2 tabs + 4 spaces + assert_eq!("\t\t ", indent.to_string(&config)); + } + + #[test] + fn shape_visual_indent() { + let config = Config::default(); + let indent = Indent::new(4, 8); + let shape = Shape::legacy(config.max_width(), indent); + let shape = shape.visual_indent(20); + + assert_eq!(config.max_width(), shape.width); + assert_eq!(4, shape.indent.block_indent); + assert_eq!(28, shape.indent.alignment); + assert_eq!(28, shape.offset); + } + + #[test] + fn shape_block_indent_without_alignment() { + let config = Config::default(); + let indent = Indent::new(4, 0); + let shape = Shape::legacy(config.max_width(), indent); + let shape = shape.block_indent(20); + + assert_eq!(config.max_width(), shape.width); + assert_eq!(24, shape.indent.block_indent); + assert_eq!(0, shape.indent.alignment); + assert_eq!(0, shape.offset); + } + + #[test] + fn shape_block_indent_with_alignment() { + let config = Config::default(); + let indent = Indent::new(4, 8); + let shape = Shape::legacy(config.max_width(), indent); + let shape = shape.block_indent(20); + + assert_eq!(config.max_width(), shape.width); + assert_eq!(4, shape.indent.block_indent); + assert_eq!(28, shape.indent.alignment); + assert_eq!(28, shape.offset); + } +} diff --git a/src/tools/rustfmt/src/skip.rs b/src/tools/rustfmt/src/skip.rs new file mode 100644 index 0000000000..6c500635a9 --- /dev/null +++ b/src/tools/rustfmt/src/skip.rs @@ -0,0 +1,76 @@ +//! Module that contains skip related stuffs. + +use rustc_ast::ast; +use rustc_ast_pretty::pprust; + +/// Take care of skip name stack. You can update it by attributes slice or +/// by other context. Query this context to know if you need skip a block. +#[derive(Default, Clone)] +pub(crate) struct SkipContext { + macros: Vec, + attributes: Vec, +} + +impl SkipContext { + pub(crate) fn update_with_attrs(&mut self, attrs: &[ast::Attribute]) { + self.macros.append(&mut get_skip_names("macros", attrs)); + self.attributes + .append(&mut get_skip_names("attributes", attrs)); + } + + pub(crate) fn update(&mut self, mut other: SkipContext) { + self.macros.append(&mut other.macros); + self.attributes.append(&mut other.attributes); + } + + pub(crate) fn skip_macro(&self, name: &str) -> bool { + self.macros.iter().any(|n| n == name) + } + + pub(crate) fn skip_attribute(&self, name: &str) -> bool { + self.attributes.iter().any(|n| n == name) + } +} + +static RUSTFMT: &'static str = "rustfmt"; +static SKIP: &'static str = "skip"; + +/// Say if you're playing with `rustfmt`'s skip attribute +pub(crate) fn is_skip_attr(segments: &[ast::PathSegment]) -> bool { + if segments.len() < 2 || segments[0].ident.to_string() != RUSTFMT { + return false; + } + match segments.len() { + 2 => segments[1].ident.to_string() == SKIP, + 3 => { + segments[1].ident.to_string() == SKIP + && ["macros", "attributes"] + .iter() + .any(|&n| n == &pprust::path_segment_to_string(&segments[2])) + } + _ => false, + } +} + +fn get_skip_names(kind: &str, attrs: &[ast::Attribute]) -> Vec { + let mut skip_names = vec![]; + let path = format!("{}::{}::{}", RUSTFMT, SKIP, kind); + for attr in attrs { + // rustc_ast::ast::Path is implemented partialEq + // but it is designed for segments.len() == 1 + if let ast::AttrKind::Normal(attr_item, _) = &attr.kind { + if pprust::path_to_string(&attr_item.path) != path { + continue; + } + } + + if let Some(list) = attr.meta_item_list() { + for nested_meta_item in list { + if let Some(name) = nested_meta_item.ident() { + skip_names.push(name.to_string()); + } + } + } + } + skip_names +} diff --git a/src/tools/rustfmt/src/source_file.rs b/src/tools/rustfmt/src/source_file.rs new file mode 100644 index 0000000000..1d22c25d92 --- /dev/null +++ b/src/tools/rustfmt/src/source_file.rs @@ -0,0 +1,104 @@ +use std::fs; +use std::io::{self, Write}; +use std::path::Path; + +use crate::config::FileName; +use crate::emitter::{self, Emitter}; +use crate::syntux::session::ParseSess; +use crate::NewlineStyle; + +#[cfg(test)] +use crate::config::Config; +#[cfg(test)] +use crate::create_emitter; +#[cfg(test)] +use crate::formatting::FileRecord; +use std::rc::Rc; + +// Append a newline to the end of each file. +pub(crate) fn append_newline(s: &mut String) { + s.push_str("\n"); +} + +#[cfg(test)] +pub(crate) fn write_all_files( + source_file: &[FileRecord], + out: &mut T, + config: &Config, +) -> Result<(), io::Error> +where + T: Write, +{ + let mut emitter = create_emitter(config); + + emitter.emit_header(out)?; + for &(ref filename, ref text) in source_file { + write_file( + None, + filename, + text, + out, + &mut *emitter, + config.newline_style(), + )?; + } + emitter.emit_footer(out)?; + + Ok(()) +} + +pub(crate) fn write_file( + parse_sess: Option<&ParseSess>, + filename: &FileName, + formatted_text: &str, + out: &mut T, + emitter: &mut dyn Emitter, + newline_style: NewlineStyle, +) -> Result +where + T: Write, +{ + fn ensure_real_path(filename: &FileName) -> &Path { + match *filename { + FileName::Real(ref path) => path, + _ => panic!("cannot format `{}` and emit to files", filename), + } + } + + impl From<&FileName> for rustc_span::FileName { + fn from(filename: &FileName) -> rustc_span::FileName { + match filename { + FileName::Real(path) => { + rustc_span::FileName::Real(rustc_span::RealFileName::Named(path.to_owned())) + } + FileName::Stdin => rustc_span::FileName::Custom("stdin".to_owned()), + } + } + } + + // SourceFile's in the SourceMap will always have Unix-style line endings + // See: https://github.com/rust-lang/rustfmt/issues/3850 + // So if the user has explicitly overridden the rustfmt `newline_style` + // config and `filename` is FileName::Real, then we must check the file system + // to get the original file value in order to detect newline_style conflicts. + // Otherwise, parse session is around (cfg(not(test))) and newline_style has been + // left as the default value, then try getting source from the parse session + // source map instead of hitting the file system. This also supports getting + // original text for `FileName::Stdin`. + let original_text = if newline_style != NewlineStyle::Auto && *filename != FileName::Stdin { + Rc::new(fs::read_to_string(ensure_real_path(filename))?) + } else { + match parse_sess.and_then(|sess| sess.get_original_snippet(filename)) { + Some(ori) => ori, + None => Rc::new(fs::read_to_string(ensure_real_path(filename))?), + } + }; + + let formatted_file = emitter::FormattedFile { + filename, + original_text: original_text.as_str(), + formatted_text, + }; + + emitter.emit_formatted_file(out, formatted_file) +} diff --git a/src/tools/rustfmt/src/source_map.rs b/src/tools/rustfmt/src/source_map.rs new file mode 100644 index 0000000000..76e0d24cf1 --- /dev/null +++ b/src/tools/rustfmt/src/source_map.rs @@ -0,0 +1,82 @@ +//! This module contains utilities that work with the `SourceMap` from `libsyntax`/`syntex_syntax`. +//! This includes extension traits and methods for looking up spans and line ranges for AST nodes. + +use rustc_span::{BytePos, Span}; + +use crate::comment::FindUncommented; +use crate::config::file_lines::LineRange; +use crate::visitor::SnippetProvider; + +pub(crate) trait SpanUtils { + fn span_after(&self, original: Span, needle: &str) -> BytePos; + fn span_after_last(&self, original: Span, needle: &str) -> BytePos; + fn span_before(&self, original: Span, needle: &str) -> BytePos; + fn span_before_last(&self, original: Span, needle: &str) -> BytePos; + fn opt_span_after(&self, original: Span, needle: &str) -> Option; + fn opt_span_before(&self, original: Span, needle: &str) -> Option; +} + +pub(crate) trait LineRangeUtils { + /// Returns the `LineRange` that corresponds to `span` in `self`. + /// + /// # Panics + /// + /// Panics if `span` crosses a file boundary, which shouldn't happen. + fn lookup_line_range(&self, span: Span) -> LineRange; +} + +impl SpanUtils for SnippetProvider { + fn span_after(&self, original: Span, needle: &str) -> BytePos { + self.opt_span_after(original, needle).unwrap_or_else(|| { + panic!( + "bad span: `{}`: `{}`", + needle, + self.span_to_snippet(original).unwrap() + ) + }) + } + + fn span_after_last(&self, original: Span, needle: &str) -> BytePos { + let snippet = self.span_to_snippet(original).unwrap(); + let mut offset = 0; + + while let Some(additional_offset) = snippet[offset..].find_uncommented(needle) { + offset += additional_offset + needle.len(); + } + + original.lo() + BytePos(offset as u32) + } + + fn span_before(&self, original: Span, needle: &str) -> BytePos { + self.opt_span_before(original, needle).unwrap_or_else(|| { + panic!( + "bad span: `{}`: `{}`", + needle, + self.span_to_snippet(original).unwrap() + ) + }) + } + + fn span_before_last(&self, original: Span, needle: &str) -> BytePos { + let snippet = self.span_to_snippet(original).unwrap(); + let mut offset = 0; + + while let Some(additional_offset) = snippet[offset..].find_uncommented(needle) { + offset += additional_offset + needle.len(); + } + + original.lo() + BytePos(offset as u32 - 1) + } + + fn opt_span_after(&self, original: Span, needle: &str) -> Option { + self.opt_span_before(original, needle) + .map(|bytepos| bytepos + BytePos(needle.len() as u32)) + } + + fn opt_span_before(&self, original: Span, needle: &str) -> Option { + let snippet = self.span_to_snippet(original)?; + let offset = snippet.find_uncommented(needle)?; + + Some(original.lo() + BytePos(offset as u32)) + } +} diff --git a/src/tools/rustfmt/src/spanned.rs b/src/tools/rustfmt/src/spanned.rs new file mode 100644 index 0000000000..1f6d0023e6 --- /dev/null +++ b/src/tools/rustfmt/src/spanned.rs @@ -0,0 +1,206 @@ +use std::cmp::max; + +use rustc_ast::{ast, ptr}; +use rustc_span::{source_map, Span}; + +use crate::macros::MacroArg; +use crate::utils::{mk_sp, outer_attributes}; + +/// Spanned returns a span including attributes, if available. +pub(crate) trait Spanned { + fn span(&self) -> Span; +} + +impl Spanned for ptr::P { + fn span(&self) -> Span { + (**self).span() + } +} + +impl Spanned for source_map::Spanned { + fn span(&self) -> Span { + self.span + } +} + +macro_rules! span_with_attrs_lo_hi { + ($this:ident, $lo:expr, $hi:expr) => {{ + let attrs = outer_attributes(&$this.attrs); + if attrs.is_empty() { + mk_sp($lo, $hi) + } else { + mk_sp(attrs[0].span.lo(), $hi) + } + }}; +} + +macro_rules! span_with_attrs { + ($this:ident) => { + span_with_attrs_lo_hi!($this, $this.span.lo(), $this.span.hi()) + }; +} + +macro_rules! implement_spanned { + ($this:ty) => { + impl Spanned for $this { + fn span(&self) -> Span { + span_with_attrs!(self) + } + } + }; +} + +// Implement `Spanned` for structs with `attrs` field. +implement_spanned!(ast::AssocItem); +implement_spanned!(ast::Expr); +implement_spanned!(ast::Field); +implement_spanned!(ast::ForeignItem); +implement_spanned!(ast::Item); +implement_spanned!(ast::Local); + +impl Spanned for ast::Stmt { + fn span(&self) -> Span { + match self.kind { + ast::StmtKind::Local(ref local) => mk_sp(local.span().lo(), self.span.hi()), + ast::StmtKind::Item(ref item) => mk_sp(item.span().lo(), self.span.hi()), + ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => { + mk_sp(expr.span().lo(), self.span.hi()) + } + ast::StmtKind::MacCall(ref mac_stmt) => { + if mac_stmt.attrs.is_empty() { + self.span + } else { + mk_sp(mac_stmt.attrs[0].span.lo(), self.span.hi()) + } + } + ast::StmtKind::Empty => self.span, + } + } +} + +impl Spanned for ast::Pat { + fn span(&self) -> Span { + self.span + } +} + +impl Spanned for ast::Ty { + fn span(&self) -> Span { + self.span + } +} + +impl Spanned for ast::Arm { + fn span(&self) -> Span { + let lo = if self.attrs.is_empty() { + self.pat.span.lo() + } else { + self.attrs[0].span.lo() + }; + span_with_attrs_lo_hi!(self, lo, self.body.span.hi()) + } +} + +impl Spanned for ast::Param { + fn span(&self) -> Span { + if crate::items::is_named_param(self) { + mk_sp(self.pat.span.lo(), self.ty.span.hi()) + } else { + self.ty.span + } + } +} + +impl Spanned for ast::GenericParam { + fn span(&self) -> Span { + let lo = if let ast::GenericParamKind::Const { + ty: _, + kw_span, + default: _, + } = self.kind + { + kw_span.lo() + } else if self.attrs.is_empty() { + self.ident.span.lo() + } else { + self.attrs[0].span.lo() + }; + let hi = if self.bounds.is_empty() { + self.ident.span.hi() + } else { + self.bounds.last().unwrap().span().hi() + }; + let ty_hi = if let ast::GenericParamKind::Type { + default: Some(ref ty), + } + | ast::GenericParamKind::Const { ref ty, .. } = self.kind + { + ty.span().hi() + } else { + hi + }; + mk_sp(lo, max(hi, ty_hi)) + } +} + +impl Spanned for ast::StructField { + fn span(&self) -> Span { + span_with_attrs_lo_hi!(self, self.span.lo(), self.ty.span.hi()) + } +} + +impl Spanned for ast::WherePredicate { + fn span(&self) -> Span { + match *self { + ast::WherePredicate::BoundPredicate(ref p) => p.span, + ast::WherePredicate::RegionPredicate(ref p) => p.span, + ast::WherePredicate::EqPredicate(ref p) => p.span, + } + } +} + +impl Spanned for ast::FnRetTy { + fn span(&self) -> Span { + match *self { + ast::FnRetTy::Default(span) => span, + ast::FnRetTy::Ty(ref ty) => ty.span, + } + } +} + +impl Spanned for ast::GenericArg { + fn span(&self) -> Span { + match *self { + ast::GenericArg::Lifetime(ref lt) => lt.ident.span, + ast::GenericArg::Type(ref ty) => ty.span(), + ast::GenericArg::Const(ref _const) => _const.value.span(), + } + } +} + +impl Spanned for ast::GenericBound { + fn span(&self) -> Span { + match *self { + ast::GenericBound::Trait(ref ptr, _) => ptr.span, + ast::GenericBound::Outlives(ref l) => l.ident.span, + } + } +} + +impl Spanned for MacroArg { + fn span(&self) -> Span { + match *self { + MacroArg::Expr(ref expr) => expr.span(), + MacroArg::Ty(ref ty) => ty.span(), + MacroArg::Pat(ref pat) => pat.span(), + MacroArg::Item(ref item) => item.span(), + MacroArg::Keyword(_, span) => span, + } + } +} + +impl Spanned for ast::NestedMetaItem { + fn span(&self) -> Span { + self.span() + } +} diff --git a/src/tools/rustfmt/src/stmt.rs b/src/tools/rustfmt/src/stmt.rs new file mode 100644 index 0000000000..0b3854425e --- /dev/null +++ b/src/tools/rustfmt/src/stmt.rs @@ -0,0 +1,116 @@ +use rustc_ast::ast; +use rustc_span::Span; + +use crate::comment::recover_comment_removed; +use crate::config::Version; +use crate::expr::{format_expr, ExprType}; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::source_map::LineRangeUtils; +use crate::spanned::Spanned; +use crate::utils::semicolon_for_stmt; + +pub(crate) struct Stmt<'a> { + inner: &'a ast::Stmt, + is_last: bool, +} + +impl<'a> Spanned for Stmt<'a> { + fn span(&self) -> Span { + self.inner.span() + } +} + +impl<'a> Stmt<'a> { + pub(crate) fn as_ast_node(&self) -> &ast::Stmt { + self.inner + } + + pub(crate) fn to_item(&self) -> Option<&ast::Item> { + match self.inner.kind { + ast::StmtKind::Item(ref item) => Some(&**item), + _ => None, + } + } + + pub(crate) fn from_ast_node(inner: &'a ast::Stmt, is_last: bool) -> Self { + Stmt { inner, is_last } + } + + pub(crate) fn from_ast_nodes(iter: I) -> Vec + where + I: Iterator, + { + let mut result = vec![]; + let mut iter = iter.peekable(); + while iter.peek().is_some() { + result.push(Stmt { + inner: iter.next().unwrap(), + is_last: iter.peek().is_none(), + }) + } + result + } + + pub(crate) fn is_empty(&self) -> bool { + matches!(self.inner.kind, ast::StmtKind::Empty) + } + + fn is_last_expr(&self) -> bool { + if !self.is_last { + return false; + } + + match self.as_ast_node().kind { + ast::StmtKind::Expr(ref expr) => match expr.kind { + ast::ExprKind::Ret(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Break(..) => { + false + } + _ => true, + }, + _ => false, + } + } +} + +impl<'a> Rewrite for Stmt<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let expr_type = if context.config.version() == Version::Two && self.is_last_expr() { + ExprType::SubExpression + } else { + ExprType::Statement + }; + format_stmt(context, shape, self.as_ast_node(), expr_type) + } +} + +impl Rewrite for ast::Stmt { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + format_stmt(context, shape, self, ExprType::Statement) + } +} + +fn format_stmt( + context: &RewriteContext<'_>, + shape: Shape, + stmt: &ast::Stmt, + expr_type: ExprType, +) -> Option { + skip_out_of_file_lines_range!(context, stmt.span()); + + let result = match stmt.kind { + ast::StmtKind::Local(ref local) => local.rewrite(context, shape), + ast::StmtKind::Expr(ref ex) | ast::StmtKind::Semi(ref ex) => { + let suffix = if semicolon_for_stmt(context, stmt) { + ";" + } else { + "" + }; + + let shape = shape.sub_width(suffix.len())?; + format_expr(ex, expr_type, context, shape).map(|s| s + suffix) + } + ast::StmtKind::MacCall(..) | ast::StmtKind::Item(..) | ast::StmtKind::Empty => None, + }; + result.and_then(|res| recover_comment_removed(res, stmt.span(), context)) +} diff --git a/src/tools/rustfmt/src/string.rs b/src/tools/rustfmt/src/string.rs new file mode 100644 index 0000000000..080c4f1778 --- /dev/null +++ b/src/tools/rustfmt/src/string.rs @@ -0,0 +1,691 @@ +// Format string literals. + +use regex::Regex; +use unicode_categories::UnicodeCategories; +use unicode_segmentation::UnicodeSegmentation; + +use crate::config::Config; +use crate::shape::Shape; +use crate::utils::{unicode_str_width, wrap_str}; + +const MIN_STRING: usize = 10; + +/// Describes the layout of a piece of text. +pub(crate) struct StringFormat<'a> { + /// The opening sequence of characters for the piece of text + pub(crate) opener: &'a str, + /// The closing sequence of characters for the piece of text + pub(crate) closer: &'a str, + /// The opening sequence of characters for a line + pub(crate) line_start: &'a str, + /// The closing sequence of characters for a line + pub(crate) line_end: &'a str, + /// The allocated box to fit the text into + pub(crate) shape: Shape, + /// Trim trailing whitespaces + pub(crate) trim_end: bool, + pub(crate) config: &'a Config, +} + +impl<'a> StringFormat<'a> { + pub(crate) fn new(shape: Shape, config: &'a Config) -> StringFormat<'a> { + StringFormat { + opener: "\"", + closer: "\"", + line_start: " ", + line_end: "\\", + shape, + trim_end: false, + config, + } + } + + /// Returns the maximum number of graphemes that is possible on a line while taking the + /// indentation into account. + /// + /// If we cannot put at least a single character per line, the rewrite won't succeed. + fn max_width_with_indent(&self) -> Option { + Some( + self.shape + .width + .checked_sub(self.opener.len() + self.line_end.len() + 1)? + + 1, + ) + } + + /// Like max_width_with_indent but the indentation is not subtracted. + /// This allows to fit more graphemes from the string on a line when + /// SnippetState::EndWithLineFeed. + fn max_width_without_indent(&self) -> Option { + Some(self.config.max_width().checked_sub(self.line_end.len())?) + } +} + +pub(crate) fn rewrite_string<'a>( + orig: &str, + fmt: &StringFormat<'a>, + newline_max_chars: usize, +) -> Option { + let max_width_with_indent = fmt.max_width_with_indent()?; + let max_width_without_indent = fmt.max_width_without_indent()?; + let indent_with_newline = fmt.shape.indent.to_string_with_newline(fmt.config); + let indent_without_newline = fmt.shape.indent.to_string(fmt.config); + + // Strip line breaks. + // With this regex applied, all remaining whitespaces are significant + let strip_line_breaks_re = Regex::new(r"([^\\](\\\\)*)\\[\n\r][[:space:]]*").unwrap(); + let stripped_str = strip_line_breaks_re.replace_all(orig, "$1"); + + let graphemes = UnicodeSegmentation::graphemes(&*stripped_str, false).collect::>(); + + // `cur_start` is the position in `orig` of the start of the current line. + let mut cur_start = 0; + let mut result = String::with_capacity( + stripped_str + .len() + .checked_next_power_of_two() + .unwrap_or(usize::max_value()), + ); + result.push_str(fmt.opener); + + // Snip a line at a time from `stripped_str` until it is used up. Push the snippet + // onto result. + let mut cur_max_width = max_width_with_indent; + let is_bareline_ok = fmt.line_start.is_empty() || is_whitespace(fmt.line_start); + loop { + // All the input starting at cur_start fits on the current line + if graphemes_width(&graphemes[cur_start..]) <= cur_max_width { + for (i, grapheme) in graphemes[cur_start..].iter().enumerate() { + if is_new_line(grapheme) { + // take care of blank lines + result = trim_end_but_line_feed(fmt.trim_end, result); + result.push_str("\n"); + if !is_bareline_ok && cur_start + i + 1 < graphemes.len() { + result.push_str(&indent_without_newline); + result.push_str(fmt.line_start); + } + } else { + result.push_str(grapheme); + } + } + result = trim_end_but_line_feed(fmt.trim_end, result); + break; + } + + // The input starting at cur_start needs to be broken + match break_string( + cur_max_width, + fmt.trim_end, + fmt.line_end, + &graphemes[cur_start..], + ) { + SnippetState::LineEnd(line, len) => { + result.push_str(&line); + result.push_str(fmt.line_end); + result.push_str(&indent_with_newline); + result.push_str(fmt.line_start); + cur_max_width = newline_max_chars; + cur_start += len; + } + SnippetState::EndWithLineFeed(line, len) => { + if line == "\n" && fmt.trim_end { + result = result.trim_end().to_string(); + } + result.push_str(&line); + if is_bareline_ok { + // the next line can benefit from the full width + cur_max_width = max_width_without_indent; + } else { + result.push_str(&indent_without_newline); + result.push_str(fmt.line_start); + cur_max_width = max_width_with_indent; + } + cur_start += len; + } + SnippetState::EndOfInput(line) => { + result.push_str(&line); + break; + } + } + } + + result.push_str(fmt.closer); + wrap_str(result, fmt.config.max_width(), fmt.shape) +} + +/// Returns the index to the end of the URL if the split at index of the given string includes an +/// URL or alike. Otherwise, returns `None`. +fn detect_url(s: &[&str], index: usize) -> Option { + let start = match s[..=index].iter().rposition(|g| is_whitespace(g)) { + Some(pos) => pos + 1, + None => 0, + }; + // 8 = minimum length for a string to contain a URL + if s.len() < start + 8 { + return None; + } + let split = s[start..].concat(); + if split.contains("https://") + || split.contains("http://") + || split.contains("ftp://") + || split.contains("file://") + { + match s[index..].iter().position(|g| is_whitespace(g)) { + Some(pos) => Some(index + pos - 1), + None => Some(s.len() - 1), + } + } else { + None + } +} + +/// Trims whitespaces to the right except for the line feed character. +fn trim_end_but_line_feed(trim_end: bool, result: String) -> String { + let whitespace_except_line_feed = |c: char| c.is_whitespace() && c != '\n'; + if trim_end && result.ends_with(whitespace_except_line_feed) { + result + .trim_end_matches(whitespace_except_line_feed) + .to_string() + } else { + result + } +} + +/// Result of breaking a string so it fits in a line and the state it ended in. +/// The state informs about what to do with the snippet and how to continue the breaking process. +#[derive(Debug, PartialEq)] +enum SnippetState { + /// The input could not be broken and so rewriting the string is finished. + EndOfInput(String), + /// The input could be broken and the returned snippet should be ended with a + /// `[StringFormat::line_end]`. The next snippet needs to be indented. + /// + /// The returned string is the line to print out and the number is the length that got read in + /// the text being rewritten. That length may be greater than the returned string if trailing + /// whitespaces got trimmed. + LineEnd(String, usize), + /// The input could be broken but a newline is present that cannot be trimmed. The next snippet + /// to be rewritten *could* use more width than what is specified by the given shape. For + /// example with a multiline string, the next snippet does not need to be indented, allowing + /// more characters to be fit within a line. + /// + /// The returned string is the line to print out and the number is the length that got read in + /// the text being rewritten. + EndWithLineFeed(String, usize), +} + +fn not_whitespace_except_line_feed(g: &str) -> bool { + is_new_line(g) || !is_whitespace(g) +} + +/// Break the input string at a boundary character around the offset `max_width`. A boundary +/// character is either a punctuation or a whitespace. +/// FIXME(issue#3281): We must follow UAX#14 algorithm instead of this. +fn break_string(max_width: usize, trim_end: bool, line_end: &str, input: &[&str]) -> SnippetState { + let break_at = |index /* grapheme at index is included */| { + // Take in any whitespaces to the left/right of `input[index]` while + // preserving line feeds + let index_minus_ws = input[0..=index] + .iter() + .rposition(|grapheme| not_whitespace_except_line_feed(grapheme)) + .unwrap_or(index); + // Take into account newlines occurring in input[0..=index], i.e., the possible next new + // line. If there is one, then text after it could be rewritten in a way that the available + // space is fully used. + for (i, grapheme) in input[0..=index].iter().enumerate() { + if is_new_line(grapheme) { + if i <= index_minus_ws { + let mut line = &input[0..i].concat()[..]; + if trim_end { + line = line.trim_end(); + } + return SnippetState::EndWithLineFeed(format!("{}\n", line), i + 1); + } + break; + } + } + + let mut index_plus_ws = index; + for (i, grapheme) in input[index + 1..].iter().enumerate() { + if !trim_end && is_new_line(grapheme) { + return SnippetState::EndWithLineFeed( + input[0..=index + 1 + i].concat(), + index + 2 + i, + ); + } else if not_whitespace_except_line_feed(grapheme) { + index_plus_ws = index + i; + break; + } + } + + if trim_end { + SnippetState::LineEnd(input[0..=index_minus_ws].concat(), index_plus_ws + 1) + } else { + SnippetState::LineEnd(input[0..=index_plus_ws].concat(), index_plus_ws + 1) + } + }; + + // find a first index where the unicode width of input[0..x] become > max_width + let max_width_index_in_input = { + let mut cur_width = 0; + let mut cur_index = 0; + for (i, grapheme) in input.iter().enumerate() { + cur_width += unicode_str_width(grapheme); + cur_index = i; + if cur_width > max_width { + break; + } + } + cur_index + }; + + // Find the position in input for breaking the string + if line_end.is_empty() + && trim_end + && !is_whitespace(input[max_width_index_in_input - 1]) + && is_whitespace(input[max_width_index_in_input]) + { + // At a breaking point already + // The line won't invalidate the rewriting because: + // - no extra space needed for the line_end character + // - extra whitespaces to the right can be trimmed + return break_at(max_width_index_in_input - 1); + } + if let Some(url_index_end) = detect_url(input, max_width_index_in_input) { + let index_plus_ws = url_index_end + + input[url_index_end..] + .iter() + .skip(1) + .position(|grapheme| not_whitespace_except_line_feed(grapheme)) + .unwrap_or(0); + return if trim_end { + SnippetState::LineEnd(input[..=url_index_end].concat(), index_plus_ws + 1) + } else { + return SnippetState::LineEnd(input[..=index_plus_ws].concat(), index_plus_ws + 1); + }; + } + + match input[0..max_width_index_in_input] + .iter() + .rposition(|grapheme| is_whitespace(grapheme)) + { + // Found a whitespace and what is on its left side is big enough. + Some(index) if index >= MIN_STRING => break_at(index), + // No whitespace found, try looking for a punctuation instead + _ => match input[0..max_width_index_in_input] + .iter() + .rposition(|grapheme| is_punctuation(grapheme)) + { + // Found a punctuation and what is on its left side is big enough. + Some(index) if index >= MIN_STRING => break_at(index), + // Either no boundary character was found to the left of `input[max_chars]`, or the line + // got too small. We try searching for a boundary character to the right. + _ => match input[max_width_index_in_input..] + .iter() + .position(|grapheme| is_whitespace(grapheme) || is_punctuation(grapheme)) + { + // A boundary was found after the line limit + Some(index) => break_at(max_width_index_in_input + index), + // No boundary to the right, the input cannot be broken + None => SnippetState::EndOfInput(input.concat()), + }, + }, + } +} + +fn is_new_line(grapheme: &str) -> bool { + let bytes = grapheme.as_bytes(); + bytes.starts_with(b"\n") || bytes.starts_with(b"\r\n") +} + +fn is_whitespace(grapheme: &str) -> bool { + grapheme.chars().all(char::is_whitespace) +} + +fn is_punctuation(grapheme: &str) -> bool { + grapheme + .chars() + .all(UnicodeCategories::is_punctuation_other) +} + +fn graphemes_width(graphemes: &[&str]) -> usize { + graphemes.iter().map(|s| unicode_str_width(s)).sum() +} + +#[cfg(test)] +mod test { + use super::{break_string, detect_url, rewrite_string, SnippetState, StringFormat}; + use crate::config::Config; + use crate::shape::{Indent, Shape}; + use unicode_segmentation::UnicodeSegmentation; + + #[test] + fn issue343() { + let config = Default::default(); + let fmt = StringFormat::new(Shape::legacy(2, Indent::empty()), &config); + rewrite_string("eq_", &fmt, 2); + } + + #[test] + fn should_break_on_whitespace() { + let string = "Placerat felis. Mauris porta ante sagittis purus."; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!( + break_string(20, false, "", &graphemes[..]), + SnippetState::LineEnd("Placerat felis. ".to_string(), 16) + ); + assert_eq!( + break_string(20, true, "", &graphemes[..]), + SnippetState::LineEnd("Placerat felis.".to_string(), 16) + ); + } + + #[test] + fn should_break_on_punctuation() { + let string = "Placerat_felis._Mauris_porta_ante_sagittis_purus."; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!( + break_string(20, false, "", &graphemes[..]), + SnippetState::LineEnd("Placerat_felis.".to_string(), 15) + ); + } + + #[test] + fn should_break_forward() { + let string = "Venenatis_tellus_vel_tellus. Aliquam aliquam dolor at justo."; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!( + break_string(20, false, "", &graphemes[..]), + SnippetState::LineEnd("Venenatis_tellus_vel_tellus. ".to_string(), 29) + ); + assert_eq!( + break_string(20, true, "", &graphemes[..]), + SnippetState::LineEnd("Venenatis_tellus_vel_tellus.".to_string(), 29) + ); + } + + #[test] + fn nothing_to_break() { + let string = "Venenatis_tellus_vel_tellus"; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!( + break_string(20, false, "", &graphemes[..]), + SnippetState::EndOfInput("Venenatis_tellus_vel_tellus".to_string()) + ); + } + + #[test] + fn significant_whitespaces() { + let string = "Neque in sem. \n Pellentesque tellus augue."; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!( + break_string(15, false, "", &graphemes[..]), + SnippetState::EndWithLineFeed("Neque in sem. \n".to_string(), 20) + ); + assert_eq!( + break_string(25, false, "", &graphemes[..]), + SnippetState::EndWithLineFeed("Neque in sem. \n".to_string(), 20) + ); + + assert_eq!( + break_string(15, true, "", &graphemes[..]), + SnippetState::LineEnd("Neque in sem.".to_string(), 19) + ); + assert_eq!( + break_string(25, true, "", &graphemes[..]), + SnippetState::EndWithLineFeed("Neque in sem.\n".to_string(), 20) + ); + } + + #[test] + fn big_whitespace() { + let string = "Neque in sem. Pellentesque tellus augue."; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!( + break_string(20, false, "", &graphemes[..]), + SnippetState::LineEnd("Neque in sem. ".to_string(), 25) + ); + assert_eq!( + break_string(20, true, "", &graphemes[..]), + SnippetState::LineEnd("Neque in sem.".to_string(), 25) + ); + } + + #[test] + fn newline_in_candidate_line() { + let string = "Nulla\nconsequat erat at massa. Vivamus id mi."; + + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!( + break_string(25, false, "", &graphemes[..]), + SnippetState::EndWithLineFeed("Nulla\n".to_string(), 6) + ); + assert_eq!( + break_string(25, true, "", &graphemes[..]), + SnippetState::EndWithLineFeed("Nulla\n".to_string(), 6) + ); + + let mut config: Config = Default::default(); + config.set().max_width(27); + let fmt = StringFormat::new(Shape::legacy(25, Indent::empty()), &config); + let rewritten_string = rewrite_string(string, &fmt, 27); + assert_eq!( + rewritten_string, + Some("\"Nulla\nconsequat erat at massa. \\\n Vivamus id mi.\"".to_string()) + ); + } + + #[test] + fn last_line_fit_with_trailing_whitespaces() { + let string = "Vivamus id mi. "; + let config: Config = Default::default(); + let mut fmt = StringFormat::new(Shape::legacy(25, Indent::empty()), &config); + + fmt.trim_end = true; + let rewritten_string = rewrite_string(string, &fmt, 25); + assert_eq!(rewritten_string, Some("\"Vivamus id mi.\"".to_string())); + + fmt.trim_end = false; // default value of trim_end + let rewritten_string = rewrite_string(string, &fmt, 25); + assert_eq!(rewritten_string, Some("\"Vivamus id mi. \"".to_string())); + } + + #[test] + fn last_line_fit_with_newline() { + let string = "Vivamus id mi.\nVivamus id mi."; + let config: Config = Default::default(); + let fmt = StringFormat { + opener: "", + closer: "", + line_start: "// ", + line_end: "", + shape: Shape::legacy(100, Indent::from_width(&config, 4)), + trim_end: true, + config: &config, + }; + + let rewritten_string = rewrite_string(string, &fmt, 100); + assert_eq!( + rewritten_string, + Some("Vivamus id mi.\n // Vivamus id mi.".to_string()) + ); + } + + #[test] + fn overflow_in_non_string_content() { + let comment = "Aenean metus.\nVestibulum ac lacus. Vivamus porttitor"; + let config: Config = Default::default(); + let fmt = StringFormat { + opener: "", + closer: "", + line_start: "// ", + line_end: "", + shape: Shape::legacy(30, Indent::from_width(&config, 8)), + trim_end: true, + config: &config, + }; + + assert_eq!( + rewrite_string(comment, &fmt, 30), + Some( + "Aenean metus.\n // Vestibulum ac lacus. Vivamus\n // porttitor" + .to_string() + ) + ); + } + + #[test] + fn overflow_in_non_string_content_with_line_end() { + let comment = "Aenean metus.\nVestibulum ac lacus. Vivamus porttitor"; + let config: Config = Default::default(); + let fmt = StringFormat { + opener: "", + closer: "", + line_start: "// ", + line_end: "@", + shape: Shape::legacy(30, Indent::from_width(&config, 8)), + trim_end: true, + config: &config, + }; + + assert_eq!( + rewrite_string(comment, &fmt, 30), + Some( + "Aenean metus.\n // Vestibulum ac lacus. Vivamus@\n // porttitor" + .to_string() + ) + ); + } + + #[test] + fn blank_line_with_non_empty_line_start() { + let config: Config = Default::default(); + let mut fmt = StringFormat { + opener: "", + closer: "", + line_start: "// ", + line_end: "", + shape: Shape::legacy(30, Indent::from_width(&config, 4)), + trim_end: true, + config: &config, + }; + + let comment = "Aenean metus. Vestibulum\n\nac lacus. Vivamus porttitor"; + assert_eq!( + rewrite_string(comment, &fmt, 30), + Some( + "Aenean metus. Vestibulum\n //\n // ac lacus. Vivamus porttitor".to_string() + ) + ); + + fmt.shape = Shape::legacy(15, Indent::from_width(&config, 4)); + let comment = "Aenean\n\nmetus. Vestibulum ac lacus. Vivamus porttitor"; + assert_eq!( + rewrite_string(comment, &fmt, 15), + Some( + r#"Aenean + // + // metus. Vestibulum + // ac lacus. Vivamus + // porttitor"# + .to_string() + ) + ); + } + + #[test] + fn retain_blank_lines() { + let config: Config = Default::default(); + let fmt = StringFormat { + opener: "", + closer: "", + line_start: "// ", + line_end: "", + shape: Shape::legacy(20, Indent::from_width(&config, 4)), + trim_end: true, + config: &config, + }; + + let comment = "Aenean\n\nmetus. Vestibulum ac lacus.\n\n"; + assert_eq!( + rewrite_string(comment, &fmt, 20), + Some( + "Aenean\n //\n // metus. Vestibulum ac\n // lacus.\n //\n".to_string() + ) + ); + + let comment = "Aenean\n\nmetus. Vestibulum ac lacus.\n"; + assert_eq!( + rewrite_string(comment, &fmt, 20), + Some("Aenean\n //\n // metus. Vestibulum ac\n // lacus.\n".to_string()) + ); + + let comment = "Aenean\n \nmetus. Vestibulum ac lacus."; + assert_eq!( + rewrite_string(comment, &fmt, 20), + Some("Aenean\n //\n // metus. Vestibulum ac\n // lacus.".to_string()) + ); + } + + #[test] + fn boundary_on_edge() { + let config: Config = Default::default(); + let mut fmt = StringFormat { + opener: "", + closer: "", + line_start: "// ", + line_end: "", + shape: Shape::legacy(13, Indent::from_width(&config, 4)), + trim_end: true, + config: &config, + }; + + let comment = "Aenean metus. Vestibulum ac lacus."; + assert_eq!( + rewrite_string(comment, &fmt, 13), + Some("Aenean metus.\n // Vestibulum ac\n // lacus.".to_string()) + ); + + fmt.trim_end = false; + let comment = "Vestibulum ac lacus."; + assert_eq!( + rewrite_string(comment, &fmt, 13), + Some("Vestibulum \n // ac lacus.".to_string()) + ); + + fmt.trim_end = true; + fmt.line_end = "\\"; + let comment = "Vestibulum ac lacus."; + assert_eq!( + rewrite_string(comment, &fmt, 13), + Some("Vestibulum\\\n // ac lacus.".to_string()) + ); + } + + #[test] + fn detect_urls() { + let string = "aaa http://example.org something"; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!(detect_url(&graphemes, 8), Some(21)); + + let string = "https://example.org something"; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!(detect_url(&graphemes, 0), Some(18)); + + let string = "aaa ftp://example.org something"; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!(detect_url(&graphemes, 8), Some(20)); + + let string = "aaa file://example.org something"; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!(detect_url(&graphemes, 8), Some(21)); + + let string = "aaa http not an url"; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!(detect_url(&graphemes, 6), None); + + let string = "aaa file://example.org"; + let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::>(); + assert_eq!(detect_url(&graphemes, 8), Some(21)); + } +} diff --git a/src/tools/rustfmt/src/syntux.rs b/src/tools/rustfmt/src/syntux.rs new file mode 100644 index 0000000000..845576bd8d --- /dev/null +++ b/src/tools/rustfmt/src/syntux.rs @@ -0,0 +1,4 @@ +//! This module defines a thin abstract layer on top of the rustc's parser and syntax libraries. + +pub(crate) mod parser; +pub(crate) mod session; diff --git a/src/tools/rustfmt/src/syntux/parser.rs b/src/tools/rustfmt/src/syntux/parser.rs new file mode 100644 index 0000000000..6b70d83ceb --- /dev/null +++ b/src/tools/rustfmt/src/syntux/parser.rs @@ -0,0 +1,286 @@ +use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::path::{Path, PathBuf}; + +use rustc_ast::ast; +use rustc_ast::token::{DelimToken, TokenKind}; +use rustc_errors::Diagnostic; +use rustc_parse::{ + new_parser_from_file, + parser::{ForceCollect, Parser as RawParser}, +}; +use rustc_span::{sym, symbol::kw, Span}; + +use crate::attr::first_attr_value_str_by_name; +use crate::syntux::session::ParseSess; +use crate::{Config, Input}; + +pub(crate) type DirectoryOwnership = rustc_expand::module::DirectoryOwnership; +pub(crate) type ModulePathSuccess = rustc_expand::module::ModulePathSuccess; + +#[derive(Clone)] +pub(crate) struct Directory { + pub(crate) path: PathBuf, + pub(crate) ownership: DirectoryOwnership, +} + +/// A parser for Rust source code. +pub(crate) struct Parser<'a> { + parser: RawParser<'a>, +} + +/// A builder for the `Parser`. +#[derive(Default)] +pub(crate) struct ParserBuilder<'a> { + config: Option<&'a Config>, + sess: Option<&'a ParseSess>, + input: Option, + directory_ownership: Option, +} + +impl<'a> ParserBuilder<'a> { + pub(crate) fn input(mut self, input: Input) -> ParserBuilder<'a> { + self.input = Some(input); + self + } + + pub(crate) fn sess(mut self, sess: &'a ParseSess) -> ParserBuilder<'a> { + self.sess = Some(sess); + self + } + + pub(crate) fn config(mut self, config: &'a Config) -> ParserBuilder<'a> { + self.config = Some(config); + self + } + + pub(crate) fn directory_ownership( + mut self, + directory_ownership: Option, + ) -> ParserBuilder<'a> { + self.directory_ownership = directory_ownership; + self + } + + pub(crate) fn build(self) -> Result, ParserError> { + let sess = self.sess.ok_or(ParserError::NoParseSess)?; + let input = self.input.ok_or(ParserError::NoInput)?; + + let parser = match Self::parser(sess.inner(), input) { + Ok(p) => p, + Err(db) => { + if let Some(diagnostics) = db { + sess.emit_diagnostics(diagnostics); + return Err(ParserError::ParserCreationError); + } + return Err(ParserError::ParsePanicError); + } + }; + + Ok(Parser { parser }) + } + + fn parser( + sess: &'a rustc_session::parse::ParseSess, + input: Input, + ) -> Result, Option>> { + match input { + Input::File(ref file) => catch_unwind(AssertUnwindSafe(move || { + new_parser_from_file(sess, file, None) + })) + .map_err(|_| None), + Input::Text(text) => rustc_parse::maybe_new_parser_from_source_str( + sess, + rustc_span::FileName::Custom("stdin".to_owned()), + text, + ) + .map_err(|db| Some(db)), + } + } +} + +#[derive(Debug, PartialEq)] +pub(crate) enum ParserError { + NoParseSess, + NoInput, + ParserCreationError, + ParseError, + ParsePanicError, +} + +impl<'a> Parser<'a> { + pub(crate) fn submod_path_from_attr(attrs: &[ast::Attribute], path: &Path) -> Option { + let path_string = first_attr_value_str_by_name(attrs, sym::path)?.as_str(); + // On windows, the base path might have the form + // `\\?\foo\bar` in which case it does not tolerate + // mixed `/` and `\` separators, so canonicalize + // `/` to `\`. + #[cfg(windows)] + let path_string = path_string.replace("/", "\\"); + + Some(path.join(&*path_string)) + } + + pub(crate) fn parse_file_as_module( + sess: &'a ParseSess, + path: &Path, + span: Span, + ) -> Result<(ast::Mod, Vec), ParserError> { + let result = catch_unwind(AssertUnwindSafe(|| { + let mut parser = new_parser_from_file(sess.inner(), &path, Some(span)); + match parser.parse_mod(&TokenKind::Eof, ast::Unsafe::No) { + Ok(result) => Some(result), + Err(mut e) => { + sess.emit_or_cancel_diagnostic(&mut e); + if sess.can_reset_errors() { + sess.reset_errors(); + } + None + } + } + })); + match result { + Ok(Some(m)) => { + if !sess.has_errors() { + return Ok(m); + } + + if sess.can_reset_errors() { + sess.reset_errors(); + return Ok(m); + } + Err(ParserError::ParseError) + } + Ok(None) => Err(ParserError::ParseError), + Err(..) if path.exists() => Err(ParserError::ParseError), + Err(_) => Err(ParserError::ParsePanicError), + } + } + + pub(crate) fn parse_crate( + config: &'a Config, + input: Input, + directory_ownership: Option, + sess: &'a ParseSess, + ) -> Result { + let krate = Parser::parse_crate_inner(config, input, directory_ownership, sess)?; + if !sess.has_errors() { + return Ok(krate); + } + + if sess.can_reset_errors() { + sess.reset_errors(); + return Ok(krate); + } + + Err(ParserError::ParseError) + } + + fn parse_crate_inner( + config: &'a Config, + input: Input, + directory_ownership: Option, + sess: &'a ParseSess, + ) -> Result { + let mut parser = ParserBuilder::default() + .config(config) + .input(input) + .directory_ownership(directory_ownership) + .sess(sess) + .build()?; + parser.parse_crate_mod() + } + + fn parse_crate_mod(&mut self) -> Result { + let mut parser = AssertUnwindSafe(&mut self.parser); + + match catch_unwind(move || parser.parse_crate_mod()) { + Ok(Ok(k)) => Ok(k), + Ok(Err(mut db)) => { + db.emit(); + Err(ParserError::ParseError) + } + Err(_) => Err(ParserError::ParsePanicError), + } + } + + pub(crate) fn parse_cfg_if( + sess: &'a ParseSess, + mac: &'a ast::MacCall, + ) -> Result, &'static str> { + match catch_unwind(AssertUnwindSafe(|| Parser::parse_cfg_if_inner(sess, mac))) { + Ok(Ok(items)) => Ok(items), + Ok(err @ Err(_)) => err, + Err(..) => Err("failed to parse cfg_if!"), + } + } + + fn parse_cfg_if_inner( + sess: &'a ParseSess, + mac: &'a ast::MacCall, + ) -> Result, &'static str> { + let token_stream = mac.args.inner_tokens(); + let mut parser = + rustc_parse::stream_to_parser(sess.inner(), token_stream.clone(), Some("")); + + let mut items = vec![]; + let mut process_if_cfg = true; + + while parser.token.kind != TokenKind::Eof { + if process_if_cfg { + if !parser.eat_keyword(kw::If) { + return Err("Expected `if`"); + } + // Inner attributes are not actually syntactically permitted here, but we don't + // care about inner vs outer attributes in this position. Our purpose with this + // special case parsing of cfg_if macros is to ensure we can correctly resolve + // imported modules that may have a custom `path` defined. + // + // As such, we just need to advance the parser past the attribute and up to + // to the opening brace. + // See also https://github.com/rust-lang/rust/pull/79433 + parser + .parse_attribute(rustc_parse::parser::attr::InnerAttrPolicy::Permitted) + .map_err(|_| "Failed to parse attributes")?; + } + + if !parser.eat(&TokenKind::OpenDelim(DelimToken::Brace)) { + return Err("Expected an opening brace"); + } + + while parser.token != TokenKind::CloseDelim(DelimToken::Brace) + && parser.token.kind != TokenKind::Eof + { + let item = match parser.parse_item(ForceCollect::No) { + Ok(Some(item_ptr)) => item_ptr.into_inner(), + Ok(None) => continue, + Err(mut err) => { + err.cancel(); + parser.sess.span_diagnostic.reset_err_count(); + return Err( + "Expected item inside cfg_if block, but failed to parse it as an item", + ); + } + }; + if let ast::ItemKind::Mod(..) = item.kind { + items.push(item); + } + } + + if !parser.eat(&TokenKind::CloseDelim(DelimToken::Brace)) { + return Err("Expected a closing brace"); + } + + if parser.eat(&TokenKind::Eof) { + break; + } + + if !parser.eat_keyword(kw::Else) { + return Err("Expected `else`"); + } + + process_if_cfg = parser.token.is_keyword(kw::If); + } + + Ok(items) + } +} diff --git a/src/tools/rustfmt/src/syntux/session.rs b/src/tools/rustfmt/src/syntux/session.rs new file mode 100644 index 0000000000..ef5ad62674 --- /dev/null +++ b/src/tools/rustfmt/src/syntux/session.rs @@ -0,0 +1,467 @@ +use std::cell::RefCell; +use std::path::Path; +use std::rc::Rc; + +use rustc_data_structures::sync::{Lrc, Send}; +use rustc_errors::emitter::{Emitter, EmitterWriter}; +use rustc_errors::{ColorConfig, Diagnostic, Handler, Level as DiagnosticLevel}; +use rustc_session::parse::ParseSess as RawParseSess; +use rustc_span::{ + source_map::{FilePathMapping, SourceMap}, + symbol, BytePos, Span, +}; + +use crate::config::file_lines::LineRange; +use crate::ignore_path::IgnorePathSet; +use crate::source_map::LineRangeUtils; +use crate::utils::starts_with_newline; +use crate::visitor::SnippetProvider; +use crate::{Config, ErrorKind, FileName}; + +/// ParseSess holds structs necessary for constructing a parser. +pub(crate) struct ParseSess { + parse_sess: RawParseSess, + ignore_path_set: Rc, + can_reset_errors: Rc>, +} + +/// Emitter which discards every error. +struct SilentEmitter; + +impl Emitter for SilentEmitter { + fn source_map(&self) -> Option<&Lrc> { + None + } + fn emit_diagnostic(&mut self, _db: &Diagnostic) {} +} + +fn silent_emitter() -> Box { + Box::new(SilentEmitter {}) +} + +/// Emit errors against every files expect ones specified in the `ignore_path_set`. +struct SilentOnIgnoredFilesEmitter { + ignore_path_set: Rc, + source_map: Rc, + emitter: Box, + has_non_ignorable_parser_errors: bool, + can_reset: Rc>, +} + +impl SilentOnIgnoredFilesEmitter { + fn handle_non_ignoreable_error(&mut self, db: &Diagnostic) { + self.has_non_ignorable_parser_errors = true; + *self.can_reset.borrow_mut() = false; + self.emitter.emit_diagnostic(db); + } +} + +impl Emitter for SilentOnIgnoredFilesEmitter { + fn source_map(&self) -> Option<&Lrc> { + None + } + fn emit_diagnostic(&mut self, db: &Diagnostic) { + if db.level == DiagnosticLevel::Fatal { + return self.handle_non_ignoreable_error(db); + } + if let Some(primary_span) = &db.span.primary_span() { + let file_name = self.source_map.span_to_filename(*primary_span); + if let rustc_span::FileName::Real(rustc_span::RealFileName::Named(ref path)) = file_name + { + if self + .ignore_path_set + .is_match(&FileName::Real(path.to_path_buf())) + { + if !self.has_non_ignorable_parser_errors { + *self.can_reset.borrow_mut() = true; + } + return; + } + }; + } + self.handle_non_ignoreable_error(db); + } +} + +fn default_handler( + source_map: Rc, + ignore_path_set: Rc, + can_reset: Rc>, + hide_parse_errors: bool, +) -> Handler { + let supports_color = term::stderr().map_or(false, |term| term.supports_color()); + let color_cfg = if supports_color { + ColorConfig::Auto + } else { + ColorConfig::Never + }; + + let emitter = if hide_parse_errors { + silent_emitter() + } else { + Box::new(EmitterWriter::stderr( + color_cfg, + Some(source_map.clone()), + false, + false, + None, + false, + )) + }; + Handler::with_emitter( + true, + None, + Box::new(SilentOnIgnoredFilesEmitter { + has_non_ignorable_parser_errors: false, + source_map, + emitter, + ignore_path_set, + can_reset, + }), + ) +} + +impl ParseSess { + pub(crate) fn new(config: &Config) -> Result { + let ignore_path_set = match IgnorePathSet::from_ignore_list(&config.ignore()) { + Ok(ignore_path_set) => Rc::new(ignore_path_set), + Err(e) => return Err(ErrorKind::InvalidGlobPattern(e)), + }; + let source_map = Rc::new(SourceMap::new(FilePathMapping::empty())); + let can_reset_errors = Rc::new(RefCell::new(false)); + + let handler = default_handler( + Rc::clone(&source_map), + Rc::clone(&ignore_path_set), + Rc::clone(&can_reset_errors), + config.hide_parse_errors(), + ); + let parse_sess = RawParseSess::with_span_handler(handler, source_map); + + Ok(ParseSess { + parse_sess, + ignore_path_set, + can_reset_errors, + }) + } + + pub(crate) fn default_submod_path( + &self, + id: symbol::Ident, + relative: Option, + dir_path: &Path, + ) -> rustc_expand::module::ModulePath<'_> { + rustc_expand::module::default_submod_path( + &self.parse_sess, + id, + rustc_span::DUMMY_SP, + relative, + dir_path, + ) + } + + pub(crate) fn is_file_parsed(&self, path: &Path) -> bool { + self.parse_sess + .source_map() + .get_source_file(&rustc_span::FileName::Real( + rustc_span::RealFileName::Named(path.to_path_buf()), + )) + .is_some() + } + + pub(crate) fn ignore_file(&self, path: &FileName) -> bool { + self.ignore_path_set.as_ref().is_match(&path) + } + + pub(crate) fn set_silent_emitter(&mut self) { + self.parse_sess.span_diagnostic = Handler::with_emitter(true, None, silent_emitter()); + } + + pub(crate) fn span_to_filename(&self, span: Span) -> FileName { + self.parse_sess.source_map().span_to_filename(span).into() + } + + pub(crate) fn span_to_first_line_string(&self, span: Span) -> String { + let file_lines = self.parse_sess.source_map().span_to_lines(span).ok(); + + match file_lines { + Some(fl) => fl + .file + .get_line(fl.lines[0].line_index) + .map_or_else(String::new, |s| s.to_string()), + None => String::new(), + } + } + + pub(crate) fn line_of_byte_pos(&self, pos: BytePos) -> usize { + self.parse_sess.source_map().lookup_char_pos(pos).line + } + + pub(crate) fn span_to_debug_info(&self, span: Span) -> String { + self.parse_sess.source_map().span_to_string(span) + } + + pub(crate) fn inner(&self) -> &RawParseSess { + &self.parse_sess + } + + pub(crate) fn snippet_provider(&self, span: Span) -> SnippetProvider { + let source_file = self.parse_sess.source_map().lookup_char_pos(span.lo()).file; + SnippetProvider::new( + source_file.start_pos, + source_file.end_pos, + Rc::clone(source_file.src.as_ref().unwrap()), + ) + } + + pub(crate) fn get_original_snippet(&self, file_name: &FileName) -> Option> { + self.parse_sess + .source_map() + .get_source_file(&file_name.into()) + .and_then(|source_file| source_file.src.clone()) + } +} + +// Methods that should be restricted within the syntux module. +impl ParseSess { + pub(super) fn emit_diagnostics(&self, diagnostics: Vec) { + for diagnostic in diagnostics { + self.parse_sess.span_diagnostic.emit_diagnostic(&diagnostic); + } + } + + pub(crate) fn emit_or_cancel_diagnostic(&self, diagnostic: &mut Diagnostic) { + self.parse_sess.span_diagnostic.emit_diagnostic(diagnostic); + // The Handler will check whether the diagnostic should be emitted + // based on the user's rustfmt configuration and the originating file + // that caused the parser error. If the Handler determined it should skip + // emission then we need to ensure the diagnostic is cancelled. + if !diagnostic.cancelled() { + diagnostic.cancel(); + } + } + + pub(super) fn can_reset_errors(&self) -> bool { + *self.can_reset_errors.borrow() + } + + pub(super) fn has_errors(&self) -> bool { + self.parse_sess.span_diagnostic.has_errors() + } + + pub(super) fn reset_errors(&self) { + self.parse_sess.span_diagnostic.reset_err_count(); + } +} + +impl LineRangeUtils for ParseSess { + fn lookup_line_range(&self, span: Span) -> LineRange { + let snippet = self + .parse_sess + .source_map() + .span_to_snippet(span) + .unwrap_or_default(); + let lo = self.parse_sess.source_map().lookup_line(span.lo()).unwrap(); + let hi = self.parse_sess.source_map().lookup_line(span.hi()).unwrap(); + + debug_assert_eq!( + lo.sf.name, hi.sf.name, + "span crossed file boundary: lo: {:?}, hi: {:?}", + lo, hi + ); + + // in case the span starts with a newline, the line range is off by 1 without the + // adjustment below + let offset = 1 + if starts_with_newline(&snippet) { 1 } else { 0 }; + // Line numbers start at 1 + LineRange { + file: lo.sf.clone(), + lo: lo.line + offset, + hi: hi.line + offset, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod emitter { + use super::*; + use crate::config::IgnoreList; + use crate::is_nightly_channel; + use crate::utils::mk_sp; + use rustc_span::{FileName as SourceMapFileName, MultiSpan, RealFileName, DUMMY_SP}; + use std::path::PathBuf; + + struct TestEmitter { + num_emitted_errors: Rc>, + } + + impl Emitter for TestEmitter { + fn source_map(&self) -> Option<&Lrc> { + None + } + fn emit_diagnostic(&mut self, _db: &Diagnostic) { + *self.num_emitted_errors.borrow_mut() += 1; + } + } + + fn build_diagnostic(level: DiagnosticLevel, span: Option) -> Diagnostic { + Diagnostic { + level, + code: None, + message: vec![], + children: vec![], + suggestions: vec![], + span: span.unwrap_or_else(MultiSpan::new), + sort_span: DUMMY_SP, + } + } + + fn build_emitter( + num_emitted_errors: Rc>, + can_reset: Rc>, + source_map: Option>, + ignore_list: Option, + ) -> SilentOnIgnoredFilesEmitter { + let emitter_writer = TestEmitter { num_emitted_errors }; + let source_map = + source_map.unwrap_or_else(|| Rc::new(SourceMap::new(FilePathMapping::empty()))); + let ignore_path_set = + Rc::new(IgnorePathSet::from_ignore_list(&ignore_list.unwrap_or_default()).unwrap()); + SilentOnIgnoredFilesEmitter { + has_non_ignorable_parser_errors: false, + source_map, + emitter: Box::new(emitter_writer), + ignore_path_set, + can_reset, + } + } + + fn get_ignore_list(config: &str) -> IgnoreList { + Config::from_toml(config, Path::new("")).unwrap().ignore() + } + + #[test] + fn handles_fatal_parse_error_in_ignored_file() { + let num_emitted_errors = Rc::new(RefCell::new(0)); + let can_reset_errors = Rc::new(RefCell::new(false)); + let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#); + let source_map = Rc::new(SourceMap::new(FilePathMapping::empty())); + let source = + String::from(r#"extern "system" fn jni_symbol!( funcName ) ( ... ) -> {} "#); + source_map.new_source_file( + SourceMapFileName::Real(RealFileName::Named(PathBuf::from("foo.rs"))), + source, + ); + let mut emitter = build_emitter( + Rc::clone(&num_emitted_errors), + Rc::clone(&can_reset_errors), + Some(Rc::clone(&source_map)), + Some(ignore_list), + ); + let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); + let fatal_diagnostic = build_diagnostic(DiagnosticLevel::Fatal, Some(span)); + emitter.emit_diagnostic(&fatal_diagnostic); + assert_eq!(*num_emitted_errors.borrow(), 1); + assert_eq!(*can_reset_errors.borrow(), false); + } + + #[test] + fn handles_recoverable_parse_error_in_ignored_file() { + if !is_nightly_channel!() { + return; + } + let num_emitted_errors = Rc::new(RefCell::new(0)); + let can_reset_errors = Rc::new(RefCell::new(false)); + let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#); + let source_map = Rc::new(SourceMap::new(FilePathMapping::empty())); + let source = String::from(r#"pub fn bar() { 1x; }"#); + source_map.new_source_file( + SourceMapFileName::Real(RealFileName::Named(PathBuf::from("foo.rs"))), + source, + ); + let mut emitter = build_emitter( + Rc::clone(&num_emitted_errors), + Rc::clone(&can_reset_errors), + Some(Rc::clone(&source_map)), + Some(ignore_list), + ); + let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); + let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(span)); + emitter.emit_diagnostic(&non_fatal_diagnostic); + assert_eq!(*num_emitted_errors.borrow(), 0); + assert_eq!(*can_reset_errors.borrow(), true); + } + + #[test] + fn handles_recoverable_parse_error_in_non_ignored_file() { + if !is_nightly_channel!() { + return; + } + let num_emitted_errors = Rc::new(RefCell::new(0)); + let can_reset_errors = Rc::new(RefCell::new(false)); + let source_map = Rc::new(SourceMap::new(FilePathMapping::empty())); + let source = String::from(r#"pub fn bar() { 1x; }"#); + source_map.new_source_file( + SourceMapFileName::Real(RealFileName::Named(PathBuf::from("foo.rs"))), + source, + ); + let mut emitter = build_emitter( + Rc::clone(&num_emitted_errors), + Rc::clone(&can_reset_errors), + Some(Rc::clone(&source_map)), + None, + ); + let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); + let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(span)); + emitter.emit_diagnostic(&non_fatal_diagnostic); + assert_eq!(*num_emitted_errors.borrow(), 1); + assert_eq!(*can_reset_errors.borrow(), false); + } + + #[test] + fn handles_mix_of_recoverable_parse_error() { + if !is_nightly_channel!() { + return; + } + let num_emitted_errors = Rc::new(RefCell::new(0)); + let can_reset_errors = Rc::new(RefCell::new(false)); + let source_map = Rc::new(SourceMap::new(FilePathMapping::empty())); + let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#); + let bar_source = String::from(r#"pub fn bar() { 1x; }"#); + let foo_source = String::from(r#"pub fn foo() { 1x; }"#); + let fatal_source = + String::from(r#"extern "system" fn jni_symbol!( funcName ) ( ... ) -> {} "#); + source_map.new_source_file( + SourceMapFileName::Real(RealFileName::Named(PathBuf::from("bar.rs"))), + bar_source, + ); + source_map.new_source_file( + SourceMapFileName::Real(RealFileName::Named(PathBuf::from("foo.rs"))), + foo_source, + ); + source_map.new_source_file( + SourceMapFileName::Real(RealFileName::Named(PathBuf::from("fatal.rs"))), + fatal_source, + ); + let mut emitter = build_emitter( + Rc::clone(&num_emitted_errors), + Rc::clone(&can_reset_errors), + Some(Rc::clone(&source_map)), + Some(ignore_list), + ); + let bar_span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); + let foo_span = MultiSpan::from_span(mk_sp(BytePos(21), BytePos(22))); + let bar_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(bar_span)); + let foo_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(foo_span)); + let fatal_diagnostic = build_diagnostic(DiagnosticLevel::Fatal, None); + emitter.emit_diagnostic(&bar_diagnostic); + emitter.emit_diagnostic(&foo_diagnostic); + emitter.emit_diagnostic(&fatal_diagnostic); + assert_eq!(*num_emitted_errors.borrow(), 2); + assert_eq!(*can_reset_errors.borrow(), false); + } + } +} diff --git a/src/tools/rustfmt/src/test/configuration_snippet.rs b/src/tools/rustfmt/src/test/configuration_snippet.rs new file mode 100644 index 0000000000..ef7dd0ddcd --- /dev/null +++ b/src/tools/rustfmt/src/test/configuration_snippet.rs @@ -0,0 +1,287 @@ +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::io::{BufRead, BufReader, Write}; +use std::iter::Enumerate; +use std::path::{Path, PathBuf}; + +use super::{print_mismatches, write_message, DIFF_CONTEXT_SIZE}; +use crate::config::{Config, EmitMode, Verbosity}; +use crate::rustfmt_diff::{make_diff, Mismatch}; +use crate::{Input, Session}; + +const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md"; + +// This enum is used to represent one of three text features in Configurations.md: a block of code +// with its starting line number, the name of a rustfmt configuration option, or the value of a +// rustfmt configuration option. +enum ConfigurationSection { + CodeBlock((String, u32)), // (String: block of code, u32: line number of code block start) + ConfigName(String), + ConfigValue(String), +} + +impl ConfigurationSection { + fn get_section>( + file: &mut Enumerate, + ) -> Option { + lazy_static! { + static ref CONFIG_NAME_REGEX: regex::Regex = + regex::Regex::new(r"^## `([^`]+)`").expect("failed creating configuration pattern"); + static ref CONFIG_VALUE_REGEX: regex::Regex = + regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#) + .expect("failed creating configuration value pattern"); + } + + loop { + match file.next() { + Some((i, line)) => { + if line.starts_with("```rust") { + // Get the lines of the code block. + let lines: Vec = file + .map(|(_i, l)| l) + .take_while(|l| !l.starts_with("```")) + .collect(); + let block = format!("{}\n", lines.join("\n")); + + // +1 to translate to one-based indexing + // +1 to get to first line of code (line after "```") + let start_line = (i + 2) as u32; + + return Some(ConfigurationSection::CodeBlock((block, start_line))); + } else if let Some(c) = CONFIG_NAME_REGEX.captures(&line) { + return Some(ConfigurationSection::ConfigName(String::from(&c[1]))); + } else if let Some(c) = CONFIG_VALUE_REGEX.captures(&line) { + return Some(ConfigurationSection::ConfigValue(String::from(&c[1]))); + } + } + None => return None, // reached the end of the file + } + } + } +} + +// This struct stores the information about code blocks in the configurations +// file, formats the code blocks, and prints formatting errors. +struct ConfigCodeBlock { + config_name: Option, + config_value: Option, + code_block: Option, + code_block_start: Option, +} + +impl ConfigCodeBlock { + fn new() -> ConfigCodeBlock { + ConfigCodeBlock { + config_name: None, + config_value: None, + code_block: None, + code_block_start: None, + } + } + + fn set_config_name(&mut self, name: Option) { + self.config_name = name; + self.config_value = None; + } + + fn set_config_value(&mut self, value: Option) { + self.config_value = value; + } + + fn set_code_block(&mut self, code_block: String, code_block_start: u32) { + self.code_block = Some(code_block); + self.code_block_start = Some(code_block_start); + } + + fn get_block_config(&self) -> Config { + let mut config = Config::default(); + config.set().verbose(Verbosity::Quiet); + if self.config_name.is_some() && self.config_value.is_some() { + config.override_value( + self.config_name.as_ref().unwrap(), + self.config_value.as_ref().unwrap(), + ); + } + config + } + + fn code_block_valid(&self) -> bool { + // We never expect to not have a code block. + assert!(self.code_block.is_some() && self.code_block_start.is_some()); + + // See if code block begins with #![rustfmt::skip]. + let fmt_skip = self + .code_block + .as_ref() + .unwrap() + .lines() + .nth(0) + .unwrap_or("") + == "#![rustfmt::skip]"; + + if self.config_name.is_none() && !fmt_skip { + write_message(&format!( + "No configuration name for {}:{}", + CONFIGURATIONS_FILE_NAME, + self.code_block_start.unwrap() + )); + return false; + } + if self.config_value.is_none() && !fmt_skip { + write_message(&format!( + "No configuration value for {}:{}", + CONFIGURATIONS_FILE_NAME, + self.code_block_start.unwrap() + )); + return false; + } + true + } + + fn has_parsing_errors(&self, session: &Session<'_, T>) -> bool { + if session.has_parsing_errors() { + write_message(&format!( + "\u{261d}\u{1f3fd} Cannot format {}:{}", + CONFIGURATIONS_FILE_NAME, + self.code_block_start.unwrap() + )); + return true; + } + + false + } + + fn print_diff(&self, compare: Vec) { + let mut mismatches = HashMap::new(); + mismatches.insert(PathBuf::from(CONFIGURATIONS_FILE_NAME), compare); + print_mismatches(mismatches, |line_num| { + format!( + "\nMismatch at {}:{}:", + CONFIGURATIONS_FILE_NAME, + line_num + self.code_block_start.unwrap() - 1 + ) + }); + } + + fn formatted_has_diff(&self, text: &str) -> bool { + let compare = make_diff(self.code_block.as_ref().unwrap(), text, DIFF_CONTEXT_SIZE); + if !compare.is_empty() { + self.print_diff(compare); + return true; + } + + false + } + + // Return a bool indicating if formatting this code block is an idempotent + // operation. This function also triggers printing any formatting failure + // messages. + fn formatted_is_idempotent(&self) -> bool { + // Verify that we have all of the expected information. + if !self.code_block_valid() { + return false; + } + + let input = Input::Text(self.code_block.as_ref().unwrap().to_owned()); + let mut config = self.get_block_config(); + config.set().emit_mode(EmitMode::Stdout); + let mut buf: Vec = vec![]; + + { + let mut session = Session::new(config, Some(&mut buf)); + session.format(input).unwrap(); + if self.has_parsing_errors(&session) { + return false; + } + } + + !self.formatted_has_diff(&String::from_utf8(buf).unwrap()) + } + + // Extract a code block from the iterator. Behavior: + // - Rust code blocks are identifed by lines beginning with "```rust". + // - One explicit configuration setting is supported per code block. + // - Rust code blocks with no configuration setting are illegal and cause an + // assertion failure, unless the snippet begins with #![rustfmt::skip]. + // - Configuration names in Configurations.md must be in the form of + // "## `NAME`". + // - Configuration values in Configurations.md must be in the form of + // "#### `VALUE`". + fn extract>( + file: &mut Enumerate, + prev: Option<&ConfigCodeBlock>, + hash_set: &mut HashSet, + ) -> Option { + let mut code_block = ConfigCodeBlock::new(); + code_block.config_name = prev.and_then(|cb| cb.config_name.clone()); + + loop { + match ConfigurationSection::get_section(file) { + Some(ConfigurationSection::CodeBlock((block, start_line))) => { + code_block.set_code_block(block, start_line); + break; + } + Some(ConfigurationSection::ConfigName(name)) => { + assert!( + Config::is_valid_name(&name), + "an unknown configuration option was found: {}", + name + ); + assert!( + hash_set.remove(&name), + "multiple configuration guides found for option {}", + name + ); + code_block.set_config_name(Some(name)); + } + Some(ConfigurationSection::ConfigValue(value)) => { + code_block.set_config_value(Some(value)); + } + None => return None, // end of file was reached + } + } + + Some(code_block) + } +} + +#[test] +fn configuration_snippet_tests() { + super::init_log(); + let blocks = get_code_blocks(); + let failures = blocks + .iter() + .map(ConfigCodeBlock::formatted_is_idempotent) + .fold(0, |acc, r| acc + (!r as u32)); + + // Display results. + println!("Ran {} configurations tests.", blocks.len()); + assert_eq!(failures, 0, "{} configurations tests failed", failures); +} + +// Read Configurations.md and build a `Vec` of `ConfigCodeBlock` structs with one +// entry for each Rust code block found. +fn get_code_blocks() -> Vec { + let mut file_iter = BufReader::new( + fs::File::open(Path::new(CONFIGURATIONS_FILE_NAME)) + .unwrap_or_else(|_| panic!("couldn't read file {}", CONFIGURATIONS_FILE_NAME)), + ) + .lines() + .map(Result::unwrap) + .enumerate(); + let mut code_blocks: Vec = Vec::new(); + let mut hash_set = Config::hash_set(); + + while let Some(cb) = ConfigCodeBlock::extract(&mut file_iter, code_blocks.last(), &mut hash_set) + { + code_blocks.push(cb); + } + + for name in hash_set { + if !Config::is_hidden_option(&name) { + panic!("{} does not have a configuration guide", name); + } + } + + code_blocks +} diff --git a/src/tools/rustfmt/src/test/mod.rs b/src/tools/rustfmt/src/test/mod.rs new file mode 100644 index 0000000000..142de28f33 --- /dev/null +++ b/src/tools/rustfmt/src/test/mod.rs @@ -0,0 +1,906 @@ +use std::collections::HashMap; +use std::env; +use std::fs; +use std::io::{self, BufRead, BufReader, Read, Write}; +use std::iter::Peekable; +use std::mem; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::str::Chars; +use std::thread; + +use crate::config::{Color, Config, EmitMode, FileName, NewlineStyle, ReportTactic}; +use crate::formatting::{ReportedErrors, SourceFile}; +use crate::rustfmt_diff::{make_diff, print_diff, DiffLine, Mismatch, ModifiedChunk, OutputWriter}; +use crate::source_file; +use crate::{is_nightly_channel, FormatReport, FormatReportFormatterBuilder, Input, Session}; + +mod configuration_snippet; +mod parser; + +const DIFF_CONTEXT_SIZE: usize = 3; + +// A list of files on which we want to skip testing. +const SKIP_FILE_WHITE_LIST: &[&str] = &[ + // We want to make sure that the `skip_children` is correctly working, + // so we do not want to test this file directly. + "configs/skip_children/foo/mod.rs", + "issue-3434/no_entry.rs", + "issue-3665/sub_mod.rs", + // Testing for issue-3779 + "issue-3779/ice.rs", + // These files and directory are a part of modules defined inside `cfg_if!`. + "cfg_if/mod.rs", + "cfg_if/detect", + "issue-3253/foo.rs", + "issue-3253/bar.rs", + "issue-3253/paths", + // These files and directory are a part of modules defined inside `cfg_attr(..)`. + "cfg_mod/dir", + "cfg_mod/bar.rs", + "cfg_mod/foo.rs", + "cfg_mod/wasm32.rs", + "skip/foo.rs", +]; + +fn init_log() { + let _ = env_logger::builder().is_test(true).try_init(); +} + +struct TestSetting { + /// The size of the stack of the thread that run tests. + stack_size: usize, +} + +impl Default for TestSetting { + fn default() -> Self { + TestSetting { + stack_size: 8_388_608, // 8MB + } + } +} + +fn run_test_with(test_setting: &TestSetting, f: F) +where + F: FnOnce(), + F: Send + 'static, +{ + thread::Builder::new() + .stack_size(test_setting.stack_size) + .spawn(f) + .expect("Failed to create a test thread") + .join() + .expect("Failed to join a test thread") +} + +fn is_subpath

(path: &Path, subpath: &P) -> bool +where + P: AsRef, +{ + (0..path.components().count()) + .map(|i| { + path.components() + .skip(i) + .take(subpath.as_ref().components().count()) + }) + .any(|c| c.zip(subpath.as_ref().components()).all(|(a, b)| a == b)) +} + +fn is_file_skip(path: &Path) -> bool { + SKIP_FILE_WHITE_LIST + .iter() + .any(|file_path| is_subpath(path, file_path)) +} + +// Returns a `Vec` containing `PathBuf`s of files with an `rs` extension in the +// given path. The `recursive` argument controls if files from subdirectories +// are also returned. +fn get_test_files(path: &Path, recursive: bool) -> Vec { + let mut files = vec![]; + if path.is_dir() { + for entry in fs::read_dir(path).expect(&format!( + "couldn't read directory {}", + path.to_str().unwrap() + )) { + let entry = entry.expect("couldn't get `DirEntry`"); + let path = entry.path(); + if path.is_dir() && recursive { + files.append(&mut get_test_files(&path, recursive)); + } else if path.extension().map_or(false, |f| f == "rs") && !is_file_skip(&path) { + files.push(path); + } + } + } + files +} + +fn verify_config_used(path: &Path, config_name: &str) { + for entry in fs::read_dir(path).expect(&format!( + "couldn't read {} directory", + path.to_str().unwrap() + )) { + let entry = entry.expect("couldn't get directory entry"); + let path = entry.path(); + if path.extension().map_or(false, |f| f == "rs") { + // check if "// rustfmt-:" appears in the file. + let filebuf = BufReader::new( + fs::File::open(&path) + .unwrap_or_else(|_| panic!("couldn't read file {}", path.display())), + ); + assert!( + filebuf + .lines() + .map(Result::unwrap) + .take_while(|l| l.starts_with("//")) + .any(|l| l.starts_with(&format!("// rustfmt-{}", config_name))), + format!( + "config option file {} does not contain expected config name", + path.display() + ) + ); + } + } +} + +#[test] +fn verify_config_test_names() { + init_log(); + for path in &[ + Path::new("tests/source/configs"), + Path::new("tests/target/configs"), + ] { + for entry in fs::read_dir(path).expect("couldn't read configs directory") { + let entry = entry.expect("couldn't get directory entry"); + let path = entry.path(); + if path.is_dir() { + let config_name = path.file_name().unwrap().to_str().unwrap(); + + // Make sure that config name is used in the files in the directory. + verify_config_used(&path, config_name); + } + } + } +} + +// This writes to the terminal using the same approach (via `term::stdout` or +// `println!`) that is used by `rustfmt::rustfmt_diff::print_diff`. Writing +// using only one or the other will cause the output order to differ when +// `print_diff` selects the approach not used. +fn write_message(msg: &str) { + let mut writer = OutputWriter::new(Color::Auto); + writer.writeln(msg, None); +} + +// Integration tests. The files in `tests/source` are formatted and compared +// to their equivalent in `tests/target`. The target file and config can be +// overridden by annotations in the source file. The input and output must match +// exactly. +#[test] +fn system_tests() { + init_log(); + run_test_with(&TestSetting::default(), || { + // Get all files in the tests/source directory. + let files = get_test_files(Path::new("tests/source"), true); + let (_reports, count, fails) = check_files(files, &None); + + // Display results. + println!("Ran {} system tests.", count); + assert_eq!(fails, 0, "{} system tests failed", fails); + assert!( + count >= 300, + "Expected a minimum of {} system tests to be executed", + 300 + ) + }); +} + +// Do the same for tests/coverage-source directory. +// The only difference is the coverage mode. +#[test] +fn coverage_tests() { + init_log(); + let files = get_test_files(Path::new("tests/coverage/source"), true); + let (_reports, count, fails) = check_files(files, &None); + + println!("Ran {} tests in coverage mode.", count); + assert_eq!(fails, 0, "{} tests failed", fails); +} + +#[test] +fn checkstyle_test() { + init_log(); + let filename = "tests/writemode/source/fn-single-line.rs"; + let expected_filename = "tests/writemode/target/checkstyle.xml"; + assert_output(Path::new(filename), Path::new(expected_filename)); +} + +#[test] +fn json_test() { + init_log(); + let filename = "tests/writemode/source/json.rs"; + let expected_filename = "tests/writemode/target/output.json"; + assert_output(Path::new(filename), Path::new(expected_filename)); +} + +#[test] +fn modified_test() { + init_log(); + use std::io::BufRead; + + // Test "modified" output + let filename = "tests/writemode/source/modified.rs"; + let mut data = Vec::new(); + let mut config = Config::default(); + config + .set() + .emit_mode(crate::config::EmitMode::ModifiedLines); + + { + let mut session = Session::new(config, Some(&mut data)); + session.format(Input::File(filename.into())).unwrap(); + } + + let mut lines = data.lines(); + let mut chunks = Vec::new(); + while let Some(Ok(header)) = lines.next() { + // Parse the header line + let values: Vec<_> = header + .split(' ') + .map(|s| s.parse::().unwrap()) + .collect(); + assert_eq!(values.len(), 3); + let line_number_orig = values[0]; + let lines_removed = values[1]; + let num_added = values[2]; + let mut added_lines = Vec::new(); + for _ in 0..num_added { + added_lines.push(lines.next().unwrap().unwrap()); + } + chunks.push(ModifiedChunk { + line_number_orig, + lines_removed, + lines: added_lines, + }); + } + + assert_eq!( + chunks, + vec![ + ModifiedChunk { + line_number_orig: 4, + lines_removed: 4, + lines: vec!["fn blah() {}".into()], + }, + ModifiedChunk { + line_number_orig: 9, + lines_removed: 6, + lines: vec!["#[cfg(a, b)]".into(), "fn main() {}".into()], + }, + ], + ); +} + +// Helper function for comparing the results of rustfmt +// to a known output file generated by one of the write modes. +fn assert_output(source: &Path, expected_filename: &Path) { + let config = read_config(source); + let (_, source_file, _) = format_file(source, config.clone()); + + // Populate output by writing to a vec. + let mut out = vec![]; + let _ = source_file::write_all_files(&source_file, &mut out, &config); + let output = String::from_utf8(out).unwrap(); + + let mut expected_file = fs::File::open(&expected_filename).expect("couldn't open target"); + let mut expected_text = String::new(); + expected_file + .read_to_string(&mut expected_text) + .expect("Failed reading target"); + + let compare = make_diff(&expected_text, &output, DIFF_CONTEXT_SIZE); + if !compare.is_empty() { + let mut failures = HashMap::new(); + failures.insert(source.to_owned(), compare); + print_mismatches_default_message(failures); + panic!("Text does not match expected output"); + } +} + +// Idempotence tests. Files in tests/target are checked to be unaltered by +// rustfmt. +#[test] +fn idempotence_tests() { + init_log(); + run_test_with(&TestSetting::default(), || { + // these tests require nightly + if !is_nightly_channel!() { + return; + } + // Get all files in the tests/target directory. + let files = get_test_files(Path::new("tests/target"), true); + let (_reports, count, fails) = check_files(files, &None); + + // Display results. + println!("Ran {} idempotent tests.", count); + assert_eq!(fails, 0, "{} idempotent tests failed", fails); + assert!( + count >= 400, + "Expected a minimum of {} idempotent tests to be executed", + 400 + ) + }); +} + +// Run rustfmt on itself. This operation must be idempotent. We also check that +// no warnings are emitted. +#[test] +fn self_tests() { + init_log(); + // Issue-3443: these tests require nightly + if !is_nightly_channel!() { + return; + } + let mut files = get_test_files(Path::new("tests"), false); + let bin_directories = vec!["cargo-fmt", "git-rustfmt", "bin", "format-diff"]; + for dir in bin_directories { + let mut path = PathBuf::from("src"); + path.push(dir); + path.push("main.rs"); + files.push(path); + } + files.push(PathBuf::from("src/lib.rs")); + + let (reports, count, fails) = check_files(files, &Some(PathBuf::from("rustfmt.toml"))); + let mut warnings = 0; + + // Display results. + println!("Ran {} self tests.", count); + assert_eq!(fails, 0, "{} self tests failed", fails); + + for format_report in reports { + println!( + "{}", + FormatReportFormatterBuilder::new(&format_report).build() + ); + warnings += format_report.warning_count(); + } + + assert_eq!( + warnings, 0, + "Rustfmt's code generated {} warnings", + warnings + ); +} + +#[test] +fn format_files_find_new_files_via_cfg_if() { + init_log(); + run_test_with(&TestSetting::default(), || { + // To repro issue-4656, it is necessary that these files are parsed + // as a part of the same session (hence this separate test runner). + let files = vec![ + Path::new("tests/source/issue-4656/lib2.rs"), + Path::new("tests/source/issue-4656/lib.rs"), + ]; + + let config = Config::default(); + let mut session = Session::::new(config, None); + + let mut write_result = HashMap::new(); + for file in files { + assert!(file.exists()); + let result = session.format(Input::File(file.into())).unwrap(); + assert!(!session.has_formatting_errors()); + assert!(!result.has_warnings()); + let mut source_file = SourceFile::new(); + mem::swap(&mut session.source_file, &mut source_file); + + for (filename, text) in source_file { + if let FileName::Real(ref filename) = filename { + write_result.insert(filename.to_owned(), text); + } + } + } + assert_eq!( + 3, + write_result.len(), + "Should have uncovered an extra file (format_me_please.rs) via lib.rs" + ); + assert!(handle_result(write_result, None).is_ok()); + }); +} + +#[test] +fn stdin_formatting_smoke_test() { + init_log(); + let input = Input::Text("fn main () {}".to_owned()); + let mut config = Config::default(); + config.set().emit_mode(EmitMode::Stdout); + let mut buf: Vec = vec![]; + { + let mut session = Session::new(config, Some(&mut buf)); + session.format(input).unwrap(); + assert!(session.has_no_errors()); + } + + #[cfg(not(windows))] + assert_eq!(buf, "stdin:\n\nfn main() {}\n".as_bytes()); + #[cfg(windows)] + assert_eq!(buf, "stdin:\n\nfn main() {}\r\n".as_bytes()); +} + +#[test] +fn stdin_parser_panic_caught() { + init_log(); + // See issue #3239. + for text in ["{", "}"].iter().cloned().map(String::from) { + let mut buf = vec![]; + let mut session = Session::new(Default::default(), Some(&mut buf)); + let _ = session.format(Input::Text(text)); + + assert!(session.has_parsing_errors()); + } +} + +/// Ensures that `EmitMode::ModifiedLines` works with input from `stdin`. Useful +/// when embedding Rustfmt (e.g. inside RLS). +#[test] +fn stdin_works_with_modified_lines() { + init_log(); + let input = "\nfn\n some( )\n{\n}\nfn main () {}\n"; + let output = "1 6 2\nfn some() {}\nfn main() {}\n"; + + let input = Input::Text(input.to_owned()); + let mut config = Config::default(); + config.set().newline_style(NewlineStyle::Unix); + config.set().emit_mode(EmitMode::ModifiedLines); + let mut buf: Vec = vec![]; + { + let mut session = Session::new(config, Some(&mut buf)); + session.format(input).unwrap(); + let errors = ReportedErrors { + has_diff: true, + ..Default::default() + }; + assert_eq!(session.errors, errors); + } + assert_eq!(buf, output.as_bytes()); +} + +#[test] +fn stdin_disable_all_formatting_test() { + init_log(); + match option_env!("CFG_RELEASE_CHANNEL") { + None | Some("nightly") => {} + // These tests require nightly. + _ => return, + } + let input = String::from("fn main() { println!(\"This should not be formatted.\"); }"); + let mut child = Command::new(rustfmt().to_str().unwrap()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .arg("--config-path=./tests/config/disable_all_formatting.toml") + .spawn() + .expect("failed to execute child"); + + { + let stdin = child.stdin.as_mut().expect("failed to get stdin"); + stdin + .write_all(input.as_bytes()) + .expect("failed to write stdin"); + } + + let output = child.wait_with_output().expect("failed to wait on child"); + assert!(output.status.success()); + assert!(output.stderr.is_empty()); + assert_eq!(input, String::from_utf8(output.stdout).unwrap()); +} + +#[test] +fn format_lines_errors_are_reported() { + init_log(); + let long_identifier = String::from_utf8(vec![b'a'; 239]).unwrap(); + let input = Input::Text(format!("fn {}() {{}}", long_identifier)); + let mut config = Config::default(); + config.set().error_on_line_overflow(true); + let mut session = Session::::new(config, None); + session.format(input).unwrap(); + assert!(session.has_formatting_errors()); +} + +#[test] +fn format_lines_errors_are_reported_with_tabs() { + init_log(); + let long_identifier = String::from_utf8(vec![b'a'; 97]).unwrap(); + let input = Input::Text(format!("fn a() {{\n\t{}\n}}", long_identifier)); + let mut config = Config::default(); + config.set().error_on_line_overflow(true); + config.set().hard_tabs(true); + let mut session = Session::::new(config, None); + session.format(input).unwrap(); + assert!(session.has_formatting_errors()); +} + +// For each file, run rustfmt and collect the output. +// Returns the number of files checked and the number of failures. +fn check_files(files: Vec, opt_config: &Option) -> (Vec, u32, u32) { + let mut count = 0; + let mut fails = 0; + let mut reports = vec![]; + + for file_name in files { + let sig_comments = read_significant_comments(&file_name); + if sig_comments.contains_key("unstable") && !is_nightly_channel!() { + debug!( + "Skipping '{}' because it requires unstable \ + features which are only available on nightly...", + file_name.display() + ); + continue; + } + + debug!("Testing '{}'...", file_name.display()); + + match idempotent_check(&file_name, &opt_config) { + Ok(ref report) if report.has_warnings() => { + print!("{}", FormatReportFormatterBuilder::new(&report).build()); + fails += 1; + } + Ok(report) => reports.push(report), + Err(err) => { + if let IdempotentCheckError::Mismatch(msg) = err { + print_mismatches_default_message(msg); + } + fails += 1; + } + } + + count += 1; + } + + (reports, count, fails) +} + +fn print_mismatches_default_message(result: HashMap>) { + for (file_name, diff) in result { + let mismatch_msg_formatter = + |line_num| format!("\nMismatch at {}:{}:", file_name.display(), line_num); + print_diff(diff, &mismatch_msg_formatter, &Default::default()); + } + + if let Some(mut t) = term::stdout() { + t.reset().unwrap_or(()); + } +} + +fn print_mismatches String>( + result: HashMap>, + mismatch_msg_formatter: T, +) { + for (_file_name, diff) in result { + print_diff(diff, &mismatch_msg_formatter, &Default::default()); + } + + if let Some(mut t) = term::stdout() { + t.reset().unwrap_or(()); + } +} + +fn read_config(filename: &Path) -> Config { + let sig_comments = read_significant_comments(filename); + // Look for a config file. If there is a 'config' property in the significant comments, use + // that. Otherwise, if there are no significant comments at all, look for a config file with + // the same name as the test file. + let mut config = if !sig_comments.is_empty() { + get_config(sig_comments.get("config").map(Path::new)) + } else { + get_config(filename.with_extension("toml").file_name().map(Path::new)) + }; + + for (key, val) in &sig_comments { + if key != "target" && key != "config" && key != "unstable" { + config.override_value(key, val); + if config.is_default(key) { + warn!("Default value {} used explicitly for {}", val, key); + } + } + } + + // Don't generate warnings for to-do items. + config.set().report_todo(ReportTactic::Never); + + config +} + +fn format_file>(filepath: P, config: Config) -> (bool, SourceFile, FormatReport) { + let filepath = filepath.into(); + let input = Input::File(filepath); + let mut session = Session::::new(config, None); + let result = session.format(input).unwrap(); + let parsing_errors = session.has_parsing_errors(); + let mut source_file = SourceFile::new(); + mem::swap(&mut session.source_file, &mut source_file); + (parsing_errors, source_file, result) +} + +enum IdempotentCheckError { + Mismatch(HashMap>), + Parse, +} + +fn idempotent_check( + filename: &PathBuf, + opt_config: &Option, +) -> Result { + let sig_comments = read_significant_comments(filename); + let config = if let Some(ref config_file_path) = opt_config { + Config::from_toml_path(config_file_path).expect("`rustfmt.toml` not found") + } else { + read_config(filename) + }; + let (parsing_errors, source_file, format_report) = format_file(filename, config); + if parsing_errors { + return Err(IdempotentCheckError::Parse); + } + + let mut write_result = HashMap::new(); + for (filename, text) in source_file { + if let FileName::Real(ref filename) = filename { + write_result.insert(filename.to_owned(), text); + } + } + + let target = sig_comments.get("target").map(|x| &(*x)[..]); + + handle_result(write_result, target).map(|_| format_report) +} + +// Reads test config file using the supplied (optional) file name. If there's no file name or the +// file doesn't exist, just return the default config. Otherwise, the file must be read +// successfully. +fn get_config(config_file: Option<&Path>) -> Config { + let config_file_name = match config_file { + None => return Default::default(), + Some(file_name) => { + let mut full_path = PathBuf::from("tests/config/"); + full_path.push(file_name); + if !full_path.exists() { + return Default::default(); + }; + full_path + } + }; + + let mut def_config_file = fs::File::open(config_file_name).expect("couldn't open config"); + let mut def_config = String::new(); + def_config_file + .read_to_string(&mut def_config) + .expect("Couldn't read config"); + + Config::from_toml(&def_config, Path::new("tests/config/")).expect("invalid TOML") +} + +// Reads significant comments of the form: `// rustfmt-key: value` into a hash map. +fn read_significant_comments(file_name: &Path) -> HashMap { + let file = fs::File::open(file_name) + .unwrap_or_else(|_| panic!("couldn't read file {}", file_name.display())); + let reader = BufReader::new(file); + let pattern = r"^\s*//\s*rustfmt-([^:]+):\s*(\S+)"; + let regex = regex::Regex::new(pattern).expect("failed creating pattern 1"); + + // Matches lines containing significant comments or whitespace. + let line_regex = regex::Regex::new(r"(^\s*$)|(^\s*//\s*rustfmt-[^:]+:\s*\S+)") + .expect("failed creating pattern 2"); + + reader + .lines() + .map(|line| line.expect("failed getting line")) + .take_while(|line| line_regex.is_match(line)) + .filter_map(|line| { + regex.captures_iter(&line).next().map(|capture| { + ( + capture + .get(1) + .expect("couldn't unwrap capture") + .as_str() + .to_owned(), + capture + .get(2) + .expect("couldn't unwrap capture") + .as_str() + .to_owned(), + ) + }) + }) + .collect() +} + +// Compares output to input. +// TODO: needs a better name, more explanation. +fn handle_result( + result: HashMap, + target: Option<&str>, +) -> Result<(), IdempotentCheckError> { + let mut failures = HashMap::new(); + + for (file_name, fmt_text) in result { + // If file is in tests/source, compare to file with same name in tests/target. + let target = get_target(&file_name, target); + let open_error = format!("couldn't open target {:?}", target); + let mut f = fs::File::open(&target).expect(&open_error); + + let mut text = String::new(); + let read_error = format!("failed reading target {:?}", target); + f.read_to_string(&mut text).expect(&read_error); + + // Ignore LF and CRLF difference for Windows. + if !string_eq_ignore_newline_repr(&fmt_text, &text) { + let diff = make_diff(&text, &fmt_text, DIFF_CONTEXT_SIZE); + assert!( + !diff.is_empty(), + "Empty diff? Maybe due to a missing a newline at the end of a file?" + ); + failures.insert(file_name, diff); + } + } + + if failures.is_empty() { + Ok(()) + } else { + Err(IdempotentCheckError::Mismatch(failures)) + } +} + +// Maps source file paths to their target paths. +fn get_target(file_name: &Path, target: Option<&str>) -> PathBuf { + if let Some(n) = file_name + .components() + .position(|c| c.as_os_str() == "source") + { + let mut target_file_name = PathBuf::new(); + for (i, c) in file_name.components().enumerate() { + if i == n { + target_file_name.push("target"); + } else { + target_file_name.push(c.as_os_str()); + } + } + if let Some(replace_name) = target { + target_file_name.with_file_name(replace_name) + } else { + target_file_name + } + } else { + // This is either and idempotence check or a self check. + file_name.to_owned() + } +} + +#[test] +fn rustfmt_diff_make_diff_tests() { + init_log(); + let diff = make_diff("a\nb\nc\nd", "a\ne\nc\nd", 3); + assert_eq!( + diff, + vec![Mismatch { + line_number: 1, + line_number_orig: 1, + lines: vec![ + DiffLine::Context("a".into()), + DiffLine::Resulting("b".into()), + DiffLine::Expected("e".into()), + DiffLine::Context("c".into()), + DiffLine::Context("d".into()), + ], + }] + ); +} + +#[test] +fn rustfmt_diff_no_diff_test() { + init_log(); + let diff = make_diff("a\nb\nc\nd", "a\nb\nc\nd", 3); + assert_eq!(diff, vec![]); +} + +// Compare strings without distinguishing between CRLF and LF +fn string_eq_ignore_newline_repr(left: &str, right: &str) -> bool { + let left = CharsIgnoreNewlineRepr(left.chars().peekable()); + let right = CharsIgnoreNewlineRepr(right.chars().peekable()); + left.eq(right) +} + +struct CharsIgnoreNewlineRepr<'a>(Peekable>); + +impl<'a> Iterator for CharsIgnoreNewlineRepr<'a> { + type Item = char; + + fn next(&mut self) -> Option { + self.0.next().map(|c| { + if c == '\r' { + if *self.0.peek().unwrap_or(&'\0') == '\n' { + self.0.next(); + '\n' + } else { + '\r' + } + } else { + c + } + }) + } +} + +#[test] +fn string_eq_ignore_newline_repr_test() { + init_log(); + assert!(string_eq_ignore_newline_repr("", "")); + assert!(!string_eq_ignore_newline_repr("", "abc")); + assert!(!string_eq_ignore_newline_repr("abc", "")); + assert!(string_eq_ignore_newline_repr("a\nb\nc\rd", "a\nb\r\nc\rd")); + assert!(string_eq_ignore_newline_repr("a\r\n\r\n\r\nb", "a\n\n\nb")); + assert!(!string_eq_ignore_newline_repr("a\r\nbcd", "a\nbcdefghijk")); +} + +struct TempFile { + path: PathBuf, +} + +fn make_temp_file(file_name: &'static str) -> TempFile { + use std::env::var; + use std::fs::File; + + // Used in the Rust build system. + let target_dir = var("RUSTFMT_TEST_DIR").unwrap_or_else(|_| ".".to_owned()); + let path = Path::new(&target_dir).join(file_name); + + let mut file = File::create(&path).expect("couldn't create temp file"); + let content = "fn main() {}\n"; + file.write_all(content.as_bytes()) + .expect("couldn't write temp file"); + TempFile { path } +} + +impl Drop for TempFile { + fn drop(&mut self) { + use std::fs::remove_file; + remove_file(&self.path).expect("couldn't delete temp file"); + } +} + +fn rustfmt() -> PathBuf { + let mut me = env::current_exe().expect("failed to get current executable"); + // Chop of the test name. + me.pop(); + // Chop off `deps`. + me.pop(); + + // If we run `cargo test --release`, we might only have a release build. + if cfg!(release) { + // `../release/` + me.pop(); + me.push("release"); + } + me.push("rustfmt"); + assert!( + me.is_file() || me.with_extension("exe").is_file(), + if cfg!(release) { + "no rustfmt bin, try running `cargo build --release` before testing" + } else { + "no rustfmt bin, try running `cargo build` before testing" + } + ); + me +} + +#[test] +fn verify_check_works() { + init_log(); + let temp_file = make_temp_file("temp_check.rs"); + + Command::new(rustfmt().to_str().unwrap()) + .arg("--check") + .arg(temp_file.path.to_str().unwrap()) + .status() + .expect("run with check option failed"); +} diff --git a/src/tools/rustfmt/src/test/parser.rs b/src/tools/rustfmt/src/test/parser.rs new file mode 100644 index 0000000000..ae4a4f94d9 --- /dev/null +++ b/src/tools/rustfmt/src/test/parser.rs @@ -0,0 +1,57 @@ +use std::io; +use std::path::PathBuf; + +use super::read_config; + +use crate::modules::{ModuleResolutionError, ModuleResolutionErrorKind}; +use crate::{ErrorKind, Input, Session}; + +#[test] +fn parser_errors_in_submods_are_surfaced() { + // See also https://github.com/rust-lang/rustfmt/issues/4126 + let filename = "tests/parser/issue-4126/lib.rs"; + let input_file = PathBuf::from(filename); + let exp_mod_name = "invalid"; + let config = read_config(&input_file); + let mut session = Session::::new(config, None); + if let Err(ErrorKind::ModuleResolutionError(ModuleResolutionError { module, kind })) = + session.format(Input::File(filename.into())) + { + assert_eq!(&module, exp_mod_name); + if let ModuleResolutionErrorKind::ParseError { + file: unparseable_file, + } = kind + { + assert_eq!( + unparseable_file, + PathBuf::from("tests/parser/issue-4126/invalid.rs"), + ); + } else { + panic!("Expected parser error"); + } + } else { + panic!("Expected ModuleResolution operation error"); + } +} + +fn assert_parser_error(filename: &str) { + let file = PathBuf::from(filename); + let config = read_config(&file); + let mut session = Session::::new(config, None); + let _ = session.format(Input::File(filename.into())).unwrap(); + assert!(session.has_parsing_errors()); +} + +#[test] +fn parser_creation_errors_on_entry_new_parser_from_file_panic() { + // See also https://github.com/rust-lang/rustfmt/issues/4418 + let filename = "tests/parser/issue_4418.rs"; + assert_parser_error(filename); +} + +#[test] +fn crate_parsing_errors_on_unclosed_delims() { + // See also https://github.com/rust-lang/rustfmt/issues/4466 + let filename = "tests/parser/unclosed-delims/issue_4466.rs"; + assert_parser_error(filename); +} diff --git a/src/tools/rustfmt/src/types.rs b/src/tools/rustfmt/src/types.rs new file mode 100644 index 0000000000..cda17e13ee --- /dev/null +++ b/src/tools/rustfmt/src/types.rs @@ -0,0 +1,1051 @@ +use std::iter::ExactSizeIterator; +use std::ops::Deref; + +use rustc_ast::ast::{self, FnRetTy, Mutability}; +use rustc_span::{symbol::kw, BytePos, Pos, Span}; + +use crate::comment::{combine_strs_with_missing_comments, contains_comment}; +use crate::config::lists::*; +use crate::config::{IndentStyle, TypeDensity, Version}; +use crate::expr::{ + format_expr, rewrite_assign_rhs, rewrite_call, rewrite_tuple, rewrite_unary_prefix, ExprType, +}; +use crate::lists::{ + definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator, +}; +use crate::macros::{rewrite_macro, MacroPosition}; +use crate::overflow; +use crate::pairs::{rewrite_pair, PairParts}; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::source_map::SpanUtils; +use crate::spanned::Spanned; +use crate::utils::{ + colon_spaces, extra_offset, first_line_width, format_extern, format_mutability, + last_line_extendable, last_line_width, mk_sp, rewrite_ident, +}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum PathContext { + Expr, + Type, + Import, +} + +// Does not wrap on simple segments. +pub(crate) fn rewrite_path( + context: &RewriteContext<'_>, + path_context: PathContext, + qself: Option<&ast::QSelf>, + path: &ast::Path, + shape: Shape, +) -> Option { + let skip_count = qself.map_or(0, |x| x.position); + + let mut result = if path.is_global() && qself.is_none() && path_context != PathContext::Import { + "::".to_owned() + } else { + String::new() + }; + + let mut span_lo = path.span.lo(); + + if let Some(qself) = qself { + result.push('<'); + + let fmt_ty = qself.ty.rewrite(context, shape)?; + result.push_str(&fmt_ty); + + if skip_count > 0 { + result.push_str(" as "); + if path.is_global() && path_context != PathContext::Import { + result.push_str("::"); + } + + // 3 = ">::".len() + let shape = shape.sub_width(3)?; + + result = rewrite_path_segments( + PathContext::Type, + result, + path.segments.iter().take(skip_count), + span_lo, + path.span.hi(), + context, + shape, + )?; + } + + result.push_str(">::"); + span_lo = qself.ty.span.hi() + BytePos(1); + } + + rewrite_path_segments( + path_context, + result, + path.segments.iter().skip(skip_count), + span_lo, + path.span.hi(), + context, + shape, + ) +} + +fn rewrite_path_segments<'a, I>( + path_context: PathContext, + mut buffer: String, + iter: I, + mut span_lo: BytePos, + span_hi: BytePos, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option +where + I: Iterator, +{ + let mut first = true; + let shape = shape.visual_indent(0); + + for segment in iter { + // Indicates a global path, shouldn't be rendered. + if segment.ident.name == kw::PathRoot { + continue; + } + if first { + first = false; + } else { + buffer.push_str("::"); + } + + let extra_offset = extra_offset(&buffer, shape); + let new_shape = shape.shrink_left(extra_offset)?; + let segment_string = rewrite_segment( + path_context, + segment, + &mut span_lo, + span_hi, + context, + new_shape, + )?; + + buffer.push_str(&segment_string); + } + + Some(buffer) +} + +#[derive(Debug)] +pub(crate) enum SegmentParam<'a> { + Const(&'a ast::AnonConst), + LifeTime(&'a ast::Lifetime), + Type(&'a ast::Ty), + Binding(&'a ast::AssocTyConstraint), +} + +impl<'a> SegmentParam<'a> { + fn from_generic_arg(arg: &ast::GenericArg) -> SegmentParam<'_> { + match arg { + ast::GenericArg::Lifetime(ref lt) => SegmentParam::LifeTime(lt), + ast::GenericArg::Type(ref ty) => SegmentParam::Type(ty), + ast::GenericArg::Const(const_) => SegmentParam::Const(const_), + } + } +} + +impl<'a> Spanned for SegmentParam<'a> { + fn span(&self) -> Span { + match *self { + SegmentParam::Const(const_) => const_.value.span, + SegmentParam::LifeTime(lt) => lt.ident.span, + SegmentParam::Type(ty) => ty.span, + SegmentParam::Binding(binding) => binding.span, + } + } +} + +impl<'a> Rewrite for SegmentParam<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match *self { + SegmentParam::Const(const_) => const_.rewrite(context, shape), + SegmentParam::LifeTime(lt) => lt.rewrite(context, shape), + SegmentParam::Type(ty) => ty.rewrite(context, shape), + SegmentParam::Binding(assoc_ty_constraint) => { + let mut result = match assoc_ty_constraint.kind { + ast::AssocTyConstraintKind::Bound { .. } => { + format!("{}: ", rewrite_ident(context, assoc_ty_constraint.ident)) + } + ast::AssocTyConstraintKind::Equality { .. } => { + match context.config.type_punctuation_density() { + TypeDensity::Wide => { + format!("{} = ", rewrite_ident(context, assoc_ty_constraint.ident)) + } + TypeDensity::Compressed => { + format!("{}=", rewrite_ident(context, assoc_ty_constraint.ident)) + } + } + } + }; + + let budget = shape.width.checked_sub(result.len())?; + let rewrite = assoc_ty_constraint + .kind + .rewrite(context, Shape::legacy(budget, shape.indent + result.len()))?; + result.push_str(&rewrite); + Some(result) + } + } + } +} + +impl Rewrite for ast::AssocTyConstraintKind { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match self { + ast::AssocTyConstraintKind::Equality { ty } => ty.rewrite(context, shape), + ast::AssocTyConstraintKind::Bound { bounds } => bounds.rewrite(context, shape), + } + } +} + +// Formats a path segment. There are some hacks involved to correctly determine +// the segment's associated span since it's not part of the AST. +// +// The span_lo is assumed to be greater than the end of any previous segment's +// parameters and lesser or equal than the start of current segment. +// +// span_hi is assumed equal to the end of the entire path. +// +// When the segment contains a positive number of parameters, we update span_lo +// so that invariants described above will hold for the next segment. +fn rewrite_segment( + path_context: PathContext, + segment: &ast::PathSegment, + span_lo: &mut BytePos, + span_hi: BytePos, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let mut result = String::with_capacity(128); + result.push_str(rewrite_ident(context, segment.ident)); + + let ident_len = result.len(); + let shape = if context.use_block_indent() { + shape.offset_left(ident_len)? + } else { + shape.shrink_left(ident_len)? + }; + + if let Some(ref args) = segment.args { + match **args { + ast::GenericArgs::AngleBracketed(ref data) if !data.args.is_empty() => { + let param_list = data + .args + .iter() + .map(|x| match x { + ast::AngleBracketedArg::Arg(generic_arg) => { + SegmentParam::from_generic_arg(generic_arg) + } + ast::AngleBracketedArg::Constraint(constraint) => { + SegmentParam::Binding(constraint) + } + }) + .collect::>(); + + // HACK: squeeze out the span between the identifier and the parameters. + // The hack is requried so that we don't remove the separator inside macro calls. + // This does not work in the presence of comment, hoping that people are + // sane about where to put their comment. + let separator_snippet = context + .snippet(mk_sp(segment.ident.span.hi(), data.span.lo())) + .trim(); + let force_separator = context.inside_macro() && separator_snippet.starts_with("::"); + let separator = if path_context == PathContext::Expr || force_separator { + "::" + } else { + "" + }; + result.push_str(separator); + + let generics_str = overflow::rewrite_with_angle_brackets( + context, + "", + param_list.iter(), + shape, + mk_sp(*span_lo, span_hi), + )?; + + // Update position of last bracket. + *span_lo = context + .snippet_provider + .span_after(mk_sp(*span_lo, span_hi), "<"); + + result.push_str(&generics_str) + } + ast::GenericArgs::Parenthesized(ref data) => { + result.push_str(&format_function_type( + data.inputs.iter().map(|x| &**x), + &data.output, + false, + data.span, + context, + shape, + )?); + } + _ => (), + } + } + + Some(result) +} + +fn format_function_type<'a, I>( + inputs: I, + output: &FnRetTy, + variadic: bool, + span: Span, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option +where + I: ExactSizeIterator, + ::Item: Deref, + ::Target: Rewrite + Spanned + 'a, +{ + debug!("format_function_type {:#?}", shape); + + let ty_shape = match context.config.indent_style() { + // 4 = " -> " + IndentStyle::Block => shape.offset_left(4)?, + IndentStyle::Visual => shape.block_left(4)?, + }; + let output = match *output { + FnRetTy::Ty(ref ty) => { + let type_str = ty.rewrite(context, ty_shape)?; + format!(" -> {}", type_str) + } + FnRetTy::Default(..) => String::new(), + }; + + let list_shape = if context.use_block_indent() { + Shape::indented( + shape.block().indent.block_indent(context.config), + context.config, + ) + } else { + // 2 for () + let budget = shape.width.checked_sub(2)?; + // 1 for ( + let offset = shape.indent + 1; + Shape::legacy(budget, offset) + }; + + let is_inputs_empty = inputs.len() == 0; + let list_lo = context.snippet_provider.span_after(span, "("); + let (list_str, tactic) = if is_inputs_empty { + let tactic = get_tactics(&[], &output, shape); + let list_hi = context.snippet_provider.span_before(span, ")"); + let comment = context + .snippet_provider + .span_to_snippet(mk_sp(list_lo, list_hi))? + .trim(); + let comment = if comment.starts_with("//") { + format!( + "{}{}{}", + &list_shape.indent.to_string_with_newline(context.config), + comment, + &shape.block().indent.to_string_with_newline(context.config) + ) + } else { + comment.to_string() + }; + (comment, tactic) + } else { + let items = itemize_list( + context.snippet_provider, + inputs, + ")", + ",", + |arg| arg.span().lo(), + |arg| arg.span().hi(), + |arg| arg.rewrite(context, list_shape), + list_lo, + span.hi(), + false, + ); + + let item_vec: Vec<_> = items.collect(); + let tactic = get_tactics(&item_vec, &output, shape); + let trailing_separator = if !context.use_block_indent() || variadic { + SeparatorTactic::Never + } else { + context.config.trailing_comma() + }; + + let fmt = ListFormatting::new(list_shape, context.config) + .tactic(tactic) + .trailing_separator(trailing_separator) + .ends_with_newline(tactic.ends_with_newline(context.config.indent_style())) + .preserve_newline(true); + (write_list(&item_vec, &fmt)?, tactic) + }; + + let args = if tactic == DefinitiveListTactic::Horizontal + || !context.use_block_indent() + || is_inputs_empty + { + format!("({})", list_str) + } else { + format!( + "({}{}{})", + list_shape.indent.to_string_with_newline(context.config), + list_str, + shape.block().indent.to_string_with_newline(context.config), + ) + }; + if output.is_empty() || last_line_width(&args) + first_line_width(&output) <= shape.width { + Some(format!("{}{}", args, output)) + } else { + Some(format!( + "{}\n{}{}", + args, + list_shape.indent.to_string(context.config), + output.trim_start() + )) + } +} + +fn type_bound_colon(context: &RewriteContext<'_>) -> &'static str { + colon_spaces(context.config) +} + +// If the return type is multi-lined, then force to use multiple lines for +// arguments as well. +fn get_tactics(item_vec: &[ListItem], output: &str, shape: Shape) -> DefinitiveListTactic { + if output.contains('\n') { + DefinitiveListTactic::Vertical + } else { + definitive_tactic( + item_vec, + ListTactic::HorizontalVertical, + Separator::Comma, + // 2 is for the case of ',\n' + shape.width.saturating_sub(2 + output.len()), + ) + } +} + +impl Rewrite for ast::WherePredicate { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + // FIXME: dead spans? + let result = match *self { + ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate { + ref bound_generic_params, + ref bounded_ty, + ref bounds, + .. + }) => { + let type_str = bounded_ty.rewrite(context, shape)?; + let colon = type_bound_colon(context).trim_end(); + let lhs = if let Some(lifetime_str) = + rewrite_lifetime_param(context, shape, bound_generic_params) + { + format!("for<{}> {}{}", lifetime_str, type_str, colon) + } else { + format!("{}{}", type_str, colon) + }; + + rewrite_assign_rhs(context, lhs, bounds, shape)? + } + ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate { + ref lifetime, + ref bounds, + .. + }) => rewrite_bounded_lifetime(lifetime, bounds, context, shape)?, + ast::WherePredicate::EqPredicate(ast::WhereEqPredicate { + ref lhs_ty, + ref rhs_ty, + .. + }) => { + let lhs_ty_str = lhs_ty.rewrite(context, shape).map(|lhs| lhs + " =")?; + rewrite_assign_rhs(context, lhs_ty_str, &**rhs_ty, shape)? + } + }; + + Some(result) + } +} + +impl Rewrite for ast::GenericArg { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match *self { + ast::GenericArg::Lifetime(ref lt) => lt.rewrite(context, shape), + ast::GenericArg::Type(ref ty) => ty.rewrite(context, shape), + ast::GenericArg::Const(ref const_) => const_.rewrite(context, shape), + } + } +} + +fn rewrite_bounded_lifetime( + lt: &ast::Lifetime, + bounds: &[ast::GenericBound], + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let result = lt.rewrite(context, shape)?; + + if bounds.is_empty() { + Some(result) + } else { + let colon = type_bound_colon(context); + let overhead = last_line_width(&result) + colon.len(); + let result = format!( + "{}{}{}", + result, + colon, + join_bounds(context, shape.sub_width(overhead)?, bounds, true)? + ); + Some(result) + } +} + +impl Rewrite for ast::AnonConst { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + format_expr(&self.value, ExprType::SubExpression, context, shape) + } +} + +impl Rewrite for ast::Lifetime { + fn rewrite(&self, context: &RewriteContext<'_>, _: Shape) -> Option { + Some(rewrite_ident(context, self.ident).to_owned()) + } +} + +impl Rewrite for ast::GenericBound { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match *self { + ast::GenericBound::Trait(ref poly_trait_ref, trait_bound_modifier) => { + let snippet = context.snippet(self.span()); + let has_paren = snippet.starts_with('(') && snippet.ends_with(')'); + let rewrite = match trait_bound_modifier { + ast::TraitBoundModifier::None => poly_trait_ref.rewrite(context, shape), + ast::TraitBoundModifier::Maybe => poly_trait_ref + .rewrite(context, shape.offset_left(1)?) + .map(|s| format!("?{}", s)), + ast::TraitBoundModifier::MaybeConst => poly_trait_ref + .rewrite(context, shape.offset_left(7)?) + .map(|s| format!("?const {}", s)), + ast::TraitBoundModifier::MaybeConstMaybe => poly_trait_ref + .rewrite(context, shape.offset_left(8)?) + .map(|s| format!("?const ?{}", s)), + }; + rewrite.map(|s| if has_paren { format!("({})", s) } else { s }) + } + ast::GenericBound::Outlives(ref lifetime) => lifetime.rewrite(context, shape), + } + } +} + +impl Rewrite for ast::GenericBounds { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + if self.is_empty() { + return Some(String::new()); + } + + join_bounds(context, shape, self, true) + } +} + +impl Rewrite for ast::GenericParam { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let mut result = String::with_capacity(128); + // FIXME: If there are more than one attributes, this will force multiline. + match self.attrs.rewrite(context, shape) { + Some(ref rw) if !rw.is_empty() => result.push_str(&format!("{} ", rw)), + _ => (), + } + + if let ast::GenericParamKind::Const { + ref ty, + kw_span: _, + default: _, + } = &self.kind + { + result.push_str("const "); + result.push_str(rewrite_ident(context, self.ident)); + result.push_str(": "); + result.push_str(&ty.rewrite(context, shape)?); + } else { + result.push_str(rewrite_ident(context, self.ident)); + } + + if !self.bounds.is_empty() { + result.push_str(type_bound_colon(context)); + result.push_str(&self.bounds.rewrite(context, shape)?) + } + if let ast::GenericParamKind::Type { + default: Some(ref def), + } = self.kind + { + let eq_str = match context.config.type_punctuation_density() { + TypeDensity::Compressed => "=", + TypeDensity::Wide => " = ", + }; + result.push_str(eq_str); + let budget = shape.width.checked_sub(result.len())?; + let rewrite = + def.rewrite(context, Shape::legacy(budget, shape.indent + result.len()))?; + result.push_str(&rewrite); + } + + Some(result) + } +} + +impl Rewrite for ast::PolyTraitRef { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + if let Some(lifetime_str) = + rewrite_lifetime_param(context, shape, &self.bound_generic_params) + { + // 6 is "for<> ".len() + let extra_offset = lifetime_str.len() + 6; + let path_str = self + .trait_ref + .rewrite(context, shape.offset_left(extra_offset)?)?; + + Some(format!("for<{}> {}", lifetime_str, path_str)) + } else { + self.trait_ref.rewrite(context, shape) + } + } +} + +impl Rewrite for ast::TraitRef { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + rewrite_path(context, PathContext::Type, None, &self.path, shape) + } +} + +impl Rewrite for ast::Ty { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + match self.kind { + ast::TyKind::TraitObject(ref bounds, tobj_syntax) => { + // we have to consider 'dyn' keyword is used or not!!! + let is_dyn = tobj_syntax == ast::TraitObjectSyntax::Dyn; + // 4 is length of 'dyn ' + let shape = if is_dyn { shape.offset_left(4)? } else { shape }; + let mut res = bounds.rewrite(context, shape)?; + // We may have falsely removed a trailing `+` inside macro call. + if context.inside_macro() && bounds.len() == 1 { + if context.snippet(self.span).ends_with('+') && !res.ends_with('+') { + res.push('+'); + } + } + if is_dyn { + Some(format!("dyn {}", res)) + } else { + Some(res) + } + } + ast::TyKind::Ptr(ref mt) => { + let prefix = match mt.mutbl { + Mutability::Mut => "*mut ", + Mutability::Not => "*const ", + }; + + rewrite_unary_prefix(context, prefix, &*mt.ty, shape) + } + ast::TyKind::Rptr(ref lifetime, ref mt) => { + let mut_str = format_mutability(mt.mutbl); + let mut_len = mut_str.len(); + let mut result = String::with_capacity(128); + result.push_str("&"); + let ref_hi = context.snippet_provider.span_after(self.span(), "&"); + let mut cmnt_lo = ref_hi; + + if let Some(ref lifetime) = *lifetime { + let lt_budget = shape.width.checked_sub(2 + mut_len)?; + let lt_str = lifetime.rewrite( + context, + Shape::legacy(lt_budget, shape.indent + 2 + mut_len), + )?; + let before_lt_span = mk_sp(cmnt_lo, lifetime.ident.span.lo()); + if contains_comment(context.snippet(before_lt_span)) { + result = combine_strs_with_missing_comments( + context, + &result, + <_str, + before_lt_span, + shape, + true, + )?; + } else { + result.push_str(<_str); + } + result.push_str(" "); + cmnt_lo = lifetime.ident.span.hi(); + } + + if ast::Mutability::Mut == mt.mutbl { + let mut_hi = context.snippet_provider.span_after(self.span(), "mut"); + let before_mut_span = mk_sp(cmnt_lo, mut_hi - BytePos::from_usize(3)); + if contains_comment(context.snippet(before_mut_span)) { + result = combine_strs_with_missing_comments( + context, + result.trim_end(), + mut_str, + before_mut_span, + shape, + true, + )?; + } else { + result.push_str(mut_str); + } + cmnt_lo = mut_hi; + } + + let before_ty_span = mk_sp(cmnt_lo, mt.ty.span.lo()); + if contains_comment(context.snippet(before_ty_span)) { + result = combine_strs_with_missing_comments( + context, + result.trim_end(), + &mt.ty.rewrite(&context, shape)?, + before_ty_span, + shape, + true, + )?; + } else { + let used_width = last_line_width(&result); + let budget = shape.width.checked_sub(used_width)?; + let ty_str = mt + .ty + .rewrite(&context, Shape::legacy(budget, shape.indent + used_width))?; + result.push_str(&ty_str); + } + + Some(result) + } + // FIXME: we drop any comments here, even though it's a silly place to put + // comments. + ast::TyKind::Paren(ref ty) => { + if context.config.version() == Version::One + || context.config.indent_style() == IndentStyle::Visual + { + let budget = shape.width.checked_sub(2)?; + return ty + .rewrite(context, Shape::legacy(budget, shape.indent + 1)) + .map(|ty_str| format!("({})", ty_str)); + } + + // 2 = () + if let Some(sh) = shape.sub_width(2) { + if let Some(ref s) = ty.rewrite(context, sh) { + if !s.contains('\n') { + return Some(format!("({})", s)); + } + } + } + + let indent_str = shape.indent.to_string_with_newline(context.config); + let shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + let rw = ty.rewrite(context, shape)?; + Some(format!( + "({}{}{})", + shape.to_string_with_newline(context.config), + rw, + indent_str + )) + } + ast::TyKind::Slice(ref ty) => { + let budget = shape.width.checked_sub(4)?; + ty.rewrite(context, Shape::legacy(budget, shape.indent + 1)) + .map(|ty_str| format!("[{}]", ty_str)) + } + ast::TyKind::Tup(ref items) => { + rewrite_tuple(context, items.iter(), self.span, shape, items.len() == 1) + } + ast::TyKind::Path(ref q_self, ref path) => { + rewrite_path(context, PathContext::Type, q_self.as_ref(), path, shape) + } + ast::TyKind::Array(ref ty, ref repeats) => rewrite_pair( + &**ty, + &*repeats.value, + PairParts::new("[", "; ", "]"), + context, + shape, + SeparatorPlace::Back, + ), + ast::TyKind::Infer => { + if shape.width >= 1 { + Some("_".to_owned()) + } else { + None + } + } + ast::TyKind::BareFn(ref bare_fn) => rewrite_bare_fn(bare_fn, self.span, context, shape), + ast::TyKind::Never => Some(String::from("!")), + ast::TyKind::MacCall(ref mac) => { + rewrite_macro(mac, None, context, shape, MacroPosition::Expression) + } + ast::TyKind::ImplicitSelf => Some(String::from("")), + ast::TyKind::ImplTrait(_, ref it) => { + // Empty trait is not a parser error. + if it.is_empty() { + return Some("impl".to_owned()); + } + let rw = if context.config.version() == Version::One { + it.rewrite(context, shape) + } else { + join_bounds(context, shape, it, false) + }; + rw.map(|it_str| { + let space = if it_str.is_empty() { "" } else { " " }; + format!("impl{}{}", space, it_str) + }) + } + ast::TyKind::CVarArgs => Some("...".to_owned()), + ast::TyKind::Err => Some(context.snippet(self.span).to_owned()), + ast::TyKind::Typeof(ref anon_const) => rewrite_call( + context, + "typeof", + &[anon_const.value.clone()], + self.span, + shape, + ), + } + } +} + +fn rewrite_bare_fn( + bare_fn: &ast::BareFnTy, + span: Span, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + debug!("rewrite_bare_fn {:#?}", shape); + + let mut result = String::with_capacity(128); + + if let Some(ref lifetime_str) = rewrite_lifetime_param(context, shape, &bare_fn.generic_params) + { + result.push_str("for<"); + // 6 = "for<> ".len(), 4 = "for<". + // This doesn't work out so nicely for multiline situation with lots of + // rightward drift. If that is a problem, we could use the list stuff. + result.push_str(lifetime_str); + result.push_str("> "); + } + + result.push_str(crate::utils::format_unsafety(bare_fn.unsafety)); + + result.push_str(&format_extern( + bare_fn.ext, + context.config.force_explicit_abi(), + false, + )); + + result.push_str("fn"); + + let func_ty_shape = if context.use_block_indent() { + shape.offset_left(result.len())? + } else { + shape.visual_indent(result.len()).sub_width(result.len())? + }; + + let rewrite = format_function_type( + bare_fn.decl.inputs.iter(), + &bare_fn.decl.output, + bare_fn.decl.c_variadic(), + span, + context, + func_ty_shape, + )?; + + result.push_str(&rewrite); + + Some(result) +} + +fn is_generic_bounds_in_order(generic_bounds: &[ast::GenericBound]) -> bool { + let is_trait = |b: &ast::GenericBound| match b { + ast::GenericBound::Outlives(..) => false, + ast::GenericBound::Trait(..) => true, + }; + let is_lifetime = |b: &ast::GenericBound| !is_trait(b); + let last_trait_index = generic_bounds.iter().rposition(is_trait); + let first_lifetime_index = generic_bounds.iter().position(is_lifetime); + match (last_trait_index, first_lifetime_index) { + (Some(last_trait_index), Some(first_lifetime_index)) => { + last_trait_index < first_lifetime_index + } + _ => true, + } +} + +fn join_bounds( + context: &RewriteContext<'_>, + shape: Shape, + items: &[ast::GenericBound], + need_indent: bool, +) -> Option { + join_bounds_inner(context, shape, items, need_indent, false) +} + +fn join_bounds_inner( + context: &RewriteContext<'_>, + shape: Shape, + items: &[ast::GenericBound], + need_indent: bool, + force_newline: bool, +) -> Option { + debug_assert!(!items.is_empty()); + + let generic_bounds_in_order = is_generic_bounds_in_order(items); + let is_bound_extendable = |s: &str, b: &ast::GenericBound| match b { + ast::GenericBound::Outlives(..) => true, + ast::GenericBound::Trait(..) => last_line_extendable(s), + }; + + let result = items.iter().enumerate().try_fold( + (String::new(), None, false), + |(strs, prev_trailing_span, prev_extendable), (i, item)| { + let trailing_span = if i < items.len() - 1 { + let hi = context + .snippet_provider + .span_before(mk_sp(items[i + 1].span().lo(), item.span().hi()), "+"); + + Some(mk_sp(item.span().hi(), hi)) + } else { + None + }; + let (leading_span, has_leading_comment) = if i > 0 { + let lo = context + .snippet_provider + .span_after(mk_sp(items[i - 1].span().hi(), item.span().lo()), "+"); + + let span = mk_sp(lo, item.span().lo()); + + let has_comments = contains_comment(context.snippet(span)); + + (Some(mk_sp(lo, item.span().lo())), has_comments) + } else { + (None, false) + }; + let prev_has_trailing_comment = match prev_trailing_span { + Some(ts) => contains_comment(context.snippet(ts)), + _ => false, + }; + + let shape = if need_indent && force_newline { + shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config) + } else { + shape + }; + let whitespace = if force_newline && (!prev_extendable || !generic_bounds_in_order) { + shape + .indent + .to_string_with_newline(context.config) + .to_string() + } else { + String::from(" ") + }; + + let joiner = match context.config.type_punctuation_density() { + TypeDensity::Compressed => String::from("+"), + TypeDensity::Wide => whitespace + "+ ", + }; + let joiner = if has_leading_comment { + joiner.trim_end() + } else { + &joiner + }; + let joiner = if prev_has_trailing_comment { + joiner.trim_start() + } else { + joiner + }; + + let (extendable, trailing_str) = if i == 0 { + let bound_str = item.rewrite(context, shape)?; + (is_bound_extendable(&bound_str, item), bound_str) + } else { + let bound_str = &item.rewrite(context, shape)?; + match leading_span { + Some(ls) if has_leading_comment => ( + is_bound_extendable(bound_str, item), + combine_strs_with_missing_comments( + context, joiner, bound_str, ls, shape, true, + )?, + ), + _ => ( + is_bound_extendable(bound_str, item), + String::from(joiner) + bound_str, + ), + } + }; + match prev_trailing_span { + Some(ts) if prev_has_trailing_comment => combine_strs_with_missing_comments( + context, + &strs, + &trailing_str, + ts, + shape, + true, + ) + .map(|v| (v, trailing_span, extendable)), + _ => Some(( + String::from(strs) + &trailing_str, + trailing_span, + extendable, + )), + } + }, + )?; + + if !force_newline + && items.len() > 1 + && (result.0.contains('\n') || result.0.len() > shape.width) + { + join_bounds_inner(context, shape, items, need_indent, true) + } else { + Some(result.0) + } +} + +pub(crate) fn can_be_overflowed_type( + context: &RewriteContext<'_>, + ty: &ast::Ty, + len: usize, +) -> bool { + match ty.kind { + ast::TyKind::Tup(..) => context.use_block_indent() && len == 1, + ast::TyKind::Rptr(_, ref mutty) | ast::TyKind::Ptr(ref mutty) => { + can_be_overflowed_type(context, &*mutty.ty, len) + } + _ => false, + } +} + +/// Returns `None` if there is no `LifetimeDef` in the given generic parameters. +fn rewrite_lifetime_param( + context: &RewriteContext<'_>, + shape: Shape, + generic_params: &[ast::GenericParam], +) -> Option { + let result = generic_params + .iter() + .filter(|p| match p.kind { + ast::GenericParamKind::Lifetime => true, + _ => false, + }) + .map(|lt| lt.rewrite(context, shape)) + .collect::>>()? + .join(", "); + if result.is_empty() { + None + } else { + Some(result) + } +} diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs new file mode 100644 index 0000000000..a3d0ed050e --- /dev/null +++ b/src/tools/rustfmt/src/utils.rs @@ -0,0 +1,702 @@ +use std::borrow::Cow; + +use rustc_ast::ast::{ + self, Attribute, CrateSugar, MetaItem, MetaItemKind, NestedMetaItem, NodeId, Path, Visibility, + VisibilityKind, +}; +use rustc_ast::ptr; +use rustc_ast_pretty::pprust; +use rustc_span::{sym, symbol, BytePos, ExpnId, Span, Symbol, SyntaxContext}; +use unicode_width::UnicodeWidthStr; + +use crate::comment::{filter_normal_code, CharClasses, FullCodeCharKind, LineClasses}; +use crate::config::{Config, Version}; +use crate::rewrite::RewriteContext; +use crate::shape::{Indent, Shape}; + +#[inline] +pub(crate) fn depr_skip_annotation() -> Symbol { + Symbol::intern("rustfmt_skip") +} + +#[inline] +pub(crate) fn skip_annotation() -> Symbol { + Symbol::intern("rustfmt::skip") +} + +pub(crate) fn rewrite_ident<'a>(context: &'a RewriteContext<'_>, ident: symbol::Ident) -> &'a str { + context.snippet(ident.span) +} + +// Computes the length of a string's last line, minus offset. +pub(crate) fn extra_offset(text: &str, shape: Shape) -> usize { + match text.rfind('\n') { + // 1 for newline character + Some(idx) => text.len().saturating_sub(idx + 1 + shape.used_width()), + None => text.len(), + } +} + +pub(crate) fn is_same_visibility(a: &Visibility, b: &Visibility) -> bool { + match (&a.kind, &b.kind) { + ( + VisibilityKind::Restricted { path: p, .. }, + VisibilityKind::Restricted { path: q, .. }, + ) => pprust::path_to_string(&p) == pprust::path_to_string(&q), + (VisibilityKind::Public, VisibilityKind::Public) + | (VisibilityKind::Inherited, VisibilityKind::Inherited) + | ( + VisibilityKind::Crate(CrateSugar::PubCrate), + VisibilityKind::Crate(CrateSugar::PubCrate), + ) + | ( + VisibilityKind::Crate(CrateSugar::JustCrate), + VisibilityKind::Crate(CrateSugar::JustCrate), + ) => true, + _ => false, + } +} + +// Uses Cow to avoid allocating in the common cases. +pub(crate) fn format_visibility( + context: &RewriteContext<'_>, + vis: &Visibility, +) -> Cow<'static, str> { + match vis.kind { + VisibilityKind::Public => Cow::from("pub "), + VisibilityKind::Inherited => Cow::from(""), + VisibilityKind::Crate(CrateSugar::PubCrate) => Cow::from("pub(crate) "), + VisibilityKind::Crate(CrateSugar::JustCrate) => Cow::from("crate "), + VisibilityKind::Restricted { ref path, .. } => { + let Path { ref segments, .. } = **path; + let mut segments_iter = segments.iter().map(|seg| rewrite_ident(context, seg.ident)); + if path.is_global() { + segments_iter + .next() + .expect("Non-global path in pub(restricted)?"); + } + let is_keyword = |s: &str| s == "self" || s == "super"; + let path = segments_iter.collect::>().join("::"); + let in_str = if is_keyword(&path) { "" } else { "in " }; + + Cow::from(format!("pub({}{}) ", in_str, path)) + } + } +} + +#[inline] +pub(crate) fn format_async(is_async: &ast::Async) -> &'static str { + match is_async { + ast::Async::Yes { .. } => "async ", + ast::Async::No => "", + } +} + +#[inline] +pub(crate) fn format_constness(constness: ast::Const) -> &'static str { + match constness { + ast::Const::Yes(..) => "const ", + ast::Const::No => "", + } +} + +#[inline] +pub(crate) fn format_constness_right(constness: ast::Const) -> &'static str { + match constness { + ast::Const::Yes(..) => " const", + ast::Const::No => "", + } +} + +#[inline] +pub(crate) fn format_defaultness(defaultness: ast::Defaultness) -> &'static str { + match defaultness { + ast::Defaultness::Default(..) => "default ", + ast::Defaultness::Final => "", + } +} + +#[inline] +pub(crate) fn format_unsafety(unsafety: ast::Unsafe) -> &'static str { + match unsafety { + ast::Unsafe::Yes(..) => "unsafe ", + ast::Unsafe::No => "", + } +} + +#[inline] +pub(crate) fn format_auto(is_auto: ast::IsAuto) -> &'static str { + match is_auto { + ast::IsAuto::Yes => "auto ", + ast::IsAuto::No => "", + } +} + +#[inline] +pub(crate) fn format_mutability(mutability: ast::Mutability) -> &'static str { + match mutability { + ast::Mutability::Mut => "mut ", + ast::Mutability::Not => "", + } +} + +#[inline] +pub(crate) fn format_extern( + ext: ast::Extern, + explicit_abi: bool, + is_mod: bool, +) -> Cow<'static, str> { + let abi = match ext { + ast::Extern::None => "Rust".to_owned(), + ast::Extern::Implicit => "C".to_owned(), + ast::Extern::Explicit(abi) => abi.symbol_unescaped.to_string(), + }; + + if abi == "Rust" && !is_mod { + Cow::from("") + } else if abi == "C" && !explicit_abi { + Cow::from("extern ") + } else { + Cow::from(format!(r#"extern "{}" "#, abi)) + } +} + +#[inline] +// Transform `Vec>` into `Vec<&T>` +pub(crate) fn ptr_vec_to_ref_vec(vec: &[ptr::P]) -> Vec<&T> { + vec.iter().map(|x| &**x).collect::>() +} + +#[inline] +pub(crate) fn filter_attributes( + attrs: &[ast::Attribute], + style: ast::AttrStyle, +) -> Vec { + attrs + .iter() + .filter(|a| a.style == style) + .cloned() + .collect::>() +} + +#[inline] +pub(crate) fn inner_attributes(attrs: &[ast::Attribute]) -> Vec { + filter_attributes(attrs, ast::AttrStyle::Inner) +} + +#[inline] +pub(crate) fn outer_attributes(attrs: &[ast::Attribute]) -> Vec { + filter_attributes(attrs, ast::AttrStyle::Outer) +} + +#[inline] +pub(crate) fn is_single_line(s: &str) -> bool { + s.chars().find(|&c| c == '\n').is_none() +} + +#[inline] +pub(crate) fn first_line_contains_single_line_comment(s: &str) -> bool { + s.lines().next().map_or(false, |l| l.contains("//")) +} + +#[inline] +pub(crate) fn last_line_contains_single_line_comment(s: &str) -> bool { + s.lines().last().map_or(false, |l| l.contains("//")) +} + +#[inline] +pub(crate) fn is_attributes_extendable(attrs_str: &str) -> bool { + !attrs_str.contains('\n') && !last_line_contains_single_line_comment(attrs_str) +} + +/// The width of the first line in s. +#[inline] +pub(crate) fn first_line_width(s: &str) -> usize { + unicode_str_width(s.splitn(2, '\n').next().unwrap_or("")) +} + +/// The width of the last line in s. +#[inline] +pub(crate) fn last_line_width(s: &str) -> usize { + unicode_str_width(s.rsplitn(2, '\n').next().unwrap_or("")) +} + +/// The total used width of the last line. +#[inline] +pub(crate) fn last_line_used_width(s: &str, offset: usize) -> usize { + if s.contains('\n') { + last_line_width(s) + } else { + offset + unicode_str_width(s) + } +} + +#[inline] +pub(crate) fn trimmed_last_line_width(s: &str) -> usize { + unicode_str_width(match s.rfind('\n') { + Some(n) => s[(n + 1)..].trim(), + None => s.trim(), + }) +} + +#[inline] +pub(crate) fn last_line_extendable(s: &str) -> bool { + if s.ends_with("\"#") { + return true; + } + for c in s.chars().rev() { + match c { + '(' | ')' | ']' | '}' | '?' | '>' => continue, + '\n' => break, + _ if c.is_whitespace() => continue, + _ => return false, + } + } + true +} + +#[inline] +fn is_skip(meta_item: &MetaItem) -> bool { + match meta_item.kind { + MetaItemKind::Word => { + let path_str = pprust::path_to_string(&meta_item.path); + path_str == &*skip_annotation().as_str() + || path_str == &*depr_skip_annotation().as_str() + } + MetaItemKind::List(ref l) => { + meta_item.has_name(sym::cfg_attr) && l.len() == 2 && is_skip_nested(&l[1]) + } + _ => false, + } +} + +#[inline] +fn is_skip_nested(meta_item: &NestedMetaItem) -> bool { + match meta_item { + NestedMetaItem::MetaItem(ref mi) => is_skip(mi), + NestedMetaItem::Literal(_) => false, + } +} + +#[inline] +pub(crate) fn contains_skip(attrs: &[Attribute]) -> bool { + attrs + .iter() + .any(|a| a.meta().map_or(false, |a| is_skip(&a))) +} + +#[inline] +pub(crate) fn semicolon_for_expr(context: &RewriteContext<'_>, expr: &ast::Expr) -> bool { + // Never try to insert semicolons on expressions when we're inside + // a macro definition - this can prevent the macro from compiling + // when used in expression position + if context.is_macro_def { + return false; + } + + match expr.kind { + ast::ExprKind::Ret(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Break(..) => { + context.config.trailing_semicolon() + } + _ => false, + } +} + +#[inline] +pub(crate) fn semicolon_for_stmt(context: &RewriteContext<'_>, stmt: &ast::Stmt) -> bool { + match stmt.kind { + ast::StmtKind::Semi(ref expr) => match expr.kind { + ast::ExprKind::While(..) | ast::ExprKind::Loop(..) | ast::ExprKind::ForLoop(..) => { + false + } + ast::ExprKind::Break(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Ret(..) => { + context.config.trailing_semicolon() + } + _ => true, + }, + ast::StmtKind::Expr(..) => false, + _ => true, + } +} + +#[inline] +pub(crate) fn stmt_expr(stmt: &ast::Stmt) -> Option<&ast::Expr> { + match stmt.kind { + ast::StmtKind::Expr(ref expr) => Some(expr), + _ => None, + } +} + +/// Returns the number of LF and CRLF respectively. +pub(crate) fn count_lf_crlf(input: &str) -> (usize, usize) { + let mut lf = 0; + let mut crlf = 0; + let mut is_crlf = false; + for c in input.as_bytes() { + match c { + b'\r' => is_crlf = true, + b'\n' if is_crlf => crlf += 1, + b'\n' => lf += 1, + _ => is_crlf = false, + } + } + (lf, crlf) +} + +pub(crate) fn count_newlines(input: &str) -> usize { + // Using bytes to omit UTF-8 decoding + bytecount::count(input.as_bytes(), b'\n') +} + +// For format_missing and last_pos, need to use the source callsite (if applicable). +// Required as generated code spans aren't guaranteed to follow on from the last span. +macro_rules! source { + ($this:ident, $sp:expr) => { + $sp.source_callsite() + }; +} + +pub(crate) fn mk_sp(lo: BytePos, hi: BytePos) -> Span { + Span::new(lo, hi, SyntaxContext::root()) +} + +// Returns `true` if the given span does not intersect with file lines. +macro_rules! out_of_file_lines_range { + ($self:ident, $span:expr) => { + !$self.config.file_lines().is_all() + && !$self + .config + .file_lines() + .intersects(&$self.parse_sess.lookup_line_range($span)) + }; +} + +macro_rules! skip_out_of_file_lines_range { + ($self:ident, $span:expr) => { + if out_of_file_lines_range!($self, $span) { + return None; + } + }; +} + +macro_rules! skip_out_of_file_lines_range_visitor { + ($self:ident, $span:expr) => { + if out_of_file_lines_range!($self, $span) { + $self.push_rewrite($span, None); + return; + } + }; +} + +// Wraps String in an Option. Returns Some when the string adheres to the +// Rewrite constraints defined for the Rewrite trait and None otherwise. +pub(crate) fn wrap_str(s: String, max_width: usize, shape: Shape) -> Option { + if is_valid_str(&filter_normal_code(&s), max_width, shape) { + Some(s) + } else { + None + } +} + +fn is_valid_str(snippet: &str, max_width: usize, shape: Shape) -> bool { + if !snippet.is_empty() { + // First line must fits with `shape.width`. + if first_line_width(snippet) > shape.width { + return false; + } + // If the snippet does not include newline, we are done. + if is_single_line(snippet) { + return true; + } + // The other lines must fit within the maximum width. + if snippet + .lines() + .skip(1) + .any(|line| unicode_str_width(line) > max_width) + { + return false; + } + // A special check for the last line, since the caller may + // place trailing characters on this line. + if last_line_width(snippet) > shape.used_width() + shape.width { + return false; + } + } + true +} + +#[inline] +pub(crate) fn colon_spaces(config: &Config) -> &'static str { + let before = config.space_before_colon(); + let after = config.space_after_colon(); + match (before, after) { + (true, true) => " : ", + (true, false) => " :", + (false, true) => ": ", + (false, false) => ":", + } +} + +#[inline] +pub(crate) fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr { + match e.kind { + ast::ExprKind::Call(ref e, _) + | ast::ExprKind::Binary(_, ref e, _) + | ast::ExprKind::Cast(ref e, _) + | ast::ExprKind::Type(ref e, _) + | ast::ExprKind::Assign(ref e, _, _) + | ast::ExprKind::AssignOp(_, ref e, _) + | ast::ExprKind::Field(ref e, _) + | ast::ExprKind::Index(ref e, _) + | ast::ExprKind::Range(Some(ref e), _, _) + | ast::ExprKind::Try(ref e) => left_most_sub_expr(e), + _ => e, + } +} + +#[inline] +pub(crate) fn starts_with_newline(s: &str) -> bool { + s.starts_with('\n') || s.starts_with("\r\n") +} + +#[inline] +pub(crate) fn first_line_ends_with(s: &str, c: char) -> bool { + s.lines().next().map_or(false, |l| l.ends_with(c)) +} + +// States whether an expression's last line exclusively consists of closing +// parens, braces, and brackets in its idiomatic formatting. +pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr: &str) -> bool { + match expr.kind { + ast::ExprKind::MacCall(..) + | ast::ExprKind::Call(..) + | ast::ExprKind::MethodCall(..) + | ast::ExprKind::Array(..) + | ast::ExprKind::Struct(..) + | ast::ExprKind::While(..) + | ast::ExprKind::If(..) + | ast::ExprKind::Block(..) + | ast::ExprKind::ConstBlock(..) + | ast::ExprKind::Async(..) + | ast::ExprKind::Loop(..) + | ast::ExprKind::ForLoop(..) + | ast::ExprKind::TryBlock(..) + | ast::ExprKind::Match(..) => repr.contains('\n'), + ast::ExprKind::Paren(ref expr) + | ast::ExprKind::Binary(_, _, ref expr) + | ast::ExprKind::Index(_, ref expr) + | ast::ExprKind::Unary(_, ref expr) + | ast::ExprKind::Closure(_, _, _, _, ref expr, _) + | ast::ExprKind::Try(ref expr) + | ast::ExprKind::Yield(Some(ref expr)) => is_block_expr(context, expr, repr), + // This can only be a string lit + ast::ExprKind::Lit(_) => { + repr.contains('\n') && trimmed_last_line_width(repr) <= context.config.tab_spaces() + } + ast::ExprKind::AddrOf(..) + | ast::ExprKind::Assign(..) + | ast::ExprKind::AssignOp(..) + | ast::ExprKind::Await(..) + | ast::ExprKind::Box(..) + | ast::ExprKind::Break(..) + | ast::ExprKind::Cast(..) + | ast::ExprKind::Continue(..) + | ast::ExprKind::Err + | ast::ExprKind::Field(..) + | ast::ExprKind::InlineAsm(..) + | ast::ExprKind::LlvmInlineAsm(..) + | ast::ExprKind::Let(..) + | ast::ExprKind::Path(..) + | ast::ExprKind::Range(..) + | ast::ExprKind::Repeat(..) + | ast::ExprKind::Ret(..) + | ast::ExprKind::Tup(..) + | ast::ExprKind::Type(..) + | ast::ExprKind::Yield(None) + | ast::ExprKind::Underscore => false, + } +} + +/// Removes trailing spaces from the specified snippet. We do not remove spaces +/// inside strings or comments. +pub(crate) fn remove_trailing_white_spaces(text: &str) -> String { + let mut buffer = String::with_capacity(text.len()); + let mut space_buffer = String::with_capacity(128); + for (char_kind, c) in CharClasses::new(text.chars()) { + match c { + '\n' => { + if char_kind == FullCodeCharKind::InString { + buffer.push_str(&space_buffer); + } + space_buffer.clear(); + buffer.push('\n'); + } + _ if c.is_whitespace() => { + space_buffer.push(c); + } + _ => { + if !space_buffer.is_empty() { + buffer.push_str(&space_buffer); + space_buffer.clear(); + } + buffer.push(c); + } + } + } + buffer +} + +/// Indent each line according to the specified `indent`. +/// e.g. +/// +/// ```rust,compile_fail +/// foo!{ +/// x, +/// y, +/// foo( +/// a, +/// b, +/// c, +/// ), +/// } +/// ``` +/// +/// will become +/// +/// ```rust,compile_fail +/// foo!{ +/// x, +/// y, +/// foo( +/// a, +/// b, +/// c, +/// ), +/// } +/// ``` +pub(crate) fn trim_left_preserve_layout( + orig: &str, + indent: Indent, + config: &Config, +) -> Option { + let mut lines = LineClasses::new(orig); + let first_line = lines.next().map(|(_, s)| s.trim_end().to_owned())?; + let mut trimmed_lines = Vec::with_capacity(16); + + let mut veto_trim = false; + let min_prefix_space_width = lines + .filter_map(|(kind, line)| { + let mut trimmed = true; + let prefix_space_width = if is_empty_line(&line) { + None + } else { + Some(get_prefix_space_width(config, &line)) + }; + + // just InString{Commented} in order to allow the start of a string to be indented + let new_veto_trim_value = (kind == FullCodeCharKind::InString + || (config.version() == Version::Two + && kind == FullCodeCharKind::InStringCommented)) + && !line.ends_with('\\'); + let line = if veto_trim || new_veto_trim_value { + veto_trim = new_veto_trim_value; + trimmed = false; + line + } else { + line.trim().to_owned() + }; + trimmed_lines.push((trimmed, line, prefix_space_width)); + + // Because there is a veto against trimming and indenting lines within a string, + // such lines should not be taken into account when computing the minimum. + match kind { + FullCodeCharKind::InStringCommented | FullCodeCharKind::EndStringCommented + if config.version() == Version::Two => + { + None + } + FullCodeCharKind::InString | FullCodeCharKind::EndString => None, + _ => prefix_space_width, + } + }) + .min()?; + + Some( + first_line + + "\n" + + &trimmed_lines + .iter() + .map( + |&(trimmed, ref line, prefix_space_width)| match prefix_space_width { + _ if !trimmed => line.to_owned(), + Some(original_indent_width) => { + let new_indent_width = indent.width() + + original_indent_width.saturating_sub(min_prefix_space_width); + let new_indent = Indent::from_width(config, new_indent_width); + format!("{}{}", new_indent.to_string(config), line) + } + None => String::new(), + }, + ) + .collect::>() + .join("\n"), + ) +} + +/// Based on the given line, determine if the next line can be indented or not. +/// This allows to preserve the indentation of multi-line literals. +pub(crate) fn indent_next_line(kind: FullCodeCharKind, _line: &str, config: &Config) -> bool { + !(kind.is_string() || (config.version() == Version::Two && kind.is_commented_string())) +} + +pub(crate) fn is_empty_line(s: &str) -> bool { + s.is_empty() || s.chars().all(char::is_whitespace) +} + +fn get_prefix_space_width(config: &Config, s: &str) -> usize { + let mut width = 0; + for c in s.chars() { + match c { + ' ' => width += 1, + '\t' => width += config.tab_spaces(), + _ => return width, + } + } + width +} + +pub(crate) trait NodeIdExt { + fn root() -> Self; +} + +impl NodeIdExt for NodeId { + fn root() -> NodeId { + NodeId::placeholder_from_expn_id(ExpnId::root()) + } +} + +pub(crate) fn unicode_str_width(s: &str) -> usize { + s.width() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_remove_trailing_white_spaces() { + let s = " r#\"\n test\n \"#"; + assert_eq!(remove_trailing_white_spaces(&s), s); + } + + #[test] + fn test_trim_left_preserve_layout() { + let s = "aaa\n\tbbb\n ccc"; + let config = Config::default(); + let indent = Indent::new(4, 0); + assert_eq!( + trim_left_preserve_layout(&s, indent, &config), + Some("aaa\n bbb\n ccc".to_string()) + ); + } +} diff --git a/src/tools/rustfmt/src/vertical.rs b/src/tools/rustfmt/src/vertical.rs new file mode 100644 index 0000000000..95d1d5c2d7 --- /dev/null +++ b/src/tools/rustfmt/src/vertical.rs @@ -0,0 +1,285 @@ +// Format with vertical alignment. + +use std::cmp; + +use itertools::Itertools; +use rustc_ast::ast; +use rustc_span::{BytePos, Span}; + +use crate::comment::combine_strs_with_missing_comments; +use crate::config::lists::*; +use crate::expr::rewrite_field; +use crate::items::{rewrite_struct_field, rewrite_struct_field_prefix}; +use crate::lists::{ + definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator, +}; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::{Indent, Shape}; +use crate::source_map::SpanUtils; +use crate::spanned::Spanned; +use crate::utils::{ + contains_skip, is_attributes_extendable, mk_sp, rewrite_ident, trimmed_last_line_width, +}; + +pub(crate) trait AlignedItem { + fn skip(&self) -> bool; + fn get_span(&self) -> Span; + fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option; + fn rewrite_aligned_item( + &self, + context: &RewriteContext<'_>, + shape: Shape, + prefix_max_width: usize, + ) -> Option; +} + +impl AlignedItem for ast::StructField { + fn skip(&self) -> bool { + contains_skip(&self.attrs) + } + + fn get_span(&self) -> Span { + self.span() + } + + fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let attrs_str = self.attrs.rewrite(context, shape)?; + let missing_span = if self.attrs.is_empty() { + mk_sp(self.span.lo(), self.span.lo()) + } else { + mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo()) + }; + let attrs_extendable = self.ident.is_none() && is_attributes_extendable(&attrs_str); + rewrite_struct_field_prefix(context, self).and_then(|field_str| { + combine_strs_with_missing_comments( + context, + &attrs_str, + &field_str, + missing_span, + shape, + attrs_extendable, + ) + }) + } + + fn rewrite_aligned_item( + &self, + context: &RewriteContext<'_>, + shape: Shape, + prefix_max_width: usize, + ) -> Option { + rewrite_struct_field(context, self, shape, prefix_max_width) + } +} + +impl AlignedItem for ast::Field { + fn skip(&self) -> bool { + contains_skip(&self.attrs) + } + + fn get_span(&self) -> Span { + self.span() + } + + fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let attrs_str = self.attrs.rewrite(context, shape)?; + let name = rewrite_ident(context, self.ident); + let missing_span = if self.attrs.is_empty() { + mk_sp(self.span.lo(), self.span.lo()) + } else { + mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo()) + }; + combine_strs_with_missing_comments( + context, + &attrs_str, + name, + missing_span, + shape, + is_attributes_extendable(&attrs_str), + ) + } + + fn rewrite_aligned_item( + &self, + context: &RewriteContext<'_>, + shape: Shape, + prefix_max_width: usize, + ) -> Option { + rewrite_field(context, self, shape, prefix_max_width) + } +} + +pub(crate) fn rewrite_with_alignment( + fields: &[T], + context: &RewriteContext<'_>, + shape: Shape, + span: Span, + one_line_width: usize, +) -> Option { + let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 { + group_aligned_items(context, fields) + } else { + ("", fields.len() - 1) + }; + let init = &fields[0..=group_index]; + let rest = &fields[group_index + 1..]; + let init_last_pos = if rest.is_empty() { + span.hi() + } else { + // Decide whether the missing comments should stick to init or rest. + let init_hi = init[init.len() - 1].get_span().hi(); + let rest_lo = rest[0].get_span().lo(); + let missing_span = mk_sp(init_hi, rest_lo); + let missing_span = mk_sp( + context.snippet_provider.span_after(missing_span, ","), + missing_span.hi(), + ); + + let snippet = context.snippet(missing_span); + if snippet.trim_start().starts_with("//") { + let offset = snippet.lines().next().map_or(0, str::len); + // 2 = "," + "\n" + init_hi + BytePos(offset as u32 + 2) + } else if snippet.trim_start().starts_with("/*") { + let comment_lines = snippet + .lines() + .position(|line| line.trim_end().ends_with("*/")) + .unwrap_or(0); + + let offset = snippet + .lines() + .take(comment_lines + 1) + .collect::>() + .join("\n") + .len(); + + init_hi + BytePos(offset as u32 + 2) + } else { + missing_span.lo() + } + }; + let init_span = mk_sp(span.lo(), init_last_pos); + let one_line_width = if rest.is_empty() { one_line_width } else { 0 }; + let result = + rewrite_aligned_items_inner(context, init, init_span, shape.indent, one_line_width)?; + if rest.is_empty() { + Some(result + spaces) + } else { + let rest_span = mk_sp(init_last_pos, span.hi()); + let rest_str = rewrite_with_alignment(rest, context, shape, rest_span, one_line_width)?; + Some(format!( + "{}{}\n{}{}", + result, + spaces, + &shape.indent.to_string(context.config), + &rest_str + )) + } +} + +fn struct_field_prefix_max_min_width( + context: &RewriteContext<'_>, + fields: &[T], + shape: Shape, +) -> (usize, usize) { + fields + .iter() + .map(|field| { + field + .rewrite_prefix(context, shape) + .map(|field_str| trimmed_last_line_width(&field_str)) + }) + .fold_options((0, ::std::usize::MAX), |(max_len, min_len), len| { + (cmp::max(max_len, len), cmp::min(min_len, len)) + }) + .unwrap_or((0, 0)) +} + +fn rewrite_aligned_items_inner( + context: &RewriteContext<'_>, + fields: &[T], + span: Span, + offset: Indent, + one_line_width: usize, +) -> Option { + // 1 = "," + let item_shape = Shape::indented(offset, context.config).sub_width(1)?; + let (mut field_prefix_max_width, field_prefix_min_width) = + struct_field_prefix_max_min_width(context, fields, item_shape); + let max_diff = field_prefix_max_width.saturating_sub(field_prefix_min_width); + if max_diff > context.config.struct_field_align_threshold() { + field_prefix_max_width = 0; + } + + let mut items = itemize_list( + context.snippet_provider, + fields.iter(), + "}", + ",", + |field| field.get_span().lo(), + |field| field.get_span().hi(), + |field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width), + span.lo(), + span.hi(), + false, + ) + .collect::>(); + + let tactic = definitive_tactic( + &items, + ListTactic::HorizontalVertical, + Separator::Comma, + one_line_width, + ); + + if tactic == DefinitiveListTactic::Horizontal { + // since the items fits on a line, there is no need to align them + let do_rewrite = + |field: &T| -> Option { field.rewrite_aligned_item(context, item_shape, 0) }; + fields + .iter() + .zip(items.iter_mut()) + .for_each(|(field, list_item): (&T, &mut ListItem)| { + if list_item.item.is_some() { + list_item.item = do_rewrite(field); + } + }); + } + + let fmt = ListFormatting::new(item_shape, context.config) + .tactic(tactic) + .trailing_separator(context.config.trailing_comma()) + .preserve_newline(true); + write_list(&items, &fmt) +} + +/// Returns the index in `fields` up to which a field belongs to the current group. +/// The returned string is the group separator to use when rewriting the fields. +/// Groups are defined by blank lines. +fn group_aligned_items( + context: &RewriteContext<'_>, + fields: &[T], +) -> (&'static str, usize) { + let mut index = 0; + for i in 0..fields.len() - 1 { + if fields[i].skip() { + return ("", index); + } + let span = mk_sp(fields[i].get_span().hi(), fields[i + 1].get_span().lo()); + let snippet = context + .snippet(span) + .lines() + .skip(1) + .collect::>() + .join("\n"); + let has_blank_line = snippet + .lines() + .dropping_back(1) + .any(|l| l.trim().is_empty()); + if has_blank_line { + return ("\n", index); + } + index += 1; + } + ("", index) +} diff --git a/src/tools/rustfmt/src/visitor.rs b/src/tools/rustfmt/src/visitor.rs new file mode 100644 index 0000000000..34e8536b7e --- /dev/null +++ b/src/tools/rustfmt/src/visitor.rs @@ -0,0 +1,1075 @@ +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +use rustc_ast::{ast, attr::HasAttrs, token::DelimToken, visit}; +use rustc_span::{symbol, BytePos, Pos, Span, DUMMY_SP}; + +use crate::attr::*; +use crate::comment::{contains_comment, rewrite_comment, CodeCharKind, CommentCodeSlices}; +use crate::config::Version; +use crate::config::{BraceStyle, Config}; +use crate::coverage::transform_missing_snippet; +use crate::items::{ + format_impl, format_trait, format_trait_alias, is_mod_decl, is_use_item, + rewrite_associated_impl_type, rewrite_extern_crate, rewrite_opaque_impl_type, + rewrite_opaque_type, rewrite_type_alias, FnBraceStyle, FnSig, StaticParts, StructParts, +}; +use crate::macros::{macro_style, rewrite_macro, rewrite_macro_def, MacroPosition}; +use crate::modules::Module; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::{Indent, Shape}; +use crate::skip::{is_skip_attr, SkipContext}; +use crate::source_map::{LineRangeUtils, SpanUtils}; +use crate::spanned::Spanned; +use crate::stmt::Stmt; +use crate::syntux::session::ParseSess; +use crate::utils::{ + self, contains_skip, count_newlines, depr_skip_annotation, format_unsafety, inner_attributes, + last_line_width, mk_sp, ptr_vec_to_ref_vec, rewrite_ident, starts_with_newline, stmt_expr, +}; +use crate::{ErrorKind, FormatReport, FormattingError}; + +/// Creates a string slice corresponding to the specified span. +pub(crate) struct SnippetProvider { + /// A pointer to the content of the file we are formatting. + big_snippet: Rc, + /// A position of the start of `big_snippet`, used as an offset. + start_pos: usize, + /// A end position of the file that this snippet lives. + end_pos: usize, +} + +impl SnippetProvider { + pub(crate) fn span_to_snippet(&self, span: Span) -> Option<&str> { + let start_index = span.lo().to_usize().checked_sub(self.start_pos)?; + let end_index = span.hi().to_usize().checked_sub(self.start_pos)?; + Some(&self.big_snippet[start_index..end_index]) + } + + pub(crate) fn new(start_pos: BytePos, end_pos: BytePos, big_snippet: Rc) -> Self { + let start_pos = start_pos.to_usize(); + let end_pos = end_pos.to_usize(); + SnippetProvider { + big_snippet, + start_pos, + end_pos, + } + } + + pub(crate) fn entire_snippet(&self) -> &str { + self.big_snippet.as_str() + } + + pub(crate) fn start_pos(&self) -> BytePos { + BytePos::from_usize(self.start_pos) + } + + pub(crate) fn end_pos(&self) -> BytePos { + BytePos::from_usize(self.end_pos) + } +} + +pub(crate) struct FmtVisitor<'a> { + parent_context: Option<&'a RewriteContext<'a>>, + pub(crate) parse_sess: &'a ParseSess, + pub(crate) buffer: String, + pub(crate) last_pos: BytePos, + // FIXME: use an RAII util or closure for indenting + pub(crate) block_indent: Indent, + pub(crate) config: &'a Config, + pub(crate) is_if_else_block: bool, + pub(crate) snippet_provider: &'a SnippetProvider, + pub(crate) line_number: usize, + /// List of 1-based line ranges which were annotated with skip + /// Both bounds are inclusifs. + pub(crate) skipped_range: Rc>>, + pub(crate) macro_rewrite_failure: bool, + pub(crate) report: FormatReport, + pub(crate) skip_context: SkipContext, + pub(crate) is_macro_def: bool, +} + +impl<'a> Drop for FmtVisitor<'a> { + fn drop(&mut self) { + if let Some(ctx) = self.parent_context { + if self.macro_rewrite_failure { + ctx.macro_rewrite_failure.replace(true); + } + } + } +} + +impl<'b, 'a: 'b> FmtVisitor<'a> { + fn set_parent_context(&mut self, context: &'a RewriteContext<'_>) { + self.parent_context = Some(context); + } + + pub(crate) fn shape(&self) -> Shape { + Shape::indented(self.block_indent, self.config) + } + + fn next_span(&self, hi: BytePos) -> Span { + mk_sp(self.last_pos, hi) + } + + fn visit_stmt(&mut self, stmt: &Stmt<'_>, include_empty_semi: bool) { + debug!( + "visit_stmt: {}", + self.parse_sess.span_to_debug_info(stmt.span()) + ); + + if stmt.is_empty() { + // If the statement is empty, just skip over it. Before that, make sure any comment + // snippet preceding the semicolon is picked up. + let snippet = self.snippet(mk_sp(self.last_pos, stmt.span().lo())); + let original_starts_with_newline = snippet + .find(|c| c != ' ') + .map_or(false, |i| starts_with_newline(&snippet[i..])); + let snippet = snippet.trim(); + if !snippet.is_empty() { + // FIXME(calebcartwright 2021-01-03) - This exists strictly to maintain legacy + // formatting where rustfmt would preserve redundant semicolons on Items in a + // statement position. + // See comment within `walk_stmts` for more info + if include_empty_semi { + self.format_missing(stmt.span().hi()); + } else { + if original_starts_with_newline { + self.push_str("\n"); + } + + self.push_str(&self.block_indent.to_string(self.config)); + self.push_str(snippet); + } + } else if include_empty_semi { + self.push_str(";"); + } + self.last_pos = stmt.span().hi(); + return; + } + + match stmt.as_ast_node().kind { + ast::StmtKind::Item(ref item) => { + self.visit_item(item); + self.last_pos = stmt.span().hi(); + } + ast::StmtKind::Local(..) | ast::StmtKind::Expr(..) | ast::StmtKind::Semi(..) => { + let attrs = get_attrs_from_stmt(stmt.as_ast_node()); + if contains_skip(attrs) { + self.push_skipped_with_span( + attrs, + stmt.span(), + get_span_without_attrs(stmt.as_ast_node()), + ); + } else { + let shape = self.shape(); + let rewrite = self.with_context(|ctx| stmt.rewrite(&ctx, shape)); + self.push_rewrite(stmt.span(), rewrite) + } + } + ast::StmtKind::MacCall(ref mac_stmt) => { + if self.visit_attrs(&mac_stmt.attrs, ast::AttrStyle::Outer) { + self.push_skipped_with_span( + &mac_stmt.attrs, + stmt.span(), + get_span_without_attrs(stmt.as_ast_node()), + ); + } else { + self.visit_mac(&mac_stmt.mac, None, MacroPosition::Statement); + } + self.format_missing(stmt.span().hi()); + } + ast::StmtKind::Empty => (), + } + } + + /// Remove spaces between the opening brace and the first statement or the inner attribute + /// of the block. + fn trim_spaces_after_opening_brace( + &mut self, + b: &ast::Block, + inner_attrs: Option<&[ast::Attribute]>, + ) { + if let Some(first_stmt) = b.stmts.first() { + let hi = inner_attrs + .and_then(|attrs| inner_attributes(attrs).first().map(|attr| attr.span.lo())) + .unwrap_or_else(|| first_stmt.span().lo()); + let missing_span = self.next_span(hi); + let snippet = self.snippet(missing_span); + let len = CommentCodeSlices::new(snippet) + .nth(0) + .and_then(|(kind, _, s)| { + if kind == CodeCharKind::Normal { + s.rfind('\n') + } else { + None + } + }); + if let Some(len) = len { + self.last_pos = self.last_pos + BytePos::from_usize(len); + } + } + } + + pub(crate) fn visit_block( + &mut self, + b: &ast::Block, + inner_attrs: Option<&[ast::Attribute]>, + has_braces: bool, + ) { + debug!( + "visit_block: {}", + self.parse_sess.span_to_debug_info(b.span), + ); + + // Check if this block has braces. + let brace_compensation = BytePos(if has_braces { 1 } else { 0 }); + + self.last_pos = self.last_pos + brace_compensation; + self.block_indent = self.block_indent.block_indent(self.config); + self.push_str("{"); + self.trim_spaces_after_opening_brace(b, inner_attrs); + + // Format inner attributes if available. + if let Some(attrs) = inner_attrs { + self.visit_attrs(attrs, ast::AttrStyle::Inner); + } + + self.walk_block_stmts(b); + + if !b.stmts.is_empty() { + if let Some(expr) = stmt_expr(&b.stmts[b.stmts.len() - 1]) { + if utils::semicolon_for_expr(&self.get_context(), expr) { + self.push_str(";"); + } + } + } + + let rest_span = self.next_span(b.span.hi()); + if out_of_file_lines_range!(self, rest_span) { + self.push_str(self.snippet(rest_span)); + self.block_indent = self.block_indent.block_unindent(self.config); + } else { + // Ignore the closing brace. + let missing_span = self.next_span(b.span.hi() - brace_compensation); + self.close_block(missing_span, self.unindent_comment_on_closing_brace(b)); + } + self.last_pos = source!(self, b.span).hi(); + } + + fn close_block(&mut self, span: Span, unindent_comment: bool) { + let config = self.config; + + let mut last_hi = span.lo(); + let mut unindented = false; + let mut prev_ends_with_newline = false; + let mut extra_newline = false; + + let skip_normal = |s: &str| { + let trimmed = s.trim(); + trimmed.is_empty() || trimmed.chars().all(|c| c == ';') + }; + + let comment_snippet = self.snippet(span); + + let align_to_right = if unindent_comment && contains_comment(&comment_snippet) { + let first_lines = comment_snippet.splitn(2, '/').next().unwrap_or(""); + last_line_width(first_lines) > last_line_width(&comment_snippet) + } else { + false + }; + + for (kind, offset, sub_slice) in CommentCodeSlices::new(comment_snippet) { + let sub_slice = transform_missing_snippet(config, sub_slice); + + debug!("close_block: {:?} {:?} {:?}", kind, offset, sub_slice); + + match kind { + CodeCharKind::Comment => { + if !unindented && unindent_comment && !align_to_right { + unindented = true; + self.block_indent = self.block_indent.block_unindent(config); + } + let span_in_between = mk_sp(last_hi, span.lo() + BytePos::from_usize(offset)); + let snippet_in_between = self.snippet(span_in_between); + let mut comment_on_same_line = !snippet_in_between.contains("\n"); + + let mut comment_shape = + Shape::indented(self.block_indent, config).comment(config); + if self.config.version() == Version::Two && comment_on_same_line { + self.push_str(" "); + // put the first line of the comment on the same line as the + // block's last line + match sub_slice.find("\n") { + None => { + self.push_str(&sub_slice); + } + Some(offset) if offset + 1 == sub_slice.len() => { + self.push_str(&sub_slice[..offset]); + } + Some(offset) => { + let first_line = &sub_slice[..offset]; + self.push_str(first_line); + self.push_str(&self.block_indent.to_string_with_newline(config)); + + // put the other lines below it, shaping it as needed + let other_lines = &sub_slice[offset + 1..]; + let comment_str = + rewrite_comment(other_lines, false, comment_shape, config); + match comment_str { + Some(ref s) => self.push_str(s), + None => self.push_str(other_lines), + } + } + } + } else { + if comment_on_same_line { + // 1 = a space before `//` + let offset_len = 1 + last_line_width(&self.buffer) + .saturating_sub(self.block_indent.width()); + match comment_shape + .visual_indent(offset_len) + .sub_width(offset_len) + { + Some(shp) => comment_shape = shp, + None => comment_on_same_line = false, + } + }; + + if comment_on_same_line { + self.push_str(" "); + } else { + if count_newlines(snippet_in_between) >= 2 || extra_newline { + self.push_str("\n"); + } + self.push_str(&self.block_indent.to_string_with_newline(config)); + } + + let comment_str = rewrite_comment(&sub_slice, false, comment_shape, config); + match comment_str { + Some(ref s) => self.push_str(s), + None => self.push_str(&sub_slice), + } + } + } + CodeCharKind::Normal if skip_normal(&sub_slice) => { + extra_newline = prev_ends_with_newline && sub_slice.contains('\n'); + continue; + } + CodeCharKind::Normal => { + self.push_str(&self.block_indent.to_string_with_newline(config)); + self.push_str(sub_slice.trim()); + } + } + prev_ends_with_newline = sub_slice.ends_with('\n'); + extra_newline = false; + last_hi = span.lo() + BytePos::from_usize(offset + sub_slice.len()); + } + if unindented { + self.block_indent = self.block_indent.block_indent(self.config); + } + self.block_indent = self.block_indent.block_unindent(self.config); + self.push_str(&self.block_indent.to_string_with_newline(config)); + self.push_str("}"); + } + + fn unindent_comment_on_closing_brace(&self, b: &ast::Block) -> bool { + self.is_if_else_block && !b.stmts.is_empty() + } + + // Note that this only gets called for function definitions. Required methods + // on traits do not get handled here. + pub(crate) fn visit_fn( + &mut self, + fk: visit::FnKind<'_>, + generics: &ast::Generics, + fd: &ast::FnDecl, + s: Span, + defaultness: ast::Defaultness, + inner_attrs: Option<&[ast::Attribute]>, + ) { + let indent = self.block_indent; + let block; + let rewrite = match fk { + visit::FnKind::Fn(_, ident, _, _, Some(ref b)) => { + block = b; + self.rewrite_fn_before_block( + indent, + ident, + &FnSig::from_fn_kind(&fk, generics, fd, defaultness), + mk_sp(s.lo(), b.span.lo()), + ) + } + _ => unreachable!(), + }; + + if let Some((fn_str, fn_brace_style)) = rewrite { + self.format_missing_with_indent(source!(self, s).lo()); + + if let Some(rw) = self.single_line_fn(&fn_str, block, inner_attrs) { + self.push_str(&rw); + self.last_pos = s.hi(); + return; + } + + self.push_str(&fn_str); + match fn_brace_style { + FnBraceStyle::SameLine => self.push_str(" "), + FnBraceStyle::NextLine => { + self.push_str(&self.block_indent.to_string_with_newline(self.config)) + } + _ => unreachable!(), + } + self.last_pos = source!(self, block.span).lo(); + } else { + self.format_missing(source!(self, block.span).lo()); + } + + self.visit_block(block, inner_attrs, true) + } + + pub(crate) fn visit_item(&mut self, item: &ast::Item) { + skip_out_of_file_lines_range_visitor!(self, item.span); + + // This is where we bail out if there is a skip attribute. This is only + // complex in the module case. It is complex because the module could be + // in a separate file and there might be attributes in both files, but + // the AST lumps them all together. + let filtered_attrs; + let mut attrs = &item.attrs; + let skip_context_saved = self.skip_context.clone(); + self.skip_context.update_with_attrs(&attrs); + + let should_visit_node_again = match item.kind { + // For use/extern crate items, skip rewriting attributes but check for a skip attribute. + ast::ItemKind::Use(..) | ast::ItemKind::ExternCrate(_) => { + if contains_skip(attrs) { + self.push_skipped_with_span(attrs.as_slice(), item.span(), item.span()); + false + } else { + true + } + } + // Module is inline, in this case we treat it like any other item. + _ if !is_mod_decl(item) => { + if self.visit_attrs(&item.attrs, ast::AttrStyle::Outer) { + self.push_skipped_with_span(item.attrs.as_slice(), item.span(), item.span()); + false + } else { + true + } + } + // Module is not inline, but should be skipped. + ast::ItemKind::Mod(..) if contains_skip(&item.attrs) => false, + // Module is not inline and should not be skipped. We want + // to process only the attributes in the current file. + ast::ItemKind::Mod(..) => { + filtered_attrs = filter_inline_attrs(&item.attrs, item.span()); + // Assert because if we should skip it should be caught by + // the above case. + assert!(!self.visit_attrs(&filtered_attrs, ast::AttrStyle::Outer)); + attrs = &filtered_attrs; + true + } + _ => { + if self.visit_attrs(&item.attrs, ast::AttrStyle::Outer) { + self.push_skipped_with_span(item.attrs.as_slice(), item.span(), item.span()); + false + } else { + true + } + } + }; + + // TODO(calebcartwright): consider enabling box_patterns feature gate + if should_visit_node_again { + match item.kind { + ast::ItemKind::Use(ref tree) => self.format_import(item, tree), + ast::ItemKind::Impl { .. } => { + let block_indent = self.block_indent; + let rw = self.with_context(|ctx| format_impl(&ctx, item, block_indent)); + self.push_rewrite(item.span, rw); + } + ast::ItemKind::Trait(..) => { + let block_indent = self.block_indent; + let rw = self.with_context(|ctx| format_trait(&ctx, item, block_indent)); + self.push_rewrite(item.span, rw); + } + ast::ItemKind::TraitAlias(ref generics, ref generic_bounds) => { + let shape = Shape::indented(self.block_indent, self.config); + let rw = format_trait_alias( + &self.get_context(), + item.ident, + &item.vis, + generics, + generic_bounds, + shape, + ); + self.push_rewrite(item.span, rw); + } + ast::ItemKind::ExternCrate(_) => { + let rw = rewrite_extern_crate(&self.get_context(), item, self.shape()); + let span = if attrs.is_empty() { + item.span + } else { + mk_sp(attrs[0].span.lo(), item.span.hi()) + }; + self.push_rewrite(span, rw); + } + ast::ItemKind::Struct(..) | ast::ItemKind::Union(..) => { + self.visit_struct(&StructParts::from_item(item)); + } + ast::ItemKind::Enum(ref def, ref generics) => { + self.format_missing_with_indent(source!(self, item.span).lo()); + self.visit_enum(item.ident, &item.vis, def, generics, item.span); + self.last_pos = source!(self, item.span).hi(); + } + ast::ItemKind::Mod(ref module) => { + let is_inline = !is_mod_decl(item); + self.format_missing_with_indent(source!(self, item.span).lo()); + self.format_mod(module, &item.vis, item.span, item.ident, attrs, is_inline); + } + ast::ItemKind::MacCall(ref mac) => { + self.visit_mac(mac, Some(item.ident), MacroPosition::Item); + } + ast::ItemKind::ForeignMod(ref foreign_mod) => { + self.format_missing_with_indent(source!(self, item.span).lo()); + self.format_foreign_mod(foreign_mod, item.span); + } + ast::ItemKind::Static(..) | ast::ItemKind::Const(..) => { + self.visit_static(&StaticParts::from_item(item)); + } + ast::ItemKind::Fn(ref fn_kind) => { + let ast::FnKind(defaultness, ref fn_signature, ref generics, ref block) = + **fn_kind; + if let Some(ref body) = block { + let inner_attrs = inner_attributes(&item.attrs); + let fn_ctxt = match fn_signature.header.ext { + ast::Extern::None => visit::FnCtxt::Free, + _ => visit::FnCtxt::Foreign, + }; + self.visit_fn( + visit::FnKind::Fn( + fn_ctxt, + item.ident, + &fn_signature, + &item.vis, + Some(body), + ), + generics, + &fn_signature.decl, + item.span, + defaultness, + Some(&inner_attrs), + ) + } else { + let indent = self.block_indent; + let rewrite = self.rewrite_required_fn( + indent, + item.ident, + &fn_signature, + generics, + item.span, + ); + self.push_rewrite(item.span, rewrite); + } + } + ast::ItemKind::TyAlias(ref alias_kind) => { + let ast::TyAliasKind(_, ref generics, ref generic_bounds, ref ty) = + **alias_kind; + match ty { + Some(ty) => { + let rewrite = rewrite_type_alias( + item.ident, + Some(&*ty), + generics, + Some(generic_bounds), + &self.get_context(), + self.block_indent, + &item.vis, + item.span, + ); + self.push_rewrite(item.span, rewrite); + } + None => { + let rewrite = rewrite_opaque_type( + &self.get_context(), + self.block_indent, + item.ident, + generic_bounds, + generics, + &item.vis, + item.span, + ); + self.push_rewrite(item.span, rewrite); + } + } + } + ast::ItemKind::GlobalAsm(..) => { + let snippet = Some(self.snippet(item.span).to_owned()); + self.push_rewrite(item.span, snippet); + } + ast::ItemKind::MacroDef(ref def) => { + let rewrite = rewrite_macro_def( + &self.get_context(), + self.shape(), + self.block_indent, + def, + item.ident, + &item.vis, + item.span, + ); + self.push_rewrite(item.span, rewrite); + } + }; + } + self.skip_context = skip_context_saved; + } + + pub(crate) fn visit_trait_item(&mut self, ti: &ast::AssocItem) { + skip_out_of_file_lines_range_visitor!(self, ti.span); + + if self.visit_attrs(&ti.attrs, ast::AttrStyle::Outer) { + self.push_skipped_with_span(ti.attrs.as_slice(), ti.span(), ti.span()); + return; + } + + // TODO(calebcartwright): consider enabling box_patterns feature gate + match ti.kind { + ast::AssocItemKind::Const(..) => self.visit_static(&StaticParts::from_trait_item(ti)), + ast::AssocItemKind::Fn(ref fn_kind) => { + let ast::FnKind(defaultness, ref sig, ref generics, ref block) = **fn_kind; + if let Some(ref body) = block { + let inner_attrs = inner_attributes(&ti.attrs); + let vis = ast::Visibility { + kind: ast::VisibilityKind::Inherited, + span: DUMMY_SP, + tokens: None, + }; + let fn_ctxt = visit::FnCtxt::Assoc(visit::AssocCtxt::Trait); + self.visit_fn( + visit::FnKind::Fn(fn_ctxt, ti.ident, sig, &vis, Some(body)), + generics, + &sig.decl, + ti.span, + defaultness, + Some(&inner_attrs), + ); + } else { + let indent = self.block_indent; + let rewrite = + self.rewrite_required_fn(indent, ti.ident, sig, generics, ti.span); + self.push_rewrite(ti.span, rewrite); + } + } + ast::AssocItemKind::TyAlias(ref ty_alias_kind) => { + let ast::TyAliasKind(_, ref generics, ref generic_bounds, ref type_default) = + **ty_alias_kind; + let rewrite = rewrite_type_alias( + ti.ident, + type_default.as_ref(), + generics, + Some(generic_bounds), + &self.get_context(), + self.block_indent, + &ti.vis, + ti.span, + ); + self.push_rewrite(ti.span, rewrite); + } + ast::AssocItemKind::MacCall(ref mac) => { + self.visit_mac(mac, Some(ti.ident), MacroPosition::Item); + } + } + } + + pub(crate) fn visit_impl_item(&mut self, ii: &ast::AssocItem) { + skip_out_of_file_lines_range_visitor!(self, ii.span); + + if self.visit_attrs(&ii.attrs, ast::AttrStyle::Outer) { + self.push_skipped_with_span(ii.attrs.as_slice(), ii.span, ii.span); + return; + } + + match ii.kind { + ast::AssocItemKind::Fn(ref fn_kind) => { + let ast::FnKind(defaultness, ref sig, ref generics, ref block) = **fn_kind; + if let Some(ref body) = block { + let inner_attrs = inner_attributes(&ii.attrs); + let fn_ctxt = visit::FnCtxt::Assoc(visit::AssocCtxt::Impl); + self.visit_fn( + visit::FnKind::Fn(fn_ctxt, ii.ident, sig, &ii.vis, Some(body)), + generics, + &sig.decl, + ii.span, + defaultness, + Some(&inner_attrs), + ); + } else { + let indent = self.block_indent; + let rewrite = + self.rewrite_required_fn(indent, ii.ident, sig, generics, ii.span); + self.push_rewrite(ii.span, rewrite); + } + } + ast::AssocItemKind::Const(..) => self.visit_static(&StaticParts::from_impl_item(ii)), + ast::AssocItemKind::TyAlias(ref ty_alias_kind) => { + let ast::TyAliasKind(defaultness, ref generics, _, ref ty) = **ty_alias_kind; + let rewrite_associated = || { + rewrite_associated_impl_type( + ii.ident, + &ii.vis, + defaultness, + ty.as_ref(), + &generics, + &self.get_context(), + self.block_indent, + ii.span, + ) + }; + let rewrite = match ty { + None => rewrite_associated(), + Some(ty) => match ty.kind { + ast::TyKind::ImplTrait(_, ref bounds) => rewrite_opaque_impl_type( + &self.get_context(), + ii.ident, + generics, + bounds, + self.block_indent, + ), + _ => rewrite_associated(), + }, + }; + self.push_rewrite(ii.span, rewrite); + } + ast::AssocItemKind::MacCall(ref mac) => { + self.visit_mac(mac, Some(ii.ident), MacroPosition::Item); + } + } + } + + fn visit_mac(&mut self, mac: &ast::MacCall, ident: Option, pos: MacroPosition) { + skip_out_of_file_lines_range_visitor!(self, mac.span()); + + // 1 = ; + let shape = self.shape().saturating_sub_width(1); + let rewrite = self.with_context(|ctx| rewrite_macro(mac, ident, ctx, shape, pos)); + // As of v638 of the rustc-ap-* crates, the associated span no longer includes + // the trailing semicolon. This determines the correct span to ensure scenarios + // with whitespace between the delimiters and trailing semi (i.e. `foo!(abc) ;`) + // are formatted correctly. + let (span, rewrite) = match macro_style(mac, &self.get_context()) { + DelimToken::Bracket | DelimToken::Paren if MacroPosition::Item == pos => { + let search_span = mk_sp(mac.span().hi(), self.snippet_provider.end_pos()); + let hi = self.snippet_provider.span_before(search_span, ";"); + let target_span = mk_sp(mac.span().lo(), hi + BytePos(1)); + let rewrite = rewrite.map(|rw| { + if !rw.ends_with(";") { + format!("{};", rw) + } else { + rw + } + }); + (target_span, rewrite) + } + _ => (mac.span(), rewrite), + }; + + self.push_rewrite(span, rewrite); + } + + pub(crate) fn push_str(&mut self, s: &str) { + self.line_number += count_newlines(s); + self.buffer.push_str(s); + } + + #[allow(clippy::needless_pass_by_value)] + fn push_rewrite_inner(&mut self, span: Span, rewrite: Option) { + if let Some(ref s) = rewrite { + self.push_str(s); + } else { + let snippet = self.snippet(span); + self.push_str(snippet.trim()); + } + self.last_pos = source!(self, span).hi(); + } + + pub(crate) fn push_rewrite(&mut self, span: Span, rewrite: Option) { + self.format_missing_with_indent(source!(self, span).lo()); + self.push_rewrite_inner(span, rewrite); + } + + pub(crate) fn push_skipped_with_span( + &mut self, + attrs: &[ast::Attribute], + item_span: Span, + main_span: Span, + ) { + self.format_missing_with_indent(source!(self, item_span).lo()); + // do not take into account the lines with attributes as part of the skipped range + let attrs_end = attrs + .iter() + .map(|attr| self.parse_sess.line_of_byte_pos(attr.span.hi())) + .max() + .unwrap_or(1); + let first_line = self.parse_sess.line_of_byte_pos(main_span.lo()); + // Statement can start after some newlines and/or spaces + // or it can be on the same line as the last attribute. + // So here we need to take a minimum between the two. + let lo = std::cmp::min(attrs_end + 1, first_line); + self.push_rewrite_inner(item_span, None); + let hi = self.line_number + 1; + self.skipped_range.borrow_mut().push((lo, hi)); + } + + pub(crate) fn from_context(ctx: &'a RewriteContext<'_>) -> FmtVisitor<'a> { + let mut visitor = FmtVisitor::from_parse_sess( + ctx.parse_sess, + ctx.config, + ctx.snippet_provider, + ctx.report.clone(), + ); + visitor.skip_context.update(ctx.skip_context.clone()); + visitor.set_parent_context(ctx); + visitor + } + + pub(crate) fn from_parse_sess( + parse_session: &'a ParseSess, + config: &'a Config, + snippet_provider: &'a SnippetProvider, + report: FormatReport, + ) -> FmtVisitor<'a> { + FmtVisitor { + parent_context: None, + parse_sess: parse_session, + buffer: String::with_capacity(snippet_provider.big_snippet.len() * 2), + last_pos: BytePos(0), + block_indent: Indent::empty(), + config, + is_if_else_block: false, + snippet_provider, + line_number: 0, + skipped_range: Rc::new(RefCell::new(vec![])), + is_macro_def: false, + macro_rewrite_failure: false, + report, + skip_context: Default::default(), + } + } + + pub(crate) fn opt_snippet(&'b self, span: Span) -> Option<&'a str> { + self.snippet_provider.span_to_snippet(span) + } + + pub(crate) fn snippet(&'b self, span: Span) -> &'a str { + self.opt_snippet(span).unwrap() + } + + // Returns true if we should skip the following item. + pub(crate) fn visit_attrs(&mut self, attrs: &[ast::Attribute], style: ast::AttrStyle) -> bool { + for attr in attrs { + if attr.has_name(depr_skip_annotation()) { + let file_name = self.parse_sess.span_to_filename(attr.span); + self.report.append( + file_name, + vec![FormattingError::from_span( + attr.span, + self.parse_sess, + ErrorKind::DeprecatedAttr, + )], + ); + } else { + match &attr.kind { + ast::AttrKind::Normal(ref attribute_item, _) + if self.is_unknown_rustfmt_attr(&attribute_item.path.segments) => + { + let file_name = self.parse_sess.span_to_filename(attr.span); + self.report.append( + file_name, + vec![FormattingError::from_span( + attr.span, + self.parse_sess, + ErrorKind::BadAttr, + )], + ); + } + _ => (), + } + } + } + if contains_skip(attrs) { + return true; + } + + let attrs: Vec<_> = attrs.iter().filter(|a| a.style == style).cloned().collect(); + if attrs.is_empty() { + return false; + } + + let rewrite = attrs.rewrite(&self.get_context(), self.shape()); + let span = mk_sp(attrs[0].span.lo(), attrs[attrs.len() - 1].span.hi()); + self.push_rewrite(span, rewrite); + + false + } + + fn is_unknown_rustfmt_attr(&self, segments: &[ast::PathSegment]) -> bool { + if segments[0].ident.to_string() != "rustfmt" { + return false; + } + !is_skip_attr(segments) + } + + fn walk_mod_items(&mut self, m: &ast::Mod) { + self.visit_items_with_reordering(&ptr_vec_to_ref_vec(&m.items)); + } + + fn walk_stmts(&mut self, stmts: &[Stmt<'_>], include_current_empty_semi: bool) { + if stmts.is_empty() { + return; + } + + // Extract leading `use ...;`. + let items: Vec<_> = stmts + .iter() + .take_while(|stmt| stmt.to_item().map_or(false, is_use_item)) + .filter_map(|stmt| stmt.to_item()) + .collect(); + + if items.is_empty() { + self.visit_stmt(&stmts[0], include_current_empty_semi); + + // FIXME(calebcartwright 2021-01-03) - This exists strictly to maintain legacy + // formatting where rustfmt would preserve redundant semicolons on Items in a + // statement position. + // + // Starting in rustc-ap-* v692 (~2020-12-01) the rustc parser now parses this as + // two separate statements (Item and Empty kinds), whereas before it was parsed as + // a single statement with the statement's span including the redundant semicolon. + // + // rustfmt typically tosses unnecessary/redundant semicolons, and eventually we + // should toss these as well, but doing so at this time would + // break the Stability Guarantee + // N.B. This could be updated to utilize the version gates. + let include_next_empty = if stmts.len() > 1 { + match (&stmts[0].as_ast_node().kind, &stmts[1].as_ast_node().kind) { + (ast::StmtKind::Item(_), ast::StmtKind::Empty) => true, + _ => false, + } + } else { + false + }; + + self.walk_stmts(&stmts[1..], include_next_empty); + } else { + self.visit_items_with_reordering(&items); + self.walk_stmts(&stmts[items.len()..], false); + } + } + + fn walk_block_stmts(&mut self, b: &ast::Block) { + self.walk_stmts(&Stmt::from_ast_nodes(b.stmts.iter()), false) + } + + fn format_mod( + &mut self, + m: &ast::Mod, + vis: &ast::Visibility, + s: Span, + ident: symbol::Ident, + attrs: &[ast::Attribute], + is_internal: bool, + ) { + let vis_str = utils::format_visibility(&self.get_context(), vis); + self.push_str(&*vis_str); + self.push_str(format_unsafety(m.unsafety)); + self.push_str("mod "); + // Calling `to_owned()` to work around borrow checker. + let ident_str = rewrite_ident(&self.get_context(), ident).to_owned(); + self.push_str(&ident_str); + + if is_internal { + match self.config.brace_style() { + BraceStyle::AlwaysNextLine => { + let indent_str = self.block_indent.to_string_with_newline(self.config); + self.push_str(&indent_str); + self.push_str("{"); + } + _ => self.push_str(" {"), + } + // Hackery to account for the closing }. + let mod_lo = self.snippet_provider.span_after(source!(self, s), "{"); + let body_snippet = + self.snippet(mk_sp(mod_lo, source!(self, m.inner).hi() - BytePos(1))); + let body_snippet = body_snippet.trim(); + if body_snippet.is_empty() { + self.push_str("}"); + } else { + self.last_pos = mod_lo; + self.block_indent = self.block_indent.block_indent(self.config); + self.visit_attrs(attrs, ast::AttrStyle::Inner); + self.walk_mod_items(m); + let missing_span = self.next_span(m.inner.hi() - BytePos(1)); + self.close_block(missing_span, false); + } + self.last_pos = source!(self, m.inner).hi(); + } else { + self.push_str(";"); + self.last_pos = source!(self, s).hi(); + } + } + + pub(crate) fn format_separate_mod(&mut self, m: &Module<'_>, end_pos: BytePos) { + self.block_indent = Indent::empty(); + if self.visit_attrs(m.attrs(), ast::AttrStyle::Inner) { + self.push_skipped_with_span(m.attrs(), m.as_ref().inner, m.as_ref().inner); + } else { + self.walk_mod_items(m.as_ref()); + self.format_missing_with_indent(end_pos); + } + } + + pub(crate) fn skip_empty_lines(&mut self, end_pos: BytePos) { + while let Some(pos) = self + .snippet_provider + .opt_span_after(self.next_span(end_pos), "\n") + { + if let Some(snippet) = self.opt_snippet(self.next_span(pos)) { + if snippet.trim().is_empty() { + self.last_pos = pos; + } else { + return; + } + } + } + } + + pub(crate) fn with_context(&mut self, f: F) -> Option + where + F: Fn(&RewriteContext<'_>) -> Option, + { + let context = self.get_context(); + let result = f(&context); + + self.macro_rewrite_failure |= context.macro_rewrite_failure.get(); + result + } + + pub(crate) fn get_context(&self) -> RewriteContext<'_> { + RewriteContext { + parse_sess: self.parse_sess, + config: self.config, + inside_macro: Rc::new(Cell::new(false)), + use_block: Cell::new(false), + is_if_else_block: Cell::new(false), + force_one_line_chain: Cell::new(false), + snippet_provider: self.snippet_provider, + macro_rewrite_failure: Cell::new(false), + is_macro_def: self.is_macro_def, + report: self.report.clone(), + skip_context: self.skip_context.clone(), + skipped_range: self.skipped_range.clone(), + } + } +} diff --git a/src/tools/rustfmt/tests/cargo-fmt/main.rs b/src/tools/rustfmt/tests/cargo-fmt/main.rs new file mode 100644 index 0000000000..5493b09e4a --- /dev/null +++ b/src/tools/rustfmt/tests/cargo-fmt/main.rs @@ -0,0 +1,73 @@ +// Integration tests for cargo-fmt. + +use std::env; +use std::process::Command; + +/// Run the cargo-fmt executable and return its output. +fn cargo_fmt(args: &[&str]) -> (String, String) { + let mut bin_dir = env::current_exe().unwrap(); + bin_dir.pop(); // chop off test exe name + if bin_dir.ends_with("deps") { + bin_dir.pop(); + } + let cmd = bin_dir.join(format!("cargo-fmt{}", env::consts::EXE_SUFFIX)); + + // Ensure cargo-fmt runs the rustfmt binary from the local target dir. + let path = env::var_os("PATH").unwrap_or_default(); + let mut paths = env::split_paths(&path).collect::>(); + paths.insert(0, bin_dir); + let new_path = env::join_paths(paths).unwrap(); + + match Command::new(&cmd).args(args).env("PATH", new_path).output() { + Ok(output) => ( + String::from_utf8(output.stdout).expect("utf-8"), + String::from_utf8(output.stderr).expect("utf-8"), + ), + Err(e) => panic!("failed to run `{:?} {:?}`: {}", cmd, args, e), + } +} + +macro_rules! assert_that { + ($args:expr, $check:ident $check_args:tt) => { + let (stdout, stderr) = cargo_fmt($args); + if !stdout.$check$check_args { + panic!( + "Output not expected for cargo-fmt {:?}\n\ + expected: {}{}\n\ + actual stdout:\n{}\n\ + actual stderr:\n{}", + $args, + stringify!($check), + stringify!($check_args), + stdout, + stderr + ); + } + }; +} + +#[ignore] +#[test] +fn version() { + assert_that!(&["--version"], starts_with("rustfmt ")); + assert_that!(&["--version"], starts_with("rustfmt ")); + assert_that!(&["--", "-V"], starts_with("rustfmt ")); + assert_that!(&["--", "--version"], starts_with("rustfmt ")); +} + +#[ignore] +#[test] +fn print_config() { + assert_that!( + &["--", "--print-config", "current", "."], + contains("max_width = ") + ); +} + +#[ignore] +#[test] +fn rustfmt_help() { + assert_that!(&["--", "--help"], contains("Format Rust code")); + assert_that!(&["--", "-h"], contains("Format Rust code")); + assert_that!(&["--", "--help=config"], contains("Configuration Options:")); +} diff --git a/src/tools/rustfmt/tests/config/disable_all_formatting.toml b/src/tools/rustfmt/tests/config/disable_all_formatting.toml new file mode 100644 index 0000000000..c7ad93bafe --- /dev/null +++ b/src/tools/rustfmt/tests/config/disable_all_formatting.toml @@ -0,0 +1 @@ +disable_all_formatting = true diff --git a/src/tools/rustfmt/tests/config/issue-1111.toml b/src/tools/rustfmt/tests/config/issue-1111.toml new file mode 100644 index 0000000000..44148a2d3c --- /dev/null +++ b/src/tools/rustfmt/tests/config/issue-1111.toml @@ -0,0 +1 @@ +reorder_imports = true diff --git a/src/tools/rustfmt/tests/config/issue-2641.toml b/src/tools/rustfmt/tests/config/issue-2641.toml new file mode 100644 index 0000000000..11c9dca8a0 --- /dev/null +++ b/src/tools/rustfmt/tests/config/issue-2641.toml @@ -0,0 +1 @@ +newline_style = "Windows" diff --git a/src/tools/rustfmt/tests/config/issue-3779.toml b/src/tools/rustfmt/tests/config/issue-3779.toml new file mode 100644 index 0000000000..6ca52aee32 --- /dev/null +++ b/src/tools/rustfmt/tests/config/issue-3779.toml @@ -0,0 +1,3 @@ +ignore = [ + "tests/**/issue-3779/ice.rs" +] diff --git a/src/tools/rustfmt/tests/config/issue-3802.toml b/src/tools/rustfmt/tests/config/issue-3802.toml new file mode 100644 index 0000000000..74ee8b010d --- /dev/null +++ b/src/tools/rustfmt/tests/config/issue-3802.toml @@ -0,0 +1,2 @@ +unstable_features = true +license_template_path = "" diff --git a/src/tools/rustfmt/tests/config/skip_children.toml b/src/tools/rustfmt/tests/config/skip_children.toml new file mode 100644 index 0000000000..f52930d50b --- /dev/null +++ b/src/tools/rustfmt/tests/config/skip_children.toml @@ -0,0 +1 @@ +skip_children = true diff --git a/src/tools/rustfmt/tests/config/small_tabs.toml b/src/tools/rustfmt/tests/config/small_tabs.toml new file mode 100644 index 0000000000..35c8fd8646 --- /dev/null +++ b/src/tools/rustfmt/tests/config/small_tabs.toml @@ -0,0 +1,12 @@ +max_width = 100 +comment_width = 80 +tab_spaces = 2 +newline_style = "Unix" +brace_style = "SameLineWhere" +fn_args_layout = "Tall" +trailing_comma = "Vertical" +indent_style = "Block" +report_todo = "Always" +report_fixme = "Never" +reorder_imports = false +format_strings = true diff --git a/src/tools/rustfmt/tests/coverage/source/comments.rs b/src/tools/rustfmt/tests/coverage/source/comments.rs new file mode 100644 index 0000000000..10940039e8 --- /dev/null +++ b/src/tools/rustfmt/tests/coverage/source/comments.rs @@ -0,0 +1,7 @@ +// rustfmt-emit_mode: coverage +/// Here's a doc comment! +fn main() { + // foo is bar + let foo = "bar"; + // loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong comment!!!!! +} diff --git a/src/tools/rustfmt/tests/coverage/target/comments.rs b/src/tools/rustfmt/tests/coverage/target/comments.rs new file mode 100644 index 0000000000..95e7b4705e --- /dev/null +++ b/src/tools/rustfmt/tests/coverage/target/comments.rs @@ -0,0 +1,7 @@ +XX XXXXXXXXXXXXXXXXXX XXXXXXXX +/// Here's a doc comment! +fn main() { + XX XXX XX XXX + let foo = "bar"; + XX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXX +} diff --git a/src/tools/rustfmt/tests/license-template/lt.txt b/src/tools/rustfmt/tests/license-template/lt.txt new file mode 100644 index 0000000000..ea4390371a --- /dev/null +++ b/src/tools/rustfmt/tests/license-template/lt.txt @@ -0,0 +1,2 @@ +// rustfmt-license_template_path: tests/license-template/lt.txt +// Copyright {\d+} The rustfmt developers. diff --git a/src/tools/rustfmt/tests/parser/issue-4126/invalid.rs b/src/tools/rustfmt/tests/parser/issue-4126/invalid.rs new file mode 100644 index 0000000000..7709c84846 --- /dev/null +++ b/src/tools/rustfmt/tests/parser/issue-4126/invalid.rs @@ -0,0 +1,6 @@ +fn foo() { + if bar && if !baz { + next_is_none = Some(true); + } + println!("foo"); +} diff --git a/src/tools/rustfmt/tests/parser/issue-4126/lib.rs b/src/tools/rustfmt/tests/parser/issue-4126/lib.rs new file mode 100644 index 0000000000..aac63e3557 --- /dev/null +++ b/src/tools/rustfmt/tests/parser/issue-4126/lib.rs @@ -0,0 +1 @@ +mod invalid; diff --git a/src/tools/rustfmt/tests/parser/issue_4418.rs b/src/tools/rustfmt/tests/parser/issue_4418.rs new file mode 100644 index 0000000000..ff30235f07 --- /dev/null +++ b/src/tools/rustfmt/tests/parser/issue_4418.rs @@ -0,0 +1 @@ +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/parser/unclosed-delims/issue_4466.rs b/src/tools/rustfmt/tests/parser/unclosed-delims/issue_4466.rs new file mode 100644 index 0000000000..2c2c81c91d --- /dev/null +++ b/src/tools/rustfmt/tests/parser/unclosed-delims/issue_4466.rs @@ -0,0 +1,11 @@ +fn main() { + if true { + println!("answer: {}", a_func(); + } else { + println!("don't think so."); + } +} + +fn a_func() -> i32 { + 42 +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/rustfmt/main.rs b/src/tools/rustfmt/tests/rustfmt/main.rs new file mode 100644 index 0000000000..8effb1c6fc --- /dev/null +++ b/src/tools/rustfmt/tests/rustfmt/main.rs @@ -0,0 +1,108 @@ +//! Integration tests for rustfmt. + +use std::env; +use std::fs::remove_file; +use std::path::Path; +use std::process::Command; + +/// Run the rustfmt executable and return its output. +fn rustfmt(args: &[&str]) -> (String, String) { + let mut bin_dir = env::current_exe().unwrap(); + bin_dir.pop(); // chop off test exe name + if bin_dir.ends_with("deps") { + bin_dir.pop(); + } + let cmd = bin_dir.join(format!("rustfmt{}", env::consts::EXE_SUFFIX)); + + // Ensure the rustfmt binary runs from the local target dir. + let path = env::var_os("PATH").unwrap_or_default(); + let mut paths = env::split_paths(&path).collect::>(); + paths.insert(0, bin_dir); + let new_path = env::join_paths(paths).unwrap(); + + match Command::new(&cmd).args(args).env("PATH", new_path).output() { + Ok(output) => ( + String::from_utf8(output.stdout).expect("utf-8"), + String::from_utf8(output.stderr).expect("utf-8"), + ), + Err(e) => panic!("failed to run `{:?} {:?}`: {}", cmd, args, e), + } +} + +macro_rules! assert_that { + ($args:expr, $($check:ident $check_args:tt)&&+) => { + let (stdout, stderr) = rustfmt($args); + if $(!stdout.$check$check_args && !stderr.$check$check_args)||* { + panic!( + "Output not expected for rustfmt {:?}\n\ + expected: {}\n\ + actual stdout:\n{}\n\ + actual stderr:\n{}", + $args, + stringify!($( $check$check_args )&&*), + stdout, + stderr + ); + } + }; +} + +#[ignore] +#[test] +fn print_config() { + assert_that!( + &["--print-config", "unknown"], + starts_with("Unknown print-config option") + ); + assert_that!(&["--print-config", "default"], contains("max_width = 100")); + assert_that!(&["--print-config", "minimal"], contains("PATH required")); + assert_that!( + &["--print-config", "minimal", "minimal-config"], + contains("doesn't work with standard input.") + ); + + let (stdout, stderr) = rustfmt(&[ + "--print-config", + "minimal", + "minimal-config", + "src/shape.rs", + ]); + assert!( + Path::new("minimal-config").exists(), + "stdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + remove_file("minimal-config").unwrap(); +} + +#[ignore] +#[test] +fn inline_config() { + // single invocation + assert_that!( + &[ + "--print-config", + "current", + ".", + "--config=color=Never,edition=2018" + ], + contains("color = \"Never\"") && contains("edition = \"2018\"") + ); + + // multiple overriding invocations + assert_that!( + &[ + "--print-config", + "current", + ".", + "--config", + "color=never,edition=2018", + "--config", + "color=always,format_strings=true" + ], + contains("color = \"Always\"") + && contains("edition = \"2018\"") + && contains("format_strings = true") + ); +} diff --git a/src/tools/rustfmt/tests/source/alignment_2633/block_style.rs b/src/tools/rustfmt/tests/source/alignment_2633/block_style.rs new file mode 100644 index 0000000000..77fb2919ed --- /dev/null +++ b/src/tools/rustfmt/tests/source/alignment_2633/block_style.rs @@ -0,0 +1,8 @@ +// rustfmt-struct_field_align_threshold: 50 + +fn func() { + Ok(ServerInformation { name: unwrap_message_string(items.get(0)), + vendor: unwrap_message_string(items.get(1)), + version: unwrap_message_string(items.get(2)), + spec_version: unwrap_message_string(items.get(3)), }); +} diff --git a/src/tools/rustfmt/tests/source/alignment_2633/visual_style.rs b/src/tools/rustfmt/tests/source/alignment_2633/visual_style.rs new file mode 100644 index 0000000000..f34cc621e6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/alignment_2633/visual_style.rs @@ -0,0 +1,9 @@ +// rustfmt-struct_field_align_threshold: 50 +// rustfmt-indent_style: Visual + +fn func() { + Ok(ServerInformation { name: unwrap_message_string(items.get(0)), + vendor: unwrap_message_string(items.get(1)), + version: unwrap_message_string(items.get(2)), + spec_version: unwrap_message_string(items.get(3)), }); +} diff --git a/src/tools/rustfmt/tests/source/array_comment.rs b/src/tools/rustfmt/tests/source/array_comment.rs new file mode 100644 index 0000000000..87372b2793 --- /dev/null +++ b/src/tools/rustfmt/tests/source/array_comment.rs @@ -0,0 +1,19 @@ +// Issue 2842 +// The comment should not make the last line shorter + +static XXX: [i8; 64] = [ + 1, // Comment + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; + +static XXX: [i8; 64] = [ + 1, + // Comment + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; + +static XXX: [i8; 64] = [ + 1, + // Comment + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; diff --git a/src/tools/rustfmt/tests/source/assignment.rs b/src/tools/rustfmt/tests/source/assignment.rs new file mode 100644 index 0000000000..71de325566 --- /dev/null +++ b/src/tools/rustfmt/tests/source/assignment.rs @@ -0,0 +1,34 @@ +// Test assignment + +fn main() { + let some_var : Type ; + + let mut mutable; + + let variable = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::BBBBBBBBBBBBBBBBBBBBBB::CCCCCCCCCCCCCCCCCCCCCC::EEEEEE; + + variable = LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONG; + + let single_line_fit = + DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD; + + single_line_fit = 5;single_lit_fit >>= 10; + + + // #2791 + let x = 2;;;; +} + +fn break_meee() { + { + (block_start, block_size, margin_block_start, margin_block_end) = match (block_start, + block_end, + block_size) { + x => 1, + _ => 2, + }; + } +} + +// #2018 +pub const EXPLAIN_UNSIZED_TUPLE_COERCION: &'static str = "Unsized tuple coercion is not stable enough for use and is subject to change"; diff --git a/src/tools/rustfmt/tests/source/associated-types-bounds-wrapping.rs b/src/tools/rustfmt/tests/source/associated-types-bounds-wrapping.rs new file mode 100644 index 0000000000..464f428c7f --- /dev/null +++ b/src/tools/rustfmt/tests/source/associated-types-bounds-wrapping.rs @@ -0,0 +1,5 @@ +// Test proper wrapping of long associated type bounds + +pub trait HttpService { + type WsService: 'static + Service; +} diff --git a/src/tools/rustfmt/tests/source/associated_type_bounds.rs b/src/tools/rustfmt/tests/source/associated_type_bounds.rs new file mode 100644 index 0000000000..8572778a5a --- /dev/null +++ b/src/tools/rustfmt/tests/source/associated_type_bounds.rs @@ -0,0 +1,13 @@ +// See #3657 - https://github.com/rust-lang/rustfmt/issues/3657 + +#![feature(associated_type_bounds)] + +fn f>() {} + +fn g>() {} + +fn h>() {} + +fn i>() {} + +fn j>() {} diff --git a/src/tools/rustfmt/tests/source/async_block.rs b/src/tools/rustfmt/tests/source/async_block.rs new file mode 100644 index 0000000000..3de51a084d --- /dev/null +++ b/src/tools/rustfmt/tests/source/async_block.rs @@ -0,0 +1,35 @@ +// rustfmt-edition: 2018 + +fn main() { + let x = async { + Ok(()) + }; +} + +fn baz() { + // test + let x = async { + // async blocks are great + Ok(()) + }; + + let y = async { + Ok(()) + }; // comment + + spawn( + a, + async move { + action(); + Ok(()) + }, + ); + + spawn( + a, + async move || { + action(); + Ok(()) + }, + ); +} diff --git a/src/tools/rustfmt/tests/source/async_fn.rs b/src/tools/rustfmt/tests/source/async_fn.rs new file mode 100644 index 0000000000..c63cf5b0f5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/async_fn.rs @@ -0,0 +1,28 @@ +// rustfmt-edition: 2018 + +async fn bar() -> Result<(), ()> { + Ok(()) +} + +pub async fn baz() -> Result<(), ()> { + Ok(()) +} + +async unsafe fn foo() { + async move { + Ok(()) + } +} + +async unsafe fn rust() { + async move { // comment + Ok(()) + } +} + +async fn await_try() { + something + .await + ? + ; +} diff --git a/src/tools/rustfmt/tests/source/attrib.rs b/src/tools/rustfmt/tests/source/attrib.rs new file mode 100644 index 0000000000..d45fba5522 --- /dev/null +++ b/src/tools/rustfmt/tests/source/attrib.rs @@ -0,0 +1,234 @@ +// rustfmt-wrap_comments: true +// Test attributes and doc comments are preserved. +#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://doc.rust-lang.org/favicon.ico", + html_root_url = "https://doc.rust-lang.org/nightly/", + html_playground_url = "https://play.rust-lang.org/", test(attr(deny(warnings))))] + +//! Doc comment + +#![attribute] + +//! Crate doc comment + +// Comment + +// Comment on attribute +#![the(attribute)] + +// Another comment + +/// Blah blah blah. +/// Blah blah blah. +/// Blah blah blah. +/// Blah blah blah. + +/// Blah blah blah. +impl Bar { + /// Blah blah blooo. + /// Blah blah blooo. + /// Blah blah blooo. + /// Blah blah blooo. + #[an_attribute] + #[doc = "an attribute that shouldn't be normalized to a doc comment"] + fn foo(&mut self) -> isize { + } + + /// Blah blah bing. + /// Blah blah bing. + /// Blah blah bing. + + + /// Blah blah bing. + /// Blah blah bing. + /// Blah blah bing. + pub fn f2(self) { + (foo, bar) + } + + #[another_attribute] + fn f3(self) -> Dog { + } + + /// Blah blah bing. + + #[attrib1] + /// Blah blah bing. + #[attrib2] + // Another comment that needs rewrite because it's tooooooooooooooooooooooooooooooo loooooooooooong. + /// Blah blah bing. + fn f4(self) -> Cat { + } + + // We want spaces around `=` + #[cfg(feature="nightly")] + fn f5(self) -> Monkey {} +} + +// #984 +struct Foo { + # [ derive ( Clone , PartialEq , Debug , Deserialize , Serialize ) ] + foo: usize, +} + +// #1668 + +/// Default path (*nix) +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))] +fn foo() { + #[cfg(target_os = "freertos")] + match port_id { + 'a' | 'A' => GpioPort { port_address: GPIO_A }, + 'b' | 'B' => GpioPort { port_address: GPIO_B }, + _ => panic!(), + } + + #[cfg_attr(not(target_os = "freertos"), allow(unused_variables))] + let x = 3; +} + +// #1777 +#[test] +#[should_panic(expected = "(")] +#[should_panic(expected = /* ( */ "(")] +#[should_panic(/* ((((( */expected /* ((((( */= /* ((((( */ "("/* ((((( */)] +#[should_panic( + /* (((((((( *//* + (((((((((()(((((((( */ + expected = "(" + // (((((((( +)] +fn foo() {} + +// #1799 +fn issue_1799() { + #[allow(unreachable_code)] // https://github.com/rust-lang/rust/issues/43336 + Some( Err(error) ) ; + + #[allow(unreachable_code)] + // https://github.com/rust-lang/rust/issues/43336 + Some( Err(error) ) ; +} + +// Formatting inner attributes +fn inner_attributes() { + #![ this_is_an_inner_attribute ( foo ) ] + + foo(); +} + +impl InnerAttributes() { + #![ this_is_an_inner_attribute ( foo ) ] + + fn foo() {} +} + +mod InnerAttributes { + #![ this_is_an_inner_attribute ( foo ) ] +} + +fn attributes_on_statements() { + // Local + # [ attr ( on ( local ) ) ] + let x = 3; + + // Item + # [ attr ( on ( item ) ) ] + use foo; + + // Expr + # [ attr ( on ( expr ) ) ] + {} + + // Semi + # [ attr ( on ( semi ) ) ] + foo(); + + // Mac + # [ attr ( on ( mac ) ) ] + foo!(); +} + +// Large derives +#[derive(Add, Sub, Mul, Div, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Serialize, Mul)] + + +/// Foo bar baz + + +#[derive(Add, Sub, Mul, Div, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Serialize, Deserialize)] +pub struct HP(pub u8); + +// Long `#[doc = "..."]` +struct A { #[doc = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] b: i32 } + +// #2647 +#[cfg(feature = "this_line_is_101_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")] +pub fn foo() {} + +// path attrs +#[clippy::bar] +#[clippy::bar(a, b, c)] +pub fn foo() {} + +mod issue_2620 { + #[derive(Debug, StructOpt)] +#[structopt(about = "Display information about the character on FF Logs")] +pub struct Params { + #[structopt(help = "The server the character is on")] + server: String, + #[structopt(help = "The character's first name")] + first_name: String, + #[structopt(help = "The character's last name")] + last_name: String, + #[structopt( + short = "j", + long = "job", + help = "The job to look at", + parse(try_from_str) + )] + job: Option +} +} + +// #2969 +#[cfg(not(all(feature="std", + any(target_os = "linux", target_os = "android", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "haiku", + target_os = "emscripten", + target_os = "solaris", + target_os = "cloudabi", + target_os = "macos", target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", + target_os = "redox", + target_os = "fuchsia", + windows, + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), + ))))] +type Os = NoSource; + +// #3313 +fn stmt_expr_attributes() { + let foo ; + #[must_use] + foo = false ; +} + +// #3509 +fn issue3509() { + match MyEnum { + MyEnum::Option1 if cfg!(target_os = "windows") => + #[cfg(target_os = "windows")]{ + 1 + } + } + match MyEnum { + MyEnum::Option1 if cfg!(target_os = "windows") => + #[cfg(target_os = "windows")] + 1, + } +} diff --git a/src/tools/rustfmt/tests/source/big-impl-block.rs b/src/tools/rustfmt/tests/source/big-impl-block.rs new file mode 100644 index 0000000000..f71e6515c4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/big-impl-block.rs @@ -0,0 +1,123 @@ +// #1357 +impl< + 'a, + Select, + From, + Distinct, + Where, + Order, + Limit, + Offset, + Groupby, + DB, +> InternalBoxedDsl<'a, DB> + for SelectStatement< + Select, + From, + Distinct, + Where, + Order, + Limit, + Offset, + GroupBy, + > where + DB: Backend, + Select: QueryFragment + SelectableExpression + 'a, + Distinct: QueryFragment + 'a, + Where: Into + 'a>>>, + Order: QueryFragment + 'a, + Limit: QueryFragment + 'a, + Offset: QueryFragment + 'a, +{ + type Output = BoxedSelectStatement<'a, Select::SqlTypeForSelect, From, DB>; + + fn internal_into_boxed(self) -> Self::Output { + BoxedSelectStatement::new( + Box::new(self.select), + self.from, + Box::new(self.distinct), + self.where_clause.into(), + Box::new(self.order), + Box::new(self.limit), + Box::new(self.offset), + ) + } +} + +// #1369 +impl< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> Foo for Bar { + fn foo() {} +} +impl Foo< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> for Bar { + fn foo() {} +} +impl< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> Foo< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> for Bar { + fn foo() {} +} +impl< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> Foo for Bar< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> { + fn foo() {} +} +impl Foo< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> for Bar< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> { + fn foo() {} +} +impl< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> Foo< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> for Bar< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> { + fn foo() {} +} + +// #1689 +impl SubSelectDirect + where + M: select::Selector, + S: event::Stream, + F: for<'t> FnMut(transform::Api< + 't, + Stream>, + >) + -> transform::Api<'t, X>, + X: event::Stream, +{ +} diff --git a/src/tools/rustfmt/tests/source/big-impl-visual.rs b/src/tools/rustfmt/tests/source/big-impl-visual.rs new file mode 100644 index 0000000000..7d906ac37f --- /dev/null +++ b/src/tools/rustfmt/tests/source/big-impl-visual.rs @@ -0,0 +1,106 @@ +// rustfmt-indent_style: Visual + +// #1357 +impl< + 'a, + Select, + From, + Distinct, + Where, + Order, + Limit, + Offset, + Groupby, + DB, +> InternalBoxedDsl<'a, DB> + for SelectStatement< + Select, + From, + Distinct, + Where, + Order, + Limit, + Offset, + GroupBy, + > where + DB: Backend, + Select: QueryFragment + SelectableExpression + 'a, + Distinct: QueryFragment + 'a, + Where: Into + 'a>>>, + Order: QueryFragment + 'a, + Limit: QueryFragment + 'a, + Offset: QueryFragment + 'a, +{ + type Output = BoxedSelectStatement<'a, Select::SqlTypeForSelect, From, DB>; + + fn internal_into_boxed(self) -> Self::Output { + BoxedSelectStatement::new( + Box::new(self.select), + self.from, + Box::new(self.distinct), + self.where_clause.into(), + Box::new(self.order), + Box::new(self.limit), + Box::new(self.offset), + ) + } +} + +// #1369 +impl< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> Foo for Bar { + fn foo() {} +} +impl Foo< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> for Bar { + fn foo() {} +} +impl< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> Foo< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> for Bar { + fn foo() {} +} +impl< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> Foo for Bar< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> { + fn foo() {} +} +impl Foo< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> for Bar< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, +> { + fn foo() {} +} +impl Foo + for Bar { + fn foo() {} +} diff --git a/src/tools/rustfmt/tests/source/binary-expr.rs b/src/tools/rustfmt/tests/source/binary-expr.rs new file mode 100644 index 0000000000..f7502931d9 --- /dev/null +++ b/src/tools/rustfmt/tests/source/binary-expr.rs @@ -0,0 +1,10 @@ +// Binary expressions + +fn foo() { + // 100 + let x = aaaaaaaaaa || bbbbbbbbbb || cccccccccc || dddddddddd && eeeeeeeeee || ffffffffff || ggg; + // 101 + let x = aaaaaaaaaa || bbbbbbbbbb || cccccccccc || dddddddddd && eeeeeeeeee || ffffffffff || gggg; + // 104 + let x = aaaaaaaaaa || bbbbbbbbbb || cccccccccc || dddddddddd && eeeeeeeeee || ffffffffff || gggggggg; +} diff --git a/src/tools/rustfmt/tests/source/break-and-continue.rs b/src/tools/rustfmt/tests/source/break-and-continue.rs new file mode 100644 index 0000000000..c01d8a0784 --- /dev/null +++ b/src/tools/rustfmt/tests/source/break-and-continue.rs @@ -0,0 +1,23 @@ +// break and continue formatting + +#![feature(loop_break_value)] + +fn main() { + 'a: loop { + break 'a; + } + + let mut done = false; + 'b: while !done { + done = true; + continue 'b; + } + + let x = loop { + break 5; + }; + + let x = 'c: loop { + break 'c 5; + }; +} diff --git a/src/tools/rustfmt/tests/source/catch.rs b/src/tools/rustfmt/tests/source/catch.rs new file mode 100644 index 0000000000..541db1dc90 --- /dev/null +++ b/src/tools/rustfmt/tests/source/catch.rs @@ -0,0 +1,28 @@ +// rustfmt-edition: 2018 +#![feature(try_blocks)] + +fn main() { + let x = try { + foo()? + }; + + let x = try /* Invisible comment */ { foo()? }; + + let x = try { + unsafe { foo()? } + }; + + let y = match (try { + foo()? + }) { + _ => (), + }; + + try { + foo()?; + }; + + try { + // Regular try block + }; +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/arch/aarch64.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/aarch64.rs new file mode 100644 index 0000000000..ebae2bd285 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/aarch64.rs @@ -0,0 +1,106 @@ +//! Aarch64 run-time features. + +/// Checks if `aarch64` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal,stdsimd)] +macro_rules! is_aarch64_feature_detected { + ("neon") => { + // FIXME: this should be removed once we rename Aarch64 neon to asimd + cfg!(target_feature = "neon") || + $crate::detect::check_for($crate::detect::Feature::asimd) + }; + ("asimd") => { + cfg!(target_feature = "neon") || + $crate::detect::check_for($crate::detect::Feature::asimd) + }; + ("pmull") => { + cfg!(target_feature = "pmull") || + $crate::detect::check_for($crate::detect::Feature::pmull) + }; + ("fp") => { + cfg!(target_feature = "fp") || + $crate::detect::check_for($crate::detect::Feature::fp) + }; + ("fp16") => { + cfg!(target_feature = "fp16") || + $crate::detect::check_for($crate::detect::Feature::fp16) + }; + ("sve") => { + cfg!(target_feature = "sve") || + $crate::detect::check_for($crate::detect::Feature::sve) + }; + ("crc") => { + cfg!(target_feature = "crc") || + $crate::detect::check_for($crate::detect::Feature::crc) + }; + ("crypto") => { + cfg!(target_feature = "crypto") || + $crate::detect::check_for($crate::detect::Feature::crypto) + }; + ("lse") => { + cfg!(target_feature = "lse") || + $crate::detect::check_for($crate::detect::Feature::lse) + }; + ("rdm") => { + cfg!(target_feature = "rdm") || + $crate::detect::check_for($crate::detect::Feature::rdm) + }; + ("rcpc") => { + cfg!(target_feature = "rcpc") || + $crate::detect::check_for($crate::detect::Feature::rcpc) + }; + ("dotprod") => { + cfg!(target_feature = "dotprod") || + $crate::detect::check_for($crate::detect::Feature::dotprod) + }; + ("ras") => { + compile_error!("\"ras\" feature cannot be detected at run-time") + }; + ("v8.1a") => { + compile_error!("\"v8.1a\" feature cannot be detected at run-time") + }; + ("v8.2a") => { + compile_error!("\"v8.2a\" feature cannot be detected at run-time") + }; + ("v8.3a") => { + compile_error!("\"v8.3a\" feature cannot be detected at run-time") + }; + ($t:tt,) => { + is_aarch64_feature_detected!($t); + }; + ($t:tt) => { compile_error!(concat!("unknown aarch64 target feature: ", $t)) }; +} + +/// ARM Aarch64 CPU Feature enum. Each variant denotes a position in a bitset +/// for a particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// ARM Advanced SIMD (ASIMD) + asimd, + /// Polynomial Multiply + pmull, + /// Floating point support + fp, + /// Half-float support. + fp16, + /// Scalable Vector Extension (SVE) + sve, + /// CRC32 (Cyclic Redundancy Check) + crc, + /// Crypto: AES + PMULL + SHA1 + SHA2 + crypto, + /// Atomics (Large System Extension) + lse, + /// Rounding Double Multiply (ASIMDRDM) + rdm, + /// Release consistent Processor consistent (RcPc) + rcpc, + /// Vector Dot-Product (ASIMDDP) + dotprod, +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/arch/arm.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/arm.rs new file mode 100644 index 0000000000..b2626bf292 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/arm.rs @@ -0,0 +1,39 @@ +//! Run-time feature detection on ARM Aarch32. + +/// Checks if `arm` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal,stdsimd)] +macro_rules! is_arm_feature_detected { + ("neon") => { + cfg!(target_feature = "neon") || + $crate::detect::check_for($crate::detect::Feature::neon) + }; + ("pmull") => { + cfg!(target_feature = "pmull") || + $crate::detect::check_for($crate::detect::Feature::pmull) + }; + ("v7") => { compile_error!("\"v7\" feature cannot be detected at run-time") }; + ("vfp2") => { compile_error!("\"vfp2\" feature cannot be detected at run-time") }; + ("vfp3") => { compile_error!("\"vfp3\" feature cannot be detected at run-time") }; + ("vfp4") => { compile_error!("\"vfp4\" feature cannot be detected at run-time") }; + ($t:tt,) => { + is_arm_feature_detected!($t); + }; + ($t:tt) => { compile_error!(concat!("unknown arm target feature: ", $t)) }; +} + +/// ARM CPU Feature enum. Each variant denotes a position in a bitset for a +/// particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// ARM Advanced SIMD (NEON) - Aarch32 + neon, + /// Polynomial Multiply + pmull, +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/arch/mips.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/mips.rs new file mode 100644 index 0000000000..f4381b811c --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/mips.rs @@ -0,0 +1,29 @@ +//! Run-time feature detection on MIPS. + +/// Checks if `mips` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal,stdsimd)] +macro_rules! is_mips_feature_detected { + ("msa") => { + cfg!(target_feature = "msa") || + $crate::detect::check_for($crate::detect::Feature::msa) + }; + ($t:tt,) => { + is_mips_feature_detected!($t); + }; + ($t:tt) => { compile_error!(concat!("unknown mips target feature: ", $t)) }; +} + +/// MIPS CPU Feature enum. Each variant denotes a position in a bitset for a +/// particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// MIPS SIMD Architecture (MSA) + msa, +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/arch/mips64.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/mips64.rs new file mode 100644 index 0000000000..2663bc68ba --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/mips64.rs @@ -0,0 +1,29 @@ +//! Run-time feature detection on MIPS64. + +/// Checks if `mips64` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal,stdsimd)] +macro_rules! is_mips64_feature_detected { + ("msa") => { + cfg!(target_feature = "msa") || + $crate::detect::check_for($crate::detect::Feature::msa) + }; + ($t:tt,) => { + is_mips64_feature_detected!($t); + }; + ($t:tt) => { compile_error!(concat!("unknown mips64 target feature: ", $t)) }; +} + +/// MIPS64 CPU Feature enum. Each variant denotes a position in a bitset +/// for a particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// MIPS SIMD Architecture (MSA) + msa, +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/arch/powerpc.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/powerpc.rs new file mode 100644 index 0000000000..a342dc1aac --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/powerpc.rs @@ -0,0 +1,42 @@ +//! Run-time feature detection on PowerPC. + +/// Checks if `powerpc` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal,stdsimd)] +macro_rules! is_powerpc_feature_detected { + ("altivec") => { + cfg!(target_feature = "altivec") || + $crate::detect::check_for($crate::detect::Feature::altivec) + }; + ("vsx") => { + cfg!(target_feature = "vsx") || + $crate::detect::check_for($crate::detect::Feature::vsx) + }; + ("power8") => { + cfg!(target_feature = "power8") || + $crate::detect::check_for($crate::detect::Feature::power8) + }; + ($t:tt,) => { + is_powerpc_feature_detected!($t); + }; + ($t:tt) => { compile_error!(concat!("unknown powerpc target feature: ", $t)) }; +} + + +/// PowerPC CPU Feature enum. Each variant denotes a position in a bitset +/// for a particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// Altivec + altivec, + /// VSX + vsx, + /// Power8 + power8, +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/arch/powerpc64.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/powerpc64.rs new file mode 100644 index 0000000000..2e82c56925 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/powerpc64.rs @@ -0,0 +1,42 @@ +//! Run-time feature detection on PowerPC64. + +/// Checks if `powerpc64` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal,stdsimd)] +macro_rules! is_powerpc64_feature_detected { + ("altivec") => { + cfg!(target_feature = "altivec") || + $crate::detect::check_for($crate::detect::Feature::altivec) + }; + ("vsx") => { + cfg!(target_feature = "vsx") || + $crate::detect::check_for($crate::detect::Feature::vsx) + }; + ("power8") => { + cfg!(target_feature = "power8") || + $crate::detect::check_for($crate::detect::Feature::power8) + }; + ($t:tt,) => { + is_powerpc64_feature_detected!($t); + }; + ($t:tt) => { compile_error!(concat!("unknown powerpc64 target feature: ", $t)) }; +} + + +/// PowerPC64 CPU Feature enum. Each variant denotes a position in a bitset +/// for a particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// Altivec + altivec, + /// VSX + vsx, + /// Power8 + power8, +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/arch/x86.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/x86.rs new file mode 100644 index 0000000000..50d5cfa87c --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/x86.rs @@ -0,0 +1,348 @@ +//! This module implements minimal run-time feature detection for x86. +//! +//! The features are detected using the `detect_features` function below. +//! This function uses the CPUID instruction to read the feature flags from the +//! CPU and encodes them in an `usize` where each bit position represents +//! whether a feature is available (bit is set) or unavaiable (bit is cleared). +//! +//! The enum `Feature` is used to map bit positions to feature names, and the +//! the `__crate::detect::check_for!` macro is used to map string literals (e.g., +//! "avx") to these bit positions (e.g., `Feature::avx`). +//! +//! The run-time feature detection is performed by the +//! `__crate::detect::check_for(Feature) -> bool` function. On its first call, +//! this functions queries the CPU for the available features and stores them +//! in a global `AtomicUsize` variable. The query is performed by just checking +//! whether the feature bit in this global variable is set or cleared. + +/// A macro to test at *runtime* whether a CPU feature is available on +/// x86/x86-64 platforms. +/// +/// This macro is provided in the standard library and will detect at runtime +/// whether the specified CPU feature is detected. This does **not** resolve at +/// compile time unless the specified feature is already enabled for the entire +/// crate. Runtime detection currently relies mostly on the `cpuid` instruction. +/// +/// This macro only takes one argument which is a string literal of the feature +/// being tested for. The feature names supported are the lowercase versions of +/// the ones defined by Intel in [their documentation][docs]. +/// +/// ## Supported arguments +/// +/// This macro supports the same names that `#[target_feature]` supports. Unlike +/// `#[target_feature]`, however, this macro does not support names separated +/// with a comma. Instead testing for multiple features must be done through +/// separate macro invocations for now. +/// +/// Supported arguments are: +/// +/// * `"aes"` +/// * `"pclmulqdq"` +/// * `"rdrand"` +/// * `"rdseed"` +/// * `"tsc"` +/// * `"mmx"` +/// * `"sse"` +/// * `"sse2"` +/// * `"sse3"` +/// * `"ssse3"` +/// * `"sse4.1"` +/// * `"sse4.2"` +/// * `"sse4a"` +/// * `"sha"` +/// * `"avx"` +/// * `"avx2"` +/// * `"avx512f"` +/// * `"avx512cd"` +/// * `"avx512er"` +/// * `"avx512pf"` +/// * `"avx512bw"` +/// * `"avx512dq"` +/// * `"avx512vl"` +/// * `"avx512ifma"` +/// * `"avx512vbmi"` +/// * `"avx512vpopcntdq"` +/// * `"f16c"` +/// * `"fma"` +/// * `"bmi1"` +/// * `"bmi2"` +/// * `"abm"` +/// * `"lzcnt"` +/// * `"tbm"` +/// * `"popcnt"` +/// * `"fxsr"` +/// * `"xsave"` +/// * `"xsaveopt"` +/// * `"xsaves"` +/// * `"xsavec"` +/// * `"adx"` +/// * `"rtm"` +/// +/// [docs]: https://software.intel.com/sites/landingpage/IntrinsicsGuide +#[macro_export] +#[stable(feature = "simd_x86", since = "1.27.0")] +#[allow_internal_unstable(stdsimd_internal,stdsimd)] +macro_rules! is_x86_feature_detected { + ("aes") => { + cfg!(target_feature = "aes") || $crate::detect::check_for( + $crate::detect::Feature::aes) }; + ("pclmulqdq") => { + cfg!(target_feature = "pclmulqdq") || $crate::detect::check_for( + $crate::detect::Feature::pclmulqdq) }; + ("rdrand") => { + cfg!(target_feature = "rdrand") || $crate::detect::check_for( + $crate::detect::Feature::rdrand) }; + ("rdseed") => { + cfg!(target_feature = "rdseed") || $crate::detect::check_for( + $crate::detect::Feature::rdseed) }; + ("tsc") => { + cfg!(target_feature = "tsc") || $crate::detect::check_for( + $crate::detect::Feature::tsc) }; + ("mmx") => { + cfg!(target_feature = "mmx") || $crate::detect::check_for( + $crate::detect::Feature::mmx) }; + ("sse") => { + cfg!(target_feature = "sse") || $crate::detect::check_for( + $crate::detect::Feature::sse) }; + ("sse2") => { + cfg!(target_feature = "sse2") || $crate::detect::check_for( + $crate::detect::Feature::sse2) + }; + ("sse3") => { + cfg!(target_feature = "sse3") || $crate::detect::check_for( + $crate::detect::Feature::sse3) + }; + ("ssse3") => { + cfg!(target_feature = "ssse3") || $crate::detect::check_for( + $crate::detect::Feature::ssse3) + }; + ("sse4.1") => { + cfg!(target_feature = "sse4.1") || $crate::detect::check_for( + $crate::detect::Feature::sse4_1) + }; + ("sse4.2") => { + cfg!(target_feature = "sse4.2") || $crate::detect::check_for( + $crate::detect::Feature::sse4_2) + }; + ("sse4a") => { + cfg!(target_feature = "sse4a") || $crate::detect::check_for( + $crate::detect::Feature::sse4a) + }; + ("sha") => { + cfg!(target_feature = "sha") || $crate::detect::check_for( + $crate::detect::Feature::sha) + }; + ("avx") => { + cfg!(target_feature = "avx") || $crate::detect::check_for( + $crate::detect::Feature::avx) + }; + ("avx2") => { + cfg!(target_feature = "avx2") || $crate::detect::check_for( + $crate::detect::Feature::avx2) + }; + ("avx512f") => { + cfg!(target_feature = "avx512f") || $crate::detect::check_for( + $crate::detect::Feature::avx512f) + }; + ("avx512cd") => { + cfg!(target_feature = "avx512cd") || $crate::detect::check_for( + $crate::detect::Feature::avx512cd) + }; + ("avx512er") => { + cfg!(target_feature = "avx512er") || $crate::detect::check_for( + $crate::detect::Feature::avx512er) + }; + ("avx512pf") => { + cfg!(target_feature = "avx512pf") || $crate::detect::check_for( + $crate::detect::Feature::avx512pf) + }; + ("avx512bw") => { + cfg!(target_feature = "avx512bw") || $crate::detect::check_for( + $crate::detect::Feature::avx512bw) + }; + ("avx512dq") => { + cfg!(target_feature = "avx512dq") || $crate::detect::check_for( + $crate::detect::Feature::avx512dq) + }; + ("avx512vl") => { + cfg!(target_Feature = "avx512vl") || $crate::detect::check_for( + $crate::detect::Feature::avx512vl) + }; + ("avx512ifma") => { + cfg!(target_feature = "avx512ifma") || $crate::detect::check_for( + $crate::detect::Feature::avx512_ifma) + }; + ("avx512vbmi") => { + cfg!(target_feature = "avx512vbmi") || $crate::detect::check_for( + $crate::detect::Feature::avx512_vbmi) + }; + ("avx512vpopcntdq") => { + cfg!(target_feature = "avx512vpopcntdq") || $crate::detect::check_for( + $crate::detect::Feature::avx512_vpopcntdq) + }; + ("f16c") => { + cfg!(target_feature = "f16c") || $crate::detect::check_for( + $crate::detect::Feature::f16c) + }; + ("fma") => { + cfg!(target_feature = "fma") || $crate::detect::check_for( + $crate::detect::Feature::fma) + }; + ("bmi1") => { + cfg!(target_feature = "bmi1") || $crate::detect::check_for( + $crate::detect::Feature::bmi) + }; + ("bmi2") => { + cfg!(target_feature = "bmi2") || $crate::detect::check_for( + $crate::detect::Feature::bmi2) + }; + ("abm") => { + cfg!(target_feature = "abm") || $crate::detect::check_for( + $crate::detect::Feature::abm) + }; + ("lzcnt") => { + cfg!(target_feature = "lzcnt") || $crate::detect::check_for( + $crate::detect::Feature::abm) + }; + ("tbm") => { + cfg!(target_feature = "tbm") || $crate::detect::check_for( + $crate::detect::Feature::tbm) + }; + ("popcnt") => { + cfg!(target_feature = "popcnt") || $crate::detect::check_for( + $crate::detect::Feature::popcnt) + }; + ("fxsr") => { + cfg!(target_feature = "fxsr") || $crate::detect::check_for( + $crate::detect::Feature::fxsr) + }; + ("xsave") => { + cfg!(target_feature = "xsave") || $crate::detect::check_for( + $crate::detect::Feature::xsave) + }; + ("xsaveopt") => { + cfg!(target_feature = "xsaveopt") || $crate::detect::check_for( + $crate::detect::Feature::xsaveopt) + }; + ("xsaves") => { + cfg!(target_feature = "xsaves") || $crate::detect::check_for( + $crate::detect::Feature::xsaves) + }; + ("xsavec") => { + cfg!(target_feature = "xsavec") || $crate::detect::check_for( + $crate::detect::Feature::xsavec) + }; + ("cmpxchg16b") => { + cfg!(target_feature = "cmpxchg16b") || $crate::detect::check_for( + $crate::detect::Feature::cmpxchg16b) + }; + ("adx") => { + cfg!(target_feature = "adx") || $crate::detect::check_for( + $crate::detect::Feature::adx) + }; + ("rtm") => { + cfg!(target_feature = "rtm") || $crate::detect::check_for( + $crate::detect::Feature::rtm) + }; + ($t:tt,) => { + is_x86_feature_detected!($t); + }; + ($t:tt) => { + compile_error!(concat!("unknown target feature: ", $t)) + }; +} + +/// X86 CPU Feature enum. Each variant denotes a position in a bitset for a +/// particular feature. +/// +/// This is an unstable implementation detail subject to change. +#[allow(non_camel_case_types)] +#[repr(u8)] +#[doc(hidden)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// AES (Advanced Encryption Standard New Instructions AES-NI) + aes, + /// CLMUL (Carry-less Multiplication) + pclmulqdq, + /// RDRAND + rdrand, + /// RDSEED + rdseed, + /// TSC (Time Stamp Counter) + tsc, + /// MMX + mmx, + /// SSE (Streaming SIMD Extensions) + sse, + /// SSE2 (Streaming SIMD Extensions 2) + sse2, + /// SSE3 (Streaming SIMD Extensions 3) + sse3, + /// SSSE3 (Supplemental Streaming SIMD Extensions 3) + ssse3, + /// SSE4.1 (Streaming SIMD Extensions 4.1) + sse4_1, + /// SSE4.2 (Streaming SIMD Extensions 4.2) + sse4_2, + /// SSE4a (Streaming SIMD Extensions 4a) + sse4a, + /// SHA + sha, + /// AVX (Advanced Vector Extensions) + avx, + /// AVX2 (Advanced Vector Extensions 2) + avx2, + /// AVX-512 F (Foundation) + avx512f, + /// AVX-512 CD (Conflict Detection Instructions) + avx512cd, + /// AVX-512 ER (Exponential and Reciprocal Instructions) + avx512er, + /// AVX-512 PF (Prefetch Instructions) + avx512pf, + /// AVX-512 BW (Byte and Word Instructions) + avx512bw, + /// AVX-512 DQ (Doubleword and Quadword) + avx512dq, + /// AVX-512 VL (Vector Length Extensions) + avx512vl, + /// AVX-512 IFMA (Integer Fused Multiply Add) + avx512_ifma, + /// AVX-512 VBMI (Vector Byte Manipulation Instructions) + avx512_vbmi, + /// AVX-512 VPOPCNTDQ (Vector Population Count Doubleword and + /// Quadword) + avx512_vpopcntdq, + /// F16C (Conversions between IEEE-754 `binary16` and `binary32` formats) + f16c, + /// FMA (Fused Multiply Add) + fma, + /// BMI1 (Bit Manipulation Instructions 1) + bmi, + /// BMI1 (Bit Manipulation Instructions 2) + bmi2, + /// ABM (Advanced Bit Manipulation) on AMD / LZCNT (Leading Zero + /// Count) on Intel + abm, + /// TBM (Trailing Bit Manipulation) + tbm, + /// POPCNT (Population Count) + popcnt, + /// FXSR (Floating-point context fast save and restor) + fxsr, + /// XSAVE (Save Processor Extended States) + xsave, + /// XSAVEOPT (Save Processor Extended States Optimized) + xsaveopt, + /// XSAVES (Save Processor Extended States Supervisor) + xsaves, + /// XSAVEC (Save Processor Extended States Compacted) + xsavec, + /// CMPXCH16B, a 16-byte compare-and-swap instruction + cmpxchg16b, + /// ADX, Intel ADX (Multi-Precision Add-Carry Instruction Extensions) + adx, + /// RTM, Intel (Restricted Transactional Memory) + rtm, +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/bit.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/bit.rs new file mode 100644 index 0000000000..578f0b16b7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/bit.rs @@ -0,0 +1,9 @@ +//! Bit manipulation utilities. + +/// Tests the `bit` of `x`. +#[allow(dead_code)] +#[inline] +pub(crate) fn test(x: usize, bit: u32) -> bool { + debug_assert!(bit < 32, "bit index out-of-bounds"); + x & (1 << bit) != 0 +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/cache.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/cache.rs new file mode 100644 index 0000000000..92bc4b58d1 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/cache.rs @@ -0,0 +1,164 @@ +//! Caches run-time feature detection so that it only needs to be computed +//! once. + +#![allow(dead_code)] // not used on all platforms + +use crate::sync::atomic::Ordering; + +#[cfg(target_pointer_width = "64")] +use crate::sync::atomic::AtomicU64; + +#[cfg(target_pointer_width = "32")] +use crate::sync::atomic::AtomicU32; + +/// Sets the `bit` of `x`. +#[inline] +const fn set_bit(x: u64, bit: u32) -> u64 { + x | 1 << bit +} + +/// Tests the `bit` of `x`. +#[inline] +const fn test_bit(x: u64, bit: u32) -> bool { + x & (1 << bit) != 0 +} + +/// Maximum number of features that can be cached. +const CACHE_CAPACITY: u32 = 63; + +/// This type is used to initialize the cache +#[derive(Copy, Clone)] +pub(crate) struct Initializer(u64); + +#[allow(clippy::use_self)] +impl Default for Initializer { + fn default() -> Self { + Initializer(0) + } +} + +impl Initializer { + /// Tests the `bit` of the cache. + #[allow(dead_code)] + #[inline] + pub(crate) fn test(self, bit: u32) -> bool { + // FIXME: this way of making sure that the cache is large enough is + // brittle. + debug_assert!( + bit < CACHE_CAPACITY, + "too many features, time to increase the cache size!" + ); + test_bit(self.0, bit) + } + + /// Sets the `bit` of the cache. + #[inline] + pub(crate) fn set(&mut self, bit: u32) { + // FIXME: this way of making sure that the cache is large enough is + // brittle. + debug_assert!( + bit < CACHE_CAPACITY, + "too many features, time to increase the cache size!" + ); + let v = self.0; + self.0 = set_bit(v, bit); + } +} + +/// This global variable is a cache of the features supported by the CPU. +static CACHE: Cache = Cache::uninitialized(); + +/// Feature cache with capacity for `CACHE_CAPACITY` features. +/// +/// Note: the last feature bit is used to represent an +/// uninitialized cache. +#[cfg(target_pointer_width = "64")] +struct Cache(AtomicU64); + +#[cfg(target_pointer_width = "64")] +#[allow(clippy::use_self)] +impl Cache { + /// Creates an uninitialized cache. + #[allow(clippy::declare_interior_mutable_const)] + const fn uninitialized() -> Self { + Cache(AtomicU64::new(u64::max_value())) + } + /// Is the cache uninitialized? + #[inline] + pub(crate) fn is_uninitialized(&self) -> bool { + self.0.load(Ordering::Relaxed) == u64::max_value() + } + + /// Is the `bit` in the cache set? + #[inline] + pub(crate) fn test(&self, bit: u32) -> bool { + test_bit(CACHE.0.load(Ordering::Relaxed), bit) + } + + /// Initializes the cache. + #[inline] + pub(crate) fn initialize(&self, value: Initializer) { + self.0.store(value.0, Ordering::Relaxed); + } +} + +/// Feature cache with capacity for `CACHE_CAPACITY` features. +/// +/// Note: the last feature bit is used to represent an +/// uninitialized cache. +#[cfg(target_pointer_width = "32")] +struct Cache(AtomicU32, AtomicU32); + +#[cfg(target_pointer_width = "32")] +impl Cache { + /// Creates an uninitialized cache. + const fn uninitialized() -> Self { + Cache( + AtomicU32::new(u32::max_value()), + AtomicU32::new(u32::max_value()), + ) + } + /// Is the cache uninitialized? + #[inline] + pub(crate) fn is_uninitialized(&self) -> bool { + self.1.load(Ordering::Relaxed) == u32::max_value() + } + + /// Is the `bit` in the cache set? + #[inline] + pub(crate) fn test(&self, bit: u32) -> bool { + if bit < 32 { + test_bit(CACHE.0.load(Ordering::Relaxed) as u64, bit) + } else { + test_bit(CACHE.1.load(Ordering::Relaxed) as u64, bit - 32) + } + } + + /// Initializes the cache. + #[inline] + pub(crate) fn initialize(&self, value: Initializer) { + let lo: u32 = value.0 as u32; + let hi: u32 = (value.0 >> 32) as u32; + self.0.store(lo, Ordering::Relaxed); + self.1.store(hi, Ordering::Relaxed); + } +} + +/// Tests the `bit` of the storage. If the storage has not been initialized, +/// initializes it with the result of `f()`. +/// +/// On its first invocation, it detects the CPU features and caches them in the +/// `CACHE` global variable as an `AtomicU64`. +/// +/// It uses the `Feature` variant to index into this variable as a bitset. If +/// the bit is set, the feature is enabled, and otherwise it is disabled. +#[inline] +pub(crate) fn test(bit: u32, f: F) -> bool +where + F: FnOnce() -> Initializer, +{ + if CACHE.is_uninitialized() { + CACHE.initialize(f()); + } + CACHE.test(bit) +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/error_macros.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/error_macros.rs new file mode 100644 index 0000000000..6769757ed9 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/error_macros.rs @@ -0,0 +1,150 @@ +//! The `is_{target_arch}_feature_detected!` macro are only available on their +//! architecture. These macros provide a better error messages when the user +//! attempts to call them in a different architecture. + +/// Prevents compilation if `is_x86_feature_detected` is used somewhere +/// else than `x86` and `x86_64` targets. +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_x86_feature_detected { + ($t: tt) => { + compile_error!( + r#" + is_x86_feature_detected can only be used on x86 and x86_64 targets. + You can prevent it from being used in other architectures by + guarding it behind a cfg(target_arch) as follows: + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + if is_x86_feature_detected(...) { ... } + } + "# + ) + }; +} + +/// Prevents compilation if `is_arm_feature_detected` is used somewhere else +/// than `ARM` targets. +#[cfg(not(target_arch = "arm"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_arm_feature_detected { + ($t:tt) => { + compile_error!( + r#" + is_arm_feature_detected can only be used on ARM targets. + You can prevent it from being used in other architectures by + guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "arm")] { + if is_arm_feature_detected(...) { ... } + } + "# + ) + }; +} + +/// Prevents compilation if `is_aarch64_feature_detected` is used somewhere else +/// than `aarch64` targets. +#[cfg(not(target_arch = "aarch64"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_aarch64_feature_detected { + ($t: tt) => { + compile_error!( + r#" + is_aarch64_feature_detected can only be used on AArch64 targets. + You can prevent it from being used in other architectures by + guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "aarch64")] { + if is_aarch64_feature_detected(...) { ... } + } + "# + ) + }; +} + +/// Prevents compilation if `is_powerpc_feature_detected` is used somewhere else +/// than `PowerPC` targets. +#[cfg(not(target_arch = "powerpc"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_powerpc_feature_detected { + ($t:tt) => { + compile_error!( + r#" +is_powerpc_feature_detected can only be used on PowerPC targets. +You can prevent it from being used in other architectures by +guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "powerpc")] { + if is_powerpc_feature_detected(...) { ... } + } +"# + ) + }; +} + +/// Prevents compilation if `is_powerpc64_feature_detected` is used somewhere +/// else than `PowerPC64` targets. +#[cfg(not(target_arch = "powerpc64"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_powerpc64_feature_detected { + ($t:tt) => { + compile_error!( + r#" +is_powerpc64_feature_detected can only be used on PowerPC64 targets. +You can prevent it from being used in other architectures by +guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "powerpc64")] { + if is_powerpc64_feature_detected(...) { ... } + } +"# + ) + }; +} + +/// Prevents compilation if `is_mips_feature_detected` is used somewhere else +/// than `MIPS` targets. +#[cfg(not(target_arch = "mips"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_mips_feature_detected { + ($t:tt) => { + compile_error!( + r#" + is_mips_feature_detected can only be used on MIPS targets. + You can prevent it from being used in other architectures by + guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "mips")] { + if is_mips_feature_detected(...) { ... } + } + "# + ) + }; +} + +/// Prevents compilation if `is_mips64_feature_detected` is used somewhere else +/// than `MIPS64` targets. +#[cfg(not(target_arch = "mips64"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_mips64_feature_detected { + ($t:tt) => { + compile_error!( + r#" + is_mips64_feature_detected can only be used on MIPS64 targets. + You can prevent it from being used in other architectures by + guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "mips64")] { + if is_mips64_feature_detected(...) { ... } + } + "# + ) + }; +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/mod.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/mod.rs new file mode 100644 index 0000000000..f446e88eed --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/mod.rs @@ -0,0 +1,85 @@ +//! This module implements run-time feature detection. +//! +//! The `is_{arch}_feature_detected!("feature-name")` macros take the name of a +//! feature as a string-literal, and return a boolean indicating whether the +//! feature is enabled at run-time or not. +//! +//! These macros do two things: +//! * map the string-literal into an integer stored as a `Feature` enum, +//! * call a `os::check_for(x: Feature)` function that returns `true` if the +//! feature is enabled. +//! +//! The `Feature` enums are also implemented in the `arch/{target_arch}.rs` +//! modules. +//! +//! The `check_for` functions are, in general, Operating System dependent. Most +//! architectures do not allow user-space programs to query the feature bits +//! due to security concerns (x86 is the big exception). These functions are +//! implemented in the `os/{target_os}.rs` modules. + +#[macro_use] +mod error_macros; + +cfg_if! { + if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + #[path = "arch/x86.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "arm")] { + #[path = "arch/arm.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "aarch64")] { + #[path = "arch/aarch64.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "powerpc")] { + #[path = "arch/powerpc.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "powerpc64")] { + #[path = "arch/powerpc64.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "mips")] { + #[path = "arch/mips.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "mips64")] { + #[path = "arch/mips64.rs"] + #[macro_use] + mod arch; + } else { + // Unimplemented architecture: + mod arch { + pub enum Feature { + Null + } + } + } +} +pub use self::arch::Feature; + +mod bit; +mod cache; + +cfg_if! { + if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + // On x86/x86_64 no OS specific functionality is required. + #[path = "os/x86.rs"] + mod os; + } else if #[cfg(all(target_os = "linux", feature = "use_std"))] { + #[path = "os/linux/mod.rs"] + mod os; + } else if #[cfg(target_os = "freebsd")] { + #[cfg(target_arch = "aarch64")] + #[path = "os/aarch64.rs"] + mod aarch64; + #[path = "os/freebsd/mod.rs"] + mod os; + } else { + #[path = "os/other.rs"] + mod os; + } +} +pub use self::os::check_for; diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/aarch64.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/aarch64.rs new file mode 100644 index 0000000000..dfb8c87707 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/aarch64.rs @@ -0,0 +1,79 @@ +//! Run-time feature detection for Aarch64 on any OS that emulates the mrs instruction. +//! +//! On FreeBSD >= 12.0, Linux >= 4.11 and other operating systems, it is possible to use +//! privileged system registers from userspace to check CPU feature support. +//! +//! AArch64 system registers ID_AA64ISAR0_EL1, ID_AA64PFR0_EL1, ID_AA64ISAR1_EL1 +//! have bits dedicated to features like AdvSIMD, CRC32, AES, atomics (LSE), etc. +//! Each part of the register indicates the level of support for a certain feature, e.g. +//! when ID_AA64ISAR0_EL1\[7:4\] is >= 1, AES is supported; when it's >= 2, PMULL is supported. +//! +//! For proper support of [SoCs where different cores have different capabilities](https://medium.com/@jadr2ddude/a-big-little-problem-a-tale-of-big-little-gone-wrong-e7778ce744bb), +//! the OS has to always report only the features supported by all cores, like [FreeBSD does](https://reviews.freebsd.org/D17137#393947). +//! +//! References: +//! +//! - [Zircon implementation](https://fuchsia.googlesource.com/zircon/+/master/kernel/arch/arm64/feature.cpp) +//! - [Linux documentation](https://www.kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt) + +use crate::detect::{Feature, cache}; + +/// Try to read the features from the system registers. +/// +/// This will cause SIGILL if the current OS is not trapping the mrs instruction. +pub(crate) fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + + { + let mut enable_feature = |f, enable| { + if enable { + value.set(f as u32); + } + }; + + // ID_AA64ISAR0_EL1 - Instruction Set Attribute Register 0 + let aa64isar0: u64; + unsafe { asm!("mrs $0, ID_AA64ISAR0_EL1" : "=r"(aa64isar0)); } + + let aes = bits_shift(aa64isar0, 7, 4) >= 1; + let pmull = bits_shift(aa64isar0, 7, 4) >= 2; + let sha1 = bits_shift(aa64isar0, 11, 8) >= 1; + let sha2 = bits_shift(aa64isar0, 15, 12) >= 1; + enable_feature(Feature::pmull, pmull); + // Crypto is specified as AES + PMULL + SHA1 + SHA2 per LLVM/hosts.cpp + enable_feature(Feature::crypto, aes && pmull && sha1 && sha2); + enable_feature(Feature::lse, bits_shift(aa64isar0, 23, 20) >= 1); + enable_feature(Feature::crc, bits_shift(aa64isar0, 19, 16) >= 1); + + // ID_AA64PFR0_EL1 - Processor Feature Register 0 + let aa64pfr0: u64; + unsafe { asm!("mrs $0, ID_AA64PFR0_EL1" : "=r"(aa64pfr0)); } + + let fp = bits_shift(aa64pfr0, 19, 16) < 0xF; + let fphp = bits_shift(aa64pfr0, 19, 16) >= 1; + let asimd = bits_shift(aa64pfr0, 23, 20) < 0xF; + let asimdhp = bits_shift(aa64pfr0, 23, 20) >= 1; + enable_feature(Feature::fp, fp); + enable_feature(Feature::fp16, fphp); + // SIMD support requires float support - if half-floats are + // supported, it also requires half-float support: + enable_feature(Feature::asimd, fp && asimd && (!fphp | asimdhp)); + // SIMD extensions require SIMD support: + enable_feature(Feature::rdm, asimd && bits_shift(aa64isar0, 31, 28) >= 1); + enable_feature(Feature::dotprod, asimd && bits_shift(aa64isar0, 47, 44) >= 1); + enable_feature(Feature::sve, asimd && bits_shift(aa64pfr0, 35, 32) >= 1); + + // ID_AA64ISAR1_EL1 - Instruction Set Attribute Register 1 + let aa64isar1: u64; + unsafe { asm!("mrs $0, ID_AA64ISAR1_EL1" : "=r"(aa64isar1)); } + + enable_feature(Feature::rcpc, bits_shift(aa64isar1, 23, 20) >= 1); + } + + value +} + +#[inline] +fn bits_shift(x: u64, high: usize, low: usize) -> u64 { + (x >> low) & ((1 << (high - low + 1)) - 1) +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/aarch64.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/aarch64.rs new file mode 100644 index 0000000000..910d2f33b3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/aarch64.rs @@ -0,0 +1,28 @@ +//! Run-time feature detection for Aarch64 on FreeBSD. + +use crate::detect::{Feature, cache}; +use super::super::aarch64::detect_features; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +#[cfg(test)] +mod tests { + #[test] + fn dump() { + println!("asimd: {:?}", is_aarch64_feature_detected!("asimd")); + println!("pmull: {:?}", is_aarch64_feature_detected!("pmull")); + println!("fp: {:?}", is_aarch64_feature_detected!("fp")); + println!("fp16: {:?}", is_aarch64_feature_detected!("fp16")); + println!("sve: {:?}", is_aarch64_feature_detected!("sve")); + println!("crc: {:?}", is_aarch64_feature_detected!("crc")); + println!("crypto: {:?}", is_aarch64_feature_detected!("crypto")); + println!("lse: {:?}", is_aarch64_feature_detected!("lse")); + println!("rdm: {:?}", is_aarch64_feature_detected!("rdm")); + println!("rcpc: {:?}", is_aarch64_feature_detected!("rcpc")); + println!("dotprod: {:?}", is_aarch64_feature_detected!("dotprod")); + } +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/arm.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/arm.rs new file mode 100644 index 0000000000..e13847dcbd --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/arm.rs @@ -0,0 +1,27 @@ +//! Run-time feature detection for ARM on FreeBSD + +use crate::detect::{Feature, cache}; +use super::{auxvec}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Try to read the features from the auxiliary vector +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + let enable_feature = |value: &mut cache::Initializer, f, enable| { + if enable { + value.set(f as u32); + } + }; + + if let Ok(auxv) = auxvec::auxv() { + enable_feature(&mut value, Feature::neon, auxv.hwcap & 0x00001000 != 0); + enable_feature(&mut value, Feature::pmull, auxv.hwcap2 & 0x00000002 != 0); + return value; + } + value +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/auxvec.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/auxvec.rs new file mode 100644 index 0000000000..a2bac76760 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/auxvec.rs @@ -0,0 +1,86 @@ +//! Parses ELF auxiliary vectors. +#![cfg_attr(any(target_arch = "arm", target_arch = "powerpc64"), allow(dead_code))] + +/// Key to access the CPU Hardware capabilities bitfield. +pub(crate) const AT_HWCAP: usize = 25; +/// Key to access the CPU Hardware capabilities 2 bitfield. +pub(crate) const AT_HWCAP2: usize = 26; + +/// Cache HWCAP bitfields of the ELF Auxiliary Vector. +/// +/// If an entry cannot be read all the bits in the bitfield are set to zero. +/// This should be interpreted as all the features being disabled. +#[derive(Debug, Copy, Clone)] +pub(crate) struct AuxVec { + pub hwcap: usize, + pub hwcap2: usize, +} + +/// ELF Auxiliary Vector +/// +/// The auxiliary vector is a memory region in a running ELF program's stack +/// composed of (key: usize, value: usize) pairs. +/// +/// The keys used in the aux vector are platform dependent. For FreeBSD, they are +/// defined in [sys/elf_common.h][elf_common_h]. The hardware capabilities of a given +/// CPU can be queried with the `AT_HWCAP` and `AT_HWCAP2` keys. +/// +/// Note that run-time feature detection is not invoked for features that can +/// be detected at compile-time. +/// +/// [elf_common.h]: https://svnweb.freebsd.org/base/release/12.0.0/sys/sys/elf_common.h?revision=341707 +pub(crate) fn auxv() -> Result { + if let Ok(hwcap) = archauxv(AT_HWCAP) { + if let Ok(hwcap2) = archauxv(AT_HWCAP2) { + if hwcap != 0 && hwcap2 != 0 { + return Ok(AuxVec { hwcap, hwcap2 }); + } + } + } + Err(()) +} + +/// Tries to read the `key` from the auxiliary vector. +fn archauxv(key: usize) -> Result { + use crate::mem; + + #[derive (Copy, Clone)] + #[repr(C)] + pub struct Elf_Auxinfo { + pub a_type: usize, + pub a_un: unnamed, + } + #[derive (Copy, Clone)] + #[repr(C)] + pub union unnamed { + pub a_val: libc::c_long, + pub a_ptr: *mut libc::c_void, + pub a_fcn: Option ()>, + } + + let mut auxv: [Elf_Auxinfo; 27] = + [Elf_Auxinfo{a_type: 0, a_un: unnamed{a_val: 0,},}; 27]; + + let mut len: libc::c_uint = mem::size_of_val(&auxv) as libc::c_uint; + + unsafe { + let mut mib = [libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_AUXV, libc::getpid()]; + + let ret = libc::sysctl(mib.as_mut_ptr(), + mib.len() as u32, + &mut auxv as *mut _ as *mut _, + &mut len as *mut _ as *mut _, + 0 as *mut libc::c_void, + 0, + ); + + if ret != -1 { + for i in 0..auxv.len() { + if auxv[i].a_type == key { + return Ok(auxv[i].a_un.a_val as usize); + } + } + } + } + return Ok(0); +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/mod.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/mod.rs new file mode 100644 index 0000000000..1a5338a355 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/mod.rs @@ -0,0 +1,22 @@ +//! Run-time feature detection on FreeBSD + +mod auxvec; + +cfg_if! { + if #[cfg(target_arch = "aarch64")] { + mod aarch64; + pub use self::aarch64::check_for; + } else if #[cfg(target_arch = "arm")] { + mod arm; + pub use self::arm::check_for; + } else if #[cfg(target_arch = "powerpc64")] { + mod powerpc; + pub use self::powerpc::check_for; + } else { + use crate::arch::detect::Feature; + /// Performs run-time feature detection. + pub fn check_for(_x: Feature) -> bool { + false + } + } +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/powerpc.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/powerpc.rs new file mode 100644 index 0000000000..c7f761d4d6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/freebsd/powerpc.rs @@ -0,0 +1,27 @@ +//! Run-time feature detection for PowerPC on FreeBSD. + +use crate::detect::{Feature, cache}; +use super::{auxvec}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + let enable_feature = |value: &mut cache::Initializer, f, enable| { + if enable { + value.set(f as u32); + } + }; + + if let Ok(auxv) = auxvec::auxv() { + enable_feature(&mut value, Feature::altivec, auxv.hwcap & 0x10000000 != 0); + enable_feature(&mut value, Feature::vsx, auxv.hwcap & 0x00000080 != 0); + enable_feature(&mut value, Feature::power8, auxv.hwcap2 & 0x80000000 != 0); + return value; + } + value +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/aarch64.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/aarch64.rs new file mode 100644 index 0000000000..f7dc0f0222 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/aarch64.rs @@ -0,0 +1,157 @@ +//! Run-time feature detection for Aarch64 on Linux. + +use crate::detect::{Feature, cache, bit}; +use super::{auxvec, cpuinfo}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Try to read the features from the auxiliary vector, and if that fails, try +/// to read them from /proc/cpuinfo. +fn detect_features() -> cache::Initializer { + if let Ok(auxv) = auxvec::auxv() { + let hwcap: AtHwcap = auxv.into(); + return hwcap.cache(); + } + if let Ok(c) = cpuinfo::CpuInfo::new() { + let hwcap: AtHwcap = c.into(); + return hwcap.cache(); + } + cache::Initializer::default() +} + +/// These values are part of the platform-specific [asm/hwcap.h][hwcap] . +/// +/// [hwcap]: https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/hwcap.h +struct AtHwcap { + fp: bool, // 0 + asimd: bool, // 1 + // evtstrm: bool, // 2 + aes: bool, // 3 + pmull: bool, // 4 + sha1: bool, // 5 + sha2: bool, // 6 + crc32: bool, // 7 + atomics: bool, // 8 + fphp: bool, // 9 + asimdhp: bool, // 10 + // cpuid: bool, // 11 + asimdrdm: bool, // 12 + // jscvt: bool, // 13 + // fcma: bool, // 14 + lrcpc: bool, // 15 + // dcpop: bool, // 16 + // sha3: bool, // 17 + // sm3: bool, // 18 + // sm4: bool, // 19 + asimddp: bool, // 20 + // sha512: bool, // 21 + sve: bool, // 22 +} + +impl From for AtHwcap { + /// Reads AtHwcap from the auxiliary vector. + fn from(auxv: auxvec::AuxVec) -> Self { + AtHwcap { + fp: bit::test(auxv.hwcap, 0), + asimd: bit::test(auxv.hwcap, 1), + // evtstrm: bit::test(auxv.hwcap, 2), + aes: bit::test(auxv.hwcap, 3), + pmull: bit::test(auxv.hwcap, 4), + sha1: bit::test(auxv.hwcap, 5), + sha2: bit::test(auxv.hwcap, 6), + crc32: bit::test(auxv.hwcap, 7), + atomics: bit::test(auxv.hwcap, 8), + fphp: bit::test(auxv.hwcap, 9), + asimdhp: bit::test(auxv.hwcap, 10), + // cpuid: bit::test(auxv.hwcap, 11), + asimdrdm: bit::test(auxv.hwcap, 12), + // jscvt: bit::test(auxv.hwcap, 13), + // fcma: bit::test(auxv.hwcap, 14), + lrcpc: bit::test(auxv.hwcap, 15), + // dcpop: bit::test(auxv.hwcap, 16), + // sha3: bit::test(auxv.hwcap, 17), + // sm3: bit::test(auxv.hwcap, 18), + // sm4: bit::test(auxv.hwcap, 19), + asimddp: bit::test(auxv.hwcap, 20), + // sha512: bit::test(auxv.hwcap, 21), + sve: bit::test(auxv.hwcap, 22), + } + } +} + +impl From for AtHwcap { + /// Reads AtHwcap from /proc/cpuinfo . + fn from(c: cpuinfo::CpuInfo) -> Self { + let f = &c.field("Features"); + AtHwcap { + // 64-bit names. FIXME: In 32-bit compatibility mode /proc/cpuinfo will + // map some of the 64-bit names to some 32-bit feature names. This does not + // cover that yet. + fp: f.has("fp"), + asimd: f.has("asimd"), + // evtstrm: f.has("evtstrm"), + aes: f.has("aes"), + pmull: f.has("pmull"), + sha1: f.has("sha1"), + sha2: f.has("sha2"), + crc32: f.has("crc32"), + atomics: f.has("atomics"), + fphp: f.has("fphp"), + asimdhp: f.has("asimdhp"), + // cpuid: f.has("cpuid"), + asimdrdm: f.has("asimdrdm"), + // jscvt: f.has("jscvt"), + // fcma: f.has("fcma"), + lrcpc: f.has("lrcpc"), + // dcpop: f.has("dcpop"), + // sha3: f.has("sha3"), + // sm3: f.has("sm3"), + // sm4: f.has("sm4"), + asimddp: f.has("asimddp"), + // sha512: f.has("sha512"), + sve: f.has("sve"), + } + } +} + +impl AtHwcap { + /// Initializes the cache from the feature -bits. + /// + /// The features are enabled approximately like in LLVM host feature detection: + /// https://github.com/llvm-mirror/llvm/blob/master/lib/Support/Host.cpp#L1273 + fn cache(self) -> cache::Initializer { + let mut value = cache::Initializer::default(); + { + let mut enable_feature = |f, enable| { + if enable { + value.set(f as u32); + } + }; + + enable_feature(Feature::fp, self.fp); + // Half-float support requires float support + enable_feature(Feature::fp16, self.fp && self.fphp); + enable_feature(Feature::pmull, self.pmull); + enable_feature(Feature::crc, self.crc32); + enable_feature(Feature::lse, self.atomics); + enable_feature(Feature::rcpc, self.lrcpc); + + // SIMD support requires float support - if half-floats are + // supported, it also requires half-float support: + let asimd = self.fp && self.asimd && (!self.fphp | self.asimdhp); + enable_feature(Feature::asimd, asimd); + // SIMD extensions require SIMD support: + enable_feature(Feature::rdm, self.asimdrdm && asimd); + enable_feature(Feature::dotprod, self.asimddp && asimd); + enable_feature(Feature::sve, self.sve && asimd); + + // Crypto is specified as AES + PMULL + SHA1 + SHA2 per LLVM/hosts.cpp + enable_feature(Feature::crypto, self.aes && self.pmull && self.sha1 && self.sha2); + } + value + } +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/arm.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/arm.rs new file mode 100644 index 0000000000..0d58a847cd --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/arm.rs @@ -0,0 +1,49 @@ +//! Run-time feature detection for ARM on Linux. + +use crate::detect::{Feature, cache, bit}; +use super::{auxvec, cpuinfo}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Try to read the features from the auxiliary vector, and if that fails, try +/// to read them from /proc/cpuinfo. +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + let enable_feature = |value: &mut cache::Initializer, f, enable| { + if enable { + value.set(f as u32); + } + }; + + // The values are part of the platform-specific [asm/hwcap.h][hwcap] + // + // [hwcap]: https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/hwcap.h + if let Ok(auxv) = auxvec::auxv() { + enable_feature(&mut value, Feature::neon, bit::test(auxv.hwcap, 12)); + enable_feature(&mut value, Feature::pmull, bit::test(auxv.hwcap2, 1)); + return value; + } + + if let Ok(c) = cpuinfo::CpuInfo::new() { + enable_feature(&mut value, Feature::neon, c.field("Features").has("neon") && + !has_broken_neon(&c)); + enable_feature(&mut value, Feature::pmull, c.field("Features").has("pmull")); + return value; + } + value +} + +/// Is the CPU known to have a broken NEON unit? +/// +/// See https://crbug.com/341598. +fn has_broken_neon(cpuinfo: &cpuinfo::CpuInfo) -> bool { + cpuinfo.field("CPU implementer") == "0x51" + && cpuinfo.field("CPU architecture") == "7" + && cpuinfo.field("CPU variant") == "0x1" + && cpuinfo.field("CPU part") == "0x04d" + && cpuinfo.field("CPU revision") == "0" +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/auxvec.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/auxvec.rs new file mode 100644 index 0000000000..07b6432eaf --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/auxvec.rs @@ -0,0 +1,307 @@ +//! Parses ELF auxiliary vectors. +#![cfg_attr(not(target_arch = "aarch64"), allow(dead_code))] + +#[cfg(feature = "std_detect_file_io")] +use crate::{fs::File, io::Read}; + +/// Key to access the CPU Hardware capabilities bitfield. +pub(crate) const AT_HWCAP: usize = 16; +/// Key to access the CPU Hardware capabilities 2 bitfield. +#[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] +pub(crate) const AT_HWCAP2: usize = 26; + +/// Cache HWCAP bitfields of the ELF Auxiliary Vector. +/// +/// If an entry cannot be read all the bits in the bitfield are set to zero. +/// This should be interpreted as all the features being disabled. +#[derive(Debug, Copy, Clone)] +pub(crate) struct AuxVec { + pub hwcap: usize, + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + pub hwcap2: usize, +} + +/// ELF Auxiliary Vector +/// +/// The auxiliary vector is a memory region in a running ELF program's stack +/// composed of (key: usize, value: usize) pairs. +/// +/// The keys used in the aux vector are platform dependent. For Linux, they are +/// defined in [linux/auxvec.h][auxvec_h]. The hardware capabilities of a given +/// CPU can be queried with the `AT_HWCAP` and `AT_HWCAP2` keys. +/// +/// There is no perfect way of reading the auxiliary vector. +/// +/// - If the `std_detect_dlsym_getauxval` cargo feature is enabled, this will use +/// `getauxval` if its linked to the binary, and otherwise proceed to a fallback implementation. +/// When `std_detect_dlsym_getauxval` is disabled, this will assume that `getauxval` is +/// linked to the binary - if that is not the case the behavior is undefined. +/// - Otherwise, if the `std_detect_file_io` cargo feature is enabled, it will +/// try to read `/proc/self/auxv`. +/// - If that fails, this function returns an error. +/// +/// Note that run-time feature detection is not invoked for features that can +/// be detected at compile-time. Also note that if this function returns an +/// error, cpuinfo still can (and will) be used to try to perform run-time +/// feature detecton on some platforms. +/// +/// For more information about when `getauxval` is available check the great +/// [`auxv` crate documentation][auxv_docs]. +/// +/// [auxvec_h]: https://github.com/torvalds/linux/blob/master/include/uapi/linux/auxvec.h +/// [auxv_docs]: https://docs.rs/auxv/0.3.3/auxv/ +pub(crate) fn auxv() -> Result { + #[cfg(feature = "std_detect_dlsym_getauxval")] { + // Try to call a dynamically-linked getauxval function. + if let Ok(hwcap) = getauxval(AT_HWCAP) { + // Targets with only AT_HWCAP: + #[cfg(any(target_arch = "aarch64", target_arch = "mips", + target_arch = "mips64"))] + { + if hwcap != 0 { + return Ok(AuxVec { hwcap }); + } + } + + // Targets with AT_HWCAP and AT_HWCAP2: + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + { + if let Ok(hwcap2) = getauxval(AT_HWCAP2) { + if hwcap != 0 && hwcap2 != 0 { + return Ok(AuxVec { hwcap, hwcap2 }); + } + } + } + drop(hwcap); + } + #[cfg(feature = "std_detect_file_io")] { + // If calling getauxval fails, try to read the auxiliary vector from + // its file: + auxv_from_file("/proc/self/auxv") + } + #[cfg(not(feature = "std_detect_file_io"))] { + Err(()) + } + } + + #[cfg(not(feature = "std_detect_dlsym_getauxval"))] { + let hwcap = unsafe { ffi_getauxval(AT_HWCAP) }; + + // Targets with only AT_HWCAP: + #[cfg(any(target_arch = "aarch64", target_arch = "mips", + target_arch = "mips64"))] + { + if hwcap != 0 { + return Ok(AuxVec { hwcap }); + } + } + + // Targets with AT_HWCAP and AT_HWCAP2: + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + { + let hwcap2 = unsafe { ffi_getauxval(AT_HWCAP2) }; + if hwcap != 0 && hwcap2 != 0 { + return Ok(AuxVec { hwcap, hwcap2 }); + } + } + } +} + +/// Tries to read the `key` from the auxiliary vector by calling the +/// dynamically-linked `getauxval` function. If the function is not linked, +/// this function return `Err`. +#[cfg(feature = "std_detect_dlsym_getauxval")] +fn getauxval(key: usize) -> Result { + use libc; + pub type F = unsafe extern "C" fn(usize) -> usize; + unsafe { + let ptr = libc::dlsym( + libc::RTLD_DEFAULT, + "getauxval\0".as_ptr() as *const _, + ); + if ptr.is_null() { + return Err(()); + } + + let ffi_getauxval: F = mem::transmute(ptr); + Ok(ffi_getauxval(key)) + } +} + +/// Tries to read the auxiliary vector from the `file`. If this fails, this +/// function returns `Err`. +#[cfg(feature = "std_detect_file_io")] +fn auxv_from_file(file: &str) -> Result { + let mut file = File::open(file).map_err(|_| ())?; + + // See . + // + // The auxiliary vector contains at most 32 (key,value) fields: from + // `AT_EXECFN = 31` to `AT_NULL = 0`. That is, a buffer of + // 2*32 `usize` elements is enough to read the whole vector. + let mut buf = [0_usize; 64]; + { + let raw: &mut [u8; 64 * mem::size_of::()] = + unsafe { mem::transmute(&mut buf) }; + file.read(raw).map_err(|_| ())?; + } + auxv_from_buf(&buf) +} + +/// Tries to interpret the `buffer` as an auxiliary vector. If that fails, this +/// function returns `Err`. +#[cfg(feature = "std_detect_file_io")] +fn auxv_from_buf(buf: &[usize; 64]) -> Result { + // Targets with only AT_HWCAP: + #[cfg(any(target_arch = "aarch64", target_arch = "mips", + target_arch = "mips64"))] + { + for el in buf.chunks(2) { + match el[0] { + AT_HWCAP => return Ok(AuxVec { hwcap: el[1] }), + _ => (), + } + } + } + // Targets with AT_HWCAP and AT_HWCAP2: + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + { + let mut hwcap = None; + let mut hwcap2 = None; + for el in buf.chunks(2) { + match el[0] { + AT_HWCAP => hwcap = Some(el[1]), + AT_HWCAP2 => hwcap2 = Some(el[1]), + _ => (), + } + } + + if let (Some(hwcap), Some(hwcap2)) = (hwcap, hwcap2) { + return Ok(AuxVec { hwcap, hwcap2 }); + } + } + drop(buf); + Err(()) +} + +#[cfg(test)] +mod tests { + extern crate auxv as auxv_crate; + use super::*; + + // Reads the Auxiliary Vector key from /proc/self/auxv + // using the auxv crate. + #[cfg(feature = "std_detect_file_io")] + fn auxv_crate_getprocfs(key: usize) -> Option { + use self::auxv_crate::AuxvType; + use self::auxv_crate::procfs::search_procfs_auxv; + let k = key as AuxvType; + match search_procfs_auxv(&[k]) { + Ok(v) => Some(v[&k] as usize), + Err(_) => None, + } + } + + // Reads the Auxiliary Vector key from getauxval() + // using the auxv crate. + #[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] + fn auxv_crate_getauxval(key: usize) -> Option { + use self::auxv_crate::AuxvType; + use self::auxv_crate::getauxval::Getauxval; + let q = auxv_crate::getauxval::NativeGetauxval {}; + match q.getauxval(key as AuxvType) { + Ok(v) => Some(v as usize), + Err(_) => None, + } + } + + // FIXME: on mips/mips64 getauxval returns 0, and /proc/self/auxv + // does not always contain the AT_HWCAP key under qemu. + #[cfg(not(any(target_arch = "mips", target_arch = "mips64", target_arch = "powerpc")))] + #[test] + fn auxv_crate() { + let v = auxv(); + if let Some(hwcap) = auxv_crate_getauxval(AT_HWCAP) { + let rt_hwcap = v.expect("failed to find hwcap key").hwcap; + assert_eq!(rt_hwcap, hwcap); + } + + // Targets with AT_HWCAP and AT_HWCAP2: + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + { + if let Some(hwcap2) = auxv_crate_getauxval(AT_HWCAP2) { + let rt_hwcap2 = v.expect("failed to find hwcap2 key").hwcap2; + assert_eq!(rt_hwcap2, hwcap2); + } + } + } + + #[test] + fn auxv_dump() { + if let Ok(auxvec) = auxv() { + println!("{:?}", auxvec); + } else { + println!("both getauxval() and reading /proc/self/auxv failed!"); + } + } + + #[cfg(feature = "std_detect_file_io")] + cfg_if! { + if #[cfg(target_arch = "arm")] { + #[test] + fn linux_rpi3() { + let file = concat!(env!("CARGO_MANIFEST_DIR"), "/src/detect/test_data/linux-rpi3.auxv"); + println!("file: {}", file); + let v = auxv_from_file(file).unwrap(); + assert_eq!(v.hwcap, 4174038); + assert_eq!(v.hwcap2, 16); + } + + #[test] + #[should_panic] + fn linux_macos_vb() { + let file = concat!(env!("CARGO_MANIFEST_DIR"), "/src/detect/test_data/macos-virtualbox-linux-x86-4850HQ.auxv"); + println!("file: {}", file); + let v = auxv_from_file(file).unwrap(); + // this file is incomplete (contains hwcap but not hwcap2), we + // want to fall back to /proc/cpuinfo in this case, so + // reading should fail. assert_eq!(v.hwcap, 126614527); + // assert_eq!(v.hwcap2, 0); + } + } else if #[cfg(target_arch = "aarch64")] { + #[test] + fn linux_x64() { + let file = concat!(env!("CARGO_MANIFEST_DIR"), "/src/detect/test_data/linux-x64-i7-6850k.auxv"); + println!("file: {}", file); + let v = auxv_from_file(file).unwrap(); + assert_eq!(v.hwcap, 3219913727); + } + } + } + + #[test] + #[cfg(feature = "std_detect_file_io")] + fn auxv_dump_procfs() { + if let Ok(auxvec) = auxv_from_file("/proc/self/auxv") { + println!("{:?}", auxvec); + } else { + println!("reading /proc/self/auxv failed!"); + } + } + + #[test] + fn auxv_crate_procfs() { + let v = auxv(); + if let Some(hwcap) = auxv_crate_getprocfs(AT_HWCAP) { + assert_eq!(v.unwrap().hwcap, hwcap); + } + + // Targets with AT_HWCAP and AT_HWCAP2: + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + { + if let Some(hwcap2) = auxv_crate_getprocfs(AT_HWCAP2) { + assert_eq!(v.unwrap().hwcap2, hwcap2); + } + } + } +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/cpuinfo.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/cpuinfo.rs new file mode 100644 index 0000000000..b316857853 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/cpuinfo.rs @@ -0,0 +1,301 @@ +//! Parses /proc/cpuinfo +#![cfg_attr(not(target_arch = "arm"), allow(dead_code))] + +extern crate std; +use self::std::{prelude::v1::*, fs::File, io, io::Read}; + +/// cpuinfo +pub(crate) struct CpuInfo { + raw: String, +} + +impl CpuInfo { + /// Reads /proc/cpuinfo into CpuInfo. + pub(crate) fn new() -> Result { + let mut file = File::open("/proc/cpuinfo")?; + let mut cpui = Self { raw: String::new() }; + file.read_to_string(&mut cpui.raw)?; + Ok(cpui) + } + /// Returns the value of the cpuinfo `field`. + pub(crate) fn field(&self, field: &str) -> CpuInfoField { + for l in self.raw.lines() { + if l.trim().starts_with(field) { + return CpuInfoField::new(l.split(": ").nth(1)); + } + } + CpuInfoField(None) + } + + /// Returns the `raw` contents of `/proc/cpuinfo` + #[cfg(test)] + fn raw(&self) -> &String { + &self.raw + } + + #[cfg(test)] + fn from_str(other: &str) -> Result { + Ok(Self { + raw: String::from(other), + }) + } +} + +/// Field of cpuinfo +#[derive(Debug)] +pub(crate) struct CpuInfoField<'a>(Option<&'a str>); + +impl<'a> PartialEq<&'a str> for CpuInfoField<'a> { + fn eq(&self, other: &&'a str) -> bool { + match self.0 { + None => other.is_empty(), + Some(f) => f == other.trim(), + } + } +} + +impl<'a> CpuInfoField<'a> { + pub(crate) fn new<'b>(v: Option<&'b str>) -> CpuInfoField<'b> { + match v { + None => CpuInfoField::<'b>(None), + Some(f) => CpuInfoField::<'b>(Some(f.trim())), + } + } + /// Does the field exist? + #[cfg(test)] + pub(crate) fn exists(&self) -> bool { + self.0.is_some() + } + /// Does the field contain `other`? + pub(crate) fn has(&self, other: &str) -> bool { + match self.0 { + None => other.is_empty(), + Some(f) => { + let other = other.trim(); + for v in f.split(' ') { + if v == other { + return true; + } + } + false + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn raw_dump() { + let cpuinfo = CpuInfo::new().unwrap(); + if cpuinfo.field("vendor_id") == "GenuineIntel" { + assert!(cpuinfo.field("flags").exists()); + assert!(!cpuinfo.field("vendor33_id").exists()); + assert!(cpuinfo.field("flags").has("sse")); + assert!(!cpuinfo.field("flags").has("avx314")); + } + println!("{}", cpuinfo.raw()); + } + + const CORE_DUO_T6500: &str = r"processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 23 +model name : Intel(R) Core(TM)2 Duo CPU T6500 @ 2.10GHz +stepping : 10 +microcode : 0xa0b +cpu MHz : 1600.000 +cache size : 2048 KB +physical id : 0 +siblings : 2 +core id : 0 +cpu cores : 2 +apicid : 0 +initial apicid : 0 +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 13 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm dtherm +bogomips : 4190.43 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: +"; + + #[test] + fn core_duo_t6500() { + let cpuinfo = CpuInfo::from_str(CORE_DUO_T6500).unwrap(); + assert_eq!(cpuinfo.field("vendor_id"), "GenuineIntel"); + assert_eq!(cpuinfo.field("cpu family"), "6"); + assert_eq!(cpuinfo.field("model"), "23"); + assert_eq!( + cpuinfo.field("model name"), + "Intel(R) Core(TM)2 Duo CPU T6500 @ 2.10GHz" + ); + assert_eq!( + cpuinfo.field("flags"), + "fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm dtherm" + ); + assert!(cpuinfo.field("flags").has("fpu")); + assert!(cpuinfo.field("flags").has("dtherm")); + assert!(cpuinfo.field("flags").has("sse2")); + assert!(!cpuinfo.field("flags").has("avx")); + } + + const ARM_CORTEX_A53: &str = + r"Processor : AArch64 Processor rev 3 (aarch64) + processor : 0 + processor : 1 + processor : 2 + processor : 3 + processor : 4 + processor : 5 + processor : 6 + processor : 7 + Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 + CPU implementer : 0x41 + CPU architecture: AArch64 + CPU variant : 0x0 + CPU part : 0xd03 + CPU revision : 3 + + Hardware : HiKey Development Board + "; + + #[test] + fn arm_cortex_a53() { + let cpuinfo = CpuInfo::from_str(ARM_CORTEX_A53).unwrap(); + assert_eq!( + cpuinfo.field("Processor"), + "AArch64 Processor rev 3 (aarch64)" + ); + assert_eq!( + cpuinfo.field("Features"), + "fp asimd evtstrm aes pmull sha1 sha2 crc32" + ); + assert!(cpuinfo.field("Features").has("pmull")); + assert!(!cpuinfo.field("Features").has("neon")); + assert!(cpuinfo.field("Features").has("asimd")); + } + + const ARM_CORTEX_A57: &str = r"Processor : Cortex A57 Processor rev 1 (aarch64) +processor : 0 +processor : 1 +processor : 2 +processor : 3 +Features : fp asimd aes pmull sha1 sha2 crc32 wp half thumb fastmult vfp edsp neon vfpv3 tlsi vfpv4 idiva idivt +CPU implementer : 0x41 +CPU architecture: 8 +CPU variant : 0x1 +CPU part : 0xd07 +CPU revision : 1"; + + #[test] + fn arm_cortex_a57() { + let cpuinfo = CpuInfo::from_str(ARM_CORTEX_A57).unwrap(); + assert_eq!( + cpuinfo.field("Processor"), + "Cortex A57 Processor rev 1 (aarch64)" + ); + assert_eq!( + cpuinfo.field("Features"), + "fp asimd aes pmull sha1 sha2 crc32 wp half thumb fastmult vfp edsp neon vfpv3 tlsi vfpv4 idiva idivt" + ); + assert!(cpuinfo.field("Features").has("pmull")); + assert!(cpuinfo.field("Features").has("neon")); + assert!(cpuinfo.field("Features").has("asimd")); + } + + const POWER8E_POWERKVM: &str = r"processor : 0 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +processor : 1 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +processor : 2 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +processor : 3 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +timebase : 512000000 +platform : pSeries +model : IBM pSeries (emulated by qemu) +machine : CHRP IBM pSeries (emulated by qemu)"; + + #[test] + fn power8_powerkvm() { + let cpuinfo = CpuInfo::from_str(POWER8E_POWERKVM).unwrap(); + assert_eq!(cpuinfo.field("cpu"), "POWER8E (raw), altivec supported"); + + assert!(cpuinfo.field("cpu").has("altivec")); + } + + const POWER5P: &str = r"processor : 0 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 1 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 2 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 3 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 4 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 5 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 6 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 7 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +timebase : 237331000 +platform : pSeries +machine : CHRP IBM,9133-55A"; + + #[test] + fn power5p() { + let cpuinfo = CpuInfo::from_str(POWER5P).unwrap(); + assert_eq!(cpuinfo.field("cpu"), "POWER5+ (gs)"); + + assert!(!cpuinfo.field("cpu").has("altivec")); + } +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/mips.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/mips.rs new file mode 100644 index 0000000000..c0a5fb2e5d --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/mips.rs @@ -0,0 +1,31 @@ +//! Run-time feature detection for MIPS on Linux. + +use crate::detect::{Feature, cache, bit}; +use super::auxvec; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Try to read the features from the auxiliary vector, and if that fails, try +/// to read them from `/proc/cpuinfo`. +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + let enable_feature = |value: &mut cache::Initializer, f, enable| { + if enable { + value.set(f as u32); + } + }; + + // The values are part of the platform-specific [asm/hwcap.h][hwcap] + // + // [hwcap]: https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/hwcap.h + if let Ok(auxv) = auxvec::auxv() { + enable_feature(&mut value, Feature::msa, bit::test(auxv.hwcap, 1)); + return value; + } + // TODO: fall back via `cpuinfo`. + value +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/mod.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/mod.rs new file mode 100644 index 0000000000..e02d5e6dcd --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/mod.rs @@ -0,0 +1,28 @@ +//! Run-time feature detection on Linux + +mod auxvec; + +#[cfg(feature = "std_detect_file_io")] +mod cpuinfo; + +cfg_if! { + if #[cfg(target_arch = "aarch64")] { + mod aarch64; + pub use self::aarch64::check_for; + } else if #[cfg(target_arch = "arm")] { + mod arm; + pub use self::arm::check_for; + } else if #[cfg(any(target_arch = "mips", target_arch = "mips64"))] { + mod mips; + pub use self::mips::check_for; + } else if #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] { + mod powerpc; + pub use self::powerpc::check_for; + } else { + use crate::detect::Feature; + /// Performs run-time feature detection. + pub fn check_for(_x: Feature) -> bool { + false + } + } +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/powerpc.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/powerpc.rs new file mode 100644 index 0000000000..1c08a58443 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/linux/powerpc.rs @@ -0,0 +1,41 @@ +//! Run-time feature detection for PowerPC on Linux. + +use crate::detect::{Feature, cache}; +use super::{auxvec, cpuinfo}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Try to read the features from the auxiliary vector, and if that fails, try +/// to read them from /proc/cpuinfo. +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + let enable_feature = |value: &mut cache::Initializer, f, enable| { + if enable { + value.set(f as u32); + } + }; + + // The values are part of the platform-specific [asm/cputable.h][cputable] + // + // [cputable]: https://github.com/torvalds/linux/blob/master/arch/powerpc/include/uapi/asm/cputable.h + if let Ok(auxv) = auxvec::auxv() { + // note: the PowerPC values are the mask to do the test (instead of the + // index of the bit to test like in ARM and Aarch64) + enable_feature(&mut value, Feature::altivec, auxv.hwcap & 0x10000000 != 0); + enable_feature(&mut value, Feature::vsx, auxv.hwcap & 0x00000080 != 0); + enable_feature(&mut value, Feature::power8, auxv.hwcap2 & 0x80000000 != 0); + return value; + } + + // PowerPC's /proc/cpuinfo lacks a proper Feature field, + // but `altivec` support is indicated in the `cpu` field. + if let Ok(c) = cpuinfo::CpuInfo::new() { + enable_feature(&mut value, Feature::altivec, c.field("cpu").has("altivec")); + return value; + } + value +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/other.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/other.rs new file mode 100644 index 0000000000..23e399ea79 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/other.rs @@ -0,0 +1,9 @@ +//! Other operating systems + +use crate::detect::Feature; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(_x: Feature) -> bool { + false +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/os/x86.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/os/x86.rs new file mode 100644 index 0000000000..9257b8a4be --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/os/x86.rs @@ -0,0 +1,375 @@ +//! x86 run-time feature detection is OS independent. + +#[cfg(target_arch = "x86")] +use crate::arch::x86::*; +#[cfg(target_arch = "x86_64")] +use crate::arch::x86_64::*; + +use crate::mem; + +use crate::detect::{Feature, cache, bit}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Run-time feature detection on x86 works by using the CPUID instruction. +/// +/// The [CPUID Wikipedia page][wiki_cpuid] contains +/// all the information about which flags to set to query which values, and in +/// which registers these are reported. +/// +/// The definitive references are: +/// - [Intel 64 and IA-32 Architectures Software Developer's Manual Volume 2: +/// Instruction Set Reference, A-Z][intel64_ref]. +/// - [AMD64 Architecture Programmer's Manual, Volume 3: General-Purpose and +/// System Instructions][amd64_ref]. +/// +/// [wiki_cpuid]: https://en.wikipedia.org/wiki/CPUID +/// [intel64_ref]: http://www.intel.de/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf +/// [amd64_ref]: http://support.amd.com/TechDocs/24594.pdf +#[allow(clippy::similar_names)] +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + + // If the x86 CPU does not support the CPUID instruction then it is too + // old to support any of the currently-detectable features. + if !has_cpuid() { + return value; + } + + // Calling `__cpuid`/`__cpuid_count` from here on is safe because the CPU + // has `cpuid` support. + + // 0. EAX = 0: Basic Information: + // - EAX returns the "Highest Function Parameter", that is, the maximum + // leaf value for subsequent calls of `cpuinfo` in range [0, + // 0x8000_0000]. - The vendor ID is stored in 12 u8 ascii chars, + // returned in EBX, EDX, and ECX (in that order): + let (max_basic_leaf, vendor_id) = unsafe { + let CpuidResult { + eax: max_basic_leaf, + ebx, + ecx, + edx, + } = __cpuid(0); + let vendor_id: [[u8; 4]; 3] = [ + mem::transmute(ebx), + mem::transmute(edx), + mem::transmute(ecx), + ]; + let vendor_id: [u8; 12] = mem::transmute(vendor_id); + (max_basic_leaf, vendor_id) + }; + + if max_basic_leaf < 1 { + // Earlier Intel 486, CPUID not implemented + return value; + } + + // EAX = 1, ECX = 0: Queries "Processor Info and Feature Bits"; + // Contains information about most x86 features. + let CpuidResult { + ecx: proc_info_ecx, + edx: proc_info_edx, + .. + } = unsafe { __cpuid(0x0000_0001_u32) }; + + // EAX = 7, ECX = 0: Queries "Extended Features"; + // Contains information about bmi,bmi2, and avx2 support. + let (extended_features_ebx, extended_features_ecx) = if max_basic_leaf >= 7 + { + let CpuidResult { ebx, ecx, .. } = unsafe { __cpuid(0x0000_0007_u32) }; + (ebx, ecx) + } else { + (0, 0) // CPUID does not support "Extended Features" + }; + + // EAX = 0x8000_0000, ECX = 0: Get Highest Extended Function Supported + // - EAX returns the max leaf value for extended information, that is, + // `cpuid` calls in range [0x8000_0000; u32::MAX]: + let CpuidResult { + eax: extended_max_basic_leaf, + .. + } = unsafe { __cpuid(0x8000_0000_u32) }; + + // EAX = 0x8000_0001, ECX=0: Queries "Extended Processor Info and Feature + // Bits" + let extended_proc_info_ecx = if extended_max_basic_leaf >= 1 { + let CpuidResult { ecx, .. } = unsafe { __cpuid(0x8000_0001_u32) }; + ecx + } else { + 0 + }; + + { + // borrows value till the end of this scope: + let mut enable = |r, rb, f| { + if bit::test(r as usize, rb) { + value.set(f as u32); + } + }; + + enable(proc_info_ecx, 0, Feature::sse3); + enable(proc_info_ecx, 1, Feature::pclmulqdq); + enable(proc_info_ecx, 9, Feature::ssse3); + enable(proc_info_ecx, 13, Feature::cmpxchg16b); + enable(proc_info_ecx, 19, Feature::sse4_1); + enable(proc_info_ecx, 20, Feature::sse4_2); + enable(proc_info_ecx, 23, Feature::popcnt); + enable(proc_info_ecx, 25, Feature::aes); + enable(proc_info_ecx, 29, Feature::f16c); + enable(proc_info_ecx, 30, Feature::rdrand); + enable(extended_features_ebx, 18, Feature::rdseed); + enable(extended_features_ebx, 19, Feature::adx); + enable(extended_features_ebx, 11, Feature::rtm); + enable(proc_info_edx, 4, Feature::tsc); + enable(proc_info_edx, 23, Feature::mmx); + enable(proc_info_edx, 24, Feature::fxsr); + enable(proc_info_edx, 25, Feature::sse); + enable(proc_info_edx, 26, Feature::sse2); + enable(extended_features_ebx, 29, Feature::sha); + + enable(extended_features_ebx, 3, Feature::bmi); + enable(extended_features_ebx, 8, Feature::bmi2); + + // `XSAVE` and `AVX` support: + let cpu_xsave = bit::test(proc_info_ecx as usize, 26); + if cpu_xsave { + // 0. Here the CPU supports `XSAVE`. + + // 1. Detect `OSXSAVE`, that is, whether the OS is AVX enabled and + // supports saving the state of the AVX/AVX2 vector registers on + // context-switches, see: + // + // - [intel: is avx enabled?][is_avx_enabled], + // - [mozilla: sse.cpp][mozilla_sse_cpp]. + // + // [is_avx_enabled]: https://software.intel.com/en-us/blogs/2011/04/14/is-avx-enabled + // [mozilla_sse_cpp]: https://hg.mozilla.org/mozilla-central/file/64bab5cbb9b6/mozglue/build/SSE.cpp#l190 + let cpu_osxsave = bit::test(proc_info_ecx as usize, 27); + + if cpu_osxsave { + // 2. The OS must have signaled the CPU that it supports saving and + // restoring the: + // + // * SSE -> `XCR0.SSE[1]` + // * AVX -> `XCR0.AVX[2]` + // * AVX-512 -> `XCR0.AVX-512[7:5]`. + // + // by setting the corresponding bits of `XCR0` to `1`. + // + // This is safe because the CPU supports `xsave` + // and the OS has set `osxsave`. + let xcr0 = unsafe { _xgetbv(0) }; + // Test `XCR0.SSE[1]` and `XCR0.AVX[2]` with the mask `0b110 == 6`: + let os_avx_support = xcr0 & 6 == 6; + // Test `XCR0.AVX-512[7:5]` with the mask `0b1110_0000 == 224`: + let os_avx512_support = xcr0 & 224 == 224; + + // Only if the OS and the CPU support saving/restoring the AVX + // registers we enable `xsave` support: + if os_avx_support { + // See "13.3 ENABLING THE XSAVE FEATURE SET AND XSAVE-ENABLED + // FEATURES" in the "Intel® 64 and IA-32 Architectures Software + // Developer’s Manual, Volume 1: Basic Architecture": + // + // "Software enables the XSAVE feature set by setting + // CR4.OSXSAVE[bit 18] to 1 (e.g., with the MOV to CR4 + // instruction). If this bit is 0, execution of any of XGETBV, + // XRSTOR, XRSTORS, XSAVE, XSAVEC, XSAVEOPT, XSAVES, and XSETBV + // causes an invalid-opcode exception (#UD)" + // + enable(proc_info_ecx, 26, Feature::xsave); + + // For `xsaveopt`, `xsavec`, and `xsaves` we need to query: + // Processor Extended State Enumeration Sub-leaf (EAX = 0DH, + // ECX = 1): + if max_basic_leaf >= 0xd { + let CpuidResult { + eax: proc_extended_state1_eax, + .. + } = unsafe { __cpuid_count(0xd_u32, 1) }; + enable(proc_extended_state1_eax, 0, Feature::xsaveopt); + enable(proc_extended_state1_eax, 1, Feature::xsavec); + enable(proc_extended_state1_eax, 3, Feature::xsaves); + } + + // FMA (uses 256-bit wide registers): + enable(proc_info_ecx, 12, Feature::fma); + + // And AVX/AVX2: + enable(proc_info_ecx, 28, Feature::avx); + enable(extended_features_ebx, 5, Feature::avx2); + + // For AVX-512 the OS also needs to support saving/restoring + // the extended state, only then we enable AVX-512 support: + if os_avx512_support { + enable(extended_features_ebx, 16, Feature::avx512f); + enable(extended_features_ebx, 17, Feature::avx512dq); + enable(extended_features_ebx, 21, Feature::avx512_ifma); + enable(extended_features_ebx, 26, Feature::avx512pf); + enable(extended_features_ebx, 27, Feature::avx512er); + enable(extended_features_ebx, 28, Feature::avx512cd); + enable(extended_features_ebx, 30, Feature::avx512bw); + enable(extended_features_ebx, 31, Feature::avx512vl); + enable(extended_features_ecx, 1, Feature::avx512_vbmi); + enable( + extended_features_ecx, + 14, + Feature::avx512_vpopcntdq, + ); + } + } + } + } + + // This detects ABM on AMD CPUs and LZCNT on Intel CPUs. + // On intel CPUs with popcnt, lzcnt implements the + // "missing part" of ABM, so we map both to the same + // internal feature. + // + // The `is_x86_feature_detected!("lzcnt")` macro then + // internally maps to Feature::abm. + enable(extended_proc_info_ecx, 5, Feature::abm); + // As Hygon Dhyana originates from AMD technology and shares most of the architecture with + // AMD's family 17h, but with different CPU Vendor ID("HygonGenuine")/Family series + // number(Family 18h). + // + // For CPUID feature bits, Hygon Dhyana(family 18h) share the same definition with AMD + // family 17h. + // + // Related AMD CPUID specification is https://www.amd.com/system/files/TechDocs/25481.pdf. + // Related Hygon kernel patch can be found on + // http://lkml.kernel.org/r/5ce86123a7b9dad925ac583d88d2f921040e859b.1538583282.git.puwen@hygon.cn + if vendor_id == *b"AuthenticAMD" || vendor_id == *b"HygonGenuine" { + // These features are available on AMD arch CPUs: + enable(extended_proc_info_ecx, 6, Feature::sse4a); + enable(extended_proc_info_ecx, 21, Feature::tbm); + } + } + + value +} + +#[cfg(test)] +mod tests { + extern crate cupid; + + #[test] + fn dump() { + println!("aes: {:?}", is_x86_feature_detected!("aes")); + println!("pclmulqdq: {:?}", is_x86_feature_detected!("pclmulqdq")); + println!("rdrand: {:?}", is_x86_feature_detected!("rdrand")); + println!("rdseed: {:?}", is_x86_feature_detected!("rdseed")); + println!("tsc: {:?}", is_x86_feature_detected!("tsc")); + println!("sse: {:?}", is_x86_feature_detected!("sse")); + println!("sse2: {:?}", is_x86_feature_detected!("sse2")); + println!("sse3: {:?}", is_x86_feature_detected!("sse3")); + println!("ssse3: {:?}", is_x86_feature_detected!("ssse3")); + println!("sse4.1: {:?}", is_x86_feature_detected!("sse4.1")); + println!("sse4.2: {:?}", is_x86_feature_detected!("sse4.2")); + println!("sse4a: {:?}", is_x86_feature_detected!("sse4a")); + println!("sha: {:?}", is_x86_feature_detected!("sha")); + println!("avx: {:?}", is_x86_feature_detected!("avx")); + println!("avx2: {:?}", is_x86_feature_detected!("avx2")); + println!("avx512f {:?}", is_x86_feature_detected!("avx512f")); + println!("avx512cd {:?}", is_x86_feature_detected!("avx512cd")); + println!("avx512er {:?}", is_x86_feature_detected!("avx512er")); + println!("avx512pf {:?}", is_x86_feature_detected!("avx512pf")); + println!("avx512bw {:?}", is_x86_feature_detected!("avx512bw")); + println!("avx512dq {:?}", is_x86_feature_detected!("avx512dq")); + println!("avx512vl {:?}", is_x86_feature_detected!("avx512vl")); + println!("avx512_ifma {:?}", is_x86_feature_detected!("avx512ifma")); + println!("avx512_vbmi {:?}", is_x86_feature_detected!("avx512vbmi")); + println!( + "avx512_vpopcntdq {:?}", + is_x86_feature_detected!("avx512vpopcntdq") + ); + println!("fma: {:?}", is_x86_feature_detected!("fma")); + println!("abm: {:?}", is_x86_feature_detected!("abm")); + println!("bmi: {:?}", is_x86_feature_detected!("bmi1")); + println!("bmi2: {:?}", is_x86_feature_detected!("bmi2")); + println!("tbm: {:?}", is_x86_feature_detected!("tbm")); + println!("popcnt: {:?}", is_x86_feature_detected!("popcnt")); + println!("lzcnt: {:?}", is_x86_feature_detected!("lzcnt")); + println!("fxsr: {:?}", is_x86_feature_detected!("fxsr")); + println!("xsave: {:?}", is_x86_feature_detected!("xsave")); + println!("xsaveopt: {:?}", is_x86_feature_detected!("xsaveopt")); + println!("xsaves: {:?}", is_x86_feature_detected!("xsaves")); + println!("xsavec: {:?}", is_x86_feature_detected!("xsavec")); + println!("cmpxchg16b: {:?}", is_x86_feature_detected!("cmpxchg16b")); + println!("adx: {:?}", is_x86_feature_detected!("adx")); + println!("rtm: {:?}", is_x86_feature_detected!("rtm")); + } + + #[test] + fn compare_with_cupid() { + let information = cupid::master().unwrap(); + assert_eq!(is_x86_feature_detected!("aes"), information.aesni()); + assert_eq!(is_x86_feature_detected!("pclmulqdq"), information.pclmulqdq()); + assert_eq!(is_x86_feature_detected!("rdrand"), information.rdrand()); + assert_eq!(is_x86_feature_detected!("rdseed"), information.rdseed()); + assert_eq!(is_x86_feature_detected!("tsc"), information.tsc()); + assert_eq!(is_x86_feature_detected!("sse"), information.sse()); + assert_eq!(is_x86_feature_detected!("sse2"), information.sse2()); + assert_eq!(is_x86_feature_detected!("sse3"), information.sse3()); + assert_eq!(is_x86_feature_detected!("ssse3"), information.ssse3()); + assert_eq!(is_x86_feature_detected!("sse4.1"), information.sse4_1()); + assert_eq!(is_x86_feature_detected!("sse4.2"), information.sse4_2()); + assert_eq!(is_x86_feature_detected!("sse4a"), information.sse4a()); + assert_eq!(is_x86_feature_detected!("sha"), information.sha()); + assert_eq!(is_x86_feature_detected!("avx"), information.avx()); + assert_eq!(is_x86_feature_detected!("avx2"), information.avx2()); + assert_eq!(is_x86_feature_detected!("avx512f"), information.avx512f()); + assert_eq!(is_x86_feature_detected!("avx512cd"), information.avx512cd()); + assert_eq!(is_x86_feature_detected!("avx512er"), information.avx512er()); + assert_eq!(is_x86_feature_detected!("avx512pf"), information.avx512pf()); + assert_eq!(is_x86_feature_detected!("avx512bw"), information.avx512bw()); + assert_eq!(is_x86_feature_detected!("avx512dq"), information.avx512dq()); + assert_eq!(is_x86_feature_detected!("avx512vl"), information.avx512vl()); + assert_eq!( + is_x86_feature_detected!("avx512ifma"), + information.avx512_ifma() + ); + assert_eq!( + is_x86_feature_detected!("avx512vbmi"), + information.avx512_vbmi() + ); + assert_eq!( + is_x86_feature_detected!("avx512vpopcntdq"), + information.avx512_vpopcntdq() + ); + assert_eq!(is_x86_feature_detected!("fma"), information.fma()); + assert_eq!(is_x86_feature_detected!("bmi1"), information.bmi1()); + assert_eq!(is_x86_feature_detected!("bmi2"), information.bmi2()); + assert_eq!(is_x86_feature_detected!("popcnt"), information.popcnt()); + assert_eq!(is_x86_feature_detected!("abm"), information.lzcnt()); + assert_eq!(is_x86_feature_detected!("tbm"), information.tbm()); + assert_eq!(is_x86_feature_detected!("lzcnt"), information.lzcnt()); + assert_eq!(is_x86_feature_detected!("xsave"), information.xsave()); + assert_eq!(is_x86_feature_detected!("xsaveopt"), information.xsaveopt()); + assert_eq!( + is_x86_feature_detected!("xsavec"), + information.xsavec_and_xrstor() + ); + assert_eq!( + is_x86_feature_detected!("xsaves"), + information.xsaves_xrstors_and_ia32_xss() + ); + assert_eq!( + is_x86_feature_detected!("cmpxchg16b"), + information.cmpxchg16b(), + ); + assert_eq!( + is_x86_feature_detected!("adx"), + information.adx(), + ); + assert_eq!( + is_x86_feature_detected!("rtm"), + information.rtm(), + ); + } +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/lib.rs b/src/tools/rustfmt/tests/source/cfg_if/lib.rs new file mode 100644 index 0000000000..8b3bb304f1 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/lib.rs @@ -0,0 +1,49 @@ +//! Run-time feature detection for the Rust standard library. +//! +//! To detect whether a feature is enabled in the system running the binary +//! use one of the appropriate macro for the target: +//! +//! * `x86` and `x86_64`: [`is_x86_feature_detected`] +//! * `arm`: [`is_arm_feature_detected`] +//! * `aarch64`: [`is_aarch64_feature_detected`] +//! * `mips`: [`is_mips_feature_detected`] +//! * `mips64`: [`is_mips64_feature_detected`] +//! * `powerpc`: [`is_powerpc_feature_detected`] +//! * `powerpc64`: [`is_powerpc64_feature_detected`] + +#![unstable(feature = "stdsimd", issue = "27731")] +#![feature(const_fn, staged_api, stdsimd, doc_cfg, allow_internal_unstable)] +#![allow(clippy::shadow_reuse)] +#![deny(clippy::missing_inline_in_public_items)] +#![cfg_attr(target_os = "linux", feature(linkage))] +#![cfg_attr(all(target_os = "freebsd", target_arch = "aarch64"), feature(asm))] +#![cfg_attr(stdsimd_strict, deny(warnings))] +#![cfg_attr(test, allow(unused_imports))] +#![no_std] + +#[macro_use] +extern crate cfg_if; + +cfg_if! { + if #[cfg(feature = "std_detect_file_io")] { + #[cfg_attr(test, macro_use(println))] + extern crate std; + + #[allow(unused_imports)] + use std::{arch, fs, io, mem, sync}; + } else { + #[cfg(test)] + #[macro_use(println)] + extern crate std; + + #[allow(unused_imports)] + use core::{arch, mem, sync}; + } +} + +#[cfg(feature = "std_detect_dlsym_getauxval")] +extern crate libc; + +#[doc(hidden)] +#[unstable(feature = "stdsimd", issue = "27731")] +pub mod detect; diff --git a/src/tools/rustfmt/tests/source/cfg_if/mod.rs b/src/tools/rustfmt/tests/source/cfg_if/mod.rs new file mode 100644 index 0000000000..b630e7ff38 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_if/mod.rs @@ -0,0 +1,5 @@ +//! `std_detect` + +#[doc(hidden)] // unstable implementation detail +#[unstable(feature = "stdsimd", issue = "27731")] +pub mod detect; diff --git a/src/tools/rustfmt/tests/source/cfg_mod/bar.rs b/src/tools/rustfmt/tests/source/cfg_mod/bar.rs new file mode 100644 index 0000000000..5b6b5f4383 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_mod/bar.rs @@ -0,0 +1,3 @@ +fn bar( ) -> &str { +"bar" +} diff --git a/src/tools/rustfmt/tests/source/cfg_mod/dir/dir1/dir2/wasm32.rs b/src/tools/rustfmt/tests/source/cfg_mod/dir/dir1/dir2/wasm32.rs new file mode 100644 index 0000000000..0f8c0a3a7a --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_mod/dir/dir1/dir2/wasm32.rs @@ -0,0 +1,6 @@ +fn + wasm32 + () -> &str +{ + "wasm32" +} diff --git a/src/tools/rustfmt/tests/source/cfg_mod/dir/dir1/dir3/wasm32.rs b/src/tools/rustfmt/tests/source/cfg_mod/dir/dir1/dir3/wasm32.rs new file mode 100644 index 0000000000..0f8c0a3a7a --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_mod/dir/dir1/dir3/wasm32.rs @@ -0,0 +1,6 @@ +fn + wasm32 + () -> &str +{ + "wasm32" +} diff --git a/src/tools/rustfmt/tests/source/cfg_mod/foo.rs b/src/tools/rustfmt/tests/source/cfg_mod/foo.rs new file mode 100644 index 0000000000..de4ce55ef6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_mod/foo.rs @@ -0,0 +1,4 @@ +fn foo( ) + -> &str { + "foo" +} diff --git a/src/tools/rustfmt/tests/source/cfg_mod/mod.rs b/src/tools/rustfmt/tests/source/cfg_mod/mod.rs new file mode 100644 index 0000000000..45ba86f11b --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_mod/mod.rs @@ -0,0 +1,10 @@ +#[cfg_attr(feature = "foo", path = "foo.rs")] +#[cfg_attr(not(feture = "foo"), path = "bar.rs")] +mod sub_mod; + +#[cfg_attr(target_arch = "wasm32", path = "dir/dir1/dir2/wasm32.rs")] +#[cfg_attr(not(target_arch = "wasm32"), path = "dir/dir1/dir3/wasm32.rs")] +mod wasm32; + +#[some_attr(path = "somewhere.rs")] +mod other; diff --git a/src/tools/rustfmt/tests/source/cfg_mod/other.rs b/src/tools/rustfmt/tests/source/cfg_mod/other.rs new file mode 100644 index 0000000000..0b5c04d219 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_mod/other.rs @@ -0,0 +1 @@ +fn other() -> &str { "other"} diff --git a/src/tools/rustfmt/tests/source/cfg_mod/wasm32.rs b/src/tools/rustfmt/tests/source/cfg_mod/wasm32.rs new file mode 100644 index 0000000000..3741e53fd4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_mod/wasm32.rs @@ -0,0 +1,4 @@ +fn + wasm32() -> &str { + "wasm32" + } diff --git a/src/tools/rustfmt/tests/source/chains-visual.rs b/src/tools/rustfmt/tests/source/chains-visual.rs new file mode 100644 index 0000000000..20a96311e3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/chains-visual.rs @@ -0,0 +1,158 @@ +// rustfmt-indent_style: Visual +// Test chain formatting. + +fn main() { + // Don't put chains on a single line if it wasn't so in source. + let a = b .c + .d.1 + .foo(|x| x + 1); + + bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc + .ddddddddddddddddddddddddddd(); + + bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd.eeeeeeee(); + + // Test case where first chain element isn't a path, but is shorter than + // the size of a tab. + x() + .y(|| match cond() { true => (), false => () }); + + loong_func() + .quux(move || if true { + 1 + } else { + 2 + }); + + some_fuuuuuuuuunction() + .method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }).method_call_b(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + fffffffffffffffffffffffffffffffffff(a, + { + SCRIPT_TASK_ROOT + .with(|root| { + *root.borrow_mut() = Some(&script_task); + }); + }); + + let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = xxxxxxx + .map(|x| x + 5) + .map(|x| x / 2) + .fold(0, |acc, x| acc + x); + + aaaaaaaaaaaaaaaa.map(|x| { + x += 1; + x + }).filter(some_mod::some_filter) +} + +fn floaters() { + let z = Foo { + field1: val1, + field2: val2, + }; + + let x = Foo { + field1: val1, + field2: val2, + }.method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } + + if cond { some(); } else { none(); } + .bar() + .baz(); + + Foo { x: val } .baz(|| { force(); multiline(); }) .quux(); + + Foo { y: i_am_multi_line, z: ok } + .baz(|| { + force(); multiline(); + }) + .quux(); + + a + match x { true => "yay!", false => "boo!" }.bar() +} + +fn is_replaced_content() -> bool { + constellat.send(ConstellationMsg::ViewportConstrained( + self.id, constraints)).unwrap(); +} + +fn issue587() { + a.b::<()>(c); + + std::mem::transmute(dl.symbol::<()>("init").unwrap()) +} + +fn issue_1389() { + let names = String::from_utf8(names)?.split('|').map(str::to_owned).collect(); +} + +fn issue1217() -> Result { +let random_chars: String = OsRng::new()? + .gen_ascii_chars() + .take(self.bit_length) + .collect(); + + Ok(Mnemonic::new(&random_chars)) +} + +fn issue1236(options: Vec) -> Result> { +let process = Command::new("dmenu").stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .chain_err(|| "failed to spawn dmenu")?; +} + +fn issue1434() { + for _ in 0..100 { + let prototype_id = PrototypeIdData::from_reader::<_, B>(&mut self.file_cursor).chain_err(|| { + format!("could not read prototype ID at offset {:#010x}", + current_offset) + })?; + } +} + +fn issue2264() { + { + something.function() + .map(|| { + if let a_very_very_very_very_very_very_very_very_long_variable = + compute_this_variable() + { + println!("Hello"); + } + }) + .collect(); + } +} diff --git a/src/tools/rustfmt/tests/source/chains.rs b/src/tools/rustfmt/tests/source/chains.rs new file mode 100644 index 0000000000..c77f5bac4c --- /dev/null +++ b/src/tools/rustfmt/tests/source/chains.rs @@ -0,0 +1,266 @@ +// rustfmt-use_small_heuristics: Off +// Test chain formatting. + +fn main() { + let a = b .c + .d.1 + .foo(|x| x + 1); + + bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc + .ddddddddddddddddddddddddddd(); + + bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd.eeeeeeee(); + + let f = fooooooooooooooooooooooooooooooooooooooooooooooooooo.baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar; + + // Test case where first chain element isn't a path, but is shorter than + // the size of a tab. + x() + .y(|| match cond() { true => (), false => () }); + + loong_func() + .quux(move || if true { + 1 + } else { + 2 + }); + + some_fuuuuuuuuunction() + .method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }).method_call_b(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + fffffffffffffffffffffffffffffffffff(a, + { + SCRIPT_TASK_ROOT + .with(|root| { + *root.borrow_mut() = Some(&script_task); + }); + }); + + let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = xxxxxxx + .map(|x| x + 5) + .map(|x| x / 2) + .fold(0, |acc, x| acc + x); + + body.fold(Body::new(), |mut body, chunk| { + body.extend(chunk); + Ok(body) + }).and_then(move |body| { + let req = Request::from_parts(parts, body); + f(req).map_err(|_| io::Error::new(io::ErrorKind::Other, "")) + }); + + aaaaaaaaaaaaaaaa.map(|x| { + x += 1; + x + }).filter(some_mod::some_filter) +} + +fn floaters() { + let z = Foo { + field1: val1, + field2: val2, + }; + + let x = Foo { + field1: val1, + field2: val2, + }.method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } + + if cond { some(); } else { none(); } + .bar() + .baz(); + + Foo { x: val } .baz(|| { force(); multiline(); }) .quux(); + + Foo { y: i_am_multi_line, z: ok } + .baz(|| { + force(); multiline(); + }) + .quux(); + + a + match x { true => "yay!", false => "boo!" }.bar() +} + +fn is_replaced_content() -> bool { + constellat.send(ConstellationMsg::ViewportConstrained( + self.id, constraints)).unwrap(); +} + +fn issue587() { + a.b::<()>(c); + + std::mem::transmute(dl.symbol::<()>("init").unwrap()) +} + +fn try_shorthand() { + let x = expr?; + let y = expr.kaas()?.test(); + let loooooooooooooooooooooooooooooooooooooooooong = does_this?.look?.good?.should_we_break?.after_the_first_question_mark?; + let yyyy = expr?.another?.another?.another?.another?.another?.another?.another?.another?.test(); + let zzzz = expr?.another?.another?.another?.another?; + let aaa = x ???????????? ?????????????? ???? ????? ?????????????? ????????? ?????????????? ??; + + let y = a.very .loooooooooooooooooooooooooooooooooooooong() .chain() + .inside() .weeeeeeeeeeeeeee()? .test() .0 + .x; + + parameterized(f, + substs, + def_id, + Ns::Value, + &[], + |tcx| tcx.lookup_item_type(def_id).generics)?; + fooooooooooooooooooooooooooo()?.bar()?.baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz()?; +} + +fn issue_1004() { + match *self { + ty::ImplOrTraitItem::MethodTraitItem(ref i) => write!(f, "{:?}", i), + ty::ImplOrTraitItem::ConstTraitItem(ref i) => write!(f, "{:?}", i), + ty::ImplOrTraitItem::TypeTraitItem(ref i) => write!(f, "{:?}", i), + } + ?; + + ty::tls::with(|tcx| { + let tap = ty::Binder(TraitAndProjections(principal, projections)); + in_binder(f, tcx, &ty::Binder(""), Some(tap)) + }) + ?; +} + +fn issue1392() { + test_method(r#" + if foo { + a(); + } + else { + b(); + } + "#.trim()); +} + +// #2067 +impl Settings { + fn save(&self) -> Result<()> { + let mut file = File::create(&settings_path).chain_err(|| ErrorKind::WriteError(settings_path.clone()))?; + } +} + +fn issue2126() { + { + { + { + { + { + let x = self.span_from(sub_span.expect("No span found for struct arant variant")); + self.sspanpan_from_span(sub_span.expect("No span found for struct variant")); + let x = self.spanpan_from_span(sub_span.expect("No span found for struct variant"))?; + } + } + } + } + } +} + +// #2200 +impl Foo { + pub fn from_ast(diagnostic: &::errors::Handler, + attrs: &[ast::Attribute]) -> Attributes { + let other_attrs = attrs.iter().filter_map(|attr| { + attr.with_desugared_doc(|attr| { + if attr.check_name("doc") { + if let Some(mi) = attr.meta() { + if let Some(value) = mi.value_str() { + doc_strings.push(DocFragment::Include(line, + attr.span, + filename, + contents)); + } + } + } + }) + }).collect(); + } +} + +// #2415 +// Avoid orphan in chain +fn issue2415() { + let base_url = (|| { + // stuff + + Ok((|| { + // stuff + Some(value.to_string()) + })() + .ok_or("")?) + })() + .unwrap_or_else(|_: Box<::std::error::Error>| String::from("")); +} + +impl issue_2786 { + fn thing(&self) { + foo(|a| { + println!("a"); + println!("b"); + }).bar(|c| { + println!("a"); + println!("b"); + }) + .baz(|c| { + println!("a"); + println!("b"); + }) + } +} + +fn issue_2773() { + let bar = Some(0); + bar.or_else(|| { + // do stuff + None + }).or_else(|| { + // do other stuff + None + }) + .and_then(|val| { + // do this stuff + None + }); +} + +fn issue_3034() { + disallowed_headers.iter().any(|header| *header == name) || + disallowed_header_prefixes.iter().any(|prefix| name.starts_with(prefix)) +} diff --git a/src/tools/rustfmt/tests/source/chains_with_comment.rs b/src/tools/rustfmt/tests/source/chains_with_comment.rs new file mode 100644 index 0000000000..91160711b8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/chains_with_comment.rs @@ -0,0 +1,121 @@ +// Chains with comment. + +fn main() { + let x = y // comment + .z; + + foo // foo + // comment after parent + .x + .y + // comment 1 + .bar() // comment after bar() + // comment 2 + .foobar + // comment after + // comment 3 + .baz(x, y, z); + + self.rev_dep_graph + .iter() + // Remove nodes that are not dirty + .filter(|&(unit, _)| dirties.contains(&unit)) + // Retain only dirty dependencies of the ones that are dirty + .map(|(k, deps)| { + ( + k.clone(), + deps.iter() + .cloned() + .filter(|d| dirties.contains(&d)) + .collect(), + ) + }); + + let y = expr /* comment */.kaas()? +// comment + .test(); + let loooooooooooooooooooooooooooooooooooooooooong = does_this?.look?.good?.should_we_break?.after_the_first_question_mark?; + let zzzz = expr? // comment after parent +// comment 0 +.another??? // comment 1 +.another???? // comment 2 +.another? // comment 3 +.another?; + + let y = a.very .loooooooooooooooooooooooooooooooooooooong() /* comment */ .chain() + .inside() /* comment */ .weeeeeeeeeeeeeee()? .test() .0 + .x; + + parameterized(f, + substs, + def_id, + Ns::Value, + &[], + |tcx| tcx.lookup_item_type(def_id).generics)?; + fooooooooooooooooooooooooooo()?.bar()?.baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz()?; + + // #2559 + App::new("cargo-cache") +.version(crate_version!()) +.bin_name("cargo") +.about("Manage cargo cache") +.author("matthiaskrgr") +.subcommand( +SubCommand::with_name("cache") +.version(crate_version!()) +.bin_name("cargo-cache") +.about("Manage cargo cache") +.author("matthiaskrgr") +.arg(&list_dirs) +.arg(&remove_dir) +.arg(&gc_repos) +.arg(&info) +.arg(&keep_duplicate_crates) .arg(&dry_run) +.arg(&auto_clean) +.arg(&auto_clean_expensive), + ) // subcommand + .arg(&list_dirs); +} + +// #2177 +impl Foo { + fn dirty_rev_dep_graph( + &self, + dirties: &HashSet, + ) -> HashMap> { + let dirties = self.transitive_dirty_units(dirties); + trace!("transitive_dirty_units: {:?}", dirties); + + self.rev_dep_graph.iter() + // Remove nodes that are not dirty + .filter(|&(unit, _)| dirties.contains(&unit)) + // Retain only dirty dependencies of the ones that are dirty + .map(|(k, deps)| (k.clone(), deps.iter().cloned().filter(|d| dirties.contains(&d)).collect())) + } +} + +// #2907 +fn foo() { + let x = foo + .bar?? ? // comment + .baz; + let x = foo + .bar? ?? + // comment + .baz; + let x = foo + .bar? ? ? // comment + // comment + .baz; + let x = foo + .bar? ?? // comment + // comment + ? ?? + // comment + ? ?? + // comment + ??? + // comment + ? ? ? + .baz; +} diff --git a/src/tools/rustfmt/tests/source/closure-block-inside-macro.rs b/src/tools/rustfmt/tests/source/closure-block-inside-macro.rs new file mode 100644 index 0000000000..b3ddfb5126 --- /dev/null +++ b/src/tools/rustfmt/tests/source/closure-block-inside-macro.rs @@ -0,0 +1,9 @@ +// #1547 +fuzz_target!(|data: &[u8]| if let Some(first) = data.first() { + let index = *first as usize; + if index >= ENCODINGS.len() { + return; + } + let encoding = ENCODINGS[index]; + dispatch_test(encoding, &data[1..]); +}); diff --git a/src/tools/rustfmt/tests/source/closure.rs b/src/tools/rustfmt/tests/source/closure.rs new file mode 100644 index 0000000000..e93cc3fb40 --- /dev/null +++ b/src/tools/rustfmt/tests/source/closure.rs @@ -0,0 +1,213 @@ +// rustfmt-normalize_comments: true +// Closures + +fn main() { + let square = ( |i: i32 | i * i ); + + let commented = |/* first */ a /*argument*/, /* second*/ b: WithType /* argument*/, /* ignored */ _ | + (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb); + + let block_body = move |xxxxxxxxxxxxxxxxxxxxxxxxxxxxx, ref yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy| { + xxxxxxxxxxxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + }; + + let loooooooooooooong_name = |field| { + // format comments. + if field.node.attrs.len() > 0 { field.node.attrs[0].span.lo() + } else { + field.span.lo() + }}; + + let unblock_me = |trivial| { + closure() + }; + + let empty = |arg| {}; + + let simple = |arg| { /* comment formatting */ foo(arg) }; + + let test = | | { do_something(); do_something_else(); }; + + let arg_test = |big_argument_name, test123| looooooooooooooooooong_function_naaaaaaaaaaaaaaaaame(); + + let arg_test = |big_argument_name, test123| {looooooooooooooooooong_function_naaaaaaaaaaaaaaaaame()}; + + let simple_closure = move || -> () {}; + + let closure = |input: Ty| -> Option { + foo() + }; + + let closure_with_return_type = |aaaaaaaaaaaaaaaaaaaaaaarg1, aaaaaaaaaaaaaaaaaaaaaaarg2| -> Strong { "sup".to_owned() }; + + |arg1, arg2, _, _, arg3, arg4| { let temp = arg4 + arg3; + arg2 * arg1 - temp }; + + let block_body_with_comment = args.iter() + .map(|a| { + // Emitting only dep-info is possible only for final crate type, as + // as others may emit required metadata for dependent crate types + if a.starts_with("--emit") && is_final_crate_type && !self.workspace_mode { + "--emit=dep-info" + } else { a } + }); +} + +fn issue311() { + let func = |x| println!("{}", x); + + (func)(0.0); +} + +fn issue863() { + let closure = |x| match x { + 0 => true, + _ => false, + } == true; +} + +fn issue934() { + let hash: &Fn(&&Block) -> u64 = &|block| -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_block(block); + h.finish() + }; + + let hash: &Fn(&&Block) -> u64 = &|block| -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_block(block); + h.finish(); + }; +} + +impl<'a, 'tcx: 'a> SpanlessEq<'a, 'tcx> { + pub fn eq_expr(&self, left: &Expr, right: &Expr) -> bool { + match (&left.node, &right.node) { + (&ExprBinary(l_op, ref ll, ref lr), &ExprBinary(r_op, ref rl, ref rr)) => { + l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr) || + swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)) + } + } + } +} + +fn foo() { + lifetimes_iter___map(|lasdfasfd| { + let hi = if l.bounds.is_empty() { + l.lifetime.span.hi() + }; + }); +} + +fn issue1405() { + open_raw_fd(fd, b'r') + .and_then(|file| Capture::new_raw(None, |_, err| unsafe { + raw::pcap_fopen_offline(file, err) + })); +} + +fn issue1466() { + let vertex_buffer = frame.scope(|ctx| { + let buffer = + ctx.create_host_visible_buffer::>(&vertices); + ctx.create_device_local_buffer(buffer) + }); +} + +fn issue470() { + {{{ + let explicit_arg_decls = + explicit_arguments.into_iter() + .enumerate() + .map(|(index, (ty, pattern))| { + let lvalue = Lvalue::Arg(index as u32); + block = this.pattern(block, + argument_extent, + hair::PatternRef::Hair(pattern), + &lvalue); + ArgDecl { ty: ty } + }); + }}} +} + +// #1509 +impl Foo { + pub fn bar(&self) { + Some(SomeType { + push_closure_out_to_100_chars: iter(otherwise_it_works_ok.into_iter().map(|f| { + Ok(f) + })), + }) + } +} + +fn issue1329() { + aaaaaaaaaaaaaaaa.map(|x| { + x += 1; + x + }) + .filter +} + +fn issue325() { + let f = || unsafe { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }; +} + +fn issue1697() { + Test.func_a(A_VERY_LONG_CONST_VARIABLE_NAME, move |arg1, arg2, arg3, arg4| arg1 + arg2 + arg3 + arg4) +} + +fn issue1694() { + foooooo(|_referencefffffffff: _, _target_reference: _, _oid: _, _target_oid: _| format!("refs/pull/{}/merge", pr_id)) +} + +fn issue1713() { + rayon::join( + || recurse(left, is_less, pred, limit), + || recurse(right, is_less, Some(pivot), limit), + ); + + rayon::join( + 1, + || recurse(left, is_less, pred, limit), + 2, + || recurse(right, is_less, Some(pivot), limit), + ); +} + +fn issue2063() { + |ctx: Ctx<(String, String)>| -> io::Result { + Ok(Response::new().with_body(ctx.params.0)) + } +} + +fn issue1524() { + let f = |x| {{{{x}}}}; + let f = |x| {{{x}}}; + let f = |x| {{x}}; + let f = |x| {x}; + let f = |x| x; +} + +fn issue2171() { + foo(|| unsafe { + if PERIPHERALS { + loop {} + } else { + PERIPHERALS = true; + } + }) +} + +fn issue2207() { + a.map(|_| unsafe { + a_very_very_very_very_very_very_very_long_function_name_or_anything_else() + }.to_string()) +} + +fn issue2262() { + result.init(&mut result.slave.borrow_mut(), &mut (result.strategy)()).map_err(|factory| Error { + factory, + slave: None, + })?; +} diff --git a/src/tools/rustfmt/tests/source/comment.rs b/src/tools/rustfmt/tests/source/comment.rs new file mode 100644 index 0000000000..b6ce5267fc --- /dev/null +++ b/src/tools/rustfmt/tests/source/comment.rs @@ -0,0 +1,90 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true + +//! Doc comment +fn test() { + /*! + * Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam */ + +// comment + // comment2 + + code(); /* leave this comment alone! + * ok? */ + + /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a + * diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam + * viverra nec consectetur ante hendrerit. Donec et mollis dolor. + * Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam + * tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut + * libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit + * amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis + * felis, pulvinar a semper sed, adipiscing id dolor. */ + + // Very looooooooooooooooooooooooooooooooooooooooooooooooooooooooong comment that should be split + + // println!("{:?}", rewrite_comment(subslice, + // false, + // comment_width, + // self.block_indent, + // self.config) + // .unwrap()); + + funk(); //dontchangeme + // or me + + // #1388 + const EXCEPTION_PATHS: &'static [&'static str] = + &[// std crates + "src/libstd/sys/", // Platform-specific code for std lives here. + "src/bootstrap"]; +} + + /// test123 +fn doc_comment() { +} + +fn chains() { + foo.bar(|| { + let x = 10; + /* comment */ x }) +} + +fn issue_1086() { + /**/ +} + +/* + * random comment */ + +fn main() {/* Test */} + +// #1643 +fn some_fn() /* some comment */ +{ +} + +fn some_fn1() +// some comment +{ +} + +fn some_fn2() // some comment +{ +} + +fn some_fn3() /* some comment some comment some comment some comment some comment some comment so */ +{ +} + +fn some_fn4() +/* some comment some comment some comment some comment some comment some comment some comment */ +{ +} + +// #1603 +pub enum Foo { + A, // `/** **/` + B, // `/*!` + C, +} diff --git a/src/tools/rustfmt/tests/source/comment2.rs b/src/tools/rustfmt/tests/source/comment2.rs new file mode 100644 index 0000000000..d68bb5483d --- /dev/null +++ b/src/tools/rustfmt/tests/source/comment2.rs @@ -0,0 +1,4 @@ +// rustfmt-wrap_comments: true + +/// This is a long line that angers rustfmt. Rustfmt shall deal with it swiftly and justly. +pub mod foo {} diff --git a/src/tools/rustfmt/tests/source/comment3.rs b/src/tools/rustfmt/tests/source/comment3.rs new file mode 100644 index 0000000000..f19a858633 --- /dev/null +++ b/src/tools/rustfmt/tests/source/comment3.rs @@ -0,0 +1,5 @@ +// rustfmt-wrap_comments: true + +//! This is a long line that angers rustfmt. Rustfmt shall deal with it swiftly and justly. + +pub mod foo {} diff --git a/src/tools/rustfmt/tests/source/comment4.rs b/src/tools/rustfmt/tests/source/comment4.rs new file mode 100644 index 0000000000..f53a8a4a1f --- /dev/null +++ b/src/tools/rustfmt/tests/source/comment4.rs @@ -0,0 +1,52 @@ +#![allow(dead_code)] // bar + +//! Doc comment +fn test() { +// comment + // comment2 + + code(); /* leave this comment alone! + * ok? */ + + /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a + * diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam + * viverra nec consectetur ante hendrerit. Donec et mollis dolor. + * Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam + * tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut + * libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit + * amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis + * felis, pulvinar a semper sed, adipiscing id dolor. */ + + // Very loooooooooooooooooooooooooooooooooooooooooooooooooooooooong comment that should be split + + // println!("{:?}", rewrite_comment(subslice, + // false, + // comment_width, + // self.block_indent, + // self.config) + // .unwrap()); + + funk(); //dontchangeme + // or me +} + + /// test123 +fn doc_comment() { +} + +/* +Regression test for issue #956 + +(some very important text) +*/ + +/* +fn debug_function() { + println!("hello"); +} +// */ + +#[link_section=".vectors"] +#[no_mangle] // Test this attribute is preserved. +#[cfg_attr(rustfmt, rustfmt::skip)] +pub static ISSUE_1284: [i32; 16] = []; diff --git a/src/tools/rustfmt/tests/source/comment5.rs b/src/tools/rustfmt/tests/source/comment5.rs new file mode 100644 index 0000000000..2835d8b257 --- /dev/null +++ b/src/tools/rustfmt/tests/source/comment5.rs @@ -0,0 +1,14 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true + +//@ special comment +//@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec adiam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam +//@ +//@foo +fn test() {} + +//@@@ another special comment +//@@@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec adiam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam +//@@@ +//@@@foo +fn bar() {} diff --git a/src/tools/rustfmt/tests/source/comment6.rs b/src/tools/rustfmt/tests/source/comment6.rs new file mode 100644 index 0000000000..e5d72113ce --- /dev/null +++ b/src/tools/rustfmt/tests/source/comment6.rs @@ -0,0 +1,10 @@ +// rustfmt-wrap_comments: true + +// Pendant la nuit du 9 mars 1860, les nuages, se confondant avec la mer, limitaient à quelques brasses la portée de la vue. +// Sur cette mer démontée, dont les lames déferlaient en projetant des lueurs livides, un léger bâtiment fuyait presque à sec de toile. + +pub mod foo {} + +// ゆく河の流れは絶えずして、しかももとの水にあらず。淀みに浮かぶうたかたは、かつ消えかつ結びて、久しくとどまりたるためしなし。世の中にある人とすみかと、またかくのごとし。 + +pub mod bar {} diff --git a/src/tools/rustfmt/tests/source/comment_crlf_newline.rs b/src/tools/rustfmt/tests/source/comment_crlf_newline.rs new file mode 100644 index 0000000000..7a65f762f6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/comment_crlf_newline.rs @@ -0,0 +1,4 @@ +// rustfmt-normalize_comments: true +/* Block comments followed by CRLF newlines should not an extra newline at the end */ + +/* Something else */ diff --git a/src/tools/rustfmt/tests/source/configs/blank_lines_lower_bound/1.rs b/src/tools/rustfmt/tests/source/configs/blank_lines_lower_bound/1.rs new file mode 100644 index 0000000000..c6058a55b0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/blank_lines_lower_bound/1.rs @@ -0,0 +1,13 @@ +// rustfmt-blank_lines_lower_bound: 1 + +fn foo() {} +fn bar() {} +// comment +fn foobar() {} + +fn foo1() {} +fn bar1() {} + +// comment + +fn foobar1() {} diff --git a/src/tools/rustfmt/tests/source/configs/brace_style/fn_always_next_line.rs b/src/tools/rustfmt/tests/source/configs/brace_style/fn_always_next_line.rs new file mode 100644 index 0000000000..d3bd9ac09a --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/brace_style/fn_always_next_line.rs @@ -0,0 +1,14 @@ +// rustfmt-brace_style: AlwaysNextLine +// Function brace style + +fn lorem() { + // body +} + +fn lorem(ipsum: usize) { + // body +} + +fn lorem(ipsum: T) where T: Add + Sub + Mul + Div { + // body +} diff --git a/src/tools/rustfmt/tests/source/configs/brace_style/fn_prefer_same_line.rs b/src/tools/rustfmt/tests/source/configs/brace_style/fn_prefer_same_line.rs new file mode 100644 index 0000000000..78a4495243 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/brace_style/fn_prefer_same_line.rs @@ -0,0 +1,14 @@ +// rustfmt-brace_style: PreferSameLine +// Function brace style + +fn lorem() { + // body +} + +fn lorem(ipsum: usize) { + // body +} + +fn lorem(ipsum: T) where T: Add + Sub + Mul + Div { + // body +} diff --git a/src/tools/rustfmt/tests/source/configs/brace_style/fn_same_line_where.rs b/src/tools/rustfmt/tests/source/configs/brace_style/fn_same_line_where.rs new file mode 100644 index 0000000000..3b78932e17 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/brace_style/fn_same_line_where.rs @@ -0,0 +1,14 @@ +// rustfmt-brace_style: SameLineWhere +// Function brace style + +fn lorem() { + // body +} + +fn lorem(ipsum: usize) { + // body +} + +fn lorem(ipsum: T) where T: Add + Sub + Mul + Div { + // body +} diff --git a/src/tools/rustfmt/tests/source/configs/brace_style/item_always_next_line.rs b/src/tools/rustfmt/tests/source/configs/brace_style/item_always_next_line.rs new file mode 100644 index 0000000000..0cc19b34da --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/brace_style/item_always_next_line.rs @@ -0,0 +1,20 @@ +// rustfmt-brace_style: AlwaysNextLine +// Item brace style + +enum Foo {} + +struct Bar {} + +struct Lorem { + ipsum: bool, +} + +struct Dolor where T: Eq { + sit: T, +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() {} +} diff --git a/src/tools/rustfmt/tests/source/configs/brace_style/item_prefer_same_line.rs b/src/tools/rustfmt/tests/source/configs/brace_style/item_prefer_same_line.rs new file mode 100644 index 0000000000..4412bc869a --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/brace_style/item_prefer_same_line.rs @@ -0,0 +1,16 @@ +// rustfmt-brace_style: PreferSameLine +// Item brace style + +struct Lorem { + ipsum: bool, +} + +struct Dolor where T: Eq { + sit: T, +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() {} +} diff --git a/src/tools/rustfmt/tests/source/configs/brace_style/item_same_line_where.rs b/src/tools/rustfmt/tests/source/configs/brace_style/item_same_line_where.rs new file mode 100644 index 0000000000..b8e69147dc --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/brace_style/item_same_line_where.rs @@ -0,0 +1,16 @@ +// rustfmt-brace_style: SameLineWhere +// Item brace style + +struct Lorem { + ipsum: bool, +} + +struct Dolor where T: Eq { + sit: T, +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() {} +} diff --git a/src/tools/rustfmt/tests/source/configs/comment_width/above.rs b/src/tools/rustfmt/tests/source/configs/comment_width/above.rs new file mode 100644 index 0000000000..36187ce0af --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/comment_width/above.rs @@ -0,0 +1,7 @@ +// rustfmt-comment_width: 40 +// rustfmt-wrap_comments: true +// Comment width + +fn main() { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. +} diff --git a/src/tools/rustfmt/tests/source/configs/comment_width/below.rs b/src/tools/rustfmt/tests/source/configs/comment_width/below.rs new file mode 100644 index 0000000000..abbc5930c4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/comment_width/below.rs @@ -0,0 +1,7 @@ +// rustfmt-comment_width: 80 +// rustfmt-wrap_comments: true +// Comment width + +fn main() { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. +} diff --git a/src/tools/rustfmt/tests/source/configs/comment_width/ignore.rs b/src/tools/rustfmt/tests/source/configs/comment_width/ignore.rs new file mode 100644 index 0000000000..c86e71c289 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/comment_width/ignore.rs @@ -0,0 +1,7 @@ +// rustfmt-comment_width: 40 +// rustfmt-wrap_comments: false +// Comment width + +fn main() { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. +} diff --git a/src/tools/rustfmt/tests/source/configs/condense_wildcard_suffixes/false.rs b/src/tools/rustfmt/tests/source/configs/condense_wildcard_suffixes/false.rs new file mode 100644 index 0000000000..3b967f35a8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/condense_wildcard_suffixes/false.rs @@ -0,0 +1,6 @@ +// rustfmt-condense_wildcard_suffixes: false +// Condense wildcard suffixes + +fn main() { + let (lorem, ipsum, _, _) = (1, 2, 3, 4); +} diff --git a/src/tools/rustfmt/tests/source/configs/condense_wildcard_suffixes/true.rs b/src/tools/rustfmt/tests/source/configs/condense_wildcard_suffixes/true.rs new file mode 100644 index 0000000000..3798a6b990 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/condense_wildcard_suffixes/true.rs @@ -0,0 +1,6 @@ +// rustfmt-condense_wildcard_suffixes: true +// Condense wildcard suffixes + +fn main() { + let (lorem, ipsum, _, _) = (1, 2, 3, 4); +} diff --git a/src/tools/rustfmt/tests/source/configs/control_brace_style/always_next_line.rs b/src/tools/rustfmt/tests/source/configs/control_brace_style/always_next_line.rs new file mode 100644 index 0000000000..c4ddad9ce2 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/control_brace_style/always_next_line.rs @@ -0,0 +1,10 @@ +// rustfmt-control_brace_style: AlwaysNextLine +// Control brace style + +fn main() { + if lorem { println!("ipsum!"); } else { println!("dolor!"); } + match magi { + Homura => "Akemi", + Madoka => "Kaname", + } +} diff --git a/src/tools/rustfmt/tests/source/configs/control_brace_style/always_same_line.rs b/src/tools/rustfmt/tests/source/configs/control_brace_style/always_same_line.rs new file mode 100644 index 0000000000..a9c699d27e --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/control_brace_style/always_same_line.rs @@ -0,0 +1,10 @@ +// rustfmt-control_brace_style: AlwaysSameLine +// Control brace style + +fn main() { + if lorem { println!("ipsum!"); } else { println!("dolor!"); } + match magi { + Homura => "Akemi", + Madoka => "Kaname", + } +} diff --git a/src/tools/rustfmt/tests/source/configs/control_brace_style/closing_next_line.rs b/src/tools/rustfmt/tests/source/configs/control_brace_style/closing_next_line.rs new file mode 100644 index 0000000000..1a74a28f26 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/control_brace_style/closing_next_line.rs @@ -0,0 +1,10 @@ +// rustfmt-control_brace_style: ClosingNextLine +// Control brace style + +fn main() { + if lorem { println!("ipsum!"); } else { println!("dolor!"); } + match magi { + Homura => "Akemi", + Madoka => "Kaname", + } +} diff --git a/src/tools/rustfmt/tests/source/configs/disable_all_formatting/false.rs b/src/tools/rustfmt/tests/source/configs/disable_all_formatting/false.rs new file mode 100644 index 0000000000..834ca7a3c8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/disable_all_formatting/false.rs @@ -0,0 +1,6 @@ +// rustfmt-disable_all_formatting: false +// Disable all formatting + +fn main() { + if lorem{println!("ipsum!");}else{println!("dolor!");} +} diff --git a/src/tools/rustfmt/tests/source/configs/disable_all_formatting/true.rs b/src/tools/rustfmt/tests/source/configs/disable_all_formatting/true.rs new file mode 100644 index 0000000000..56955bf384 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/disable_all_formatting/true.rs @@ -0,0 +1,6 @@ +// rustfmt-disable_all_formatting: true +// Disable all formatting + +fn main() { + iflorem{println!("ipsum!");}else{println!("dolor!");} +} diff --git a/src/tools/rustfmt/tests/source/configs/empty_item_single_line/false.rs b/src/tools/rustfmt/tests/source/configs/empty_item_single_line/false.rs new file mode 100644 index 0000000000..9bfb2b964e --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/empty_item_single_line/false.rs @@ -0,0 +1,16 @@ +// rustfmt-empty_item_single_line: false +// Empty impl on single line + +impl Lorem { + +} + +impl Ipsum { + +} + +fn lorem() { +} + +fn lorem() { +} diff --git a/src/tools/rustfmt/tests/source/configs/empty_item_single_line/true.rs b/src/tools/rustfmt/tests/source/configs/empty_item_single_line/true.rs new file mode 100644 index 0000000000..8af8b88fff --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/empty_item_single_line/true.rs @@ -0,0 +1,16 @@ +// rustfmt-empty_item_single_line: true +// Empty impl on single line + +impl Lorem { + +} + +impl Ipsum { + +} + +fn lorem() { +} + +fn lorem() { +} diff --git a/src/tools/rustfmt/tests/source/configs/enum_discrim_align_threshold/40.rs b/src/tools/rustfmt/tests/source/configs/enum_discrim_align_threshold/40.rs new file mode 100644 index 0000000000..796e47c384 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/enum_discrim_align_threshold/40.rs @@ -0,0 +1,34 @@ +// rustfmt-enum_discrim_align_threshold: 40 + +enum Standard { + A = 1, + Bcdef = 2, +} + +enum NoDiscrims { + ThisIsAFairlyLongEnumVariantWithoutDiscrimLongerThan40, + A = 1, + ThisIsAnotherFairlyLongEnumVariantWithoutDiscrimLongerThan40, + Bcdef = 2, +} + +enum TooLong { + ThisOneHasDiscrimAaaaaaaaaaaaaaaaaaaaaaChar40 = 10, + A = 1, + Bcdef = 2, +} + +enum Borderline { + ThisOneHasDiscrimAaaaaaaaaaaaaaaaaaaaaa = 10, + A = 1, + Bcdef = 2, +} + +// Live specimen from #1686 +enum LongWithSmallDiff { + SceneColorimetryEstimates = 0x73636F65, + SceneAppearanceEstimates = 0x73617065, + FocalPlaneColorimetryEstimates = 0x66706365, + ReflectionHardcopyOriginalColorimetry = 0x72686F63, + ReflectionPrintOutputColorimetry = 0x72706F63, +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/configs/error_on_line_overflow/false.rs b/src/tools/rustfmt/tests/source/configs/error_on_line_overflow/false.rs new file mode 100644 index 0000000000..fa70ae7835 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/error_on_line_overflow/false.rs @@ -0,0 +1,6 @@ +// rustfmt-error_on_line_overflow: false +// Error on line overflow + +fn main() { + let lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit; +} diff --git a/src/tools/rustfmt/tests/source/configs/fn_args_layout/compressed.rs b/src/tools/rustfmt/tests/source/configs/fn_args_layout/compressed.rs new file mode 100644 index 0000000000..66a371c259 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/fn_args_layout/compressed.rs @@ -0,0 +1,16 @@ +// rustfmt-fn_args_layout: Compressed +// Function arguments density + +trait Lorem { + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { + // body + } + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, adipiscing: Adipiscing, elit: Elit); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, adipiscing: Adipiscing, elit: Elit) { + // body + } +} diff --git a/src/tools/rustfmt/tests/source/configs/fn_args_layout/tall.rs b/src/tools/rustfmt/tests/source/configs/fn_args_layout/tall.rs new file mode 100644 index 0000000000..f11e86fd31 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/fn_args_layout/tall.rs @@ -0,0 +1,16 @@ +// rustfmt-fn_args_layout: Tall +// Function arguments density + +trait Lorem { + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { + // body + } + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, adipiscing: Adipiscing, elit: Elit); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, adipiscing: Adipiscing, elit: Elit) { + // body + } +} diff --git a/src/tools/rustfmt/tests/source/configs/fn_args_layout/vertical.rs b/src/tools/rustfmt/tests/source/configs/fn_args_layout/vertical.rs new file mode 100644 index 0000000000..a23cc02522 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/fn_args_layout/vertical.rs @@ -0,0 +1,16 @@ +// rustfmt-fn_args_layout: Vertical +// Function arguments density + +trait Lorem { + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { + // body + } + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, adipiscing: Adipiscing, elit: Elit); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, adipiscing: Adipiscing, elit: Elit) { + // body + } +} diff --git a/src/tools/rustfmt/tests/source/configs/fn_single_line/false.rs b/src/tools/rustfmt/tests/source/configs/fn_single_line/false.rs new file mode 100644 index 0000000000..3d092f0c0b --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/fn_single_line/false.rs @@ -0,0 +1,11 @@ +// rustfmt-fn_single_line: false +// Single-expression function on single line + +fn lorem() -> usize { + 42 +} + +fn lorem() -> usize { + let ipsum = 42; + ipsum +} diff --git a/src/tools/rustfmt/tests/source/configs/fn_single_line/true.rs b/src/tools/rustfmt/tests/source/configs/fn_single_line/true.rs new file mode 100644 index 0000000000..3cb0fdedf0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/fn_single_line/true.rs @@ -0,0 +1,11 @@ +// rustfmt-fn_single_line: true +// Single-expression function on single line + +fn lorem() -> usize { + 42 +} + +fn lorem() -> usize { + let ipsum = 42; + ipsum +} diff --git a/src/tools/rustfmt/tests/source/configs/force_explicit_abi/false.rs b/src/tools/rustfmt/tests/source/configs/force_explicit_abi/false.rs new file mode 100644 index 0000000000..3c48f8e0c7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/force_explicit_abi/false.rs @@ -0,0 +1,6 @@ +// rustfmt-force_explicit_abi: false +// Force explicit abi + +extern { + pub static lorem: c_int; +} diff --git a/src/tools/rustfmt/tests/source/configs/force_explicit_abi/true.rs b/src/tools/rustfmt/tests/source/configs/force_explicit_abi/true.rs new file mode 100644 index 0000000000..e5ff6cf7dd --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/force_explicit_abi/true.rs @@ -0,0 +1,6 @@ +// rustfmt-force_explicit_abi: true +// Force explicit abi + +extern { + pub static lorem: c_int; +} diff --git a/src/tools/rustfmt/tests/source/configs/force_multiline_block/false.rs b/src/tools/rustfmt/tests/source/configs/force_multiline_block/false.rs new file mode 100644 index 0000000000..b97e348e5d --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/force_multiline_block/false.rs @@ -0,0 +1,22 @@ +// rustfmt-force_multiline_blocks: false +// Option forces multiline match arm and closure bodies to be wrapped in a block + +fn main() { + match lorem { + Lorem::Ipsum => { + if ipsum { + println!("dolor"); + } + } + Lorem::Dolor => println!("amet"), + } +} + +fn main() { + result.and_then(|maybe_value| { + match maybe_value { + None => Err("oops"), + Some(value) => Ok(1), + } + }); +} diff --git a/src/tools/rustfmt/tests/source/configs/force_multiline_block/true.rs b/src/tools/rustfmt/tests/source/configs/force_multiline_block/true.rs new file mode 100644 index 0000000000..db9d3de461 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/force_multiline_block/true.rs @@ -0,0 +1,18 @@ +// rustfmt-force_multiline_blocks: true +// Option forces multiline match arm and closure bodies to be wrapped in a block + +fn main() { + match lorem { + Lorem::Ipsum => if ipsum { + println!("dolor"); + }, + Lorem::Dolor => println!("amet"), + } +} + +fn main() { + result.and_then(|maybe_value| match maybe_value { + None => Err("oops"), + Some(value) => Ok(1), + }); +} diff --git a/src/tools/rustfmt/tests/source/configs/format_macro_bodies/false.rs b/src/tools/rustfmt/tests/source/configs/format_macro_bodies/false.rs new file mode 100644 index 0000000000..d618a1ac3f --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/format_macro_bodies/false.rs @@ -0,0 +1,7 @@ +// rustfmt-format_macro_bodies: false + +macro_rules! foo { + ($a: ident : $b: ty) => { $a(42): $b; }; + ($a: ident $b: ident $c: ident) => { $a=$b+$c; }; +} + diff --git a/src/tools/rustfmt/tests/source/configs/format_macro_bodies/true.rs b/src/tools/rustfmt/tests/source/configs/format_macro_bodies/true.rs new file mode 100644 index 0000000000..b254b82d71 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/format_macro_bodies/true.rs @@ -0,0 +1,7 @@ +// rustfmt-format_macro_bodies: true + +macro_rules! foo { + ($a: ident : $b: ty) => { $a(42): $b; }; + ($a: ident $b: ident $c: ident) => { $a=$b+$c; }; +} + diff --git a/src/tools/rustfmt/tests/source/configs/format_macro_matchers/false.rs b/src/tools/rustfmt/tests/source/configs/format_macro_matchers/false.rs new file mode 100644 index 0000000000..a721bb55c2 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/format_macro_matchers/false.rs @@ -0,0 +1,7 @@ +// rustfmt-format_macro_matchers: false + +macro_rules! foo { + ($a: ident : $b: ty) => { $a(42): $b; }; + ($a: ident $b: ident $c: ident) => { $a=$b+$c; }; +} + diff --git a/src/tools/rustfmt/tests/source/configs/format_macro_matchers/true.rs b/src/tools/rustfmt/tests/source/configs/format_macro_matchers/true.rs new file mode 100644 index 0000000000..fa0442e228 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/format_macro_matchers/true.rs @@ -0,0 +1,6 @@ +// rustfmt-format_macro_matchers: true + +macro_rules! foo { + ($a: ident : $b: ty) => { $a(42): $b; }; + ($a: ident $b: ident $c: ident) => { $a=$b+$c; }; +} diff --git a/src/tools/rustfmt/tests/source/configs/format_strings/false.rs b/src/tools/rustfmt/tests/source/configs/format_strings/false.rs new file mode 100644 index 0000000000..ecca0d7d1f --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/format_strings/false.rs @@ -0,0 +1,8 @@ +// rustfmt-format_strings: false +// rustfmt-max_width: 50 +// rustfmt-error_on_line_overflow: false +// Force format strings + +fn main() { + let lorem = "ipsum dolor sit amet consectetur adipiscing elit lorem ipsum dolor sit"; +} diff --git a/src/tools/rustfmt/tests/source/configs/format_strings/true.rs b/src/tools/rustfmt/tests/source/configs/format_strings/true.rs new file mode 100644 index 0000000000..3373144782 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/format_strings/true.rs @@ -0,0 +1,7 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 50 +// Force format strings + +fn main() { + let lorem = "ipsum dolor sit amet consectetur adipiscing elit lorem ipsum dolor sit"; +} diff --git a/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate-merge_imports.rs b/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate-merge_imports.rs new file mode 100644 index 0000000000..ea7f6280a6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate-merge_imports.rs @@ -0,0 +1,17 @@ +// rustfmt-group_imports: StdExternalCrate +// rustfmt-imports_granularity: Crate +use chrono::Utc; +use super::update::convert_publish_payload; + +use juniper::{FieldError, FieldResult}; +use uuid::Uuid; +use alloc::alloc::Layout; + +use std::sync::Arc; +use alloc::vec::Vec; + +use broker::database::PooledConnection; + +use super::schema::{Context, Payload}; +use core::f32; +use crate::models::Event; diff --git a/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate-nested.rs b/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate-nested.rs new file mode 100644 index 0000000000..08f4e07b70 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate-nested.rs @@ -0,0 +1,6 @@ +// rustfmt-group_imports: StdExternalCrate +mod test { + use crate::foo::bar; + use std::path; + use crate::foo::bar2; +} diff --git a/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate-no_reorder.rs b/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate-no_reorder.rs new file mode 100644 index 0000000000..08c9a72ae6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate-no_reorder.rs @@ -0,0 +1,17 @@ +// rustfmt-group_imports: StdExternalCrate +// rustfmt-reorder_imports: false + +use chrono::Utc; +use super::update::convert_publish_payload; + +use juniper::{FieldError, FieldResult}; +use uuid::Uuid; +use alloc::alloc::Layout; + +use std::sync::Arc; + +use broker::database::PooledConnection; + +use super::schema::{Context, Payload}; +use core::f32; +use crate::models::Event; diff --git a/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate.rs b/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate.rs new file mode 100644 index 0000000000..d49c8941e6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/group_imports/StdExternalCrate.rs @@ -0,0 +1,15 @@ +// rustfmt-group_imports: StdExternalCrate +use chrono::Utc; +use super::update::convert_publish_payload; + +use juniper::{FieldError, FieldResult}; +use uuid::Uuid; +use alloc::alloc::Layout; + +use std::sync::Arc; + +use broker::database::PooledConnection; + +use super::schema::{Context, Payload}; +use core::f32; +use crate::models::Event; diff --git a/src/tools/rustfmt/tests/source/configs/hard_tabs/false.rs b/src/tools/rustfmt/tests/source/configs/hard_tabs/false.rs new file mode 100644 index 0000000000..bf92162b42 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/hard_tabs/false.rs @@ -0,0 +1,6 @@ +// rustfmt-hard_tabs: false +// Hard tabs + +fn lorem() -> usize { +42 // spaces before 42 +} diff --git a/src/tools/rustfmt/tests/source/configs/hard_tabs/true.rs b/src/tools/rustfmt/tests/source/configs/hard_tabs/true.rs new file mode 100644 index 0000000000..738922a4df --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/hard_tabs/true.rs @@ -0,0 +1,6 @@ +// rustfmt-hard_tabs: true +// Hard tabs + +fn lorem() -> usize { +42 // spaces before 42 +} diff --git a/src/tools/rustfmt/tests/source/configs/imports_layout/merge_mixed.rs b/src/tools/rustfmt/tests/source/configs/imports_layout/merge_mixed.rs new file mode 100644 index 0000000000..477c4aa168 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/imports_layout/merge_mixed.rs @@ -0,0 +1,6 @@ +// rustfmt-imports_indent: Block +// rustfmt-imports_granularity: Crate +// rustfmt-imports_layout: Mixed + +use std::{fmt, io, str}; +use std::str::FromStr; diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/block_args.rs b/src/tools/rustfmt/tests/source/configs/indent_style/block_args.rs new file mode 100644 index 0000000000..4d2d280a16 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/block_args.rs @@ -0,0 +1,26 @@ +// rustfmt-indent_style: Block +// Function arguments layout + +fn lorem() {} + +fn lorem(ipsum: usize) {} + +fn lorem(ipsum: usize, dolor: usize, sit: usize, amet: usize, consectetur: usize, adipiscing: usize, elit: usize) { + // body +} + +// #1441 +extern "system" { + pub fn GetConsoleHistoryInfo(console_history_info: *mut ConsoleHistoryInfo) -> Boooooooooooooool; +} + +// rustfmt should not add trailing comma for variadic function. See #1623. +extern "C" { + pub fn variadic_fn(first_parameter: FirstParameterType, + second_parameter: SecondParameterType, + ...); +} + +// #1652 +fn deconstruct(foo: Bar) -> (SocketAddr, Header, Method, RequestUri, HttpVersion, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) { +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/block_array.rs b/src/tools/rustfmt/tests/source/configs/indent_style/block_array.rs new file mode 100644 index 0000000000..8404f65f47 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/block_array.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Block +// Array layout + +fn main() { + let lorem = vec!["ipsum","dolor","sit","amet","consectetur","adipiscing","elit"]; +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/block_call.rs b/src/tools/rustfmt/tests/source/configs/indent_style/block_call.rs new file mode 100644 index 0000000000..c82b6b8e38 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/block_call.rs @@ -0,0 +1,133 @@ +// rustfmt-indent_style: Block +// Function call style + +fn main() { + lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit"); + // #1501 + let hyper = Arc::new(Client::with_connector(HttpsConnector::new(TlsClient::new()))); + + // chain + let x = yooooooooooooo.fooooooooooooooo.baaaaaaaaaaaaar(hello, world); + + // #1380 + { + { + let creds = self.client + .client_credentials(&self.config.auth.oauth2.id, &self.config.auth.oauth2.secret)?; + } + } + + // nesting macro and function call + try!(foo(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)); + try!(foo(try!(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx))); +} + +// #1521 +impl Foo { + fn map_pixel_to_coords(&self, point: &Vector2i, view: &View) -> Vector2f { + unsafe { + Vector2f::from_raw(ffi::sfRenderTexture_mapPixelToCoords(self.render_texture, point.raw(), view.raw())) + } + } +} + +fn issue1420() { + given( + r#" + # Getting started + ... + "#, + ) + .running(waltz) +} + +// #1563 +fn query(conn: &Connection) -> Result<()> { + conn.query_row( + r#" + SELECT title, date + FROM posts, + WHERE DATE(date) = $1 + "#, + &[], + |row| { + Post { + title: row.get(0), + date: row.get(1), + } + }, + )?; + + Ok(()) +} + +// #1449 +fn future_rayon_wait_1_thread() { + // run with only 1 worker thread; this would deadlock if we couldn't make progress + let mut result = None; + ThreadPool::new(Configuration::new().num_threads(1)) + .unwrap() + .install( + || { + scope( + |s| { + use std::sync::mpsc::channel; + let (tx, rx) = channel(); + let a = s.spawn_future(lazy(move || Ok::(rx.recv().unwrap()))); + // ^^^^ FIXME: why is this needed? + let b = s.spawn_future(a.map(|v| v + 1)); + let c = s.spawn_future(b.map(|v| v + 1)); + s.spawn(move |_| tx.send(20).unwrap()); + result = Some(c.rayon_wait().unwrap()); + }, + ); + }, + ); + assert_eq!(result, Some(22)); +} + +// #1494 +impl Cursor { + fn foo() { + self.cur_type() + .num_template_args() + .or_else(|| { + let n: c_int = unsafe { clang_Cursor_getNumTemplateArguments(self.x) }; + + if n >= 0 { + Some(n as u32) + } else { + debug_assert_eq!(n, -1); + None + } + }) + .or_else(|| { + let canonical = self.canonical(); + if canonical != *self { + canonical.num_template_args() + } else { + None + } + }); + } +} + +fn issue1581() { + bootstrap.checks.register( + "PERSISTED_LOCATIONS", + move || if locations2.0.inner_mut.lock().poisoned { + Check::new( + State::Error, + "Persisted location storage is poisoned due to a write failure", + ) + } else { + Check::new(State::Healthy, "Persisted location storage is healthy") + }, + ); +} + +fn issue1651() { + { + let type_list: Vec<_> = try_opt!(types.iter().map(|ty| ty.rewrite(context, shape)).collect()); + } +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/block_chain.rs b/src/tools/rustfmt/tests/source/configs/indent_style/block_chain.rs new file mode 100644 index 0000000000..41d9146911 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/block_chain.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Block +// Chain indent + +fn main() { + let lorem = ipsum.dolor().sit().amet().consectetur().adipiscing().elite(); +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/block_generic.rs b/src/tools/rustfmt/tests/source/configs/indent_style/block_generic.rs new file mode 100644 index 0000000000..2cf17be56e --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/block_generic.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Block +// Generics indent + +fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, adipiscing: Adipiscing, consectetur: Consectetur, elit: Elit) -> T { + // body +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/block_struct_lit.rs b/src/tools/rustfmt/tests/source/configs/indent_style/block_struct_lit.rs new file mode 100644 index 0000000000..47a6994f40 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/block_struct_lit.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Block +// Struct literal-style + +fn main() { + let lorem = Lorem { ipsum: dolor, sit: amet }; +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/block_trailing_comma_call/one.rs b/src/tools/rustfmt/tests/source/configs/indent_style/block_trailing_comma_call/one.rs new file mode 100644 index 0000000000..6d48ea742f --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/block_trailing_comma_call/one.rs @@ -0,0 +1,9 @@ +// rustfmt-version: One +// rustfmt-error_on_line_overflow: false +// rustfmt-indent_style: Block + +// rustfmt should not add trailing comma when rewriting macro. See #1528. +fn a() { + panic!("this is a long string that goes past the maximum line length causing rustfmt to insert a comma here:"); + foo(a, oooptoptoptoptptooptoptoptoptptooptoptoptoptptoptoptoptoptpt()); +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/block_trailing_comma_call/two.rs b/src/tools/rustfmt/tests/source/configs/indent_style/block_trailing_comma_call/two.rs new file mode 100644 index 0000000000..7a62d722c6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/block_trailing_comma_call/two.rs @@ -0,0 +1,9 @@ +// rustfmt-version: Two +// rustfmt-error_on_line_overflow: false +// rustfmt-indent_style: Block + +// rustfmt should not add trailing comma when rewriting macro. See #1528. +fn a() { + panic!("this is a long string that goes past the maximum line length causing rustfmt to insert a comma here:"); + foo(a, oooptoptoptoptptooptoptoptoptptooptoptoptoptptoptoptoptoptpt()); +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/block_where_pred.rs b/src/tools/rustfmt/tests/source/configs/indent_style/block_where_pred.rs new file mode 100644 index 0000000000..450491f027 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/block_where_pred.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Block +// Where predicate indent + +fn lorem() -> T where Ipsum: Eq, Dolor: Eq, Sit: Eq, Amet: Eq { + // body +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/default.rs b/src/tools/rustfmt/tests/source/configs/indent_style/default.rs new file mode 100644 index 0000000000..f08f5c6446 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/default.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Visual +// Where style + +fn lorem() -> T where Ipsum: Eq, Dolor: Eq, Sit: Eq, Amet: Eq { + // body +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/rfc_where.rs b/src/tools/rustfmt/tests/source/configs/indent_style/rfc_where.rs new file mode 100644 index 0000000000..012840be28 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/rfc_where.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Block +// Where style + +fn lorem() -> T where Ipsum: Eq, Dolor: Eq, Sit: Eq, Amet: Eq { + // body +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/visual_args.rs b/src/tools/rustfmt/tests/source/configs/indent_style/visual_args.rs new file mode 100644 index 0000000000..5aa28a62b9 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/visual_args.rs @@ -0,0 +1,32 @@ +// rustfmt-indent_style: Visual +// Function arguments layout + +fn lorem() {} + +fn lorem(ipsum: usize) {} + +fn lorem(ipsum: usize, dolor: usize, sit: usize, amet: usize, consectetur: usize, adipiscing: usize, elit: usize) { + // body +} + +// #1922 +extern "C" { + pub fn LAPACKE_csytrs_rook_work(matrix_layout: c_int, + uplo: c_char, + n: lapack_int, + nrhs: lapack_int, + a: *const lapack_complex_float, + lda: lapack_int, ipiv: *const lapack_int, + b: *mut lapack_complex_float, + ldb: lapack_int + )-> lapack_int; + + pub fn LAPACKE_csytrs_rook_work(matrix_layout: c_int, + uplo: c_char, + n: lapack_int, + nrhs: lapack_int, + lda: lapack_int, ipiv: *const lapack_int, + b: *mut lapack_complex_float, + ldb: lapack_int + ) -> lapack_int; +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/visual_array.rs b/src/tools/rustfmt/tests/source/configs/indent_style/visual_array.rs new file mode 100644 index 0000000000..05bbf00b1d --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/visual_array.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Visual +// Array layout + +fn main() { + let lorem = vec!["ipsum","dolor","sit","amet","consectetur","adipiscing","elit"]; +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/visual_call.rs b/src/tools/rustfmt/tests/source/configs/indent_style/visual_call.rs new file mode 100644 index 0000000000..9a679d6bb4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/visual_call.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Visual +// Function call style + +fn main() { + lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit"); +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/visual_chain.rs b/src/tools/rustfmt/tests/source/configs/indent_style/visual_chain.rs new file mode 100644 index 0000000000..b749487539 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/visual_chain.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Visual +// Chain indent + +fn main() { + let lorem = ipsum.dolor().sit().amet().consectetur().adipiscing().elite(); +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/visual_generics.rs b/src/tools/rustfmt/tests/source/configs/indent_style/visual_generics.rs new file mode 100644 index 0000000000..1f910d32d8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/visual_generics.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Visual +// Generics indent + +fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, adipiscing: Adipiscing, consectetur: Consectetur, elit: Elit) -> T { + // body +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/visual_struct_lit.rs b/src/tools/rustfmt/tests/source/configs/indent_style/visual_struct_lit.rs new file mode 100644 index 0000000000..45538e7048 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/visual_struct_lit.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Visual +// Struct literal-style + +fn main() { + let lorem = Lorem { ipsum: dolor, sit: amet }; +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/visual_trailing_comma.rs b/src/tools/rustfmt/tests/source/configs/indent_style/visual_trailing_comma.rs new file mode 100644 index 0000000000..9738d397db --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/visual_trailing_comma.rs @@ -0,0 +1,7 @@ +// rustfmt-error_on_line_overflow: false +// rustfmt-indent_style: Visual + +// rustfmt should not add trailing comma when rewriting macro. See #1528. +fn a() { + panic!("this is a long string that goes past the maximum line length causing rustfmt to insert a comma here:"); +} diff --git a/src/tools/rustfmt/tests/source/configs/indent_style/visual_where_pred.rs b/src/tools/rustfmt/tests/source/configs/indent_style/visual_where_pred.rs new file mode 100644 index 0000000000..055806b686 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/indent_style/visual_where_pred.rs @@ -0,0 +1,6 @@ +// rustfmt-indent_style: Visual +// Where predicate indent + +fn lorem() -> T where Ipsum: Eq, Dolor: Eq, Sit: Eq, Amet: Eq { + // body +} diff --git a/src/tools/rustfmt/tests/source/configs/match_arm_blocks/false.rs b/src/tools/rustfmt/tests/source/configs/match_arm_blocks/false.rs new file mode 100644 index 0000000000..53e37e13c4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/match_arm_blocks/false.rs @@ -0,0 +1,11 @@ +// rustfmt-match_arm_blocks: false +// Wrap match-arms + +fn main() { + match lorem { + true => foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + false => { + println!("{}", sit) + } + } +} diff --git a/src/tools/rustfmt/tests/source/configs/match_arm_blocks/true.rs b/src/tools/rustfmt/tests/source/configs/match_arm_blocks/true.rs new file mode 100644 index 0000000000..a452b13cd2 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/match_arm_blocks/true.rs @@ -0,0 +1,11 @@ +// rustfmt-match_arm_blocks: true +// Wrap match-arms + +fn main() { + match lorem { + true => foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + false => { + println!("{}", sit) + } + } +} diff --git a/src/tools/rustfmt/tests/source/configs/match_arm_leading_pipes/always.rs b/src/tools/rustfmt/tests/source/configs/match_arm_leading_pipes/always.rs new file mode 100644 index 0000000000..162d812d8c --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/match_arm_leading_pipes/always.rs @@ -0,0 +1,27 @@ +// rustfmt-match_arm_leading_pipes: Always + +fn foo() { + match foo { + "foo" | "bar" => {} + "baz" + | "something relatively long" + | "something really really really realllllllllllllly long" => println!("x"), + "qux" => println!("y"), + _ => {} + } +} + +fn issue_3973() { + match foo { + "foo" | "bar" => {} + _ => {} + } +} + +fn bar() { + match baz { + "qux" => {} + "foo" | "bar" => {} + _ => {} + } +} diff --git a/src/tools/rustfmt/tests/source/configs/match_arm_leading_pipes/never.rs b/src/tools/rustfmt/tests/source/configs/match_arm_leading_pipes/never.rs new file mode 100644 index 0000000000..8a68fe2140 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/match_arm_leading_pipes/never.rs @@ -0,0 +1,28 @@ +// rustfmt-match_arm_leading_pipes: Never + +fn foo() { + match foo { + | "foo" | "bar" => {} + | "baz" + | "something relatively long" + | "something really really really realllllllllllllly long" => println!("x"), + | "qux" => println!("y"), + _ => {} + } +} + +fn issue_3973() { + match foo { + | "foo" + | "bar" => {} + _ => {} + } +} + +fn bar() { + match baz { + "qux" => {} + "foo" | "bar" => {} + _ => {} + } +} diff --git a/src/tools/rustfmt/tests/source/configs/match_arm_leading_pipes/preserve.rs b/src/tools/rustfmt/tests/source/configs/match_arm_leading_pipes/preserve.rs new file mode 100644 index 0000000000..ea303e857d --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/match_arm_leading_pipes/preserve.rs @@ -0,0 +1,28 @@ +// rustfmt-match_arm_leading_pipes: Preserve + +fn foo() { + match foo { + | "foo" | "bar" => {} + | "baz" + | "something relatively long" + | "something really really really realllllllllllllly long" => println!("x"), + | "qux" => println!("y"), + _ => {} + } +} + +fn issue_3973() { + match foo { + | "foo" + | "bar" => {} + _ => {} + } +} + +fn bar() { + match baz { + "qux" => { } + "foo" | "bar" => {} + _ => {} + } +} diff --git a/src/tools/rustfmt/tests/source/configs/match_block_trailing_comma/false.rs b/src/tools/rustfmt/tests/source/configs/match_block_trailing_comma/false.rs new file mode 100644 index 0000000000..70e02955fb --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/match_block_trailing_comma/false.rs @@ -0,0 +1,11 @@ +// rustfmt-match_block_trailing_comma: false +// Match block trailing comma + +fn main() { + match lorem { + Lorem::Ipsum => { + println!("ipsum"); + } + Lorem::Dolor => println!("dolor"), + } +} diff --git a/src/tools/rustfmt/tests/source/configs/match_block_trailing_comma/true.rs b/src/tools/rustfmt/tests/source/configs/match_block_trailing_comma/true.rs new file mode 100644 index 0000000000..b9af3d4720 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/match_block_trailing_comma/true.rs @@ -0,0 +1,11 @@ +// rustfmt-match_block_trailing_comma: true +// Match block trailing comma + +fn main() { + match lorem { + Lorem::Ipsum => { + println!("ipsum"); + } + Lorem::Dolor => println!("dolor"), + } +} diff --git a/src/tools/rustfmt/tests/source/configs/merge_derives/true.rs b/src/tools/rustfmt/tests/source/configs/merge_derives/true.rs new file mode 100644 index 0000000000..18b8443f0d --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/merge_derives/true.rs @@ -0,0 +1,46 @@ +// rustfmt-merge_derives: true +// Merge multiple derives to a single one. + +#[bar] +#[derive(Eq, PartialEq)] +#[foo] +#[derive(Debug)] +#[foobar] +#[derive(Copy, Clone)] +pub enum Foo {} + +#[derive(Eq, PartialEq)] +#[derive(Debug)] +#[foobar] +#[derive(Copy, Clone)] +pub enum Bar {} + +#[derive(Eq, PartialEq)] +#[derive(Debug)] +#[derive(Copy, Clone)] +pub enum FooBar {} + +mod foo { +#[bar] +#[derive(Eq, PartialEq)] +#[foo] +#[derive(Debug)] +#[foobar] +#[derive(Copy, Clone)] +pub enum Foo {} +} + +mod bar { +#[derive(Eq, PartialEq)] +#[derive(Debug)] +#[foobar] +#[derive(Copy, Clone)] +pub enum Bar {} +} + +mod foobar { +#[derive(Eq, PartialEq)] +#[derive(Debug)] +#[derive(Copy, Clone)] +pub enum FooBar {} +} diff --git a/src/tools/rustfmt/tests/source/configs/normalize_comments/false.rs b/src/tools/rustfmt/tests/source/configs/normalize_comments/false.rs new file mode 100644 index 0000000000..488962ed93 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/normalize_comments/false.rs @@ -0,0 +1,13 @@ +// rustfmt-normalize_comments: false +// Normalize comments + +// Lorem ipsum: +fn dolor() -> usize {} + +/* sit amet: */ +fn adipiscing() -> usize {} + +// #652 +//////////////////////////////////////////////////////////////////////////////// +// Basic slice extension methods +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tools/rustfmt/tests/source/configs/normalize_comments/true.rs b/src/tools/rustfmt/tests/source/configs/normalize_comments/true.rs new file mode 100644 index 0000000000..c74a9808e6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/normalize_comments/true.rs @@ -0,0 +1,13 @@ +// rustfmt-normalize_comments: true +// Normalize comments + +// Lorem ipsum: +fn dolor() -> usize {} + +/* sit amet: */ +fn adipiscing() -> usize {} + +// #652 +//////////////////////////////////////////////////////////////////////////////// +// Basic slice extension methods +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tools/rustfmt/tests/source/configs/normalize_doc_attributes/false.rs b/src/tools/rustfmt/tests/source/configs/normalize_doc_attributes/false.rs new file mode 100644 index 0000000000..f8eb64273c --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/normalize_doc_attributes/false.rs @@ -0,0 +1,13 @@ +// rustfmt-normalize_doc_attributes: false +// Normalize doc attributes + +#![doc = " Example documentation"] + +#[doc = " Example item documentation"] +pub enum Foo {} + +#[doc = " Lots of space"] +pub enum Bar {} + +#[doc = "no leading space"] +pub mod FooBar {} diff --git a/src/tools/rustfmt/tests/source/configs/normalize_doc_attributes/true.rs b/src/tools/rustfmt/tests/source/configs/normalize_doc_attributes/true.rs new file mode 100644 index 0000000000..894c00a4dc --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/normalize_doc_attributes/true.rs @@ -0,0 +1,13 @@ +// rustfmt-normalize_doc_attributes: true +// Normalize doc attributes + +#![doc = " Example documentation"] + +#[doc = " Example item documentation"] +pub enum Foo {} + +#[doc = " Lots of space"] +pub enum Bar {} + +#[doc = "no leading space"] +pub mod FooBar {} diff --git a/src/tools/rustfmt/tests/source/configs/remove_nested_parens/remove_nested_parens.rs b/src/tools/rustfmt/tests/source/configs/remove_nested_parens/remove_nested_parens.rs new file mode 100644 index 0000000000..87aed09c14 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/remove_nested_parens/remove_nested_parens.rs @@ -0,0 +1,5 @@ +// rustfmt-remove_nested_parens: true + +fn main() { + ((((((foo())))))); +} diff --git a/src/tools/rustfmt/tests/source/configs/reorder_impl_items/false.rs b/src/tools/rustfmt/tests/source/configs/reorder_impl_items/false.rs new file mode 100644 index 0000000000..beb99f0fb8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/reorder_impl_items/false.rs @@ -0,0 +1,11 @@ +// rustfmt-reorder_impl_items: false + +struct Dummy; + +impl Iterator for Dummy { + fn next(&mut self) -> Option { + None + } + + type Item = i32; +} diff --git a/src/tools/rustfmt/tests/source/configs/reorder_impl_items/true.rs b/src/tools/rustfmt/tests/source/configs/reorder_impl_items/true.rs new file mode 100644 index 0000000000..612b1c84ab --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/reorder_impl_items/true.rs @@ -0,0 +1,11 @@ +// rustfmt-reorder_impl_items: true + +struct Dummy; + +impl Iterator for Dummy { + fn next(&mut self) -> Option { + None + } + + type Item = i32; +} diff --git a/src/tools/rustfmt/tests/source/configs/reorder_imports/false.rs b/src/tools/rustfmt/tests/source/configs/reorder_imports/false.rs new file mode 100644 index 0000000000..4b85684dc0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/reorder_imports/false.rs @@ -0,0 +1,7 @@ +// rustfmt-reorder_imports: false +// Reorder imports + +use lorem; +use ipsum; +use dolor; +use sit; diff --git a/src/tools/rustfmt/tests/source/configs/reorder_imports/true.rs b/src/tools/rustfmt/tests/source/configs/reorder_imports/true.rs new file mode 100644 index 0000000000..2a40f6d069 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/reorder_imports/true.rs @@ -0,0 +1,19 @@ +// rustfmt-reorder_imports: true +// Reorder imports + +use lorem; +use ipsum; +use dolor; +use sit; + +fn foo() { + use C; + use B; + use A; + + bar(); + + use F; + use E; + use D; +} diff --git a/src/tools/rustfmt/tests/source/configs/reorder_modules/dolor/mod.rs b/src/tools/rustfmt/tests/source/configs/reorder_modules/dolor/mod.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/reorder_modules/dolor/mod.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/source/configs/reorder_modules/false.rs b/src/tools/rustfmt/tests/source/configs/reorder_modules/false.rs new file mode 100644 index 0000000000..56b1aa03ed --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/reorder_modules/false.rs @@ -0,0 +1,7 @@ +// rustfmt-reorder_modules: false +// Reorder modules + +mod lorem; +mod ipsum; +mod dolor; +mod sit; diff --git a/src/tools/rustfmt/tests/source/configs/reorder_modules/ipsum/mod.rs b/src/tools/rustfmt/tests/source/configs/reorder_modules/ipsum/mod.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/reorder_modules/ipsum/mod.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/source/configs/reorder_modules/lorem/mod.rs b/src/tools/rustfmt/tests/source/configs/reorder_modules/lorem/mod.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/reorder_modules/lorem/mod.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/source/configs/reorder_modules/sit/mod.rs b/src/tools/rustfmt/tests/source/configs/reorder_modules/sit/mod.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/reorder_modules/sit/mod.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/source/configs/reorder_modules/true.rs b/src/tools/rustfmt/tests/source/configs/reorder_modules/true.rs new file mode 100644 index 0000000000..79b0ab1e35 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/reorder_modules/true.rs @@ -0,0 +1,7 @@ +// rustfmt-reorder_modules: true +// Reorder modules + +mod lorem; +mod ipsum; +mod dolor; +mod sit; diff --git a/src/tools/rustfmt/tests/source/configs/skip_children/foo/mod.rs b/src/tools/rustfmt/tests/source/configs/skip_children/foo/mod.rs new file mode 100644 index 0000000000..d7ff6cdb82 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/skip_children/foo/mod.rs @@ -0,0 +1,3 @@ +fn skip_formatting_this() { + println ! ( "Skip this" ) ; +} diff --git a/src/tools/rustfmt/tests/source/configs/skip_children/true.rs b/src/tools/rustfmt/tests/source/configs/skip_children/true.rs new file mode 100644 index 0000000000..e51889dd4c --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/skip_children/true.rs @@ -0,0 +1,4 @@ +// rustfmt-skip_children: true + +mod foo ; +mod void; diff --git a/src/tools/rustfmt/tests/source/configs/space_before_colon/true.rs b/src/tools/rustfmt/tests/source/configs/space_before_colon/true.rs new file mode 100644 index 0000000000..0a59760252 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/space_before_colon/true.rs @@ -0,0 +1,11 @@ +// rustfmt-space_before_colon: true +// Space before colon + +fn lorem(t : T) { + let ipsum: Dolor = sit; +} + +const LOREM : Lorem = Lorem { + ipsum : dolor, + sit : amet, +}; diff --git a/src/tools/rustfmt/tests/source/configs/spaces_around_ranges/false.rs b/src/tools/rustfmt/tests/source/configs/spaces_around_ranges/false.rs new file mode 100644 index 0000000000..1878c68a5a --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/spaces_around_ranges/false.rs @@ -0,0 +1,34 @@ +// rustfmt-spaces_around_ranges: false +// Spaces around ranges + +fn main() { + let lorem = 0 .. 10; + let ipsum = 0 ..= 10; + + match lorem { + 1 .. 5 => foo(), + _ => bar, + } + + match lorem { + 1 ..= 5 => foo(), + _ => bar, + } + + match lorem { + 1 ... 5 => foo(), + _ => bar, + } +} + +fn half_open() { + match [5 .. 4, 99 .. 105, 43 .. 44] { + [_, 99 .., _] => {} + [_, .. 105, _] => {} + _ => {} + }; + + if let ..= 5 = 0 {} + if let .. 5 = 0 {} + if let 5 .. = 0 {} +} diff --git a/src/tools/rustfmt/tests/source/configs/spaces_around_ranges/true.rs b/src/tools/rustfmt/tests/source/configs/spaces_around_ranges/true.rs new file mode 100644 index 0000000000..0eadfb2851 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/spaces_around_ranges/true.rs @@ -0,0 +1,34 @@ +// rustfmt-spaces_around_ranges: true +// Spaces around ranges + +fn main() { + let lorem = 0..10; + let ipsum = 0..=10; + + match lorem { + 1..5 => foo(), + _ => bar, + } + + match lorem { + 1..=5 => foo(), + _ => bar, + } + + match lorem { + 1...5 => foo(), + _ => bar, + } +} + +fn half_open() { + match [5..4, 99..105, 43..44] { + [_, 99.., _] => {} + [_, ..105, _] => {} + _ => {} + }; + + if let ..=5 = 0 {} + if let ..5 = 0 {} + if let 5.. = 0 {} +} diff --git a/src/tools/rustfmt/tests/source/configs/struct_field_align_threshold/20.rs b/src/tools/rustfmt/tests/source/configs/struct_field_align_threshold/20.rs new file mode 100644 index 0000000000..81253c4603 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/struct_field_align_threshold/20.rs @@ -0,0 +1,383 @@ +// rustfmt-struct_field_align_threshold: 20 +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-error_on_line_overflow: false + +struct Foo { + x: u32, + yy: u32, // comment + zzz: u32, +} + +pub struct Bar { + x: u32, + yy: u32, + zzz: u32, + + xxxxxxx: u32, +} + +fn main() { + let foo = Foo { + x: 0, + yy: 1, + zzz: 2, + }; + + let bar = Bar { + x: 0, + yy: 1, + zzz: 2, + + xxxxxxx: 3, + }; +} + + /// A Doc comment +#[AnAttribute] +pub struct Foo { + #[rustfmt::skip] + f : SomeType, // Comment beside a field + f: SomeType, // Comment beside a field + // Comment on a field + #[AnAttribute] + g: SomeOtherType, + /// A doc comment on a field + h: AThirdType, + pub i: TypeForPublicField +} + +// #1029 +pub struct Foo { + #[doc(hidden)] + // This will NOT get deleted! + bar: String, // hi +} + +// #1029 +struct X { + // `x` is an important number. + #[allow(unused)] // TODO: use + x: u32, +} + +// #410 +#[allow(missing_docs)] +pub struct Writebatch { + #[allow(dead_code)] //only used for holding the internal pointer + writebatch: RawWritebatch, + marker: PhantomData, +} + +struct Bar; + +struct NewType(Type, OtherType); + +struct +NewInt (pub i32, SomeType /* inline comment */, T /* sup */ + + + ); + +struct Qux<'a, + N: Clone + 'a, + E: Clone + 'a, + G: Labeller<'a, N, E> + GraphWalk<'a, N, E>, + W: Write + Copy> +( + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, // Comment + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, + #[AnAttr] + // Comment + /// Testdoc + G, + pub W, +); + +struct Tuple(/*Comment 1*/ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + /* Comment 2 */ BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB,); + +// With a where-clause and generics. +pub struct Foo<'a, Y: Baz> + where X: Whatever +{ + f: SomeType, // Comment beside a field +} + +struct Baz { + + a: A, // Comment A + b: B, // Comment B + c: C, // Comment C + +} + +struct Baz { + a: A, // Comment A + + b: B, // Comment B + + + + + c: C, // Comment C +} + +struct Baz { + + a: A, + + b: B, + c: C, + + + + + d: D + +} + +struct Baz +{ + // Comment A + a: A, + + // Comment B +b: B, + // Comment C + c: C,} + +// Will this be a one-liner? +struct Tuple( + A, //Comment + B +); + +pub struct State time::Timespec> { now: F } + +pub struct State ()> { now: F } + +pub struct State { now: F } + +struct Palette { /// A map of indices in the palette to a count of pixels in approximately that color + foo: i32} + +// Splitting a single line comment into a block previously had a misalignment +// when the field had attributes +struct FieldsWithAttributes { + // Pre Comment + #[rustfmt::skip] pub host:String, // Post comment BBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBB BBBBBBBBBBB + //Another pre comment + #[attr1] + #[attr2] pub id: usize // CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCC CCCCCCCCCCCC +} + +struct Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: node::Handle>, + Type, + NodeType>, +} + +struct Foo(T); +struct Foo(T) where T: Copy, T: Eq; +struct Foo(TTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUUUUUU, TTTTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUU); +struct Foo(TTTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUUUUUU, TTTTTTTTTTTTTTTTTTT) where T: PartialEq; +struct Foo(TTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUUUUUU, TTTTTTTTTTTTTTTTTTTTT) where T: PartialEq; +struct Foo(TTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUUUUUU, TTTTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUU) where T: PartialEq; +struct Foo(TTTTTTTTTTTTTTTTT, // Foo + UUUUUUUUUUUUUUUUUUUUUUUU /* Bar */, + // Baz + TTTTTTTTTTTTTTTTTTT, + // Qux (FIXME #572 - doc comment) + UUUUUUUUUUUUUUUUUUU); + +mod m { + struct X where T: Sized { + a: T, + } +} + +struct Foo(TTTTTTTTTTTTTTTTTTT, + /// Qux + UUUUUUUUUUUUUUUUUUU); + +struct Issue677 { + pub ptr: *const libc::c_void, + pub trace: fn( obj: + *const libc::c_void, tracer : *mut JSTracer ), +} + +struct Foo {} +struct Foo { + } +struct Foo { + // comment + } +struct Foo { + // trailing space -> + + + } +struct Foo { /* comment */ } +struct Foo( /* comment */ ); + +struct LongStruct { + a: A, + the_quick_brown_fox_jumps_over_the_lazy_dog:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +} + +struct Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: node::Handle>, + Type, + NodeType>, +} + +struct Foo(String); + +// #1364 +fn foo() { + convex_shape.set_point(0, &Vector2f { x: 400.0, y: 100.0 }); + convex_shape.set_point(1, &Vector2f { x: 500.0, y: 70.0 }); + convex_shape.set_point(2, &Vector2f { x: 450.0, y: 100.0 }); + convex_shape.set_point(3, &Vector2f { x: 580.0, y: 150.0 }); +} + +fn main() { + let x = Bar; + + // Comment + let y = Foo {a: x }; + + Foo { a: foo() /* comment*/, /* comment*/ b: bar(), ..something }; + + Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: f(), b: b(), }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: f(), b: b(), }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + // Comment + a: foo(), // Comment + // Comment + b: bar(), // Comment + }; + + Foo { a:Bar, + b:f() }; + + Quux { x: if cond { bar(); }, y: baz() }; + + A { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. + first: item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + second: Item + }; + + Some(Data::MethodCallData(MethodCallData { + span: sub_span.unwrap(), + scope: self.enclosing_scope(id), + ref_id: def_id, + decl_id: Some(decl_id), + })); + + Diagram { /* o This graph demonstrates how + * / \ significant whitespace is + * o o preserved. + * /|\ \ + * o o o o */ + graph: G, } +} + +fn matcher() { + TagTerminatedByteMatcher { + matcher: ByteMatcher { + pattern: b" { memb: T } + let foo = Foo:: { memb: 10 }; +} + +fn issue201() { + let s = S{a:0, .. b}; +} + +fn issue201_2() { + let s = S{a: S2{ .. c}, .. b}; +} + +fn issue278() { + let s = S { + a: 0, + // + b: 0, + }; + let s1 = S { + a: 0, + // foo + // + // bar + b: 0, + }; +} + +fn struct_exprs() { + Foo + { a : 1, b:f( 2)}; + Foo{a:1,b:f(2),..g(3)}; + LoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongStruct { ..base }; + IntrinsicISizesContribution { content_intrinsic_sizes: IntrinsicISizes { minimum_inline_size: 0, }, }; +} + +fn issue123() { + Foo { a: b, c: d, e: f }; + + Foo { a: bb, c: dd, e: ff }; + + Foo { a: ddddddddddddddddddddd, b: cccccccccccccccccccccccccccccccccccccc }; +} + +fn issue491() { + Foo { + guard: None, + arm: 0, // Comment + }; + + Foo { + arm: 0, // Comment + }; + + Foo { a: aaaaaaaaaa, b: bbbbbbbb, c: cccccccccc, d: dddddddddd, /* a comment */ + e: eeeeeeeee }; +} + +fn issue698() { + Record { + ffffffffffffffffffffffffffields: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + }; + Record { + ffffffffffffffffffffffffffields: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + } +} + +fn issue835() { + MyStruct {}; + MyStruct { /* a comment */ }; + MyStruct { + // Another comment + }; + MyStruct {} +} + +fn field_init_shorthand() { + MyStruct { x, y, z }; + MyStruct { x, y, z, .. base }; + Foo { aaaaaaaaaa, bbbbbbbb, cccccccccc, dddddddddd, /* a comment */ + eeeeeeeee }; + Record { ffffffffffffffffffffffffffieldsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa }; +} diff --git a/src/tools/rustfmt/tests/source/configs/struct_lit_single_line/false.rs b/src/tools/rustfmt/tests/source/configs/struct_lit_single_line/false.rs new file mode 100644 index 0000000000..17cad8dde2 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/struct_lit_single_line/false.rs @@ -0,0 +1,6 @@ +// rustfmt-struct_lit_single_line: false +// Struct literal multiline-style + +fn main() { + let lorem = Lorem { ipsum: dolor, sit: amet }; +} diff --git a/src/tools/rustfmt/tests/source/configs/tab_spaces/2.rs b/src/tools/rustfmt/tests/source/configs/tab_spaces/2.rs new file mode 100644 index 0000000000..5c2667bc2c --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/tab_spaces/2.rs @@ -0,0 +1,11 @@ +// rustfmt-tab_spaces: 2 +// rustfmt-max_width: 30 +// rustfmt-indent_style: Block +// Tab spaces + +fn lorem() { +let ipsum = dolor(); +let sit = vec![ +"amet", "consectetur", "adipiscing", "elit." +]; +} diff --git a/src/tools/rustfmt/tests/source/configs/tab_spaces/4.rs b/src/tools/rustfmt/tests/source/configs/tab_spaces/4.rs new file mode 100644 index 0000000000..da61bbd42a --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/tab_spaces/4.rs @@ -0,0 +1,11 @@ +// rustfmt-tab_spaces: 4 +// rustfmt-max_width: 30 +// rustfmt-indent_style: Block +// Tab spaces + +fn lorem() { +let ipsum = dolor(); +let sit = vec![ +"amet", "consectetur", "adipiscing", "elit." +]; +} diff --git a/src/tools/rustfmt/tests/source/configs/trailing_comma/always.rs b/src/tools/rustfmt/tests/source/configs/trailing_comma/always.rs new file mode 100644 index 0000000000..57e874cd82 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/trailing_comma/always.rs @@ -0,0 +1,7 @@ +// rustfmt-trailing_comma: Always +// Trailing comma + +fn main() { + let Lorem { ipsum, dolor, sit, } = amet; + let Lorem { ipsum, dolor, sit, amet, consectetur, adipiscing } = elit; +} diff --git a/src/tools/rustfmt/tests/source/configs/trailing_comma/never.rs b/src/tools/rustfmt/tests/source/configs/trailing_comma/never.rs new file mode 100644 index 0000000000..4da3b996f2 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/trailing_comma/never.rs @@ -0,0 +1,23 @@ +// rustfmt-trailing_comma: Never +// Trailing comma + +fn main() { + let Lorem { ipsum, dolor, sit, } = amet; + let Lorem { ipsum, dolor, sit, amet, consectetur, adipiscing } = elit; + + // #1544 + if let VrMsg::ClientReply {request_num: reply_req_num, value, ..} = msg { + let _ = safe_assert_eq!(reply_req_num, request_num, op); + return Ok((request_num, op, value)); + } + + // #1710 + pub struct FileInput { + input: StringInput, + file_name: OsString, + } + match len { + Some(len) => Ok(new(self.input, self.pos + len)), + None => Err(self), + } +} diff --git a/src/tools/rustfmt/tests/source/configs/trailing_comma/vertical.rs b/src/tools/rustfmt/tests/source/configs/trailing_comma/vertical.rs new file mode 100644 index 0000000000..c903e82215 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/trailing_comma/vertical.rs @@ -0,0 +1,7 @@ +// rustfmt-trailing_comma: Vertical +// Trailing comma + +fn main() { + let Lorem { ipsum, dolor, sit, } = amet; + let Lorem { ipsum, dolor, sit, amet, consectetur, adipiscing } = elit; +} diff --git a/src/tools/rustfmt/tests/source/configs/type_punctuation_density/compressed.rs b/src/tools/rustfmt/tests/source/configs/type_punctuation_density/compressed.rs new file mode 100644 index 0000000000..223b9a2f0f --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/type_punctuation_density/compressed.rs @@ -0,0 +1,37 @@ +// rustfmt-type_punctuation_density: Compressed +// Type punctuation density + +fn lorem() { + // body +} + +struct Foo +where U: Eq + Clone { + // body +} + +trait Foo<'a, T = usize> +where T: 'a + Eq + Clone +{ + type Bar: Eq + Clone; +} + +trait Foo: Eq + Clone { + // body +} + +impl Foo<'a> for Bar +where for<'a> T: 'a + Eq + Clone +{ + // body +} + +fn foo<'a, 'b, 'c>() +where 'a: 'b + 'c +{ + // body +} + +fn Foo + Foo>() { + let i = 6; +} diff --git a/src/tools/rustfmt/tests/source/configs/type_punctuation_density/wide.rs b/src/tools/rustfmt/tests/source/configs/type_punctuation_density/wide.rs new file mode 100644 index 0000000000..fe0c081670 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/type_punctuation_density/wide.rs @@ -0,0 +1,37 @@ +// rustfmt-type_punctuation_density: Wide +// Type punctuation density + +fn lorem() { + // body +} + +struct Foo +where U: Eq + Clone { + // body +} + +trait Foo<'a, T = usize> +where T: 'a + Eq + Clone +{ + type Bar: Eq + Clone; +} + +trait Foo: Eq + Clone { + // body +} + +impl Foo<'a> for Bar +where for<'a> T: 'a + Eq + Clone +{ + // body +} + +fn foo<'a, 'b, 'c>() +where 'a: 'b + 'c +{ + // body +} + +fn Foo + Foo>() { + let i = 6; +} diff --git a/src/tools/rustfmt/tests/source/configs/use_field_init_shorthand/false.rs b/src/tools/rustfmt/tests/source/configs/use_field_init_shorthand/false.rs new file mode 100644 index 0000000000..4c2eb1de17 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/use_field_init_shorthand/false.rs @@ -0,0 +1,19 @@ +// rustfmt-use_field_init_shorthand: false +// Use field initialization shorthand if possible. + +fn main() { + let a = Foo { + x: x, + y: y, + z: z, + }; + + let b = Bar { + x: x, + y: y, + #[attr] + z: z, + #[rustfmt::skip] + skipped: skipped, + }; +} diff --git a/src/tools/rustfmt/tests/source/configs/use_field_init_shorthand/true.rs b/src/tools/rustfmt/tests/source/configs/use_field_init_shorthand/true.rs new file mode 100644 index 0000000000..dcde28d74e --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/use_field_init_shorthand/true.rs @@ -0,0 +1,19 @@ +// rustfmt-use_field_init_shorthand: true +// Use field initialization shorthand if possible. + +fn main() { + let a = Foo { + x: x, + y: y, + z: z, + }; + + let b = Bar { + x: x, + y: y, + #[attr] + z: z, + #[rustfmt::skip] + skipped: skipped, + }; +} diff --git a/src/tools/rustfmt/tests/source/configs/use_small_heuristics/max.rs b/src/tools/rustfmt/tests/source/configs/use_small_heuristics/max.rs new file mode 100644 index 0000000000..8d30932e2c --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/use_small_heuristics/max.rs @@ -0,0 +1,25 @@ +// rustfmt-use_small_heuristics: Max + +enum Lorem { + Ipsum, + Dolor(bool), + Sit { + amet: Consectetur, + adipiscing: Elit, + }, +} + +fn main() { + lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); + + let lorem = Lorem { + ipsum: dolor, + sit: amet, + }; + + let lorem = if ipsum { + dolor + } else { + sit + }; +} diff --git a/src/tools/rustfmt/tests/source/configs/use_try_shorthand/false.rs b/src/tools/rustfmt/tests/source/configs/use_try_shorthand/false.rs new file mode 100644 index 0000000000..de7f8b4a5e --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/use_try_shorthand/false.rs @@ -0,0 +1,6 @@ +// rustfmt-use_try_shorthand: false +// Use try! shorthand + +fn main() { + let lorem = try!(ipsum.map(|dolor| dolor.sit())); +} diff --git a/src/tools/rustfmt/tests/source/configs/use_try_shorthand/true.rs b/src/tools/rustfmt/tests/source/configs/use_try_shorthand/true.rs new file mode 100644 index 0000000000..9015ec41e5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/use_try_shorthand/true.rs @@ -0,0 +1,6 @@ +// rustfmt-use_try_shorthand: true +// Use try! shorthand + +fn main() { + let lorem = try!(ipsum.map(|dolor| dolor.sit())); +} diff --git a/src/tools/rustfmt/tests/source/configs/where_single_line/true.rs b/src/tools/rustfmt/tests/source/configs/where_single_line/true.rs new file mode 100644 index 0000000000..9de98283b5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/where_single_line/true.rs @@ -0,0 +1,26 @@ +// rustfmt-where_single_line: true +// Where style + + +fn lorem_two_items() -> T where Ipsum: Eq, Lorem: Eq { + // body +} + +fn lorem_multi_line( + a: Aaaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbbbb, + c: Ccccccccccccccccc, + d: Ddddddddddddddddddddddddd, + e: Eeeeeeeeeeeeeeeeeee, +) -> T +where + Ipsum: Eq, +{ + // body +} + +fn lorem() -> T where Ipsum: Eq { + // body +} + +unsafe impl Sync for Foo where (): Send {} diff --git a/src/tools/rustfmt/tests/source/configs/wrap_comments/false.rs b/src/tools/rustfmt/tests/source/configs/wrap_comments/false.rs new file mode 100644 index 0000000000..48ecd88acc --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/wrap_comments/false.rs @@ -0,0 +1,8 @@ +// rustfmt-wrap_comments: false +// rustfmt-max_width: 50 +// rustfmt-error_on_line_overflow: false +// Wrap comments + +fn main() { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +} diff --git a/src/tools/rustfmt/tests/source/configs/wrap_comments/true.rs b/src/tools/rustfmt/tests/source/configs/wrap_comments/true.rs new file mode 100644 index 0000000000..39a79a4cac --- /dev/null +++ b/src/tools/rustfmt/tests/source/configs/wrap_comments/true.rs @@ -0,0 +1,15 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 50 +// Wrap comments + +fn main() { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +} + +fn code_block() { + // ```rust + // let x = 3; + // + // println!("x = {}", x); + // ``` +} diff --git a/src/tools/rustfmt/tests/source/const_generics.rs b/src/tools/rustfmt/tests/source/const_generics.rs new file mode 100644 index 0000000000..01b764dbe4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/const_generics.rs @@ -0,0 +1,44 @@ +struct Message { + field2: Vec< + "MessageEntity" + >, + field3: Vec< + 1 + >, + field4: Vec< + 2 , 3 + >, + +} + +struct RectangularArray { + array: [[T; WIDTH]; HEIGHT], +} + +fn main() { + const X: usize = 7; + let x: RectangularArray; + let y: RectangularArray; +} + +fn foo() { + const Y: usize = X * 2; + static Z: (usize, usize) = (X, X); + + struct Foo([i32; X]); +} + +type Foo = [i32; N + 1]; + +pub trait Foo: Bar<{Baz::COUNT}> { + const ASD: usize; +} + +// #4263 +fn const_generics_on_params< + // AAAA + const BBBB: usize, + /* CCCC */ + const DDDD: usize, + >() {} diff --git a/src/tools/rustfmt/tests/source/control-brace-style-always-next-line.rs b/src/tools/rustfmt/tests/source/control-brace-style-always-next-line.rs new file mode 100644 index 0000000000..9079fb46c0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/control-brace-style-always-next-line.rs @@ -0,0 +1,44 @@ +// rustfmt-control_brace_style: AlwaysNextLine + +fn main() { + loop { + (); + (); + } + + + 'label: loop // loop comment + { + (); + } + + + cond = true; + while cond { + (); + } + + + 'while_label: while cond { // while comment + (); + } + + + for obj in iter { + for sub_obj in obj + { + 'nested_while_label: while cond { + (); + } + } + } + + match some_var { // match comment + pattern0 => val0, + pattern1 => val1, + pattern2 | pattern3 => { + do_stuff(); + val2 + }, + }; +} diff --git a/src/tools/rustfmt/tests/source/control-brace-style-always-same-line.rs b/src/tools/rustfmt/tests/source/control-brace-style-always-same-line.rs new file mode 100644 index 0000000000..45111aaab0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/control-brace-style-always-same-line.rs @@ -0,0 +1,42 @@ +fn main() { + loop { + (); + (); + } + + + 'label: loop // loop comment + { + (); + } + + + cond = true; + while cond { + (); + } + + + 'while_label: while cond { // while comment + (); + } + + + for obj in iter { + for sub_obj in obj + { + 'nested_while_label: while cond { + (); + } + } + } + + match some_var { // match comment + pattern0 => val0, + pattern1 => val1, + pattern2 | pattern3 => { + do_stuff(); + val2 + }, + }; +} diff --git a/src/tools/rustfmt/tests/source/doc-attrib.rs b/src/tools/rustfmt/tests/source/doc-attrib.rs new file mode 100644 index 0000000000..dde88c6e95 --- /dev/null +++ b/src/tools/rustfmt/tests/source/doc-attrib.rs @@ -0,0 +1,118 @@ +// rustfmt-wrap_comments: true +// rustfmt-normalize_doc_attributes: true + +// Only doc = "" attributes should be normalized +#![doc = " Example doc attribute comment"] +#![doc = " Example doc attribute comment with 10 leading spaces"] +#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://doc.rust-lang.org/favicon.ico", + html_root_url = "https://doc.rust-lang.org/nightly/", + html_playground_url = "https://play.rust-lang.org/", test(attr(deny(warnings))))] + + +// Long `#[doc = "..."]` +struct A { #[doc = " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] b: i32 } + + +#[doc = " The `nodes` and `edges` method each return instantiations of `Cow<[T]>` to leave implementers the freedom to create entirely new vectors or to pass back slices into internally owned vectors."] +struct B { b: i32 } + + +#[doc = " Level 1 comment"] +mod tests { + #[doc = " Level 2 comment"] + impl A { + #[doc = " Level 3 comment"] + fn f() { + #[doc = " Level 4 comment"] + fn g() { + } + } + } +} + +struct C { + #[doc = " item doc attrib comment"] + // regular item comment + b: i32, + + // regular item comment + #[doc = " item doc attrib comment"] + c: i32, +} + +// non-regression test for regular attributes, from #2647 +#[cfg(feature = "this_line_is_101_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")] +pub fn foo() {} + +// path attrs +#[clippy::bar] +#[clippy::bar(a, b, c)] +pub fn foo() {} + +mod issue_2620 { + #[derive(Debug, StructOpt)] +#[structopt(about = "Display information about the character on FF Logs")] +pub struct Params { + #[structopt(help = "The server the character is on")] + server: String, + #[structopt(help = "The character's first name")] + first_name: String, + #[structopt(help = "The character's last name")] + last_name: String, + #[structopt( + short = "j", + long = "job", + help = "The job to look at", + parse(try_from_str) + )] + job: Option +} +} + +// non-regression test for regular attributes, from #2969 +#[cfg(not(all(feature="std", + any(target_os = "linux", target_os = "android", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "haiku", + target_os = "emscripten", + target_os = "solaris", + target_os = "cloudabi", + target_os = "macos", target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", + target_os = "redox", + target_os = "fuchsia", + windows, + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), + ))))] +type Os = NoSource; + +// use cases from bindgen needing precise control over leading spaces +#[doc = "

"] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct ContradictAccessors { + #[doc = "no leading spaces here"] + pub mBothAccessors: ::std::os::raw::c_int, + #[doc = "
"] + pub mNoAccessors: ::std::os::raw::c_int, + #[doc = "
"] + pub mUnsafeAccessors: ::std::os::raw::c_int, + #[doc = "
"] + pub mImmutableAccessor: ::std::os::raw::c_int, +} + +#[doc = " \\brief MPI structure"] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct mbedtls_mpi { + #[doc = "< integer sign"] + pub s: ::std::os::raw::c_int, + #[doc = "< total # of limbs"] + pub n: ::std::os::raw::c_ulong, + #[doc = "< pointer to limbs"] + pub p: *mut mbedtls_mpi_uint, +} diff --git a/src/tools/rustfmt/tests/source/doc-comment-with-example.rs b/src/tools/rustfmt/tests/source/doc-comment-with-example.rs new file mode 100644 index 0000000000..e74ceefd19 --- /dev/null +++ b/src/tools/rustfmt/tests/source/doc-comment-with-example.rs @@ -0,0 +1,12 @@ +// rustfmt-format_code_in_doc_comments: true + +/// Foo +/// +/// # Example +/// ``` +/// # #![cfg_attr(not(dox), feature(cfg_target_feature, target_feature, stdsimd))] +/// # #![cfg_attr(not(dox), no_std)] +/// fn foo() { } +/// ``` +/// +fn foo() {} diff --git a/src/tools/rustfmt/tests/source/doc.rs b/src/tools/rustfmt/tests/source/doc.rs new file mode 100644 index 0000000000..3b25918b12 --- /dev/null +++ b/src/tools/rustfmt/tests/source/doc.rs @@ -0,0 +1,5 @@ +// rustfmt-normalize_comments: true +// Part of multiple.rs + +// sadfsdfa +//sdffsdfasdf diff --git a/src/tools/rustfmt/tests/source/dyn_trait.rs b/src/tools/rustfmt/tests/source/dyn_trait.rs new file mode 100644 index 0000000000..012643be92 --- /dev/null +++ b/src/tools/rustfmt/tests/source/dyn_trait.rs @@ -0,0 +1,20 @@ +#![feature(dyn_trait)] + +fn main() { + // #2506 + // checks rustfmt doesn't remove dyn + trait MyTrait { + fn method(&self) -> u64; + } + fn f1(a: Box) {} + + // checks if line wrap works correctly + trait Very_______________________Long__________________Name_______________________________Trait { + fn method(&self) -> u64; + } + + fn f2(a: Box) {} + + // #2582 + let _: &dyn (::std::any::Any) = &msg; +} diff --git a/src/tools/rustfmt/tests/source/else-if-brace-style-always-next-line.rs b/src/tools/rustfmt/tests/source/else-if-brace-style-always-next-line.rs new file mode 100644 index 0000000000..7b4870fc65 --- /dev/null +++ b/src/tools/rustfmt/tests/source/else-if-brace-style-always-next-line.rs @@ -0,0 +1,54 @@ +// rustfmt-control_brace_style: AlwaysNextLine + +fn main() { + if false + { + (); + (); + } + + if false // lone if comment + { + (); + (); + } + + + let a = + if 0 > 1 { + unreachable!() + } + else + { + 0x0 + }; + + + if true + { + (); + } else if false { + (); + (); + } + else { + (); + (); + (); + } + + if true // else-if-chain if comment + { + (); + } + else if false // else-if-chain else-if comment + { + (); + (); + } else // else-if-chain else comment + { + (); + (); + (); + } +} diff --git a/src/tools/rustfmt/tests/source/else-if-brace-style-always-same-line.rs b/src/tools/rustfmt/tests/source/else-if-brace-style-always-same-line.rs new file mode 100644 index 0000000000..37c9417ea1 --- /dev/null +++ b/src/tools/rustfmt/tests/source/else-if-brace-style-always-same-line.rs @@ -0,0 +1,52 @@ +fn main() { + if false + { + (); + (); + } + + if false // lone if comment + { + (); + (); + } + + + let a = + if 0 > 1 { + unreachable!() + } + else + { + 0x0 + }; + + + if true + { + (); + } else if false { + (); + (); + } + else { + (); + (); + (); + } + + if true // else-if-chain if comment + { + (); + } + else if false // else-if-chain else-if comment + { + (); + (); + } else // else-if-chain else comment + { + (); + (); + (); + } +} diff --git a/src/tools/rustfmt/tests/source/else-if-brace-style-closing-next-line.rs b/src/tools/rustfmt/tests/source/else-if-brace-style-closing-next-line.rs new file mode 100644 index 0000000000..3b885b3faa --- /dev/null +++ b/src/tools/rustfmt/tests/source/else-if-brace-style-closing-next-line.rs @@ -0,0 +1,54 @@ +// rustfmt-control_brace_style: ClosingNextLine + +fn main() { + if false + { + (); + (); + } + + if false // lone if comment + { + (); + (); + } + + + let a = + if 0 > 1 { + unreachable!() + } + else + { + 0x0 + }; + + + if true + { + (); + } else if false { + (); + (); + } + else { + (); + (); + (); + } + + if true // else-if-chain if comment + { + (); + } + else if false // else-if-chain else-if comment + { + (); + (); + } else // else-if-chain else comment + { + (); + (); + (); + } +} diff --git a/src/tools/rustfmt/tests/source/empty_file.rs b/src/tools/rustfmt/tests/source/empty_file.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tools/rustfmt/tests/source/enum.rs b/src/tools/rustfmt/tests/source/enum.rs new file mode 100644 index 0000000000..0ed9651abe --- /dev/null +++ b/src/tools/rustfmt/tests/source/enum.rs @@ -0,0 +1,212 @@ +// rustfmt-wrap_comments: true +// Enums test + +#[atrr] +pub enum Test { + A, B(u32, + A /* comment */, + SomeType), + /// Doc comment + C, +} + +pub enum Foo<'a, Y: Baz> where X: Whatever +{ A, } + +enum EmtpyWithComment { + // Some comment +} + +// C-style enum +enum Bar { + A = 1, + #[someAttr(test)] + B = 2, // comment + C, +} + +enum LongVariants { +First(LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONG, // comment +VARIANT), + // This is the second variant + Second +} + +enum StructLikeVariants { + Normal(u32, String, ), + StructLike { x: i32, // Test comment + // Pre-comment + #[Attr50] y: SomeType, // Aanother Comment + }, SL { a: A } +} + +enum X { + CreateWebGLPaintTask(Size2D, GLContextAttributes, IpcSender, usize), String>>), // This is a post comment +} + +pub enum EnumWithAttributes { + //This is a pre comment AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + TupleVar(usize, usize, usize), // AAAA AAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + // Pre Comment + #[rustfmt::skip] + SkippedItem(String,String,), // Post-comment + #[another_attr] + #[attr2] + ItemStruct {x: usize, y: usize}, // Comment AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + // And another + ForcedPreflight // AAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +} + +pub enum SingleTuple { + // Pre Comment AAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + Match(usize, usize, String) // Post-comment AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +} + +pub enum SingleStruct { + Match {name: String, loc: usize} // Post-comment +} + +pub enum GenericEnum +where I: Iterator { + // Pre Comment + Left {list: I, root: T}, // Post-comment + Right {list: I, root: T} // Post Comment +} + + +enum EmtpyWithComment { + // Some comment +} + +enum TestFormatFails { + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +} + +fn nested_enum_test() { + if true { + enum TestEnum { + One(usize, usize, usize, usize, usize, usize, usize, usize, usize, usize, usize, usize, usize, usize, usize, usize,), // AAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAA + Two // AAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAA + } + enum TestNestedFormatFail { + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + } + } +} + + pub struct EmtpyWithComment { + // FIXME: Implement this struct +} + +// #1115 +pub enum Bencoding<'i> { + Str(&'i [u8]), + Int(i64), + List(Vec>), + /// A bencoded dict value. The first element the slice of bytes in the source that the dict is + /// composed of. The second is the dict, decoded into an ordered map. + // TODO make Dict "structlike" AKA name the two values. + Dict(&'i [u8], BTreeMap<&'i [u8], Bencoding<'i>>), +} + +// #1261 +pub enum CoreResourceMsg { + SetCookieForUrl( + ServoUrl, + #[serde(deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize")] + Cookie, + CookieSource + ), +} + +enum Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong {} +enum Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong {} +enum Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong {} +enum Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong { Foo } + +// #1046 +pub enum Entry<'a, K: 'a, V: 'a> { + Vacant( + #[ stable( feature = "rust1", since = "1.0.0" ) ] VacantEntry<'a, K, V>, + ), + Occupied( + #[ stable( feature = "rust1", since = "1.0.0" ) ] + OccupiedEntry<'a, K, V>, + ), +} + +// #2081 +pub enum ForegroundColor { + CYAN = (winapi::FOREGROUND_INTENSITY | winapi::FOREGROUND_GREEN | winapi::FOREGROUND_BLUE) as u16, +} + +// #2098 +pub enum E<'a> { + V ( < std::slice::Iter<'a, Xxxxxxxxxxxxxx> as Iterator> :: Item ) , +} + +// #1809 +enum State { + TryRecv { + pos: usize, + lap: u8, + closed_count: usize, + }, + Subscribe { pos: usize }, + IsReady { pos: usize, ready: bool }, + Unsubscribe { + pos: usize, + lap: u8, + id_woken: usize, + }, + FinalTryRecv { pos: usize, id_woken: usize }, + TimedOut, + Disconnected, +} + +// #2190 +#[derive(Debug, Fail)] +enum AnError { + #[fail(display = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")] + UnexpectedSingleToken { token: syn::Token }, +} + +// #2193 +enum WidthOf101 { + #[fail(display = ".....................................................")] Io(::std::io::Error), + #[fail(display = ".....................................................")] Ioo(::std::io::Error), + Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(::std::io::Error), + Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(::std::io::Error), +} + +// #2389 +pub enum QlError { + #[fail(display = "Parsing error: {}", 0)] LexError(parser::lexer::LexError), + #[fail(display = "Parsing error: {:?}", 0)] ParseError(parser::ParseError), + #[fail(display = "Validation error: {:?}", 0)] ValidationError(Vec), + #[fail(display = "Execution error: {}", 0)] ExecutionError(String), + // (from, to) + #[fail(display = "Translation error: from {} to {}", 0, 1)] TranslationError(String, String), + // (kind, input, expected) + #[fail(display = "aaaaaaaaaaaaCould not find {}: Found: {}, expected: {:?}", 0, 1, 2)] ResolveError(&'static str, String, Option), +} + +// #2594 +enum Foo {} +enum Bar { } + +// #3562 +enum PublishedFileVisibility { + Public = sys::ERemoteStoragePublishedFileVisibility_k_ERemoteStoragePublishedFileVisibilityPublic, + FriendsOnly = sys::ERemoteStoragePublishedFileVisibility_k_ERemoteStoragePublishedFileVisibilityFriendsOnly, + Private = sys::ERemoteStoragePublishedFileVisibility_k_ERemoteStoragePublishedFileVisibilityPrivate, +} + +// #3771 +//#![feature(arbitrary_enum_discriminant)] +#[repr(u32)] +pub enum E { + A { a: u32 } = 0x100, + B { field1: u32, field2: u8, field3: m::M } = 0x300 // comment +} diff --git a/src/tools/rustfmt/tests/source/existential_type.rs b/src/tools/rustfmt/tests/source/existential_type.rs new file mode 100644 index 0000000000..33bb9a9515 --- /dev/null +++ b/src/tools/rustfmt/tests/source/existential_type.rs @@ -0,0 +1,23 @@ +// Opaque type. + + #![feature(type_alias_impl_trait)] + +pub type Adder +where + T: Clone, + F: Copy + = impl Fn(T) -> T; + +pub type Adderrr = impl Fn( T ) -> T; + +impl Foo for Bar { +type E = impl Trait; +} + +pub type Adder_without_impl +where + T: Clone, + F: Copy + = Fn(T) -> T; + +pub type Adderrr_without_impl = Fn( T ) -> T; diff --git a/src/tools/rustfmt/tests/source/expr-block.rs b/src/tools/rustfmt/tests/source/expr-block.rs new file mode 100644 index 0000000000..a3e6100b7f --- /dev/null +++ b/src/tools/rustfmt/tests/source/expr-block.rs @@ -0,0 +1,300 @@ +// Test expressions with block formatting. + +fn arrays() { + [ ]; + let empty = []; + + let foo = [a_long_name, a_very_lng_name, a_long_name]; + + let foo = [a_long_name, a_very_lng_name, a_long_name, a_very_lng_name, a_long_name, a_very_lng_name, a_long_name, a_very_lng_name]; + + vec![a_long_name, a_very_lng_name, a_long_name, a_very_lng_name, a_long_name, a_very_lng_name, a_very_lng_name]; + + [a_long_name, a_very_lng_name, a_long_name, a_very_lng_name, a_long_name, a_very_lng_name, a_very_lng_name] +} + +fn arrays() { + let x = [0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 7, + 8, + 9, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0]; + + let y = [/* comment */ 1, 2 /* post comment */, 3]; + + let xy = [ strukt { test123: value_one_two_three_four, turbo: coolio(), } , /* comment */ 1 ]; + + let a =WeightedChoice::new(&mut [Weighted { + weight: x, + item: 0, + }, + Weighted { + weight: 1, + item: 1, + }, + Weighted { + weight: x, + item: 2, + }, + Weighted { + weight: 1, + item: 3, + }]); + + let z = [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzz, q]; + + [ 1 + 3, 4 , 5, 6, 7, 7, fncall::>(3-1)] +} + +fn function_calls() { + let items = itemize_list(context.source_map, + args.iter(), + ")", + |item| item.span.lo(), + |item| item.span.hi(), + |item| { + item.rewrite(context, + Shape { + width: remaining_width, + ..nested_shape + }) + }, + span.lo(), + span.hi()); + + itemize_list(context.source_map, + args.iter(), + ")", + |item| item.span.lo(), + |item| item.span.hi(), + |item| { + item.rewrite(context, + Shape { + width: remaining_width, + ..nested_shape + }) + }, + span.lo(), + span.hi()) +} + +fn macros() { + baz!(do_not, add, trailing, commas, inside, of, function, like, macros, even, if_they, are, long); + + baz!(one_item_macro_which_is_also_loooooooooooooooooooooooooooooooooooooooooooooooong); + + let _ = match option { + None => baz!(function, like, macro_as, expression, which, is, loooooooooooooooong), + Some(p) => baz!(one_item_macro_as_expression_which_is_also_loooooooooooooooong), + }; +} + +fn issue_1450() { + if selfstate + .compare_exchandsfasdsdfgsdgsdfgsdfgsdfgsdfgsdfgfsfdsage_weak( + STATE_PARKED, + STATE_UNPARKED, + Release, + Relaxed, + Release, + Relaxed, + ) + .is_ok() { + return; + } +} + +fn foo() { + if real_total <= limit && !pre_line_comments && + !items.into_iter().any(|item| item.as_ref().is_multiline()) { + DefinitiveListTactic::Horizontal + } +} + +fn combine_block() { + foo( + Bar { + x: value, + y: value2, + }, + ); + + foo((Bar { + x: value, + y: value2, + },)); + + foo((1, 2, 3, Bar { + x: value, + y: value2, + })); + + foo((1, 2, 3, |x| { + let y = x + 1; + let z = y + 1; + z + })); + + let opt = Some( + Struct( + long_argument_one, + long_argument_two, + long_argggggggg, + ), + ); + + do_thing( + |param| { + action(); + foo(param) + }, + ); + + do_thing( + x, + |param| { + action(); + foo(param) + }, + ); + + do_thing( + x, + ( + 1, + 2, + 3, + |param| { + action(); + foo(param) + }, + ), + ); + + Ok( + some_function( + lllllllllong_argument_one, + lllllllllong_argument_two, + lllllllllllllllllllllllllllllong_argument_three, + ), + ); + + foo( + thing, + bar( + param2, + pparam1param1param1param1param1param1param1param1param1param1aram1, + param3, + ), + ); + + foo.map_or( + || { + Ok( + SomeStruct { + f1: 0, + f2: 0, + f3: 0, + }, + ) + }, + ); + + match opt { + Some(x) => somefunc(anotherfunc( + long_argument_one, + long_argument_two, + long_argument_three, + )), + Some(x) => |x| { + let y = x + 1; + let z = y + 1; + z + }, + Some(x) => (1, 2, |x| { + let y = x + 1; + let z = y + 1; + z + }), + Some(x) => SomeStruct { + f1: long_argument_one, + f2: long_argument_two, + f3: long_argument_three, + }, + None => Ok(SomeStruct { + f1: long_argument_one, + f2: long_argument_two, + f3: long_argument_three, + }), + }; + + match x { + y => func( + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + ), + _ => func( + x, + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, + zzz, + ), + } +} + +fn issue_1862() { + foo( + /* bar = */ None , + something_something, + /* baz = */ None , + /* This comment waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay too long to be kept on the same line */ None , + /* com */ this_last_arg_is_tooooooooooooooooooooooooooooooooo_long_to_be_kept_with_the_pre_comment , + ) +} + +fn issue_3025() { + foo( + // This describes the argument below. + /* bar = */ None , + // This describes the argument below. + something_something, + // This describes the argument below. */ + None , + // This describes the argument below. + /* This comment waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay too long to be kept on the same line */ None , + // This describes the argument below. + /* com */ this_last_arg_is_tooooooooooooooooooooooooooooooooo_long_to_be_kept_with_the_pre_comment , + ) +} + +fn issue_1878() { + let channel: &str = seq.next_element()?.ok_or_else(|| de::Error::invalid_length(2, &self))?; +} diff --git a/src/tools/rustfmt/tests/source/expr-overflow-delimited.rs b/src/tools/rustfmt/tests/source/expr-overflow-delimited.rs new file mode 100644 index 0000000000..cd80ca6fce --- /dev/null +++ b/src/tools/rustfmt/tests/source/expr-overflow-delimited.rs @@ -0,0 +1,155 @@ +// rustfmt-overflow_delimited_expr: true + +fn combine_blocklike() { + do_thing( + |param| { + action(); + foo(param) + }, + ); + + do_thing( + x, + |param| { + action(); + foo(param) + }, + ); + + do_thing( + x, + + // I'll be discussing the `action` with your para(m)legal counsel + |param| { + action(); + foo(param) + }, + ); + + do_thing( + Bar { + x: value, + y: value2, + }, + ); + + do_thing( + x, + Bar { + x: value, + y: value2, + }, + ); + + do_thing( + x, + + // Let me tell you about that one time at the `Bar` + Bar { + x: value, + y: value2, + }, + ); + + do_thing( + &[ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ], + ); + + do_thing( + x, + &[ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ], + ); + + do_thing( + x, + + // Just admit it; my list is longer than can be folded on to one line + &[ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ], + ); + + do_thing( + vec![ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ], + ); + + do_thing( + x, + vec![ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ], + ); + + do_thing( + x, + + // Just admit it; my list is longer than can be folded on to one line + vec![ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ], + ); + + do_thing( + x, + ( + 1, + 2, + 3, + |param| { + action(); + foo(param) + }, + ), + ); +} + +fn combine_struct_sample() { + let identity = verify( + &ctx, + VerifyLogin { + type_: LoginType::Username, + username: args.username.clone(), + password: Some(args.password.clone()), + domain: None, + }, + )?; +} + +fn combine_macro_sample() { + rocket::ignite() + .mount( + "/", + routes![ + http::auth::login, + http::auth::logout, + http::cors::options, + http::action::dance, + http::action::sleep, + ], + ) + .launch(); +} diff --git a/src/tools/rustfmt/tests/source/expr.rs b/src/tools/rustfmt/tests/source/expr.rs new file mode 100644 index 0000000000..21f8a4a436 --- /dev/null +++ b/src/tools/rustfmt/tests/source/expr.rs @@ -0,0 +1,579 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// Test expressions + +fn foo() -> bool { + let boxed: Box = box 5; + let referenced = &5 ; + + let very_long_variable_name = ( a + first + simple + test ); + let very_long_variable_name = (a + first + simple + test + AAAAAAAAAAAAA + BBBBBBBBBBBBBBBBB + b + c); + + let is_internalxxxx = self.source_map.span_to_filename(s) == self.source_map.span_to_filename(m.inner); + + let some_val = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * bbbb / (bbbbbb - + function_call(x, *very_long_pointer, y)) + + 1000 ; + +some_ridiculously_loooooooooooooooooooooong_function(10000 * 30000000000 + 40000 / 1002200000000 + - 50000 * sqrt(-1), + trivial_value); + (((((((((aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + a + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaa))))))))) ; + + { for _ in 0..10 {} } + + {{{{}}}} + + if 1 + 2 > 0 { let result = 5; result } else { 4}; + + if let Some(x) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { + // Nothing + } + + if let Some(x) = (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {} + + if let (some_very_large, + tuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuple) = 1 + + 2 + 3 { + } + + if let (some_very_large, + tuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuple) = 1111 + 2222 {} + + if let (some_very_large, tuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuple) = 1 + + 2 + 3 { + } + + if let ast::ItemKind::Trait(_, unsafety, ref generics, ref type_param_bounds, ref trait_items) = item.node + { + // nothing + } + + let test = if true { 5 } else { 3 }; + + if cond() { + something(); + } else if different_cond() { + something_else(); + } else { + // Check subformatting + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + } + + // #2884 + let _ = [0; {struct Foo; impl Foo {const fn get(&self) -> usize {5}}; Foo.get()}]; +} + +fn bar() { + let range = ( 111111111 + 333333333333333333 + 1111 + 400000000000000000) .. (2222 + 2333333333333333); + + let another_range = 5..some_func( a , b /* comment */); + + for _ in 1 ..{ call_forever(); } + + syntactically_correct(loop { sup( '?'); }, if cond { 0 } else { 1 }); + + let third = ..10; + let infi_range = .. ; + let foo = 1..; + let bar = 5 ; + let nonsense = (10 .. 0)..(0..10); + + loop{if true {break}} + + let x = (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa && + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + a); +} + +fn baz() { + unsafe /* {}{}{}{{{{}} */ { + let foo = 1u32; + } + + unsafe /* very looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong comment */ {} + + unsafe // So this is a very long comment. + // Multi-line, too. + // Will it still format correctly? + { + } + + unsafe { + // Regular unsafe block + } + + unsafe { + foo() + } + + unsafe { + foo(); + } + + // #2289 + let identifier_0 = unsafe { this_is_58_chars_long_and_line_is_93_chars_long_xxxxxxxxxx }; + let identifier_1 = unsafe { this_is_59_chars_long_and_line_is_94_chars_long_xxxxxxxxxxx }; + let identifier_2 = unsafe { this_is_65_chars_long_and_line_is_100_chars_long_xxxxxxxxxxxxxxxx }; + let identifier_3 = unsafe { this_is_66_chars_long_and_line_is_101_chars_long_xxxxxxxxxxxxxxxxx }; +} + +// Test some empty blocks. +fn qux() { + {} + // FIXME this one could be done better. + { /* a block with a comment */ } + { + + } + { + // A block with a comment. + } +} + +fn issue227() { + { + let handler = box DocumentProgressHandler::new(addr, DocumentProgressTask::DOMContentLoaded); + } +} + +fn issue184(source: &str) { + for c in source.chars() { + if index < 'a' { + continue; + } + } +} + +fn arrays() { + let x = [0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 7, + 8, + 9, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0]; + + let y = [/* comment */ 1, 2 /* post comment */, 3]; + + let xy = [ strukt { test123: value_one_two_three_four, turbo: coolio(), } , /* comment */ 1 ]; + + let a =WeightedChoice::new(&mut [Weighted { + weightweight: x, + item: 0, + }, + Weighted { + weightweight: 1, + item: 1, + }, + Weighted { + weightweight: x, + item: 2, + }, + Weighted { + weightweight: 1, + item: 3, + }]); + + let z = [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzz, q]; + + [ 1 + 3, 4 , 5, 6, 7, 7, fncall::>(3-1)] +} + +fn returns() { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa && return; + + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; +} + +fn addrof() { + & mut(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb); + & (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb); + + // raw reference operator + & raw const a; + & raw mut b; +} + +fn casts() { + fn unpack(packed: u32) -> [u16; 2] { + [ + (packed >> 16) as u16, + (packed >> 0) as u16, + ] + } + + let some_trait_xxx = xxxxxxxxxxx + xxxxxxxxxxxxx + as SomeTraitXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX; + let slightly_longer_trait = yyyyyyyyy + yyyyyyyyyyy as SomeTraitYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY; +} + +fn indices() { + let x = (aaaaaaaaaaaaaaaaaaaaaaaaaaaa+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb+cccccccccccccccc) [ x + y + z ]; + let y = (aaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + cccccccccccccccc)[ xxxxx + yyyyy + zzzzz ]; + let z = xxxxxxxxxx.x().y().zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz()[aaaaa]; + let z = xxxxxxxxxx.x().y().zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz()[aaaaa]; +} + +fn repeats() { + let x = [aaaaaaaaaaaaaaaaaaaaaaaaaaaa+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb+cccccccccccccccc; x + y + z ]; + let y = [aaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + cccccccccccccccc; xxxxx + yyyyy + zzzzz ]; +} + +fn blocks() { + if 1 + 1 == 2 { + println!("yay arithmetix!"); + }; +} + +fn issue767() { + if false { + if false { + } else { + // A let binding here seems necessary to trigger it. + let _ = (); + } + } else if let false = false { + } +} + +fn ranges() { + let x = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa .. bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; + let y = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ..= bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; + let z = ..= x ; + + // #1766 + let x = [0. ..10.0]; + let x = [0. ..=10.0]; + + a ..= b + + // the expr below won't compile because inclusive ranges need a defined end + // let a = 0 ..= ; +} + +fn if_else() { + let exact = diff / + (if size == 0 { + 1 +} else { + size +}); + + let cx = tp1.x + + any * radius * + if anticlockwise { + 1.0 + } else { + -1.0 + }; +} + +fn complex_if_else() { + if let Some(x) = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx { + } else if let Some(x) = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx { + ha(); + } else if xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxx { + yo(); + } else if let Some(x) = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx { + ha(); + } else if xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxx { + yo(); + } +} + +fn issue1106() { + { + if let hir::ItemEnum(ref enum_def, ref generics) = self.ast_map.expect_item(enum_node_id).node { + } + } + + for entry in + WalkDir::new(path) + .into_iter() + .filter_entry(|entry| exclusions.filter_entry(entry)) { + } +} + +fn issue1570() { + a_very_long_function_name({some_func(1, {1})}) +} + +fn issue1714() { + v = &mut {v}[mid..]; + let (left, right) = {v}.split_at_mut(mid); +} + +// Multi-lined index should be put on the next line if it fits in one line. +fn issue1749() { + { + { + { + if self.shape[(r as f32 + self.x_offset) as usize][(c as f32 + self.y_offset) as usize] != 0 { + // hello + } + } + } + } +} + +// #1172 +fn newlines_between_list_like_expr() { + foo( + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, + + zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz, + ); + + vec![ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, + + zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz, + ]; + + match x { + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | + + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy | + + zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz => foo(a, b, c), + _ => bar(), + }; +} + +fn issue2178() { + Ok(result.iter().map(|item| ls_util::rls_to_location(item)).collect()) +} + +// #2493 +impl Foo { +fn bar(&self) { + { + let x = match () { + () => { + let i; + i == self.install_config.storage.experimental_compressed_block_size as usize + } + }; + } +} +} + +fn dots() { + .. .. ..; // (.. (.. (..))) + ..= ..= ..; + (..) .. ..; // ((..) .. (..)) +} + +// #2676 +// A function call with a large single argument. +fn foo() { + let my_var = + Mutex::new(RpcClientType::connect(server_iddd).chain_err(|| "Unable to create RPC client")?); +} + +// #2704 +// Method call with prefix and suffix. +fn issue2704() { + // We should not combine the callee with a multi-lined method call. + let requires = requires.set(&requires0 + .concat(&requires1) + .concat(&requires2) + .distinct_total()); + let requires = requires.set(box requires0 + .concat(&requires1) + .concat(&requires2) + .distinct_total()); + let requires = requires.set(requires0 + .concat(&requires1) + .concat(&requires2) + .distinct_total() as u32); + let requires = requires.set(requires0 + .concat(&requires1) + .concat(&requires2) + .distinct_total()?); + let requires = requires.set(!requires0 + .concat(&requires1) + .concat(&requires2) + .distinct_total()); + // We should combine a small callee with an argument. + bar(vec![22] + .into_iter() + .map(|x| x * 2) + .filter(|_| true) + .collect()); + // But we should not combine a long callee with an argument. + barrrr(vec![22] + .into_iter() + .map(|x| x * 2) + .filter(|_| true) + .collect()); +} + +// #2782 +fn issue2782() { + {let f={let f={{match f{F(f,_)=>{{loop{let f={match f{F(f,_)=>{{match f{F(f,_)=>{{loop{let f={let f={match f{'-'=>F(f,()),}};};}}}}}}}};}}}}}};};} +} + +fn issue_2802() { + function_to_fill_this_line(some_arg, some_arg, some_arg) + * a_very_specific_length(specific_length_arg) * very_specific_length(Foo { + a: some_much_much_longer_value, + }) * some_value +} + +fn issue_3003() { + let mut path: PathBuf = [ + env!("CARGO_MANIFEST_DIR"), + "tests", + "support", + "dejavu-fonts-ttf-2.37", + "ttf", + ] + .iter() + .collect(); +} + +fn issue3226() { + { + { + { + return Err(ErrorKind::ManagementInterfaceError("Server exited unexpectedly").into()) + } + } + } + { + { + { + break Err(ErrorKind::ManagementInterfaceError("Server exited unexpectedlyy").into()) + } + } + } +} + +// #3457 +fn issue3457() { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + println!("Test"); + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// #3498 +static REPRO: &[usize] = &[#[cfg(feature = "zero")] + 0]; + +fn overflow_with_attr() { + foo(#[cfg(feature = "zero")] + 0); + foobar(#[cfg(feature = "zero")] + 0); + foobar(x, y, #[cfg(feature = "zero")] + {}); +} + + +// https://github.com/rust-lang/rustfmt/issues/3765 +fn foo() { + async { + // Do + // some + // work + } + .await; + + async { + // Do + // some + // work + } + .await; +} + +fn underscore() { + _= 1; + _; + [ _,a,_ ] = [1, 2, 3]; + (a, _) = (8, 9); + TupleStruct( _, a) = TupleStruct(2, 2); + + let _ : usize = foo(_, _); +} diff --git a/src/tools/rustfmt/tests/source/extern.rs b/src/tools/rustfmt/tests/source/extern.rs new file mode 100644 index 0000000000..d0a033b124 --- /dev/null +++ b/src/tools/rustfmt/tests/source/extern.rs @@ -0,0 +1,79 @@ +// rustfmt-normalize_comments: true + + extern crate foo ; + extern crate foo as bar ; + +extern crate futures; +extern crate dotenv; +extern crate chrono; + +extern crate foo; +extern crate bar; + +// #2315 +extern crate proc_macro2; +extern crate proc_macro; + +// #3128 +extern crate serde; // 1.0.78 +extern crate serde_derive; // 1.0.78 +extern crate serde_json; // 1.0.27 + + extern "C" { + fn c_func(x: *mut *mut libc::c_void); + + fn c_func(x: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX, y: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY); + + #[test123] + fn foo() -> uint64_t; + +pub fn bar() ; + } + +extern { + fn DMR_GetDevice(pHDev: *mut HDEV, searchMode: DeviceSearchMode, pSearchString: *const c_char, devNr: c_uint, wildcard: c_char) -> TDMR_ERROR; + + fn quux() -> (); // Post comment + + pub type + Foo; + + type Bar; +} + +extern "Rust" { static ext: u32; + // Some comment. + pub static mut var : SomeType ; } + +extern "C" { + fn syscall(number: libc::c_long /* comment 1 */, /* comm 2 */ ... /* sup? */) -> libc::c_long; + + fn foo (x: *const c_char , ... ) -> +libc::c_long; + } + + extern { + pub fn freopen(filename: *const c_char, mode: *const c_char + , mode2: *const c_char + , mode3: *const c_char, + file: *mut FILE) + -> *mut FILE; + + + const fn foo( + + ) -> + *mut Bar; + unsafe fn foo( + + ) -> * + mut + Bar; + + pub(super) const fn foo() -> *mut Bar; + pub(crate) unsafe fn foo() -> *mut Bar; + } + +extern { + +} diff --git a/src/tools/rustfmt/tests/source/extern_not_explicit.rs b/src/tools/rustfmt/tests/source/extern_not_explicit.rs new file mode 100644 index 0000000000..9d6c4c2a1c --- /dev/null +++ b/src/tools/rustfmt/tests/source/extern_not_explicit.rs @@ -0,0 +1,14 @@ +// rustfmt-force_explicit_abi: false + + extern "C" { + fn some_fn() -> (); + } + + extern "C" fn sup() { + + } + +type funky_func = extern "C" fn (unsafe extern "rust-call" fn(*const JSJitInfo, *mut JSContext, + HandleObject, *mut libc::c_void, u32, + *mut JSVal) + -> u8); diff --git a/src/tools/rustfmt/tests/source/file-lines-1.rs b/src/tools/rustfmt/tests/source/file-lines-1.rs new file mode 100644 index 0000000000..0164e30a85 --- /dev/null +++ b/src/tools/rustfmt/tests/source/file-lines-1.rs @@ -0,0 +1,29 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-1.rs","range":[4,8]}] + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // comment + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/src/tools/rustfmt/tests/source/file-lines-2.rs b/src/tools/rustfmt/tests/source/file-lines-2.rs new file mode 100644 index 0000000000..6f44ec6e69 --- /dev/null +++ b/src/tools/rustfmt/tests/source/file-lines-2.rs @@ -0,0 +1,29 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-2.rs","range":[10,15]}] + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // comment + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/src/tools/rustfmt/tests/source/file-lines-3.rs b/src/tools/rustfmt/tests/source/file-lines-3.rs new file mode 100644 index 0000000000..4b825b9f58 --- /dev/null +++ b/src/tools/rustfmt/tests/source/file-lines-3.rs @@ -0,0 +1,29 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-3.rs","range":[4,8]},{"file":"tests/source/file-lines-3.rs","range":[10,15]}] + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // comment + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/src/tools/rustfmt/tests/source/file-lines-4.rs b/src/tools/rustfmt/tests/source/file-lines-4.rs new file mode 100644 index 0000000000..83928bf6fe --- /dev/null +++ b/src/tools/rustfmt/tests/source/file-lines-4.rs @@ -0,0 +1,30 @@ +// rustfmt-file_lines: [] +// (Test that nothing is formatted if an empty array is specified.) + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + // aaaaaaaaaaaaa + { + match x { + PushParam => { + // comment + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/src/tools/rustfmt/tests/source/file-lines-5.rs b/src/tools/rustfmt/tests/source/file-lines-5.rs new file mode 100644 index 0000000000..8ec2c67bc4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/file-lines-5.rs @@ -0,0 +1,17 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-5.rs","range":[3,5]}] + +struct A { +t: i64, +} + +mod foo { + fn bar() { + // test + let i = 12; + // test + } + // aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + fn baz() { + let j = 15; + } +} diff --git a/src/tools/rustfmt/tests/source/file-lines-6.rs b/src/tools/rustfmt/tests/source/file-lines-6.rs new file mode 100644 index 0000000000..2eacc8a0e7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/file-lines-6.rs @@ -0,0 +1,18 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-6.rs","range":[9,10]}] + +struct A { + t: i64, +} + +mod foo { + fn bar() { + // test + let i = 12; + // test + } + + fn baz() { +/// + let j = 15; + } +} diff --git a/src/tools/rustfmt/tests/source/file-lines-7.rs b/src/tools/rustfmt/tests/source/file-lines-7.rs new file mode 100644 index 0000000000..b227ac35dc --- /dev/null +++ b/src/tools/rustfmt/tests/source/file-lines-7.rs @@ -0,0 +1,24 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-7.rs","range":[8,15]}] + +struct A { + t: i64, +} + +mod foo { + fn bar() { + + + + // test + let i = 12; + // test + } + + fn baz() { + + + + /// + let j = 15; + } +} diff --git a/src/tools/rustfmt/tests/source/file-lines-item.rs b/src/tools/rustfmt/tests/source/file-lines-item.rs new file mode 100644 index 0000000000..fe52a7fa17 --- /dev/null +++ b/src/tools/rustfmt/tests/source/file-lines-item.rs @@ -0,0 +1,21 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-item.rs","range":[6,8]}] + +use foo::{c, b, a}; +use bar; + +fn foo() { + bar ( ) ; +} + +impl Drop for Context { + fn drop(&mut self) { + } +} + +impl Bar for Baz { + fn foo() { + bar( + baz, // Who knows? + ) + } +} diff --git a/src/tools/rustfmt/tests/source/fn-custom-2.rs b/src/tools/rustfmt/tests/source/fn-custom-2.rs new file mode 100644 index 0000000000..a3697c36d0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-custom-2.rs @@ -0,0 +1,35 @@ +// Test different indents. + +fn foo(a: Aaaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbbbb, c: Ccccccccccccccccc, d: Ddddddddddddddddddddddddd, e: Eeeeeeeeeeeeeeeeeee) { + foo(); +} + +fn bar<'a: 'bbbbbbbbbbbbbbbbbbbbbbbbbbb, TTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUU: WWWWWWWWWWWWWWWWWWWWWWWW>(a: Aaaaaaaaaaaaaaa) { + bar(); +} + +fn baz() where X: TTTTTTTT { + baz(); +} + +fn qux() where X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT { + baz(); +} + +impl Foo { + fn foo(self, a: Aaaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbbbb, c: Ccccccccccccccccc, d: Ddddddddddddddddddddddddd, e: Eeeeeeeeeeeeeeeeeee) { + foo(); + } + + fn bar<'a: 'bbbbbbbbbbbbbbbbbbbbbbbbbbb, TTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUU: WWWWWWWWWWWWWWWWWWWWWWWW>(a: Aaaaaaaaaaaaaaa) { + bar(); + } + + fn baz() where X: TTTTTTTT { + baz(); + } +} + +struct Foo { + foo: Foo, +} diff --git a/src/tools/rustfmt/tests/source/fn-custom-3.rs b/src/tools/rustfmt/tests/source/fn-custom-3.rs new file mode 100644 index 0000000000..a5e0f9af26 --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-custom-3.rs @@ -0,0 +1,31 @@ +// Test different indents. + +fn foo(a: Aaaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbbbb, c: Ccccccccccccccccc, d: Ddddddddddddddddddddddddd, e: Eeeeeeeeeeeeeeeeeee) { + foo(); +} + +fn bar<'a: 'bbbbbbbbbbbbbbbbbbbbbbbbbbb, TTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUU: WWWWWWWWWWWWWWWWWWWWWWWW>(a: Aaaaaaaaaaaaaaa) { + bar(); +} + +fn qux() where X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT { + baz(); +} + +fn qux() where X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT { + baz(); +} + +impl Foo { + fn foo(self, a: Aaaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbbbb, c: Ccccccccccccccccc, d: Ddddddddddddddddddddddddd, e: Eeeeeeeeeeeeeeeeeee) { + foo(); + } + + fn bar<'a: 'bbbbbbbbbbbbbbbbbbbbbbbbbbb, TTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUU: WWWWWWWWWWWWWWWWWWWWWWWW>(a: Aaaaaaaaaaaaaaa) { + bar(); + } +} + +struct Foo { + foo: Foo, +} diff --git a/src/tools/rustfmt/tests/source/fn-custom-4.rs b/src/tools/rustfmt/tests/source/fn-custom-4.rs new file mode 100644 index 0000000000..6e18b6f9fe --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-custom-4.rs @@ -0,0 +1,13 @@ +// Test different indents. + +fn qux() where X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT { + baz(); +} + +fn qux() where X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT { + baz(); +} + +fn qux(a: Aaaaaaaaaaaaaaaaa) where X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT { + baz(); +} diff --git a/src/tools/rustfmt/tests/source/fn-custom-6.rs b/src/tools/rustfmt/tests/source/fn-custom-6.rs new file mode 100644 index 0000000000..807084575f --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-custom-6.rs @@ -0,0 +1,40 @@ +// rustfmt-brace_style: PreferSameLine +// Test different indents. + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) { + foo(); +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) -> String { + foo(); +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) -> String { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) where T: UUUUUUUUUUU { + foo(); +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) where T: UUUUUUUUUUU { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) -> String where T: UUUUUUUUUUU { + foo(); +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) -> String where T: UUUUUUUUUUU { + bar(); +} + +trait Test { + fn foo(a: u8) {} + + fn bar(a: u8) -> String {} +} diff --git a/src/tools/rustfmt/tests/source/fn-custom-7.rs b/src/tools/rustfmt/tests/source/fn-custom-7.rs new file mode 100644 index 0000000000..d5330196bf --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-custom-7.rs @@ -0,0 +1,24 @@ +// rustfmt-normalize_comments: true +// rustfmt-fn_args_layout: Vertical +// rustfmt-brace_style: AlwaysNextLine + +// Case with only one variable. +fn foo(a: u8) -> u8 { + bar() +} + +// Case with 2 variables and some pre-comments. +fn foo(a: u8 /* Comment 1 */, b: u8 /* Comment 2 */) -> u8 { + bar() +} + +// Case with 2 variables and some post-comments. +fn foo(/* Comment 1 */ a: u8, /* Comment 2 */ b: u8) -> u8 { + bar() +} + +trait Test { + fn foo(a: u8) {} + + fn bar(a: u8) -> String {} +} diff --git a/src/tools/rustfmt/tests/source/fn-custom-8.rs b/src/tools/rustfmt/tests/source/fn-custom-8.rs new file mode 100644 index 0000000000..0dd64868b2 --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-custom-8.rs @@ -0,0 +1,48 @@ +// rustfmt-brace_style: PreferSameLine +// Test different indents. + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) { + foo(); +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) -> String { + foo(); +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) -> String { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) where T: UUUUUUUUUUU { + foo(); +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) where T: UUUUUUUUUUU { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) -> String where T: UUUUUUUUUUU { + foo(); +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) -> String where T: UUUUUUUUUUU { + bar(); +} + +trait Test { + fn foo( + a: u8) { + + } + + fn bar(a: u8) + -> String { + + } + + fn bar(a: u8) -> String where Foo: foooo, Bar: barrr {} +} diff --git a/src/tools/rustfmt/tests/source/fn-custom.rs b/src/tools/rustfmt/tests/source/fn-custom.rs new file mode 100644 index 0000000000..77ced4c5e0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-custom.rs @@ -0,0 +1,13 @@ +// rustfmt-fn_args_layout: Compressed +// Test some of the ways function signatures can be customised. + +// Test compressed layout of args. +fn foo(a: Aaaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbbbb, c: Ccccccccccccccccc, d: Ddddddddddddddddddddddddd, e: Eeeeeeeeeeeeeeeeeee) { + foo(); +} + +impl Foo { + fn foo(self, a: Aaaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbbbb, c: Ccccccccccccccccc, d: Ddddddddddddddddddddddddd, e: Eeeeeeeeeeeeeeeeeee) { + foo(); + } +} diff --git a/src/tools/rustfmt/tests/source/fn-param-attributes.rs b/src/tools/rustfmt/tests/source/fn-param-attributes.rs new file mode 100644 index 0000000000..3407a3b2ef --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-param-attributes.rs @@ -0,0 +1,57 @@ +// https://github.com/rust-lang/rustfmt/issues/3623 + +fn foo(#[cfg(something)] x: i32, y: i32) -> i32 { + x + y +} + +fn foo_b(#[cfg(something)]x: i32, y: i32) -> i32 { + x + y +} + +fn add(#[cfg(something)]#[deny(C)] x: i32, y: i32) -> i32 { + x + y +} + +struct NamedSelfRefStruct {} +impl NamedSelfRefStruct { + fn foo( +#[cfg(something)] self: &Self, + ) {} +} + +struct MutStruct {} +impl MutStruct { + fn foo( + #[cfg(foo)]&mut self,#[deny(C)] b: i32, + ) {} +} + +fn main() { + let c = | + #[allow(C)]a: u32, + #[cfg(something)] b: i32, + #[cfg_attr(something, cfg(nothing))]#[deny(C)] c: i32, + | {}; + let _ = c(1, 2); +} + +pub fn bar( + /// bar +#[test] a: u32, + /// Bar + #[must_use] +/// Baz + #[no_mangle] b: i32, +) {} + + +fn abc( + #[foo] + #[bar] param: u32, +) { + // ... +} + +fn really_really_really_loooooooooooooooooooong(#[cfg(some_even_longer_config_feature_that_keeps_going_and_going_and_going_forever_and_ever_and_ever_on_and_on)] b: i32) { + // ... +} diff --git a/src/tools/rustfmt/tests/source/fn-simple.rs b/src/tools/rustfmt/tests/source/fn-simple.rs new file mode 100644 index 0000000000..528b9a0292 --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-simple.rs @@ -0,0 +1,74 @@ +// rustfmt-normalize_comments: true + +fn simple(/*pre-comment on a function!?*/ i: i32/*yes, it's possible! */ + ,response: NoWay /* hose */) { +fn op(x: Typ, key : &[u8], upd : Box) -> (memcache::Status, Result>)>) -> MapResult {} + + "cool"} + + +fn weird_comment(/* /*/ double level */ comment */ x: Hello /*/*/* triple, even */*/*/, +// Does this work? +y: World +) { + simple(/* does this preserve comments now? */ 42, NoWay) +} + +fn generic(arg: T) -> &SomeType + where T: Fn(// First arg + A, + // Second argument + B, C, D, /* pre comment */ E /* last comment */) -> &SomeType { + arg(a, b, c, d, e) +} + +fn foo() -> ! {} + +pub fn http_fetch_async(listener:Box< AsyncCORSResponseListener+Send >, script_chan: Box) { +} + +fn some_func>(val:T){} + +fn zzzzzzzzzzzzzzzzzzzz + (selff: Type, mut handle: node::Handle>, Type, NodeType>) + -> SearchStack<'a, K, V, Type, NodeType>{ +} + +unsafe fn generic_call(cx: *mut JSContext, argc: libc::c_uint, vp: *mut JSVal, + is_lenient: bool, + call: unsafe extern fn(*const JSJitInfo, *mut JSContext, + HandleObject, *mut libc::c_void, u32, + *mut JSVal) + -> u8) { + let f: fn ( _ , _ ) -> _ = panic!() ; +} + +pub fn start_export_thread(database: &Database, crypto_scheme: &C, block_size: usize, source_path: &Path) -> BonzoResult> {} + +pub fn waltz(cwd: &Path) -> CliAssert { + { + { + formatted_comment = rewrite_comment(comment, block_style, width, offset, formatting_fig); + } + } +} + +// #2003 +mod foo { + fn __bindgen_test_layout_i_open0_c_open1_char_a_open2_char_close2_close1_close0_instantiation() { + foo(); + } +} + +// #2082 +pub(crate) fn init() {} + +crate fn init() {} + +// #2630 +fn make_map String)>(records: &Vec, key_fn: F) -> HashMap {} + +// #2956 +fn bar(beans: Asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf, spam: bool, eggs: bool) -> bool{ + unimplemented!(); +} diff --git a/src/tools/rustfmt/tests/source/fn-single-line/version_one.rs b/src/tools/rustfmt/tests/source/fn-single-line/version_one.rs new file mode 100644 index 0000000000..469ab62156 --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-single-line/version_one.rs @@ -0,0 +1,80 @@ +// rustfmt-fn_single_line: true +// rustfmt-version: One +// Test single-line functions. + +fn foo_expr() { + 1 +} + +fn foo_stmt() { + foo(); +} + +fn foo_decl_local() { + let z = 5; + } + +fn foo_decl_item(x: &mut i32) { + x = 3; +} + + fn empty() { + +} + +fn foo_return() -> String { + "yay" +} + +fn foo_where() -> T where T: Sync { + let x = 2; +} + +fn fooblock() { + { + "inner-block" + } +} + +fn fooblock2(x: i32) { + let z = match x { + _ => 2, + }; +} + +fn comment() { + // this is a test comment + 1 +} + +fn comment2() { + // multi-line comment + let z = 2; + 1 +} + +fn only_comment() { + // Keep this here +} + +fn aaaaaaaaaaaaaaaaa_looooooooooooooooooooooong_name() { + let z = "aaaaaaawwwwwwwwwwwwwwwwwwwwwwwwwwww"; +} + +fn lots_of_space () { + 1 +} + +fn mac() -> Vec { vec![] } + +trait CoolTypes { + fn dummy(&self) { + } +} + +trait CoolerTypes { fn dummy(&self) { +} +} + +fn Foo() where T: Bar { +} diff --git a/src/tools/rustfmt/tests/source/fn-single-line/version_two.rs b/src/tools/rustfmt/tests/source/fn-single-line/version_two.rs new file mode 100644 index 0000000000..bf381ff106 --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn-single-line/version_two.rs @@ -0,0 +1,80 @@ +// rustfmt-fn_single_line: true +// rustfmt-version: Two +// Test single-line functions. + +fn foo_expr() { + 1 +} + +fn foo_stmt() { + foo(); +} + +fn foo_decl_local() { + let z = 5; + } + +fn foo_decl_item(x: &mut i32) { + x = 3; +} + + fn empty() { + +} + +fn foo_return() -> String { + "yay" +} + +fn foo_where() -> T where T: Sync { + let x = 2; +} + +fn fooblock() { + { + "inner-block" + } +} + +fn fooblock2(x: i32) { + let z = match x { + _ => 2, + }; +} + +fn comment() { + // this is a test comment + 1 +} + +fn comment2() { + // multi-line comment + let z = 2; + 1 +} + +fn only_comment() { + // Keep this here +} + +fn aaaaaaaaaaaaaaaaa_looooooooooooooooooooooong_name() { + let z = "aaaaaaawwwwwwwwwwwwwwwwwwwwwwwwwwww"; +} + +fn lots_of_space () { + 1 +} + +fn mac() -> Vec { vec![] } + +trait CoolTypes { + fn dummy(&self) { + } +} + +trait CoolerTypes { fn dummy(&self) { +} +} + +fn Foo() where T: Bar { +} diff --git a/src/tools/rustfmt/tests/source/fn_args_indent-block.rs b/src/tools/rustfmt/tests/source/fn_args_indent-block.rs new file mode 100644 index 0000000000..955f390cca --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn_args_indent-block.rs @@ -0,0 +1,77 @@ +// rustfmt-normalize_comments: true + +fn foo() { + foo(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) { + foo(); +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) -> String { + foo(); +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) -> String { + bar(); +} + +fn foo(a: u8 /* Comment 1 */, b: u8 /* Comment 2 */) -> u8 { + bar() +} + +fn foo(a: u8 /* Comment 1 */, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee /* Comment 2 */) -> u8 { + bar() +} + +fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) -> String where X: Fooooo, Y: Baaar { + bar(); +} + +fn foo() -> T { + foo(); +} + +fn foo() -> T where X: Foooo, Y: Baaar { + foo(); +} + +fn foo() where X: Foooo { +} + +fn foo() where X: Foooo, Y: Baaar { +} + +fn foo() -> (Loooooooooooooooooooooong, Reeeeeeeeeeeeeeeeeeeeeeeeturn, iiiiiiiiis, Looooooooooooooooong) { + foo(); +} + +fn foo() { + foo(); +} + +fn foo() { + foo(); +} + +fn foo() { + foo(); +} + +trait Test { + fn foo(a: u8) {} + + fn bar(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd, e: Eeeeeeeeeeeeeee) -> String {} +} + +fn foo(a: Aaaaaaaaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbbbbb, c: Cccccccccccccccccc, d: Dddddddddddddddd) { + foo(); +} + +fn foo() -> (Looooooooooooooooooooooooooong, Reeeeeeeeeeeeeeeeeeeeeeeeeeeeeturn, iiiiiiiiiiiiiis, Loooooooooooooooooooooong) { + foo(); +} diff --git a/src/tools/rustfmt/tests/source/fn_args_layout-vertical.rs b/src/tools/rustfmt/tests/source/fn_args_layout-vertical.rs new file mode 100644 index 0000000000..759bc83d01 --- /dev/null +++ b/src/tools/rustfmt/tests/source/fn_args_layout-vertical.rs @@ -0,0 +1,33 @@ +// rustfmt-fn_args_layout: Vertical + +// Empty list should stay on one line. +fn do_bar( + +) -> u8 { + bar() +} + +// A single argument should stay on the same line. +fn do_bar( + a: u8) -> u8 { + bar() +} + +// Multiple arguments should each get their own line. +fn do_bar(a: u8, mut b: u8, c: &u8, d: &mut u8, closure: &Fn(i32) -> i32) -> i32 { + // This feature should not affect closures. + let bar = |x: i32, y: i32| -> i32 { x + y }; + bar(a, b) +} + +// If the first argument doesn't fit on the same line with the function name, +// the whole list should probably be pushed to the next line with hanging +// indent. That's not what happens though, so check current behaviour instead. +// In any case, it should maintain single argument per line. +fn do_this_that_and_the_other_thing( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: u8, + b: u8, c: u8, d: u8) { + this(); + that(); + the_other_thing(); +} diff --git a/src/tools/rustfmt/tests/source/hard-tabs.rs b/src/tools/rustfmt/tests/source/hard-tabs.rs new file mode 100644 index 0000000000..e4a0f41700 --- /dev/null +++ b/src/tools/rustfmt/tests/source/hard-tabs.rs @@ -0,0 +1,84 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-hard_tabs: true + +fn main() { +let x = Bar; + +let y = Foo {a: x }; + +Foo { a: foo() /* comment*/, /* comment*/ b: bar(), ..something }; + +fn foo(a: i32, a: i32, a: i32, a: i32, a: i32, a: i32, a: i32, a: i32, a: i32, a: i32, a: i32) {} + +let str = "AAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAa"; + +if let (some_very_large, tuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuple) = 1 ++ 2 + 3 { +} + + if cond() { + something(); + } else if different_cond() { + something_else(); + } else { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + } + +unsafe /* very looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong comment */ {} + +unsafe // So this is a very long comment. + // Multi-line, too. + // Will it still format correctly? +{ +} + +let chain = funktion_kall().go_to_next_line_with_tab().go_to_next_line_with_tab().go_to_next_line_with_tab(); + +let z = [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzz, q]; + +fn generic(arg: T) -> &SomeType + where T: Fn(// First arg + A, + // Second argument + B, C, D, /* pre comment */ E /* last comment */) -> &SomeType { + arg(a, b, c, d, e) +} + + loong_func().quux(move || { + if true { + 1 + } else { + 2 + } + }); + + fffffffffffffffffffffffffffffffffff(a, + { + SCRIPT_TASK_ROOT + .with(|root| { + *root.borrow_mut() = Some(&script_task); + }); + }); + a.b + .c + .d(); + + x().y(|| { + match cond() { + true => (), + false => (), + } + }); +} + +// #2296 +impl Foo { + // a comment + // on multiple lines + fn foo() { + // another comment + // on multiple lines + let x = true; + } +} diff --git a/src/tools/rustfmt/tests/source/hello.rs b/src/tools/rustfmt/tests/source/hello.rs new file mode 100644 index 0000000000..f892e6debb --- /dev/null +++ b/src/tools/rustfmt/tests/source/hello.rs @@ -0,0 +1,6 @@ +// rustfmt-config: small_tabs.toml +// rustfmt-target: hello.rs + +// Smoke test - hello world. + +fn main() { println!("Hello world!"); } diff --git a/src/tools/rustfmt/tests/source/hello2.rs b/src/tools/rustfmt/tests/source/hello2.rs new file mode 100644 index 0000000000..48af7de388 --- /dev/null +++ b/src/tools/rustfmt/tests/source/hello2.rs @@ -0,0 +1,8 @@ +// rustfmt-config: small_tabs.toml +// rustfmt-target: hello.rs + +// Smoke test - hello world. + +fn main( ) { +println!("Hello world!"); +} diff --git a/src/tools/rustfmt/tests/source/if_while_or_patterns.rs b/src/tools/rustfmt/tests/source/if_while_or_patterns.rs new file mode 100644 index 0000000000..f01df7e915 --- /dev/null +++ b/src/tools/rustfmt/tests/source/if_while_or_patterns.rs @@ -0,0 +1,27 @@ +#![feature(if_while_or_patterns)] + +fn main() { + if let 0 | 1 = 0 { + println!("hello, world"); + }; + + if let aaaaaaaaaaaaaaaaaaaaaaaaaa | bbbbbbbbbbbbbbbbbbbbbbbbbbb | cccccccccccccccc | d_100 = 0 { + println!("hello, world"); + } + + if let aaaaaaaaaaaaaaaaaaaaaaaaaa | bbbbbbbbbbbbbbbbbbbbbbb | ccccccccccccccccccccc | d_101 = 0 { + println!("hello, world"); + } + + if let aaaaaaaaaaaaaaaaaaaaaaaaaaaa | bbbbbbbbbbbbbbbbbbbbbbb | ccccccccccccccccccccc | d_103 = 0 { + println!("hello, world"); + } + + if let aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | bbbbbbbbbbbbbbbbbbbbbbb | ccccccccccccccccccccc | d_105 = 0 { + println!("hello, world"); + } + + while let xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx = foo_bar(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, cccccccccccccccccccccccccccccccccccccccc) { + println!("hello, world"); + } +} diff --git a/src/tools/rustfmt/tests/source/immovable_generators.rs b/src/tools/rustfmt/tests/source/immovable_generators.rs new file mode 100644 index 0000000000..c57a1e1448 --- /dev/null +++ b/src/tools/rustfmt/tests/source/immovable_generators.rs @@ -0,0 +1,7 @@ +#![feature(generators)] + +unsafe fn foo() { + let mut ga = static || { + yield 1; + }; +} diff --git a/src/tools/rustfmt/tests/source/impls.rs b/src/tools/rustfmt/tests/source/impls.rs new file mode 100644 index 0000000000..fb8701989f --- /dev/null +++ b/src/tools/rustfmt/tests/source/impls.rs @@ -0,0 +1,170 @@ +// rustfmt-normalize_comments: true +impl Foo for Bar { fn foo() { "hi" } } + +pub impl Foo for Bar { + // Associated Constants + const Baz: i32 = 16; + // Associated Types + type FooBar = usize; + // Comment 1 + fn foo() { "hi" } + // Comment 2 + fn foo() { "hi" } + // Comment 3 +} + +pub unsafe impl<'a, 'b, X, Y: Foo> !Foo<'a, X> for Bar<'b, Y> where X: Foo<'a, Z> { + fn foo() { "hi" } +} + +impl<'a, 'b, X, Y: Foo> Foo<'a, X> for Bar<'b, Y> where X: Fooooooooooooooooooooooooooooo<'a, Z> +{ + fn foo() { "hi" } +} + +impl<'a, 'b, X, Y: Foo> Foo<'a, X> for Bar<'b, Y> where X: Foooooooooooooooooooooooooooo<'a, Z> +{ + fn foo() { "hi" } +} + +impl Foo for Bar where T: Baz +{ +} + +impl Foo for Bar where T: Baz { /* Comment */ } + +impl Foo { + fn foo() {} +} + +impl Boo { + + // BOO + fn boo() {} + // FOO + + + +} + +mod a { + impl Foo { + // Hello! + fn foo() {} + } +} + + +mod b { + mod a { + impl Foo { + fn foo() {} + } + } +} + +impl Foo { add_fun!(); } + +impl Blah { + fn boop() {} + add_fun!(); +} + +impl X { fn do_parse( mut self : X ) {} } + +impl Y5000 { + fn bar(self: X< 'a , 'b >, y: Y) {} + + fn bad(&self, ( x, y): CoorT) {} + + fn turbo_bad(self: X< 'a , 'b > , ( x, y): CoorT) { + + } +} + +pub impl Foo for Bar where T: Foo +{ + fn foo() { "hi" } +} + +pub impl Foo for Bar where T: Foo, Z: Baz {} + +mod m { + impl PartialEq for S where T: PartialEq { + fn eq(&self, other: &Self) { + true + } + } + + impl PartialEq for S where T: PartialEq { } + } + +impl Handle, HandleType> { +} + +impl PartialEq for Handle, HandleType> { +} + +mod x { + impl Foo + where A: 'static, + B: 'static, + C: 'static, + D: 'static { } +} + +impl Issue1249 { + // Creates a new flow constructor. + fn foo() {} +} + +// #1600 +impl<#[may_dangle] K, #[may_dangle] V> Drop for RawTable { + fn drop() {} +} + +// #1168 +pub trait Number: Copy + Eq + Not + Shl + + Shr + + BitAnd + BitOr + BitAndAssign + BitOrAssign + + + +{ + // test + fn zero() -> Self; +} + +// #1642 +pub trait SomeTrait : Clone + Eq + PartialEq + Ord + PartialOrd + Default + Hash + Debug + Display + Write + Read + FromStr { + // comment +} + +// #1995 +impl Foo { + fn f( + S { + aaaaaaaaaa: aaaaaaaaaa, + bbbbbbbbbb: bbbbbbbbbb, + cccccccccc: cccccccccc, + }: S + ) -> u32{ + 1 + } +} + +// #2491 +impl<'a, 'b, 'c> SomeThing for (&'a mut SomethingLong, &'b mut SomethingLong, &'c mut SomethingLong) { + fn foo() {} +} + +// #2746 +impl<'seq1, 'seq2, 'body, 'scope, Channel> Adc12< Dual, MasterRunningDma<'seq1, 'body, 'scope, Channel>, SlaveRunningDma<'seq2, 'body, 'scope>, > where Channel: DmaChannel, {} + +// #4084 +impl const std::default::Default for Struct { + #[inline] + fn default() -> Self { + Self { f: 12.5 } + } +} diff --git a/src/tools/rustfmt/tests/source/imports-impl-only-use.rs b/src/tools/rustfmt/tests/source/imports-impl-only-use.rs new file mode 100644 index 0000000000..d290d8d918 --- /dev/null +++ b/src/tools/rustfmt/tests/source/imports-impl-only-use.rs @@ -0,0 +1,4 @@ +#![feature(underscore_imports)] + +use attr; +use std::iter::Iterator as _; diff --git a/src/tools/rustfmt/tests/source/imports-reorder-lines-and-items.rs b/src/tools/rustfmt/tests/source/imports-reorder-lines-and-items.rs new file mode 100644 index 0000000000..b6380f31c6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/imports-reorder-lines-and-items.rs @@ -0,0 +1,7 @@ +/// This comment should stay with `use std::str;` +use std::str; +use std::cmp::{d, c, b, a}; +use std::ddd::aaa; +use std::ddd::{d as p, c as g, b, a}; +// This comment should stay with `use std::ddd:bbb;` +use std::ddd::bbb; diff --git a/src/tools/rustfmt/tests/source/imports-reorder-lines.rs b/src/tools/rustfmt/tests/source/imports-reorder-lines.rs new file mode 100644 index 0000000000..2b018544ea --- /dev/null +++ b/src/tools/rustfmt/tests/source/imports-reorder-lines.rs @@ -0,0 +1,32 @@ +use std::str; +use std::cmp::{d, c, b, a}; +use std::cmp::{b, e, g, f}; +use std::ddd::aaa; +// This comment should stay with `use std::ddd;` +use std::ddd; +use std::ddd::bbb; + +mod test { +} + +use aaa::bbb; +use aaa; +use aaa::*; + +mod test {} +// If item names are equal, order by rename + +use test::{a as bb, b}; +use test::{a as aa, c}; + +mod test {} +// If item names are equal, order by rename - no rename comes before a rename + +use test::{a as bb, b}; +use test::{a, c}; + +mod test {} +// `self` always comes first + +use test::{a as aa, c}; +use test::{self as bb, b}; diff --git a/src/tools/rustfmt/tests/source/imports-reorder.rs b/src/tools/rustfmt/tests/source/imports-reorder.rs new file mode 100644 index 0000000000..cbe9d6ca78 --- /dev/null +++ b/src/tools/rustfmt/tests/source/imports-reorder.rs @@ -0,0 +1,5 @@ +// rustfmt-normalize_comments: true + +use path::{C,/*A*/ A, B /* B */, self /* self */}; + +use {ab, ac, aa, Z, b}; diff --git a/src/tools/rustfmt/tests/source/imports.rs b/src/tools/rustfmt/tests/source/imports.rs new file mode 100644 index 0000000000..4dfc6ed94e --- /dev/null +++ b/src/tools/rustfmt/tests/source/imports.rs @@ -0,0 +1,107 @@ +// rustfmt-normalize_comments: true + +// Imports. + +// Long import. +use rustc_ast::ast::{ItemForeignMod, ItemImpl, ItemMac, ItemMod, ItemStatic, ItemDefaultImpl}; +use exceedingly::looooooooooooooooooooooooooooooooooooooooooooooooooooooooooong::import::path::{ItemA, ItemB}; +use exceedingly::loooooooooooooooooooooooooooooooooooooooooooooooooooooooong::import::path::{ItemA, ItemB}; + +use list::{ + // Some item + SomeItem /* Comment */, /* Another item */ AnotherItem /* Another Comment */, // Last Item + LastItem +}; + +use test::{ Other /* C */ , /* A */ self /* B */ }; + +use rustc_ast::{self}; +use {/* Pre-comment! */ + Foo, Bar /* comment */}; +use Foo::{Bar, Baz}; +pub use rustc_ast::ast::{Expr_, Expr, ExprAssign, ExprCall, ExprMethodCall, ExprPath}; + +use rustc_ast::some::{}; + +use self; +use std::io::{self}; +use std::io::self; + +mod Foo { + pub use rustc_ast::ast::{ + ItemForeignMod, + ItemImpl, + ItemMac, + ItemMod, + ItemStatic, + ItemDefaultImpl + }; + + mod Foo2 { + pub use rustc_ast::ast::{ItemForeignMod, ItemImpl, ItemMac, ItemMod, ItemStatic, self, ItemDefaultImpl}; + } +} + +fn test() { +use Baz::*; + use Qux; +} + +// Simple imports +use foo::bar::baz as baz ; +use bar::quux as kaas; +use foo; + +// With aliases. +use foo::{self as bar, baz}; +use foo::{self as bar}; +use foo::{qux as bar}; +use foo::{baz, qux as bar}; + +// With absolute paths +use ::foo; +use ::foo::{Bar}; +use ::foo::{Bar, Baz}; +use ::{Foo}; +use ::{Bar, Baz}; + +// Root globs +use *; +use ::*; + +// spaces used to cause glob imports to disappear (#1356) +use super:: * ; +use foo::issue_1356:: * ; + +// We shouldn't remove imports which have attributes attached (#1858) +#[cfg(unix)] +use self::unix::{}; + +// nested imports +use foo::{a, bar::{baz, qux, xxxxxxxxxxx, yyyyyyyyyyyyy, zzzzzzzzzzzzzzzz, foo::{a, b, cxxxxxxxxxxxxx, yyyyyyyyyyyyyy, zzzzzzzzzzzzzzzz}}, b, boo, c,}; + +use fooo::{baar::{foobar::{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz}}, z, bar, bar::*, x, y}; + +use exonum::{api::{Api, ApiError}, blockchain::{self, BlockProof, Blockchain, Transaction, TransactionSet}, crypto::{Hash, PublicKey}, helpers::Height, node::TransactionSend, storage::{ListProof, MapProof}}; + +// nested imports with a single sub-tree. +use a::{b::{c::*}}; +use a::{b::{c::{}}}; +use a::{b::{c::d}}; +use a::{b::{c::{xxx, yyy, zzz}}}; + +// #2645 +/// This line is not affected. +// This line is deleted. +use c; + +// #2670 +#[macro_use] +use imports_with_attr; + +// #2888 +use std::f64::consts::{SQRT_2, E, PI}; + +// #3273 +#[rustfmt::skip] +use std::fmt::{self, {Display, Formatter}}; diff --git a/src/tools/rustfmt/tests/source/imports_block_indent.rs b/src/tools/rustfmt/tests/source/imports_block_indent.rs new file mode 100644 index 0000000000..016deefe58 --- /dev/null +++ b/src/tools/rustfmt/tests/source/imports_block_indent.rs @@ -0,0 +1,2 @@ +// #2569 +use apns2::request::notification::{Notificatio, NotificationBuilder, Priority, SilentNotificationBuilder}; diff --git a/src/tools/rustfmt/tests/source/imports_granularity_crate.rs b/src/tools/rustfmt/tests/source/imports_granularity_crate.rs new file mode 100644 index 0000000000..d16681b01b --- /dev/null +++ b/src/tools/rustfmt/tests/source/imports_granularity_crate.rs @@ -0,0 +1,37 @@ +// rustfmt-imports_granularity: Crate + +use a::{c,d,b}; +use a::{d, e, b, a, f}; +use a::{f, g, c}; + +#[doc(hidden)] +use a::b; +use a::c; +use a::d; + +use a::{c, d, e}; +#[doc(hidden)] +use a::b; +use a::d; + +pub use foo::bar; +use foo::{a, b, c}; +pub use foo::foobar; + +use a::{b::{c::*}}; +use a::{b::{c::{}}}; +use a::{b::{c::d}}; +use a::{b::{c::{xxx, yyy, zzz}}}; + +// https://github.com/rust-lang/rustfmt/issues/3808 +use d::{self}; +use e::{self as foo}; +use f::{self, b}; +use g::a; +use g::{self, b}; +use h::{a}; +use i::a::{self}; +use j::{a::{self}}; + +use {k::{a, b}, l::{a, b}}; +use {k::{c, d}, l::{c, d}}; diff --git a/src/tools/rustfmt/tests/source/imports_granularity_item.rs b/src/tools/rustfmt/tests/source/imports_granularity_item.rs new file mode 100644 index 0000000000..d0e94df66a --- /dev/null +++ b/src/tools/rustfmt/tests/source/imports_granularity_item.rs @@ -0,0 +1,6 @@ +// rustfmt-imports_granularity: Item + +use a::{b, c, d}; +use a::{f::g, h::{i, j}}; +use a::{l::{self, m, n::o, p::*}}; +use a::q::{self}; diff --git a/src/tools/rustfmt/tests/source/imports_granularity_module.rs b/src/tools/rustfmt/tests/source/imports_granularity_module.rs new file mode 100644 index 0000000000..5a4fad5872 --- /dev/null +++ b/src/tools/rustfmt/tests/source/imports_granularity_module.rs @@ -0,0 +1,18 @@ +// rustfmt-imports_granularity: Module + +use a::{b::c, d::e}; +use a::{f, g::{h, i}}; +use a::{j::{self, k::{self, l}, m}, n::{o::p, q}}; +pub use a::{r::s, t}; + +#[cfg(test)] +use foo::{a::b, c::d}; +use foo::e; + +use bar::{ + // comment + a::b, + // more comment + c::d, + e::f, +}; diff --git a/src/tools/rustfmt/tests/source/invalid-rust-code-in-doc-comment.rs b/src/tools/rustfmt/tests/source/invalid-rust-code-in-doc-comment.rs new file mode 100644 index 0000000000..835b0261b7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/invalid-rust-code-in-doc-comment.rs @@ -0,0 +1,20 @@ +// rustfmt-format_code_in_doc_comments: true + +/// ```rust +/// if (true) { … } +/// ``` +fn a() { +} + +/// ```rust +/// if foo() { +/// … +/// } +/// ``` +fn a() { +} + +/// ```rust +/// k1 == k2 ⇒ hash(k1) == hash(k2) +/// ``` +pub struct a ; diff --git a/src/tools/rustfmt/tests/source/issue-1021.rs b/src/tools/rustfmt/tests/source/issue-1021.rs new file mode 100644 index 0000000000..380e24cc0b --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1021.rs @@ -0,0 +1,22 @@ +// rustfmt-normalize_comments: true +fn main() { + match x { + S(true , .., true ) => (), + S(true , .. ) => (), + S(.., true ) => (), + S( .. ) => (), + S(_) => (), + S(/* .. */ .. ) => (), + S(/* .. */ .., true ) => (), + } + + match y { + (true , .., true ) => (), + (true , .. ) => (), + (.., true ) => (), + ( .. ) => (), + (_,) => (), + (/* .. */ .. ) => (), + (/* .. */ .., true ) => (), + } +} diff --git a/src/tools/rustfmt/tests/source/issue-1049.rs b/src/tools/rustfmt/tests/source/issue-1049.rs new file mode 100644 index 0000000000..bcfba41e76 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1049.rs @@ -0,0 +1,18 @@ +// Test overlong function signature +pub unsafe fn reborrow_mut(&mut X: Abcde) -> Handle, HandleType> { +} + +pub fn merge(mut X: Abcdef) -> Handle, K, V, marker::Internal>, marker::Edge> { +} + +impl Handle { + pub fn merge(a: Abcd) -> Handle, K, V, marker::Internal>, marker::Edge> { + } +} + +// Long function without return type that should not be reformatted. +fn veeeeeeeeeeeeeeeeeeeeery_long_name(a: FirstTypeeeeeeeeee, b: SecondTypeeeeeeeeeeeeeeeeeeeeeee) {} + +fn veeeeeeeeeeeeeeeeeeeeeery_long_name(a: FirstTypeeeeeeeeee, b: SecondTypeeeeeeeeeeeeeeeeeeeeeee) {} + +fn veeeeeeeeeeeeeeeeeeeeeeery_long_name(a: FirstTypeeeeeeeeee, b: SecondTypeeeeeeeeeeeeeeeeeeeeeee) {} diff --git a/src/tools/rustfmt/tests/source/issue-1111.rs b/src/tools/rustfmt/tests/source/issue-1111.rs new file mode 100644 index 0000000000..2e1a89ad78 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1111.rs @@ -0,0 +1 @@ +use bar; diff --git a/src/tools/rustfmt/tests/source/issue-1120.rs b/src/tools/rustfmt/tests/source/issue-1120.rs new file mode 100644 index 0000000000..e85c9af99d --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1120.rs @@ -0,0 +1,9 @@ +// rustfmt-reorder_imports: true + +// Ensure that a use at the start of an inline module is correctly formatted. +mod foo {use bar;} + +// Ensure that an indented `use` gets the correct indentation. +mod foo { + use bar; +} diff --git a/src/tools/rustfmt/tests/source/issue-1124.rs b/src/tools/rustfmt/tests/source/issue-1124.rs new file mode 100644 index 0000000000..35c2197fa3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1124.rs @@ -0,0 +1,15 @@ +// rustfmt-reorder_imports: true + +use d; use c; use b; use a; +// The previous line has a space after the `use a;` + +mod a { use d; use c; use b; use a; } + +use z; + +use y; + + + +use x; +use a; diff --git a/src/tools/rustfmt/tests/source/issue-1127.rs b/src/tools/rustfmt/tests/source/issue-1127.rs new file mode 100644 index 0000000000..b49db4e3f6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1127.rs @@ -0,0 +1,23 @@ +// rustfmt-max_width: 120 +// rustfmt-match_arm_blocks: false +// rustfmt-match_block_trailing_comma: true + +fn a_very_very_very_very_very_very_very_very_very_very_very_long_function_name() -> i32 { + 42 +} + +enum TestEnum { + AVeryVeryLongEnumName, + AnotherVeryLongEnumName, + TheLastVeryLongEnumName, +} + +fn main() { + let var = TestEnum::AVeryVeryLongEnumName; + let num = match var { + TestEnum::AVeryVeryLongEnumName => a_very_very_very_very_very_very_very_very_very_very_very_long_function_name(), + TestEnum::AnotherVeryLongEnumName => a_very_very_very_very_very_very_very_very_very_very_very_long_function_name(), + TestEnum::TheLastVeryLongEnumName => a_very_very_very_very_very_very_very_very_very_very_very_long_function_name(), + }; +} + diff --git a/src/tools/rustfmt/tests/source/issue-1158.rs b/src/tools/rustfmt/tests/source/issue-1158.rs new file mode 100644 index 0000000000..6742e17459 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1158.rs @@ -0,0 +1,3 @@ +trait T { + itemmacro!(this, is.now() .formatted(yay)); +} diff --git a/src/tools/rustfmt/tests/source/issue-1177.rs b/src/tools/rustfmt/tests/source/issue-1177.rs new file mode 100644 index 0000000000..3ac423c5ae --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1177.rs @@ -0,0 +1,7 @@ +// rustfmt-normalize_comments: true +fn main() { + // Line Comment + /* Block Comment */ + + let d = 5; +} diff --git a/src/tools/rustfmt/tests/source/issue-1192.rs b/src/tools/rustfmt/tests/source/issue-1192.rs new file mode 100644 index 0000000000..4e39fbf9a3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1192.rs @@ -0,0 +1,3 @@ +fn main() { + assert!(true) ; +} diff --git a/src/tools/rustfmt/tests/source/issue-1210/a.rs b/src/tools/rustfmt/tests/source/issue-1210/a.rs new file mode 100644 index 0000000000..6bb9964b4e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1210/a.rs @@ -0,0 +1,12 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 50 + +impl Foo { + fn cxx(&self, target: &str) -> &Path { + match self.cxx.get(target) { + Some(p) => p.path(), + None => panic!("\n\ntarget `{}` is not configured as a host, + only as a target\n\n", target), + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-1210/b.rs b/src/tools/rustfmt/tests/source/issue-1210/b.rs new file mode 100644 index 0000000000..8c71ef98b7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1210/b.rs @@ -0,0 +1,12 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 50 + +impl Foo { + fn cxx(&self, target: &str) -> &Path { + match self.cxx.get(target) { + Some(p) => p.path(), + None => panic!("\ntarget `{}`: is not, configured as a host, + only as a target\n\n", target), + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-1210/c.rs b/src/tools/rustfmt/tests/source/issue-1210/c.rs new file mode 100644 index 0000000000..c080cef950 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1210/c.rs @@ -0,0 +1,5 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 50 + +const foo: String = "trailing_spaces!! + keep them! Amet neque. Praesent rhoncus eros non velit."; diff --git a/src/tools/rustfmt/tests/source/issue-1210/d.rs b/src/tools/rustfmt/tests/source/issue-1210/d.rs new file mode 100644 index 0000000000..783736bc3b --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1210/d.rs @@ -0,0 +1,4 @@ +// rustfmt-wrap_comments: true + +// trailing_spaces_in_comment!! +// remove those from above diff --git a/src/tools/rustfmt/tests/source/issue-1210/e.rs b/src/tools/rustfmt/tests/source/issue-1210/e.rs new file mode 100644 index 0000000000..9abada1d6d --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1210/e.rs @@ -0,0 +1,8 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 50 + +// explicit line breaks should be kept in order to preserve the layout + +const foo: String = "Suspendisse vel augue at felis tincidunt sollicitudin. Fusce arcu. + Duis et odio et leo + sollicitudin consequat. Aliquam lobortis. Phasellus condimentum."; diff --git a/src/tools/rustfmt/tests/source/issue-1211.rs b/src/tools/rustfmt/tests/source/issue-1211.rs new file mode 100644 index 0000000000..5818736bf6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1211.rs @@ -0,0 +1,15 @@ +fn main() { + for iface in &ifaces { + match iface.addr { + get_if_addrs::IfAddr::V4(ref addr) => { + match addr.broadcast { + Some(ip) => { + sock.send_to(&buf, (ip, 8765)).expect("foobar"); + } + _ => () + } + } + _ => () + }; + } +} diff --git a/src/tools/rustfmt/tests/source/issue-1216.rs b/src/tools/rustfmt/tests/source/issue-1216.rs new file mode 100644 index 0000000000..d727c158ab --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1216.rs @@ -0,0 +1,5 @@ +// rustfmt-normalize_comments: true +enum E { + A, //* I am not a block comment (caused panic) + B, +} diff --git a/src/tools/rustfmt/tests/source/issue-1239.rs b/src/tools/rustfmt/tests/source/issue-1239.rs new file mode 100644 index 0000000000..913058257e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1239.rs @@ -0,0 +1,9 @@ +fn foo() { + let with_alignment = if condition__uses_alignment_for_first_if__0 || + condition__uses_alignment_for_first_if__1 || + condition__uses_alignment_for_first_if__2 { + } else if condition__no_alignment_for_later_else__0 || + condition__no_alignment_for_later_else__1 || + condition__no_alignment_for_later_else__2 { + }; +} diff --git a/src/tools/rustfmt/tests/source/issue-1278.rs b/src/tools/rustfmt/tests/source/issue-1278.rs new file mode 100644 index 0000000000..e25376561a --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1278.rs @@ -0,0 +1,9 @@ +// rustfmt-indent_style = "block" + +#![feature(pub_restricted)] + +mod inner_mode { + pub(super) fn func_name(abc: i32) -> i32 { + abc + } +} diff --git a/src/tools/rustfmt/tests/source/issue-1350.rs b/src/tools/rustfmt/tests/source/issue-1350.rs new file mode 100644 index 0000000000..1baa1985a7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1350.rs @@ -0,0 +1,16 @@ +// rustfmt-max_width: 120 +// rustfmt-comment_width: 110 + +impl Struct { + fn fun() { + let result = match ::deserialize(&json) { + Ok(v) => v, + Err(e) => { + match ::deserialize(&json) { + Ok(v) => return Err(Error::with_json(v)), + Err(e2) => return Err(Error::with_json(e)), + } + } + }; + } +} diff --git a/src/tools/rustfmt/tests/source/issue-1366.rs b/src/tools/rustfmt/tests/source/issue-1366.rs new file mode 100644 index 0000000000..9d2964fc77 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1366.rs @@ -0,0 +1,12 @@ +fn main() { + fn f() -> Option { + Some("fffffffsssssssssddddssssfffffddddff").map(|s| s).map(|s| s.to_string()).map(|res| { + match Some(res) { + Some(ref s) if s == "" => 41, + Some(_) => 42, + _ => 43, + } + }) + } + println!("{:?}", f()) +} diff --git a/src/tools/rustfmt/tests/source/issue-1468.rs b/src/tools/rustfmt/tests/source/issue-1468.rs new file mode 100644 index 0000000000..4d0d4f0eb9 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1468.rs @@ -0,0 +1,27 @@ +fn issue1468() { +euc_jp_decoder_functions!({ +let trail_minus_offset = byte.wrapping_sub(0xA1); +// Fast-track Hiragana (60% according to Lunde) +// and Katakana (10% according to Lunde). +if jis0208_lead_minus_offset == 0x03 && +trail_minus_offset < 0x53 { +// Hiragana +handle.write_upper_bmp(0x3041 + trail_minus_offset as u16) +} else if jis0208_lead_minus_offset == 0x04 && +trail_minus_offset < 0x56 { +// Katakana +handle.write_upper_bmp(0x30A1 + trail_minus_offset as u16) +} else if trail_minus_offset > (0xFE - 0xA1) { +if byte < 0x80 { +return (DecoderResult::Malformed(1, 0), +unread_handle_trail.unread(), +handle.written()); +} +return (DecoderResult::Malformed(2, 0), +unread_handle_trail.consumed(), +handle.written()); +} else { +unreachable!(); +} +}); +} diff --git a/src/tools/rustfmt/tests/source/issue-1693.rs b/src/tools/rustfmt/tests/source/issue-1693.rs new file mode 100644 index 0000000000..0622ce5023 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1693.rs @@ -0,0 +1,3 @@ +fn issue1693() { + let pixel_data = vec![(f16::from_f32(0.82), f16::from_f32(1.78), f16::from_f32(0.21)); 256 * 256]; +} diff --git a/src/tools/rustfmt/tests/source/issue-1800.rs b/src/tools/rustfmt/tests/source/issue-1800.rs new file mode 100644 index 0000000000..eae2265325 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1800.rs @@ -0,0 +1,3 @@ +#![doc(html_root_url = "http://example.com")] +#[cfg(feature = "foo")] +fn a() {} diff --git a/src/tools/rustfmt/tests/source/issue-1914.rs b/src/tools/rustfmt/tests/source/issue-1914.rs new file mode 100644 index 0000000000..447296c4b8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-1914.rs @@ -0,0 +1,6 @@ +// rustfmt-max_width: 80 + +extern "C" { +#[link_name = "_ZN7MyClass26example_check_no_collisionE"] + pub static mut MyClass_example_check_no_collision : * const :: std :: os :: raw :: c_int ; +} diff --git a/src/tools/rustfmt/tests/source/issue-2025.rs b/src/tools/rustfmt/tests/source/issue-2025.rs new file mode 100644 index 0000000000..c6f61b4e3e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2025.rs @@ -0,0 +1,8 @@ + + + + +// See if rustfmt removes empty lines on top of the file. +pub fn foo() { + println!("hello, world"); +} diff --git a/src/tools/rustfmt/tests/source/issue-2111.rs b/src/tools/rustfmt/tests/source/issue-2111.rs new file mode 100644 index 0000000000..ccd113696e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2111.rs @@ -0,0 +1,26 @@ +// An import with single line comments. +use super::{ + SCHEMA_VERSIONS, + LodaModel, + ModelProperties, + StringMap, + ModelSelector, + RequestDescription, + MethodDescription, + ModelBehaviour, + ModelRequestGraph, + DelayChoice, + Holding, + Destinations, + ModelEdges, + Switch, + // ModelMetaData, + // Generated, + // SecondsString, + // DateString, + // ModelConfiguration, + // ModelRequests, + // RestResponse, + // RestResponseCode, + // UniformHolding +}; diff --git a/src/tools/rustfmt/tests/source/issue-2164.rs b/src/tools/rustfmt/tests/source/issue-2164.rs new file mode 100644 index 0000000000..6c288e1bd6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2164.rs @@ -0,0 +1,4 @@ +// A stress test against code generated by bindgen. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct emacs_env_25 { pub size : isize , pub private_members : * mut emacs_env_private , pub make_global_ref : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , any_reference : emacs_value ) -> emacs_value > , pub free_global_ref : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , global_reference : emacs_value ) > , pub non_local_exit_check : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env ) -> emacs_funcall_exit > , pub non_local_exit_clear : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env ) > , pub non_local_exit_get : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , non_local_exit_symbol_out : * mut emacs_value , non_local_exit_data_out : * mut emacs_value ) -> emacs_funcall_exit > , pub non_local_exit_signal : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , non_local_exit_symbol : emacs_value , non_local_exit_data : emacs_value ) > , pub non_local_exit_throw : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , tag : emacs_value , value : emacs_value ) > , pub make_function : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , min_arity : isize , max_arity : isize , function : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , nargs : isize , args : * mut emacs_value , arg1 : * mut ::libc :: c_void ) -> emacs_value > , documentation : * const ::libc :: c_char , data : * mut ::libc :: c_void ) -> emacs_value > , pub funcall : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , function : emacs_value , nargs : isize , args : * mut emacs_value ) -> emacs_value > , pub intern : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , symbol_name : * const ::libc :: c_char ) -> emacs_value > , pub type_of : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , value : emacs_value ) -> emacs_value > , pub is_not_nil : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , value : emacs_value ) -> bool > , pub eq : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , a : emacs_value , b : emacs_value ) -> bool > , pub extract_integer : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , value : emacs_value ) -> intmax_t > , pub make_integer : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , value : intmax_t ) -> emacs_value > , pub extract_float : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , value : emacs_value ) -> f64 > , pub make_float : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , value : f64 ) -> emacs_value > , pub copy_string_contents : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , value : emacs_value , buffer : * mut ::libc :: c_char , size_inout : * mut isize ) -> bool > , pub make_string : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , contents : * const ::libc :: c_char , length : isize ) -> emacs_value > , pub make_user_ptr : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , fin : :: std :: option :: Option < unsafe extern "C" fn ( arg1 : * mut ::libc :: c_void ) > , ptr : * mut ::libc :: c_void ) -> emacs_value > , pub get_user_ptr : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , uptr : emacs_value ) -> * mut ::libc :: c_void > , pub set_user_ptr : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , uptr : emacs_value , ptr : * mut ::libc :: c_void ) > , pub get_user_finalizer : :: std :: option :: Option < unsafe extern "C" fn ( arg1 : * mut ::libc :: c_void , env : * mut emacs_env , uptr : emacs_value ) -> :: std :: option :: Option < unsafe extern "C" fn ( arg1 : * mut ::libc :: c_void , env : * mut emacs_env , uptr : emacs_value ) > > , pub set_user_finalizer : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , uptr : emacs_value , fin : :: std :: option :: Option < unsafe extern "C" fn ( arg1 : * mut ::libc :: c_void ) > ) > , pub vec_get : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , vec : emacs_value , i : isize ) -> emacs_value > , pub vec_set : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , vec : emacs_value , i : isize , val : emacs_value ) > , pub vec_size : :: std :: option :: Option < unsafe extern "C" fn ( env : * mut emacs_env , vec : emacs_value ) -> isize > , } diff --git a/src/tools/rustfmt/tests/source/issue-2179/one.rs b/src/tools/rustfmt/tests/source/issue-2179/one.rs new file mode 100644 index 0000000000..d23947931f --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2179/one.rs @@ -0,0 +1,36 @@ +// rustfmt-version: One +// rustfmt-error_on_line_overflow: false + +fn issue_2179() { + let (opts, rustflags, clear_env_rust_log) = + { + // We mustn't lock configuration for the whole build process + let rls_config = rls_config.lock().unwrap(); + + let opts = CargoOptions::new(&rls_config); + trace!("Cargo compilation options:\n{:?}", opts); + let rustflags = prepare_cargo_rustflags(&rls_config); + + // Warn about invalid specified bin target or package depending on current mode + // TODO: Return client notifications along with diagnostics to inform the user + if !rls_config.workspace_mode { + let cur_pkg_targets = ws.current().unwrap().targets(); + + if let &Some(ref build_bin) = rls_config.build_bin.as_ref() { + let mut bins = cur_pkg_targets.iter().filter(|x| x.is_bin()); + if let None = bins.find(|x| x.name() == build_bin) { + warn!("cargo - couldn't find binary `{}` specified in `build_bin` configuration", build_bin); + } + } + } else { + for package in &opts.package { + if let None = ws.members().find(|x| x.name() == package) { + warn!("cargo - couldn't find member package `{}` specified in `analyze_package` configuration", package); + } + } + } + + (opts, rustflags, rls_config.clear_env_rust_log) + }; + +} diff --git a/src/tools/rustfmt/tests/source/issue-2179/two.rs b/src/tools/rustfmt/tests/source/issue-2179/two.rs new file mode 100644 index 0000000000..f4cc9cc488 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2179/two.rs @@ -0,0 +1,36 @@ +// rustfmt-version: Two +// rustfmt-error_on_line_overflow: false + +fn issue_2179() { + let (opts, rustflags, clear_env_rust_log) = + { + // We mustn't lock configuration for the whole build process + let rls_config = rls_config.lock().unwrap(); + + let opts = CargoOptions::new(&rls_config); + trace!("Cargo compilation options:\n{:?}", opts); + let rustflags = prepare_cargo_rustflags(&rls_config); + + // Warn about invalid specified bin target or package depending on current mode + // TODO: Return client notifications along with diagnostics to inform the user + if !rls_config.workspace_mode { + let cur_pkg_targets = ws.current().unwrap().targets(); + + if let &Some(ref build_bin) = rls_config.build_bin.as_ref() { + let mut bins = cur_pkg_targets.iter().filter(|x| x.is_bin()); + if let None = bins.find(|x| x.name() == build_bin) { + warn!("cargo - couldn't find binary `{}` specified in `build_bin` configuration", build_bin); + } + } + } else { + for package in &opts.package { + if let None = ws.members().find(|x| x.name() == package) { + warn!("cargo - couldn't find member package `{}` specified in `analyze_package` configuration", package); + } + } + } + + (opts, rustflags, rls_config.clear_env_rust_log) + }; + +} diff --git a/src/tools/rustfmt/tests/source/issue-2256.rs b/src/tools/rustfmt/tests/source/issue-2256.rs new file mode 100644 index 0000000000..a206e8db6c --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2256.rs @@ -0,0 +1,12 @@ +// こんにちは +use std::{}; +use std::borrow::Cow; + +/* comment 1 */ use std::{}; +/* comment 2 */ use std::{}; + + + + + +/* comment 3 */ use std::{}; diff --git a/src/tools/rustfmt/tests/source/issue-2342.rs b/src/tools/rustfmt/tests/source/issue-2342.rs new file mode 100644 index 0000000000..f86d24a146 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2342.rs @@ -0,0 +1,5 @@ +// rustfmt-max_width: 80 + +struct Foo { + #[cfg(feature = "serde")] bytes: [[u8; 17]; 5], // Same size as signature::ED25519_PKCS8_V2_LEN +} diff --git a/src/tools/rustfmt/tests/source/issue-2445.rs b/src/tools/rustfmt/tests/source/issue-2445.rs new file mode 100644 index 0000000000..84ce6e647b --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2445.rs @@ -0,0 +1,21 @@ +test!(RunPassPretty { + // comment + path: "src/test/run-pass/pretty", + mode: "pretty", + suite: "run-pass", + default: false, + host: true // should, force, , no trailing comma here +}); + +test!(RunPassPretty { + // comment + path: "src/test/run-pass/pretty", + mode: "pretty", + suite: "run-pass", + default: false, + host: true, // should, , preserve, the trailing comma +}); + +test!(Test{ + field: i32, // comment +}); diff --git a/src/tools/rustfmt/tests/source/issue-2446.rs b/src/tools/rustfmt/tests/source/issue-2446.rs new file mode 100644 index 0000000000..ad649d95c5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2446.rs @@ -0,0 +1,11 @@ +enum Issue2446 { + V { + f: u8, // x + }, +} + +enum Issue2446TrailingCommentsOnly { + V { + f: u8, /* */ + } +} diff --git a/src/tools/rustfmt/tests/source/issue-2479.rs b/src/tools/rustfmt/tests/source/issue-2479.rs new file mode 100644 index 0000000000..df50236d05 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2479.rs @@ -0,0 +1,2 @@ +// Long attributes. +# [ derive ( Clone , Copy , Debug , PartialEq ) ] pub enum POLARITYR { # [ doc = "Task mode: No effect on pin from OUT[n] task. Event mode: no IN[n] event generated on pin activity." ] NONE , # [ doc = "Task mode: Set pin from OUT[n] task. Event mode: Generate IN[n] event when rising edge on pin." ] LOTOHI , # [ doc = "Task mode: Clear pin from OUT[n] task. Event mode: Generate IN[n] event when falling edge on pin." ] HITOLO , # [ doc = "Task mode: Toggle pin from OUT[n]. Event mode: Generate IN[n] when any change on pin." ] TOGGLE } diff --git a/src/tools/rustfmt/tests/source/issue-2482/a.rs b/src/tools/rustfmt/tests/source/issue-2482/a.rs new file mode 100644 index 0000000000..fbbcb52a87 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2482/a.rs @@ -0,0 +1,9 @@ +// rustfmt-reorder_modules: true + +// Do not reorder inline modules. + +mod c; +mod a { + fn a() {} +} +mod b; diff --git a/src/tools/rustfmt/tests/source/issue-2482/b.rs b/src/tools/rustfmt/tests/source/issue-2482/b.rs new file mode 100644 index 0000000000..40a8d94212 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2482/b.rs @@ -0,0 +1 @@ +pub fn b() {} diff --git a/src/tools/rustfmt/tests/source/issue-2482/c.rs b/src/tools/rustfmt/tests/source/issue-2482/c.rs new file mode 100644 index 0000000000..d937545511 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2482/c.rs @@ -0,0 +1 @@ +pub fn c() {} diff --git a/src/tools/rustfmt/tests/source/issue-2496.rs b/src/tools/rustfmt/tests/source/issue-2496.rs new file mode 100644 index 0000000000..0ebd4b510e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2496.rs @@ -0,0 +1,16 @@ +// rustfmt-indent_style: Visual +fn main() { + match option { + None => some_function(first_reasonably_long_argument, + second_reasonably_long_argument), + } +} + +fn main() { + match option { + None => { + some_function(first_reasonably_long_argument, + second_reasonably_long_argument) + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-2520.rs b/src/tools/rustfmt/tests/source/issue-2520.rs new file mode 100644 index 0000000000..5a23f10430 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2520.rs @@ -0,0 +1,13 @@ +// rustfmt-normalize_comments: true +// rustfmt-format_code_in_doc_comments: true + +//! ```rust +//! println!( "hello, world" ); +//! ``` + +#![deny( missing_docs )] + +//! ```rust +//! println!("hello, world"); + +#![deny( missing_docs )] diff --git a/src/tools/rustfmt/tests/source/issue-2523.rs b/src/tools/rustfmt/tests/source/issue-2523.rs new file mode 100644 index 0000000000..491d5c38fc --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2523.rs @@ -0,0 +1,18 @@ +// rustfmt-normalize_comments: true +// rustfmt-format_code_in_doc_comments: true + +// Do not unindent macro calls in comment with unformattable syntax. +//! ```rust +//! let x = 3 ; +//! some_macro!(pub fn fn foo() ( +//! println!("Don't unindent me!"); +//! )); +//! ``` + +// Format items that appear as arguments of macro call. +//! ```rust +//! let x = 3 ; +//! some_macro!(pub fn foo() { +//! println!("Don't unindent me!"); +//! }); +//! ``` diff --git a/src/tools/rustfmt/tests/source/issue-2582.rs b/src/tools/rustfmt/tests/source/issue-2582.rs new file mode 100644 index 0000000000..bba8ce1504 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2582.rs @@ -0,0 +1 @@ + fn main() {} diff --git a/src/tools/rustfmt/tests/source/issue-2641.rs b/src/tools/rustfmt/tests/source/issue-2641.rs new file mode 100644 index 0000000000..c7ad60674f --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2641.rs @@ -0,0 +1,3 @@ +macro_rules! a { + () => {{}} +} diff --git a/src/tools/rustfmt/tests/source/issue-2644.rs b/src/tools/rustfmt/tests/source/issue-2644.rs new file mode 100644 index 0000000000..fa9d16f444 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2644.rs @@ -0,0 +1,11 @@ +// rustfmt-max_width: 80 +fn foo(e: Enum) { + match e { + Enum::Var { + element1, + element2, + } => { + return; + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-2728.rs b/src/tools/rustfmt/tests/source/issue-2728.rs new file mode 100644 index 0000000000..6cb41b75b6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2728.rs @@ -0,0 +1,8 @@ +// rustfmt-wrap_comments: true +// rustfmt-newline_style: Windows + +//! ```rust +//! extern crate uom; +//! ``` + +fn main() {} diff --git a/src/tools/rustfmt/tests/source/issue-2761.rs b/src/tools/rustfmt/tests/source/issue-2761.rs new file mode 100644 index 0000000000..bc31231903 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2761.rs @@ -0,0 +1,15 @@ +const DATA: &'static [u8] = &[ + 0x42, 0x50, 0x54, 0x44, //type + 0x23, 0x00, 0x00, 0x00, //size + 0x00, 0x00, 0x04, 0x00, //flags + 0xEC, 0x0C, 0x00, 0x00, //id + 0x00, 0x00, 0x00, 0x00, //revision + 0x2B, 0x00, //version + 0x00, 0x00, //unknown + 0x42, 0x50, 0x54, 0x4E, //field type + 0x1D, 0x00, //field size + 0x19, 0x00, 0x00, 0x00, //decompressed field size + 0x75, 0xc5, 0x21, 0x0d, 0x00, 0x00, 0x08, 0x05, 0xd1, 0x6c, //field data (compressed) + 0x6c, 0xdc, 0x57, 0x48, 0x3c, 0xfd, 0x5b, 0x5c, 0x02, 0xd4, //field data (compressed) + 0x6b, 0x32, 0xb5, 0xdc, 0xa3 //field data (compressed) +]; diff --git a/src/tools/rustfmt/tests/source/issue-2781.rs b/src/tools/rustfmt/tests/source/issue-2781.rs new file mode 100644 index 0000000000..2c15b29b6d --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2781.rs @@ -0,0 +1,11 @@ +pub // Oh, no. A line comment. +struct Foo {} + +pub /* Oh, no. A block comment. */ struct Foo {} + +mod inner { +pub // Oh, no. A line comment. +struct Foo {} + +pub /* Oh, no. A block comment. */ struct Foo {} +} diff --git a/src/tools/rustfmt/tests/source/issue-2794.rs b/src/tools/rustfmt/tests/source/issue-2794.rs new file mode 100644 index 0000000000..c3f9c0412a --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2794.rs @@ -0,0 +1,7 @@ +// rustfmt-indent_style: Block +// rustfmt-imports_indent: Block +// rustfmt-imports_layout: Vertical + +use std::{ + env, fs, io::{Read, Write}, +}; diff --git a/src/tools/rustfmt/tests/source/issue-2835.rs b/src/tools/rustfmt/tests/source/issue-2835.rs new file mode 100644 index 0000000000..2219b0b38e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2835.rs @@ -0,0 +1,7 @@ +// rustfmt-brace_style: AlwaysNextLine +// rustfmt-fn_single_line: true + +fn lorem() -> i32 +{ + 42 +} diff --git a/src/tools/rustfmt/tests/source/issue-2863.rs b/src/tools/rustfmt/tests/source/issue-2863.rs new file mode 100644 index 0000000000..1bda857be7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2863.rs @@ -0,0 +1,25 @@ +// rustfmt-reorder_impl_items: true + +impl IntoIterator for SafeVec { + type F = impl Trait; + type IntoIter = self::IntoIter; + type Item = T; + // comment on foo() + fn foo() {println!("hello, world");} + type Bar = u32; + fn foo1() {println!("hello, world");} + type FooBar = u32; + fn foo2() {println!("hello, world");} + fn foo3() {println!("hello, world");} + const SomeConst: i32 = 100; + fn foo4() {println!("hello, world");} + fn foo5() {println!("hello, world");} + // comment on FoooooBar + type FoooooBar = u32; + fn foo6() {println!("hello, world");} + fn foo7() {println!("hello, world");} + type BarFoo = u32; + type E = impl Trait; + const AnotherConst: i32 = 100; + fn foo8() {println!("hello, world");} +} diff --git a/src/tools/rustfmt/tests/source/issue-2869.rs b/src/tools/rustfmt/tests/source/issue-2869.rs new file mode 100644 index 0000000000..d18adfb462 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2869.rs @@ -0,0 +1,41 @@ +// rustfmt-struct_field_align_threshold: 50 + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +struct AuditLog1 { + creation_time: String, + id: String, + operation: String, + organization_id: String, + record_type: u32, + result_status: Option, + #[serde(rename = "ClientIP")] + client_ip: Option, + object_id: String, + actor: Option>, + actor_context_id: Option, + actor_ip_address: Option, + azure_active_directory_event_type: Option, + + #[serde(rename = "very")] + aaaaa: String, + #[serde(rename = "cool")] + bb: i32, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +struct AuditLog2 { + creation_time: String, + id: String, + operation: String, + organization_id: String, + record_type: u32, + result_status: Option, + client_ip: Option, + object_id: String, + actor: Option>, + actor_context_id: Option, + actor_ip_address: Option, + azure_active_directory_event_type: Option, +} diff --git a/src/tools/rustfmt/tests/source/issue-2896.rs b/src/tools/rustfmt/tests/source/issue-2896.rs new file mode 100644 index 0000000000..f648e64b1e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2896.rs @@ -0,0 +1,161 @@ +extern crate rand; +extern crate timely; +extern crate differential_dataflow; + +use rand::{Rng, SeedableRng, StdRng}; + +use timely::dataflow::operators::*; + +use differential_dataflow::AsCollection; +use differential_dataflow::operators::*; +use differential_dataflow::input::InputSession; + +// mod loglikelihoodratio; + +fn main() { + + // define a new timely dataflow computation. + timely::execute_from_args(std::env::args().skip(6), move |worker| { + + // capture parameters of the experiment. + let users: usize = std::env::args().nth(1).unwrap().parse().unwrap(); + let items: usize = std::env::args().nth(2).unwrap().parse().unwrap(); + let scale: usize = std::env::args().nth(3).unwrap().parse().unwrap(); + let batch: usize = std::env::args().nth(4).unwrap().parse().unwrap(); + let noisy: bool = std::env::args().nth(5).unwrap() == "noisy"; + + let index = worker.index(); + let peers = worker.peers(); + + let (input, probe) = worker.dataflow(|scope| { + + // input of (user, item) collection. + let (input, occurrences) = scope.new_input(); + let occurrences = occurrences.as_collection(); + + //TODO adjust code to only work with upper triangular half of cooccurrence matrix + + /* Compute the cooccurrence matrix C = A'A from the binary interaction matrix A. */ + let cooccurrences = + occurrences + .join_map(&occurrences, |_user, &item_a, &item_b| (item_a, item_b)) + .filter(|&(item_a, item_b)| item_a != item_b) + .count(); + + /* compute the rowsums of C indicating how often we encounter individual items. */ + let row_sums = + occurrences + .map(|(_user, item)| item) + .count(); + + // row_sums.inspect(|record| println!("[row_sums] {:?}", record)); + + /* Join the cooccurrence pairs with the corresponding row sums. */ + let mut cooccurrences_with_row_sums = cooccurrences + .map(|((item_a, item_b), num_cooccurrences)| (item_a, (item_b, num_cooccurrences))) + .join_map(&row_sums, |&item_a, &(item_b, num_cooccurrences), &row_sum_a| { + assert!(row_sum_a > 0); + (item_b, (item_a, num_cooccurrences, row_sum_a)) + }) + .join_map(&row_sums, |&item_b, &(item_a, num_cooccurrences, row_sum_a), &row_sum_b| { + assert!(row_sum_a > 0); + assert!(row_sum_b > 0); + (item_a, (item_b, num_cooccurrences, row_sum_a, row_sum_b)) + }); + + // cooccurrences_with_row_sums + // .inspect(|record| println!("[cooccurrences_with_row_sums] {:?}", record)); + + // //TODO compute top-k "similar items" per item + // /* Compute LLR scores for each item pair. */ + // let llr_scores = cooccurrences_with_row_sums.map( + // |(item_a, (item_b, num_cooccurrences, row_sum_a, row_sum_b))| { + + // println!( + // "[llr_scores] item_a={} item_b={}, num_cooccurrences={} row_sum_a={} row_sum_b={}", + // item_a, item_b, num_cooccurrences, row_sum_a, row_sum_b); + + // let k11: isize = num_cooccurrences; + // let k12: isize = row_sum_a as isize - k11; + // let k21: isize = row_sum_b as isize - k11; + // let k22: isize = 10000 - k12 - k21 + k11; + + // let llr_score = loglikelihoodratio::log_likelihood_ratio(k11, k12, k21, k22); + + // ((item_a, item_b), llr_score) + // }); + + if noisy { + cooccurrences_with_row_sums = + cooccurrences_with_row_sums + .inspect(|x| println!("change: {:?}", x)); + } + + let probe = + cooccurrences_with_row_sums + .probe(); +/* + // produce the (item, item) collection + let cooccurrences = occurrences + .join_map(&occurrences, |_user, &item_a, &item_b| (item_a, item_b)); + // count the occurrences of each item. + let counts = cooccurrences + .map(|(item_a,_)| item_a) + .count(); + // produce ((item1, item2), count1, count2, count12) tuples + let cooccurrences_with_counts = cooccurrences + .join_map(&counts, |&item_a, &item_b, &count_item_a| (item_b, (item_a, count_item_a))) + .join_map(&counts, |&item_b, &(item_a, count_item_a), &count_item_b| { + ((item_a, item_b), count_item_a, count_item_b) + }); + let probe = cooccurrences_with_counts + .inspect(|x| println!("change: {:?}", x)) + .probe(); +*/ + (input, probe) + }); + + let seed: &[_] = &[1, 2, 3, index]; + let mut rng1: StdRng = SeedableRng::from_seed(seed); // rng for edge additions + let mut rng2: StdRng = SeedableRng::from_seed(seed); // rng for edge deletions + + let mut input = InputSession::from(input); + + for count in 0 .. scale { + if count % peers == index { + let user = rng1.gen_range(0, users); + let item = rng1.gen_range(0, items); + // println!("[INITIAL INPUT] ({}, {})", user, item); + input.insert((user, item)); + } + } + + // load the initial data up! + while probe.less_than(input.time()) { worker.step(); } + + for round in 1 .. { + + for element in (round * batch) .. ((round + 1) * batch) { + if element % peers == index { + // advance the input timestamp. + input.advance_to(round * batch); + // insert a new item. + let user = rng1.gen_range(0, users); + let item = rng1.gen_range(0, items); + if noisy { println!("[INPUT: insert] ({}, {})", user, item); } + input.insert((user, item)); + // remove an old item. + let user = rng2.gen_range(0, users); + let item = rng2.gen_range(0, items); + if noisy { println!("[INPUT: remove] ({}, {})", user, item); } + input.remove((user, item)); + } + } + + input.advance_to(round * batch); + input.flush(); + + while probe.less_than(input.time()) { worker.step(); } + } + }).unwrap(); +} diff --git a/src/tools/rustfmt/tests/source/issue-2916.rs b/src/tools/rustfmt/tests/source/issue-2916.rs new file mode 100644 index 0000000000..ccb1f8486c --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2916.rs @@ -0,0 +1,2 @@ +a_macro!(name, +) ; diff --git a/src/tools/rustfmt/tests/source/issue-2917/packed_simd.rs b/src/tools/rustfmt/tests/source/issue-2917/packed_simd.rs new file mode 100644 index 0000000000..afa9e67c8a --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2917/packed_simd.rs @@ -0,0 +1,63 @@ +// rustfmt-wrap_comments: true +//! Implements `From` and `Into` for vector types. + +macro_rules! impl_from_vector { + ([$elem_ty:ident; $elem_count:expr]: $id:ident | $test_tt:tt | $source:ident) => { + impl From<$source> for $id { + #[inline] + fn from(source: $source) -> Self { + fn static_assert_same_number_of_lanes() + where + T: crate::sealed::Simd, + U: crate::sealed::Simd, + { + } + use llvm::simd_cast; + static_assert_same_number_of_lanes::<$id, $source>(); + Simd(unsafe { simd_cast(source.0) }) + } + } + + // FIXME: `Into::into` is not inline, but due to + // the blanket impl in `std`, which is not + // marked `default`, we cannot override it here with + // specialization. + /* + impl Into<$id> for $source { + #[inline] + fn into(self) -> $id { + unsafe { simd_cast(self) } + } + } + */ + + test_if!{ + $test_tt: + interpolate_idents! { + mod [$id _from_ $source] { + use super::*; + #[test] + fn from() { + assert_eq!($id::lanes(), $source::lanes()); + let source: $source = Default::default(); + let vec: $id = Default::default(); + + let e = $id::from(source); + assert_eq!(e, vec); + + let e: $id = source.into(); + assert_eq!(e, vec); + } + } + } + } + }; +} + +macro_rules! impl_from_vectors { + ([$elem_ty:ident; $elem_count:expr]: $id:ident | $test_tt:tt | $($source:ident),*) => { + $( + impl_from_vector!([$elem_ty; $elem_count]: $id | $test_tt | $source); + )* + } +} diff --git a/src/tools/rustfmt/tests/source/issue-2922.rs b/src/tools/rustfmt/tests/source/issue-2922.rs new file mode 100644 index 0000000000..44fae0b645 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2922.rs @@ -0,0 +1,9 @@ +// rustfmt-indent_style: Visual +struct Functions { + RunListenServer: unsafe extern "C" fn(*mut c_void, + *mut c_char, + *mut c_char, + *mut c_char, + *mut c_void, + *mut c_void) -> c_int, +} diff --git a/src/tools/rustfmt/tests/source/issue-2927-2.rs b/src/tools/rustfmt/tests/source/issue-2927-2.rs new file mode 100644 index 0000000000..d87761fdc9 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2927-2.rs @@ -0,0 +1,7 @@ +// rustfmt-edition: 2015 +#![feature(rust_2018_preview, uniform_paths)] +use futures::prelude::*; +use http_03::cli::Cli; +use hyper::{service::service_fn_ok, Body, Response, Server}; +use ::log::{error, info, log}; +use structopt::StructOpt; diff --git a/src/tools/rustfmt/tests/source/issue-2927.rs b/src/tools/rustfmt/tests/source/issue-2927.rs new file mode 100644 index 0000000000..a7df32084f --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2927.rs @@ -0,0 +1,7 @@ +// rustfmt-edition: 2018 +#![feature(rust_2018_preview, uniform_paths)] +use futures::prelude::*; +use http_03::cli::Cli; +use hyper::{service::service_fn_ok, Body, Response, Server}; +use ::log::{error, info, log}; +use structopt::StructOpt; diff --git a/src/tools/rustfmt/tests/source/issue-2930.rs b/src/tools/rustfmt/tests/source/issue-2930.rs new file mode 100644 index 0000000000..962c3e4fe0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2930.rs @@ -0,0 +1,5 @@ +// rustfmt-indent_style: Visual +fn main() { + let (first_variable, second_variable) = (this_is_something_with_an_extraordinarily_long_name, + this_variable_name_is_also_pretty_long); +} diff --git a/src/tools/rustfmt/tests/source/issue-2936.rs b/src/tools/rustfmt/tests/source/issue-2936.rs new file mode 100644 index 0000000000..55b5c56e69 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2936.rs @@ -0,0 +1,19 @@ +struct AStruct { + A: u32, + B: u32, + C: u32, +} + +impl Something for AStruct { + fn a_func() { + match a_val { + ContextualParseError::InvalidMediaRule(ref err) => { + let err: &CStr = match err.kind { + ParseErrorKind::Custom(StyleParseErrorKind::MediaQueryExpectedFeatureName(..)) => { + cstr!("PEMQExpectedFeatureName") + }, + }; + } + }; + } +} diff --git a/src/tools/rustfmt/tests/source/issue-2955.rs b/src/tools/rustfmt/tests/source/issue-2955.rs new file mode 100644 index 0000000000..525e070a57 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2955.rs @@ -0,0 +1,6 @@ +// rustfmt-condense_wildcard_suffixes: true +fn main() { + match (1, 2, 3) { + (_, _, _) => (), + } +} diff --git a/src/tools/rustfmt/tests/source/issue-2973.rs b/src/tools/rustfmt/tests/source/issue-2973.rs new file mode 100644 index 0000000000..5256dd7c96 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2973.rs @@ -0,0 +1,158 @@ +#[cfg(test)] +mod test { + summary_test! { + tokenize_recipe_interpolation_eol, + "foo: # some comment + {{hello}} +", + "foo: \ + {{hello}} \ +{{ahah}}", + "N:#$>^{N}$<.", + } + + summary_test! { + tokenize_strings, + r#"a = "'a'" + '"b"' + "'c'" + '"d"'#echo hello"#, + r#"N="+'+"+'#."#, + } + + summary_test! { + tokenize_recipe_interpolation_eol, + "foo: # some comment + {{hello}} +", + "N:#$>^{N}$<.", + } + + summary_test! { + tokenize_recipe_interpolation_eof, + "foo: # more comments + {{hello}} +# another comment +", + "N:#$>^{N}$<#$.", + } + + summary_test! { + tokenize_recipe_complex_interpolation_expression, + "foo: #lol\n {{a + b + \"z\" + blarg}}", + "N:#$>^{N+N+\"+N}<.", + } + + summary_test! { + tokenize_recipe_multiple_interpolations, + "foo:,#ok\n {{a}}0{{b}}1{{c}}", + "N:,#$>^{N}_{N}_{N}<.", + } + + summary_test! { + tokenize_junk, + "bob + +hello blah blah blah : a b c #whatever + ", + "N$$NNNN:NNN#$.", + } + + summary_test! { + tokenize_empty_lines, + " +# this does something +hello: + asdf + bsdf + + csdf + + dsdf # whatever + +# yolo + ", + "$#$N:$>^_$^_$$^_$$^_$$<#$.", + } + + summary_test! { + tokenize_comment_before_variable, + " +# +A='1' +echo: + echo {{A}} + ", + "$#$N='$N:$>^_{N}$<.", + } + + summary_test! { + tokenize_interpolation_backticks, + "hello:\n echo {{`echo hello` + `echo goodbye`}}", + "N:$>^_{`+`}<.", + } + + summary_test! { + tokenize_assignment_backticks, + "a = `echo hello` + `echo goodbye`", + "N=`+`.", + } + + summary_test! { + tokenize_multiple, + " +hello: + a + b + + c + + d + +# hello +bob: + frank + ", + + "$N:$>^_$^_$$^_$$^_$$<#$N:$>^_$<.", + } + + summary_test! { + tokenize_comment, + "a:=#", + "N:=#." + } + + summary_test! { + tokenize_comment_with_bang, + "a:=#foo!", + "N:=#." + } + + summary_test! { + tokenize_order, + r" +b: a + @mv a b + +a: + @touch F + @touch a + +d: c + @rm c + +c: b + @mv b c", + "$N:N$>^_$$^_$^_$$^_$$^_<.", + } + + summary_test! { + tokenize_parens, + r"((())) )abc(+", + "((())))N(+.", + } + + summary_test! { + crlf_newline, + "#\r\n#asdf\r\n", + "#$#$.", + } +} diff --git a/src/tools/rustfmt/tests/source/issue-2977/impl.rs b/src/tools/rustfmt/tests/source/issue-2977/impl.rs new file mode 100644 index 0000000000..8d7bb9414e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2977/impl.rs @@ -0,0 +1,44 @@ +macro_rules! atomic_bits { + // the println macro cannot be rewritten because of the asm macro + ($type:ty, $ldrex:expr, $strex:expr) => { + impl AtomicBits for $type { + unsafe fn load_excl(address: usize) -> Self { + let raw: $type; + asm!($ldrex + : "=r"(raw) + : "r"(address) + : + : "volatile"); + raw + } + + unsafe fn store_excl(self, address: usize) -> bool { + let status: $type; + println!("{}", + status); + status == 0 + } + } + }; + + // the println macro should be rewritten here + ($type:ty) => { + fn some_func(self) { + let status: $type; + println!("{}", status); + } + }; + + // unrewritale macro in func + ($type:ty, $ldrex:expr) => { + unsafe fn load_excl(address: usize) -> Self { + let raw: $type; + asm!($ldrex + : "=r"(raw) + : "r"(address) + : + : "volatile"); + raw + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-2977/trait.rs b/src/tools/rustfmt/tests/source/issue-2977/trait.rs new file mode 100644 index 0000000000..ae20668cd7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2977/trait.rs @@ -0,0 +1,44 @@ +macro_rules! atomic_bits { + // the println macro cannot be rewritten because of the asm macro + ($type:ty, $ldrex:expr, $strex:expr) => { + trait $type { + unsafe fn load_excl(address: usize) -> Self { + let raw: $type; + asm!($ldrex + : "=r"(raw) + : "r"(address) + : + : "volatile"); + raw + } + + unsafe fn store_excl(self, address: usize) -> bool { + let status: $type; + println!("{}", + status); + status == 0 + } + } + }; + + // the println macro should be rewritten here + ($type:ty) => { + fn some_func(self) { + let status: $type; + println!("{}", status); + } + }; + + // unrewritale macro in func + ($type:ty, $ldrex:expr) => { + unsafe fn load_excl(address: usize) -> Self { + let raw: $type; + asm!($ldrex + : "=r"(raw) + : "r"(address) + : + : "volatile"); + raw + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-2985.rs b/src/tools/rustfmt/tests/source/issue-2985.rs new file mode 100644 index 0000000000..bde4da8314 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2985.rs @@ -0,0 +1,35 @@ +// rustfmt-indent_style: Visual +fn foo() { + { + { + let extra_encoder_settings = extra_encoder_settings.iter() + .filter_map(|&(name, value)| { + value.split() + .next() + .something() + .something2() + .something3() + .something4() + }); + let extra_encoder_settings = extra_encoder_settings.iter() + .filter_map(|&(name, value)| { + value.split() + .next() + .something() + .something2() + .something3() + .something4() + }) + .something(); + if let Some(subpod) = pod.subpods.iter().find(|s| { + !s.plaintext + .as_ref() + .map(String::as_ref) + .unwrap_or("") + .is_empty() + }) { + do_something(); + } + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-2995.rs b/src/tools/rustfmt/tests/source/issue-2995.rs new file mode 100644 index 0000000000..accf7c3a1a --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-2995.rs @@ -0,0 +1,7 @@ +fn issue_2995() { + // '\u{2028}' is inserted in the code below. + + [0, 
1]; + [0, 
/* */ 1]; + 
[
0
,
1
]
; +} diff --git a/src/tools/rustfmt/tests/source/issue-3029.rs b/src/tools/rustfmt/tests/source/issue-3029.rs new file mode 100644 index 0000000000..a7ac5c32b0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3029.rs @@ -0,0 +1,94 @@ +fn keep_if() { + { + { + { + EvaluateJSReply::NumberValue( + if FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + unimplemented!(); + }, + ) + } + } + } +} + +fn keep_if_let() { + { + { + { + EvaluateJSReply::NumberValue( + if let Some(e) = FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + unimplemented!(); + }, + ) + } + } + } +} + +fn keep_for() { + { + { + { + EvaluateJSReply::NumberValue( + for conv in FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + unimplemented!(); + }, + ) + } + } + } +} + +fn keep_loop() { + { + { + { + EvaluateJSReply::NumberValue(loop { + FromJSValConvertible::from_jsval(cx, rval.handle(), ()); + }) + } + } + } +} + +fn keep_while() { + { + { + { + EvaluateJSReply::NumberValue( + while FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + unimplemented!(); + }, + ) + } + } + } +} + +fn keep_while_let() { + { + { + { + EvaluateJSReply::NumberValue( + while let Some(e) = FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + unimplemented!(); + }, + ) + } + } + } +} + +fn keep_match() { + { + { + EvaluateJSReply::NumberValue( + match FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + Ok(ConversionResult::Success(v)) => v, + _ => unreachable!(), + }, + ) + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3038.rs b/src/tools/rustfmt/tests/source/issue-3038.rs new file mode 100644 index 0000000000..0fbb05ddc0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3038.rs @@ -0,0 +1,20 @@ +impl HTMLTableElement { + fn func() { + if number_of_row_elements == 0 { + if let Some(last_tbody) = node.rev_children() + .filter_map(DomRoot::downcast::) + .find(|n| n.is::() && n.local_name() == &local_name!("tbody")) { + last_tbody.upcast::().AppendChild(new_row.upcast::()) + .expect("InsertRow failed to append first row."); + } + } + + if number_of_row_elements == 0 { + if let Some(last_tbody) = node + .find(|n| n.is::() && n.local_name() == &local_name!("tbody")) { + last_tbody.upcast::().AppendChild(new_row.upcast::()) + .expect("InsertRow failed to append first row."); + } + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3049.rs b/src/tools/rustfmt/tests/source/issue-3049.rs new file mode 100644 index 0000000000..43742683e4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3049.rs @@ -0,0 +1,45 @@ +// rustfmt-indent_style: Visual +fn main() { + something.aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .bench_function(|| { + let x = hello(); + }); + + something.aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .bench_function(arg, || { + let x = hello(); + }); + + something.aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .bench_function(arg, + || { + let x = hello(); + }, + arg); + + AAAAAAAAAAA.function(|| { + let _ = (); + }); + + AAAAAAAAAAA.chain().function(|| { + let _ = (); + }) +} diff --git a/src/tools/rustfmt/tests/source/issue-3055/original.rs b/src/tools/rustfmt/tests/source/issue-3055/original.rs new file mode 100644 index 0000000000..45e58473a4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3055/original.rs @@ -0,0 +1,43 @@ +// rustfmt-wrap_comments: true +// rustfmt-format_code_in_doc_comments: true + +/// Vestibulum elit nibh, rhoncus non, euismod sit amet, pretium eu, enim. Nunc commodo ultricies dui. +/// +/// Should not format with text attribute +/// ```text +/// .--------------. +/// | v +/// Park <- Idle -> Poll -> Probe -> Download -> Install -> Reboot +/// ^ ^ ' ' ' +/// ' ' ' ' ' +/// ' `--------' ' ' +/// `---------------' ' ' +/// `--------------------------' ' +/// `-------------------------------------' +/// ``` +/// +/// Should not format with ignore attribute +/// ```text +/// .--------------. +/// | v +/// Park <- Idle -> Poll -> Probe -> Download -> Install -> Reboot +/// ^ ^ ' ' ' +/// ' ' ' ' ' +/// ' `--------' ' ' +/// `---------------' ' ' +/// `--------------------------' ' +/// `-------------------------------------' +/// ``` +/// +/// Should format with rust attribute +/// ```rust +/// let x = +/// 42; +/// ``` +/// +/// Should format with no attribute as it defaults to rust +/// ``` +/// let x = +/// 42; +/// ``` +fn func() {} diff --git a/src/tools/rustfmt/tests/source/issue-3059.rs b/src/tools/rustfmt/tests/source/issue-3059.rs new file mode 100644 index 0000000000..49a75cd67a --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3059.rs @@ -0,0 +1,7 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 80 + +/// Vestibulum elit nibh, rhoncus non, euismod sit amet, pretium eu, enim. Nunc commodo ultricies dui. +/// Cras gravida rutrum massa. Donec accumsan mattis turpis. Quisque sem. Quisque elementum sapien +/// iaculis augue. In dui sem, congue sit amet, feugiat quis, lobortis at, eros. +fn func4() {} diff --git a/src/tools/rustfmt/tests/source/issue-3066.rs b/src/tools/rustfmt/tests/source/issue-3066.rs new file mode 100644 index 0000000000..4d1ece43de --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3066.rs @@ -0,0 +1,7 @@ +// rustfmt-indent_style: Visual +fn main() { + Struct { field: aaaaaaaaaaa }; + Struct { field: aaaaaaaaaaaa, }; + Struct { field: value, + field2: value2, }; +} diff --git a/src/tools/rustfmt/tests/source/issue-3131.rs b/src/tools/rustfmt/tests/source/issue-3131.rs new file mode 100644 index 0000000000..c4cb2d8c03 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3131.rs @@ -0,0 +1,8 @@ +fn main() { + match 3 { + t if match t { + _ => true, + } => {}, + _ => {} + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3153.rs b/src/tools/rustfmt/tests/source/issue-3153.rs new file mode 100644 index 0000000000..2836ce97cf --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3153.rs @@ -0,0 +1,9 @@ +// rustfmt-wrap_comments: true + +/// This may panic if: +/// - there are fewer than `max_header_bytes` bytes preceding the body +/// - there are fewer than `max_footer_bytes` bytes following the body +/// - the sum of the body bytes and post-body bytes is less than the sum +/// of `min_body_and_padding_bytes` and `max_footer_bytes` (in other +/// words, the minimum body and padding byte requirement is not met) +fn foo() {} diff --git a/src/tools/rustfmt/tests/source/issue-3194.rs b/src/tools/rustfmt/tests/source/issue-3194.rs new file mode 100644 index 0000000000..b80ce346b6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3194.rs @@ -0,0 +1,13 @@ +mod m { struct S where A: B; } + +mod n { struct Foo where A: B { foo: usize } } + +mod o { enum Bar where A: B { Bar } } + +mod with_comments { + mod m { struct S /* before where */ where A: B; /* after where */ } + + mod n { struct Foo /* before where */ where A: B /* after where */ { foo: usize } } + + mod o { enum Bar /* before where */ where A: B /* after where */ { Bar } } +} diff --git a/src/tools/rustfmt/tests/source/issue-3198.rs b/src/tools/rustfmt/tests/source/issue-3198.rs new file mode 100644 index 0000000000..48cb24a002 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3198.rs @@ -0,0 +1,99 @@ +impl TestTrait { + fn foo_one_pre(/* Important comment1 */ + self) { + } + + fn foo_one_post(self + /* Important comment1 */) { + } + + fn foo_pre( + /* Important comment1 */ + self, + /* Important comment2 */ + a: i32, + ) { + } + + fn foo_post( + self + /* Important comment1 */, + a: i32 + /* Important comment2 */, + ) { + } + + fn bar_pre( + /* Important comment1 */ + &mut self, + /* Important comment2 */ + a: i32, + ) { + } + + fn bar_post( + &mut self + /* Important comment1 */, + a: i32 + /* Important comment2 */, + ) { + } + + fn baz_pre( + /* Important comment1 */ + self: X< 'a , 'b >, + /* Important comment2 */ + a: i32, + ) { + } + + fn baz_post( + self: X< 'a , 'b > + /* Important comment1 */, + a: i32 + /* Important comment2 */, + ) { + } + + fn baz_tree_pre( + /* Important comment1 */ + self: X< 'a , 'b >, + /* Important comment2 */ + a: i32, + /* Important comment3 */ + b: i32, + ) { + } + + fn baz_tree_post( + self: X< 'a , 'b > + /* Important comment1 */, + a: i32 + /* Important comment2 */, + b: i32 + /* Important comment3 */,){ + } + + fn multi_line( + self: X<'a, 'b>, /* Important comment1-1 */ + /* Important comment1-2 */ + a: i32, /* Important comment2 */ + b: i32, /* Important comment3 */ + ) { + } + + fn two_line_comment( + self: X<'a, 'b>, /* Important comment1-1 + Important comment1-2 */ + a: i32, /* Important comment2 */ + b: i32, /* Important comment3 */ + ) { + } + + fn no_first_line_comment( + self: X<'a, 'b>, + /* Important comment2 */a: i32, + /* Important comment3 */b: i32, + ) { + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3213/version_one.rs b/src/tools/rustfmt/tests/source/issue-3213/version_one.rs new file mode 100644 index 0000000000..f9f4cab55e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3213/version_one.rs @@ -0,0 +1,9 @@ +// rustfmt-version: One + +fn foo() { + match 0 { + 0 => return AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + 1 => AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + _ => "", + }; +} diff --git a/src/tools/rustfmt/tests/source/issue-3213/version_two.rs b/src/tools/rustfmt/tests/source/issue-3213/version_two.rs new file mode 100644 index 0000000000..0f068c19d7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3213/version_two.rs @@ -0,0 +1,9 @@ +// rustfmt-version: Two + +fn foo() { + match 0 { + 0 => return AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + 1 => AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + _ => "", + }; +} diff --git a/src/tools/rustfmt/tests/source/issue-3217.rs b/src/tools/rustfmt/tests/source/issue-3217.rs new file mode 100644 index 0000000000..176c702002 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3217.rs @@ -0,0 +1,8 @@ +#![feature(label_break_value)] + +fn main() { + let mut res = 0; + 's_39: { if res == 0i32 { println!("Hello, world!"); } } + 's_40: loop { println!("res = {}", res); res += 1; if res == 3i32 { break 's_40; } } + let toto = || { if true { 42 } else { 24 } }; +} diff --git a/src/tools/rustfmt/tests/source/issue-3227/two.rs b/src/tools/rustfmt/tests/source/issue-3227/two.rs new file mode 100644 index 0000000000..c1572c00d5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3227/two.rs @@ -0,0 +1,13 @@ +// rustfmt-version: Two + +fn main() { + thread::spawn(|| { + while true { + println!("iteration"); + } + }); + + thread::spawn(|| loop { + println!("iteration"); + }); +} diff --git a/src/tools/rustfmt/tests/source/issue-3234.rs b/src/tools/rustfmt/tests/source/issue-3234.rs new file mode 100644 index 0000000000..120740a723 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3234.rs @@ -0,0 +1,14 @@ +macro_rules! fuzz_target { + (|$data:ident: &[u8]| $body:block) => {}; +} + +fuzz_target!(|data: &[u8]| { + + if let Ok(app_img) = AppImage::parse(data) { + if let Ok(app_img) = app_img.sign_for_secureboot(include_str!("../../test-data/signing-key")) { + assert!(app_img.is_signed()); + Gbl::from_app_image(app_img).to_bytes(); + } + } + +}); diff --git a/src/tools/rustfmt/tests/source/issue-3241.rs b/src/tools/rustfmt/tests/source/issue-3241.rs new file mode 100644 index 0000000000..090284a21f --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3241.rs @@ -0,0 +1,11 @@ +// rustfmt-edition: 2018 + +use ::ignore; +use ::ignore::some::more; +use ::{foo, bar}; +use ::*; +use ::baz::{foo, bar}; + +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/rustfmt/tests/source/issue-3253/bar.rs b/src/tools/rustfmt/tests/source/issue-3253/bar.rs new file mode 100644 index 0000000000..eaeffd3ad0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3253/bar.rs @@ -0,0 +1,4 @@ +// Empty + fn empty() { + +} diff --git a/src/tools/rustfmt/tests/source/issue-3253/foo.rs b/src/tools/rustfmt/tests/source/issue-3253/foo.rs new file mode 100644 index 0000000000..4ebe5326b3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3253/foo.rs @@ -0,0 +1,6 @@ +pub fn hello( ) + { +println!("Hello World!"); + + } + diff --git a/src/tools/rustfmt/tests/source/issue-3253/lib.rs b/src/tools/rustfmt/tests/source/issue-3253/lib.rs new file mode 100644 index 0000000000..3eef586bd6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3253/lib.rs @@ -0,0 +1,14 @@ +#[macro_use] +extern crate cfg_if; + +cfg_if! { + if #[cfg(target_family = "unix")] { + mod foo; + #[path = "paths/bar_foo.rs"] + mod bar_foo; + } else { + mod bar; + #[path = "paths/foo_bar.rs"] + mod foo_bar; + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3253/paths/bar_foo.rs b/src/tools/rustfmt/tests/source/issue-3253/paths/bar_foo.rs new file mode 100644 index 0000000000..da19f9dfaa --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3253/paths/bar_foo.rs @@ -0,0 +1,3 @@ +fn foo_decl_item(x: &mut i32) { + x = 3; +} diff --git a/src/tools/rustfmt/tests/source/issue-3253/paths/excluded.rs b/src/tools/rustfmt/tests/source/issue-3253/paths/excluded.rs new file mode 100644 index 0000000000..5c63eb8320 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3253/paths/excluded.rs @@ -0,0 +1,6 @@ +// This module is not imported in the cfg_if macro in lib.rs so it is ignored +// while the foo and bar mods are formatted. +// Check the corresponding file in tests/target/issue-3253/paths/excluded.rs +trait CoolerTypes { fn dummy(&self) { +} +} diff --git a/src/tools/rustfmt/tests/source/issue-3253/paths/foo_bar.rs b/src/tools/rustfmt/tests/source/issue-3253/paths/foo_bar.rs new file mode 100644 index 0000000000..fbb5d92c6e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3253/paths/foo_bar.rs @@ -0,0 +1,4 @@ + + +fn Foo() where T: Bar { +} diff --git a/src/tools/rustfmt/tests/source/issue-3265.rs b/src/tools/rustfmt/tests/source/issue-3265.rs new file mode 100644 index 0000000000..e927cf2be4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3265.rs @@ -0,0 +1,14 @@ +// rustfmt-newline_style: Windows +#[cfg(test)] +mod test { + summary_test! { + tokenize_recipe_interpolation_eol, + "foo: # some comment + {{hello}} +", + "foo: \ + {{hello}} \ +{{ahah}}", + "N:#$>^{N}$<.", + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3270/one.rs b/src/tools/rustfmt/tests/source/issue-3270/one.rs new file mode 100644 index 0000000000..3c2e27e229 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3270/one.rs @@ -0,0 +1,12 @@ +// rustfmt-version: One + +pub fn main() { + /* let s = String::from( + " +hello +world +", + ); */ + + assert_eq!(s, "\nhello\nworld\n"); +} diff --git a/src/tools/rustfmt/tests/source/issue-3270/two.rs b/src/tools/rustfmt/tests/source/issue-3270/two.rs new file mode 100644 index 0000000000..0eb756471e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3270/two.rs @@ -0,0 +1,12 @@ +// rustfmt-version: Two + +pub fn main() { + /* let s = String::from( + " +hello +world +", + ); */ + + assert_eq!(s, "\nhello\nworld\n"); +} diff --git a/src/tools/rustfmt/tests/source/issue-3272/v1.rs b/src/tools/rustfmt/tests/source/issue-3272/v1.rs new file mode 100644 index 0000000000..f4c1b7c992 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3272/v1.rs @@ -0,0 +1,15 @@ +// rustfmt-version: One + +fn main() { + assert!(HAYSTACK + .par_iter() + .find_any(|&&x| x[0] % 1000 == 999) + .is_some()); + + assert( + HAYSTACK + .par_iter() + .find_any(|&&x| x[0] % 1000 == 999) + .is_some(), + ); +} diff --git a/src/tools/rustfmt/tests/source/issue-3272/v2.rs b/src/tools/rustfmt/tests/source/issue-3272/v2.rs new file mode 100644 index 0000000000..0148368edc --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3272/v2.rs @@ -0,0 +1,15 @@ +// rustfmt-version: Two + +fn main() { + assert!(HAYSTACK + .par_iter() + .find_any(|&&x| x[0] % 1000 == 999) + .is_some()); + + assert( + HAYSTACK + .par_iter() + .find_any(|&&x| x[0] % 1000 == 999) + .is_some(), + ); +} diff --git a/src/tools/rustfmt/tests/source/issue-3278/version_one.rs b/src/tools/rustfmt/tests/source/issue-3278/version_one.rs new file mode 100644 index 0000000000..580679fbae --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3278/version_one.rs @@ -0,0 +1,8 @@ +// rustfmt-version: One + +pub fn parse_conditional<'a, I: 'a>( +) -> impl Parser + 'a +where + I: Stream, +{ +} diff --git a/src/tools/rustfmt/tests/source/issue-3278/version_two.rs b/src/tools/rustfmt/tests/source/issue-3278/version_two.rs new file mode 100644 index 0000000000..c17b1742d3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3278/version_two.rs @@ -0,0 +1,8 @@ +// rustfmt-version: Two + +pub fn parse_conditional<'a, I: 'a>() +-> impl Parser + 'a +where + I: Stream, +{ +} diff --git a/src/tools/rustfmt/tests/source/issue-3295/two.rs b/src/tools/rustfmt/tests/source/issue-3295/two.rs new file mode 100644 index 0000000000..0eaf022249 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3295/two.rs @@ -0,0 +1,13 @@ +// rustfmt-version: Two +pub enum TestEnum { + a, + b, +} + +fn the_test(input: TestEnum) { + match input { + TestEnum::a => String::from("aaa"), + TestEnum::b => String::from("this is a very very very very very very very very very very very very very very very ong string"), + + }; +} diff --git a/src/tools/rustfmt/tests/source/issue-3302.rs b/src/tools/rustfmt/tests/source/issue-3302.rs new file mode 100644 index 0000000000..c037584fd7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3302.rs @@ -0,0 +1,43 @@ +// rustfmt-version: Two + +macro_rules! moo1 { + () => { + bar! { +" +" + } + }; +} + +macro_rules! moo2 { + () => { + bar! { + " +" + } + }; +} + +macro_rules! moo3 { + () => { + 42 + /* + bar! { + " + toto +tata" + } + */ + }; +} + +macro_rules! moo4 { + () => { + bar! { +" + foo + bar +baz" + } + }; +} diff --git a/src/tools/rustfmt/tests/source/issue-3343.rs b/src/tools/rustfmt/tests/source/issue-3343.rs new file mode 100644 index 0000000000..5670b04f5d --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3343.rs @@ -0,0 +1,47 @@ +// rustfmt-inline_attribute_width: 50 + +#[cfg(feature = "alloc")] +use core::slice; + +#[cfg(feature = "alloc")] +use total_len_is::_50__; + +#[cfg(feature = "alloc")] +use total_len_is::_51___; + +#[cfg(feature = "alloc")] +extern crate len_is_50_; + +#[cfg(feature = "alloc")] +extern crate len_is_51__; + +/// this is a comment to test is_sugared_doc property +use core::convert; + +#[fooooo] +#[barrrrr] +use total_len_is_::_51______; + +#[cfg(not(all( + feature = "std", + any( + target_os = "linux", + target_os = "android", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "haiku", + target_os = "emscripten", + target_os = "solaris", + target_os = "cloudabi", + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", + target_os = "redox", + target_os = "fuchsia", + windows, + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), + ) +)))] +use core::slice; diff --git a/src/tools/rustfmt/tests/source/issue-3423.rs b/src/tools/rustfmt/tests/source/issue-3423.rs new file mode 100644 index 0000000000..fbe8e5c372 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3423.rs @@ -0,0 +1,5 @@ +/* a nice comment with a trailing whitespace */ +fn foo() {} + +/* a nice comment with a trailing tab */ +fn bar() {} diff --git a/src/tools/rustfmt/tests/source/issue-3434/lib.rs b/src/tools/rustfmt/tests/source/issue-3434/lib.rs new file mode 100644 index 0000000000..7e396b3838 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3434/lib.rs @@ -0,0 +1,57 @@ +#![rustfmt::skip::macros(skip_macro_mod)] + +mod no_entry; + +#[rustfmt::skip::macros(html, skip_macro)] +fn main() { + let macro_result1 = html! {
+this should be skipped
+ } + .to_string(); + + let macro_result2 = not_skip_macro! {
+this should be mangled
+ } + .to_string(); + + skip_macro! { +this should be skipped +}; + + foo(); +} + +fn foo() { + let macro_result1 = html! {
+this should be mangled
+ } + .to_string(); +} + +fn bar() { + let macro_result1 = skip_macro_mod! {
+this should be skipped
+ } + .to_string(); +} + +fn visitor_made_from_same_context() { + let pair = ( + || { + foo!(
+this should be mangled
+ ); + skip_macro_mod!(
+this should be skipped
+ ); + }, + || { + foo!(
+this should be mangled
+ ); + skip_macro_mod!(
+this should be skipped
+ ); + }, + ); +} diff --git a/src/tools/rustfmt/tests/source/issue-3434/no_entry.rs b/src/tools/rustfmt/tests/source/issue-3434/no_entry.rs new file mode 100644 index 0000000000..0838829fed --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3434/no_entry.rs @@ -0,0 +1,18 @@ +#[rustfmt::skip::macros(another_macro)] +fn foo() { + another_macro!( +This should be skipped. + ); +} + +fn bar() { + skip_macro_mod!( +This should be skipped. + ); +} + +fn baz() { + let macro_result1 = no_skip_macro! {
+this should be mangled
+ }.to_string(); +} diff --git a/src/tools/rustfmt/tests/source/issue-3434/not_skip_macro.rs b/src/tools/rustfmt/tests/source/issue-3434/not_skip_macro.rs new file mode 100644 index 0000000000..1d7d73c523 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3434/not_skip_macro.rs @@ -0,0 +1,8 @@ +#[this::is::not::skip::macros(ouch)] + +fn main() { + let macro_result1 = ouch! {
+this should be mangled
+ } + .to_string(); +} diff --git a/src/tools/rustfmt/tests/source/issue-3465.rs b/src/tools/rustfmt/tests/source/issue-3465.rs new file mode 100644 index 0000000000..0bc95ad461 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3465.rs @@ -0,0 +1,42 @@ +fn main() { + ((((((((((((((((((((((((((((((((((((((((((0) + 1) + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1); +} diff --git a/src/tools/rustfmt/tests/source/issue-3494/crlf.rs b/src/tools/rustfmt/tests/source/issue-3494/crlf.rs new file mode 100644 index 0000000000..9ce457c7b0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3494/crlf.rs @@ -0,0 +1,8 @@ +// rustfmt-file_lines: [{"file":"tests/source/issue-3494/crlf.rs","range":[4,5]}] + +pub fn main() +{ +let world1 = "world"; println!("Hello, {}!", world1); +let world2 = "world"; println!("Hello, {}!", world2); +let world3 = "world"; println!("Hello, {}!", world3); +} diff --git a/src/tools/rustfmt/tests/source/issue-3494/lf.rs b/src/tools/rustfmt/tests/source/issue-3494/lf.rs new file mode 100644 index 0000000000..bdbe69cef4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3494/lf.rs @@ -0,0 +1,8 @@ +// rustfmt-file_lines: [{"file":"tests/source/issue-3494/lf.rs","range":[4,5]}] + +pub fn main() +{ +let world1 = "world"; println!("Hello, {}!", world1); +let world2 = "world"; println!("Hello, {}!", world2); +let world3 = "world"; println!("Hello, {}!", world3); +} diff --git a/src/tools/rustfmt/tests/source/issue-3508.rs b/src/tools/rustfmt/tests/source/issue-3508.rs new file mode 100644 index 0000000000..821e947c70 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3508.rs @@ -0,0 +1,29 @@ +fn foo(foo2: F) +where + F: Fn( + // this comment is deleted +), +{ +} + +fn foo_block(foo2: F) +where + F: Fn( + /* this comment is deleted */ + ), +{ +} + +fn bar( + bar2: impl Fn( + // this comment is deleted + ), +) { +} + +fn bar_block( + bar2: impl Fn( + /* this comment is deleted */ + ), +) { +} diff --git a/src/tools/rustfmt/tests/source/issue-3515.rs b/src/tools/rustfmt/tests/source/issue-3515.rs new file mode 100644 index 0000000000..9f760cb94e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3515.rs @@ -0,0 +1,6 @@ +// rustfmt-reorder_imports: false + +use std :: fmt :: { self , Display } ; +use std :: collections :: HashMap ; + +fn main() {} diff --git a/src/tools/rustfmt/tests/source/issue-3532.rs b/src/tools/rustfmt/tests/source/issue-3532.rs new file mode 100644 index 0000000000..ec0c01610c --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3532.rs @@ -0,0 +1,7 @@ +fn foo(a: T) { + match a { +1 => {} + 0 => {} + // _ => panic!("doesn't format!"), + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3585/extern_crate.rs b/src/tools/rustfmt/tests/source/issue-3585/extern_crate.rs new file mode 100644 index 0000000000..6716983ba0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3585/extern_crate.rs @@ -0,0 +1,12 @@ +// rustfmt-inline_attribute_width: 100 + +#[macro_use] +extern crate static_assertions; + +#[cfg(unix)] +extern crate static_assertions; + +// a comment before the attribute +#[macro_use] +// some comment after +extern crate static_assertions; diff --git a/src/tools/rustfmt/tests/source/issue-3585/reorder_imports_disabled.rs b/src/tools/rustfmt/tests/source/issue-3585/reorder_imports_disabled.rs new file mode 100644 index 0000000000..45b1bb9fdd --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3585/reorder_imports_disabled.rs @@ -0,0 +1,12 @@ +// rustfmt-inline_attribute_width: 100 +// rustfmt-reorder_imports: false + +#[cfg(unix)] +extern crate crateb; +#[cfg(unix)] +extern crate cratea; + +#[cfg(unix)] +use crateb; +#[cfg(unix)] +use cratea; diff --git a/src/tools/rustfmt/tests/source/issue-3585/reorder_imports_enabled.rs b/src/tools/rustfmt/tests/source/issue-3585/reorder_imports_enabled.rs new file mode 100644 index 0000000000..9f433e5ca2 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3585/reorder_imports_enabled.rs @@ -0,0 +1,12 @@ +// rustfmt-inline_attribute_width: 100 +// rustfmt-reorder_imports: true + +#[cfg(unix)] +extern crate crateb; +#[cfg(unix)] +extern crate cratea; + +#[cfg(unix)] +use crateb; +#[cfg(unix)] +use cratea; diff --git a/src/tools/rustfmt/tests/source/issue-3585/use.rs b/src/tools/rustfmt/tests/source/issue-3585/use.rs new file mode 100644 index 0000000000..e71ba9008c --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3585/use.rs @@ -0,0 +1,7 @@ +// rustfmt-inline_attribute_width: 100 + +#[macro_use] +use static_assertions; + +#[cfg(unix)] +use static_assertions; diff --git a/src/tools/rustfmt/tests/source/issue-3636.rs b/src/tools/rustfmt/tests/source/issue-3636.rs new file mode 100644 index 0000000000..edfa03012f --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3636.rs @@ -0,0 +1,10 @@ +// rustfmt-file_lines: [{"file":"tests/source/issue-3636.rs","range":[4,7]},{"file":"tests/target/issue-3636.rs","range":[3,6]}] + +fn foo() { + let x = + 42; + let y = + 42; + let z = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + let z = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; +} diff --git a/src/tools/rustfmt/tests/source/issue-3639.rs b/src/tools/rustfmt/tests/source/issue-3639.rs new file mode 100644 index 0000000000..7b16b2dfde --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3639.rs @@ -0,0 +1,5 @@ +trait Foo where {} +struct Bar where {} +struct Bax where; +struct Baz(String) where; +impl<> Foo<> for Bar<> where {} diff --git a/src/tools/rustfmt/tests/source/issue-3651.rs b/src/tools/rustfmt/tests/source/issue-3651.rs new file mode 100644 index 0000000000..c153e99d0f --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3651.rs @@ -0,0 +1,4 @@ +fn f() -> Box< + dyn FnMut() -> Thing< WithType = LongItemName, Error = LONGLONGLONGLONGLONGONGEvenLongerErrorNameLongerLonger>, +>{ +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue-3665/lib.rs b/src/tools/rustfmt/tests/source/issue-3665/lib.rs new file mode 100644 index 0000000000..e049fbc568 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3665/lib.rs @@ -0,0 +1,33 @@ +#![rustfmt::skip::attributes(skip_mod_attr)] + +mod sub_mod; + +#[rustfmt::skip::attributes(other, skip_attr)] +fn main() { + #[other(should, +skip, + this, format)] + struct S {} + + #[skip_attr(should, skip, +this, format,too)] + fn doesnt_mater() {} + + #[skip_mod_attr(should, skip, +this, format, + enerywhere)] + fn more() {} + + #[not_skip(not, +skip, me)] + struct B {} +} + +#[other(should, not, skip, +this, format, here)] +fn foo() {} + +#[skip_mod_attr(should, skip, +this, format,in, master, + and, sub, module)] +fn bar() {} diff --git a/src/tools/rustfmt/tests/source/issue-3665/not_skip_attribute.rs b/src/tools/rustfmt/tests/source/issue-3665/not_skip_attribute.rs new file mode 100644 index 0000000000..14985259a5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3665/not_skip_attribute.rs @@ -0,0 +1,4 @@ +#![this::is::not::skip::attribute(ouch)] + +#[ouch(not, skip, me)] +fn main() {} diff --git a/src/tools/rustfmt/tests/source/issue-3665/sub_mod.rs b/src/tools/rustfmt/tests/source/issue-3665/sub_mod.rs new file mode 100644 index 0000000000..75fb24b4a6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3665/sub_mod.rs @@ -0,0 +1,14 @@ +#[rustfmt::skip::attributes(more_skip)] +#[more_skip(should, + skip, +this, format)] +fn foo() {} + +#[skip_mod_attr(should, skip, +this, format,in, master, + and, sub, module)] +fn bar() {} + +#[skip_attr(should, not, + skip, this, attribute, here)] +fn baz() {} diff --git a/src/tools/rustfmt/tests/source/issue-3672.rs b/src/tools/rustfmt/tests/source/issue-3672.rs new file mode 100644 index 0000000000..82616bd425 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3672.rs @@ -0,0 +1,4 @@ +fn main() { + let x = 5;; + +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue-3675.rs b/src/tools/rustfmt/tests/source/issue-3675.rs new file mode 100644 index 0000000000..f16efb2dca --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3675.rs @@ -0,0 +1,5 @@ + fn main() { + println!("{}" + // comment + , 111); + } \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue-3701/one.rs b/src/tools/rustfmt/tests/source/issue-3701/one.rs new file mode 100644 index 0000000000..a7f0bd3aa1 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3701/one.rs @@ -0,0 +1,12 @@ +// rustfmt-version: One + +fn build_sorted_static_get_entry_names( + mut entries: Vec<(u8, &'static str)>, +) -> (impl Fn( + AlphabeticalTraversal, + Box>, +) -> BoxFuture<'static, Result, Status>> + + Send + + Sync + + 'static) { +} diff --git a/src/tools/rustfmt/tests/source/issue-3701/two.rs b/src/tools/rustfmt/tests/source/issue-3701/two.rs new file mode 100644 index 0000000000..8e15c58b8b --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3701/two.rs @@ -0,0 +1,12 @@ +// rustfmt-version: Two + +fn build_sorted_static_get_entry_names( + mut entries: Vec<(u8, &'static str)>, +) -> (impl Fn( + AlphabeticalTraversal, + Box>, +) -> BoxFuture<'static, Result, Status>> + + Send + + Sync + + 'static) { +} diff --git a/src/tools/rustfmt/tests/source/issue-3709.rs b/src/tools/rustfmt/tests/source/issue-3709.rs new file mode 100644 index 0000000000..73c2a624e5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3709.rs @@ -0,0 +1,10 @@ +// rustfmt-edition: 2018 + +macro_rules! token { + ($t:tt) => {}; +} + +fn main() { + token!(dyn); + token!(dyn ); +} diff --git a/src/tools/rustfmt/tests/source/issue-3740.rs b/src/tools/rustfmt/tests/source/issue-3740.rs new file mode 100644 index 0000000000..2769a8cc9b --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3740.rs @@ -0,0 +1,10 @@ +impl IntoNormalized for Vector + where + Vector: Div>, + for<'a> &'a Vector: IntoLength, +{ + type Output = Vector; + fn into_normalized(self) -> Self::Output { + + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3750.rs b/src/tools/rustfmt/tests/source/issue-3750.rs new file mode 100644 index 0000000000..1189a99d2b --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3750.rs @@ -0,0 +1,16 @@ +// rustfmt-imports_granularity: Crate + +pub mod foo { + pub mod bar { + pub struct Bar; + } + + pub fn bar() {} +} + +use foo::bar; +use foo::bar::Bar; + +fn main() { + bar(); +} diff --git a/src/tools/rustfmt/tests/source/issue-3751.rs b/src/tools/rustfmt/tests/source/issue-3751.rs new file mode 100644 index 0000000000..1343f80e66 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3751.rs @@ -0,0 +1,10 @@ +// rustfmt-format_code_in_doc_comments: true + +//! Empty pound line +//! +//! ```rust +//! # +//! # fn main() { +//! foo ( ) ; +//! # } +//! ``` diff --git a/src/tools/rustfmt/tests/source/issue-3779/ice.rs b/src/tools/rustfmt/tests/source/issue-3779/ice.rs new file mode 100644 index 0000000000..cde21412d9 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3779/ice.rs @@ -0,0 +1,3 @@ +pub fn bar() { + 1x; +} diff --git a/src/tools/rustfmt/tests/source/issue-3779/lib.rs b/src/tools/rustfmt/tests/source/issue-3779/lib.rs new file mode 100644 index 0000000000..16e9d48337 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3779/lib.rs @@ -0,0 +1,9 @@ +// rustfmt-unstable: true +// rustfmt-config: issue-3779.toml + +#[path = "ice.rs"] +mod ice; + +fn foo() { +println!("abc") ; + } diff --git a/src/tools/rustfmt/tests/source/issue-3786.rs b/src/tools/rustfmt/tests/source/issue-3786.rs new file mode 100644 index 0000000000..54f8211ed4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3786.rs @@ -0,0 +1,12 @@ +fn main() { + let _ = +r#" +this is a very long string exceeded maximum width in this case maximum 100. (current this line width is about 115) +"#; + + let _with_newline = + +r#" +this is a very long string exceeded maximum width in this case maximum 100. (current this line width is about 115) +"#; +} diff --git a/src/tools/rustfmt/tests/source/issue-3787.rs b/src/tools/rustfmt/tests/source/issue-3787.rs new file mode 100644 index 0000000000..bcdc131a05 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3787.rs @@ -0,0 +1,9 @@ +// rustfmt-wrap_comments: true + +//! URLs in items +//! * [This is a link with a very loooooooooooooooooooooooooooooooooooooooooong URL.](https://example.com/This/is/a/link/with/a/very/loooooooooooooooooooooooooooooooooooooooooong/URL) +//! * This is a [link](https://example.com/This/is/a/link/with/a/very/loooooooooooooooooooooooooooooooooooooooooong/URL) with a very loooooooooooooooooooooooooooooooooooooooooong URL. +//! * there is no link here: In hac habitasse platea dictumst. Maecenas in ligula. Duis tincidunt odio sollicitudin quam. Nullam non mauris. Phasellus lacinia, velit sit amet bibendum euismod, leo diam interdum ligula, eu scelerisque sem purus in tellus. +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/rustfmt/tests/source/issue-3840/version-one_hard-tabs.rs b/src/tools/rustfmt/tests/source/issue-3840/version-one_hard-tabs.rs new file mode 100644 index 0000000000..bf7ea7da0e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3840/version-one_hard-tabs.rs @@ -0,0 +1,15 @@ +// rustfmt-hard_tabs: true + +impl + FromEvent, A: Widget2, B: Widget2, C: for<'a> CtxFamily<'a>> Widget2 for WidgetEventLifter +{ + type Ctx = C; + type Event = Vec; +} + +mod foo { + impl + FromEvent, A: Widget2, B: Widget2, C: for<'a> CtxFamily<'a>> Widget2 for WidgetEventLifter + { + type Ctx = C; + type Event = Vec; + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3840/version-one_soft-tabs.rs b/src/tools/rustfmt/tests/source/issue-3840/version-one_soft-tabs.rs new file mode 100644 index 0000000000..3fc26224d5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3840/version-one_soft-tabs.rs @@ -0,0 +1,13 @@ +impl + FromEvent, A: Widget2, B: Widget2, C: for<'a> CtxFamily<'a>> Widget2 for WidgetEventLifter +{ + type Ctx = C; + type Event = Vec; +} + +mod foo { + impl + FromEvent, A: Widget2, B: Widget2, C: for<'a> CtxFamily<'a>> Widget2 for WidgetEventLifter + { + type Ctx = C; + type Event = Vec; + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3840/version-two_hard-tabs.rs b/src/tools/rustfmt/tests/source/issue-3840/version-two_hard-tabs.rs new file mode 100644 index 0000000000..7b505fda87 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3840/version-two_hard-tabs.rs @@ -0,0 +1,16 @@ +// rustfmt-hard_tabs: true +// rustfmt-version: Two + +impl + FromEvent, A: Widget2, B: Widget2, C: for<'a> CtxFamily<'a>> Widget2 for WidgetEventLifter +{ + type Ctx = C; + type Event = Vec; +} + +mod foo { + impl + FromEvent, A: Widget2, B: Widget2, C: for<'a> CtxFamily<'a>> Widget2 for WidgetEventLifter + { + type Ctx = C; + type Event = Vec; + } +} diff --git a/src/tools/rustfmt/tests/source/issue-3840/version-two_soft-tabs.rs b/src/tools/rustfmt/tests/source/issue-3840/version-two_soft-tabs.rs new file mode 100644 index 0000000000..39c8ef3129 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3840/version-two_soft-tabs.rs @@ -0,0 +1,15 @@ +// rustfmt-version: Two + +impl + FromEvent, A: Widget2, B: Widget2, C: for<'a> CtxFamily<'a>> Widget2 for WidgetEventLifter +{ + type Ctx = C; + type Event = Vec; +} + +mod foo { + impl + FromEvent, A: Widget2, B: Widget2, C: for<'a> CtxFamily<'a>> Widget2 for WidgetEventLifter + { + type Ctx = C; + type Event = Vec; + } +} diff --git a/src/tools/rustfmt/tests/source/issue-4018.rs b/src/tools/rustfmt/tests/source/issue-4018.rs new file mode 100644 index 0000000000..9a91dd9a30 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4018.rs @@ -0,0 +1,13 @@ +fn main() { + ; + /* extra comment */ ; +} + +fn main() { + println!(""); + // comment 1 + // comment 2 + // comment 3 + // comment 4 + ; +} diff --git a/src/tools/rustfmt/tests/source/issue-4079.rs b/src/tools/rustfmt/tests/source/issue-4079.rs new file mode 100644 index 0000000000..eb1ce5ed2a --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4079.rs @@ -0,0 +1,8 @@ +// rustfmt-wrap_comments: true + +/*! + * Lorem ipsum dolor sit amet, consectetur adipiscing elit. In lacinia + * ullamcorper lorem, non hendrerit enim convallis ut. Curabitur id sem volutpat + */ + +/*! Lorem ipsum dolor sit amet, consectetur adipiscing elit. In lacinia ullamcorper lorem, non hendrerit enim convallis ut. Curabitur id sem volutpat */ diff --git a/src/tools/rustfmt/tests/source/issue-4120.rs b/src/tools/rustfmt/tests/source/issue-4120.rs new file mode 100644 index 0000000000..c9ce838c51 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4120.rs @@ -0,0 +1,85 @@ +fn main() { + let x = if true { + 1 + // In if + } else { + 0 + // In else + }; + + let x = if true { + 1 + /* In if */ + } else { + 0 + /* In else */ + }; + + let z = if true { + if true { + 1 + + // In if level 2 + } else { + 2 + } + } else { + 3 + }; + + let a = if true { + 1 + // In if + } else { + 0 + // In else + }; + + let a = if true { + 1 + + // In if + } else { + 0 + // In else + }; + + let b = if true { + 1 + + // In if + } else { + 0 + // In else + }; + + let c = if true { + 1 + + // In if + } else { + 0 + // In else + }; + for i in 0..2 { + println!("Something"); + // In for + } + + for i in 0..2 { + println!("Something"); + /* In for */ + } + + extern "C" { + fn first(); + + // In foreign mod + } + + extern "C" { + fn first(); + + /* In foreign mod */ + } +} diff --git a/src/tools/rustfmt/tests/source/issue-4243.rs b/src/tools/rustfmt/tests/source/issue-4243.rs new file mode 100644 index 0000000000..d8a27f7a4a --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4243.rs @@ -0,0 +1,21 @@ +fn main() { + type A: AA /*AA*/ + /*AB*/ AB ++ AC = AA +/*AA*/ + + /*AB*/ AB+AC; + + type B: BA /*BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA*/+/*BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*/ BB + + BC = BA /*BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA*/ + /*BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*/ BB+ BC; + + type C: CA // CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +// CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + + // CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + // CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + CB + CC = CA // CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + // CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + + // CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + // CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + CB+ CC; +} diff --git a/src/tools/rustfmt/tests/source/issue-4244.rs b/src/tools/rustfmt/tests/source/issue-4244.rs new file mode 100644 index 0000000000..34b51085e1 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4244.rs @@ -0,0 +1,16 @@ +pub struct SS {} + +pub type A /* A Comment */ = SS; + +pub type B // Comment +// B += SS; + +pub type C + /* Comment C */ = SS; + +pub trait D { + type E /* Comment E */ = SS; +} + +type F<'a: 'static, T: Ord + 'static>: Eq + PartialEq where T: 'static + Copy /* x */ = Vec; diff --git a/src/tools/rustfmt/tests/source/issue-4245.rs b/src/tools/rustfmt/tests/source/issue-4245.rs new file mode 100644 index 0000000000..57d7e192d0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4245.rs @@ -0,0 +1,26 @@ + + +fn a(a: & // Comment + // Another comment + 'a File) {} + +fn b(b: & /* Another Comment */'a File) {} + +fn c(c: &'a /*Comment */ mut /*Comment */ File){} + +fn d(c: & // Comment +'b // Multi Line +// Comment +mut // Multi Line +// Comment +File +) {} + +fn e(c: &// Comment +File) {} + +fn d(c: &// Comment +mut // Multi Line +// Comment +File +) {} diff --git a/src/tools/rustfmt/tests/source/issue-4382.rs b/src/tools/rustfmt/tests/source/issue-4382.rs new file mode 100644 index 0000000000..cbf0c4ed6d --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4382.rs @@ -0,0 +1,4 @@ +pub const NAME_MAX: usize = { + #[cfg(target_os = "linux")] { 1024 } + #[cfg(target_os = "freebsd")] { 255 } +}; diff --git a/src/tools/rustfmt/tests/source/issue-4398.rs b/src/tools/rustfmt/tests/source/issue-4398.rs new file mode 100644 index 0000000000..b0095aaac7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4398.rs @@ -0,0 +1,19 @@ +impl Struct { + /// Documentation for `foo` + #[rustfmt::skip] // comment on why use a skip here + pub fn foo(&self) {} +} + +impl Struct { + /// Documentation for `foo` + #[rustfmt::skip] // comment on why use a skip here + pub fn foo(&self) {} +} + +/// Documentation for `Struct` +#[rustfmt::skip] // comment +impl Struct { + /// Documentation for `foo` + #[rustfmt::skip] // comment on why use a skip here + pub fn foo(&self) {} +} diff --git a/src/tools/rustfmt/tests/source/issue-447.rs b/src/tools/rustfmt/tests/source/issue-447.rs new file mode 100644 index 0000000000..7c542cb58c --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-447.rs @@ -0,0 +1,39 @@ +// rustfmt-normalize_comments: true + +fn main() { + if /* shouldn't be dropped + shouldn't be dropped */ + + cond /* shouldn't be dropped + shouldn't be dropped */ + + { + } /* shouldn't be dropped + shouldn't be dropped */ + + else /* shouldn't be dropped + shouldn't be dropped */ + + if /* shouldn't be dropped + shouldn't be dropped */ + + cond /* shouldn't be dropped + shouldn't be dropped */ + + { + } /* shouldn't be dropped + shouldn't be dropped */ + + else /* shouldn't be dropped + shouldn't be dropped */ + + { + } + + if /* shouldn't be dropped + shouldn't be dropped */ + let Some(x) = y/* shouldn't be dropped + shouldn't be dropped */ + { + } +} diff --git a/src/tools/rustfmt/tests/source/issue-4577.rs b/src/tools/rustfmt/tests/source/issue-4577.rs new file mode 100644 index 0000000000..79975dd73f --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4577.rs @@ -0,0 +1,20 @@ +fn main() { + let s: String = "ABAABBAA".chars() + .filter(|c| { + if *c == 'A' { + true + } + else { + false + } + }) + .map(|c| -> char { + if c == 'A' { + '0' + } else { + '1' + } + }).collect(); + + println!("{}", s); +} diff --git a/src/tools/rustfmt/tests/source/issue-4646.rs b/src/tools/rustfmt/tests/source/issue-4646.rs new file mode 100644 index 0000000000..ee0f23220c --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4646.rs @@ -0,0 +1,20 @@ +trait Foo { + fn bar(&self) + // where + // Self: Bar + ; +} + +trait Foo { + fn bar(&self) + // where + // Self: Bar +; +} + +trait Foo { + fn bar(&self) + // where + // Self: Bar + ; +} diff --git a/src/tools/rustfmt/tests/source/issue-4656/format_me_please.rs b/src/tools/rustfmt/tests/source/issue-4656/format_me_please.rs new file mode 100644 index 0000000000..7de7530164 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4656/format_me_please.rs @@ -0,0 +1,2 @@ + +pub fn hello( ) { } diff --git a/src/tools/rustfmt/tests/source/issue-4656/lib.rs b/src/tools/rustfmt/tests/source/issue-4656/lib.rs new file mode 100644 index 0000000000..5dac91b8aa --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4656/lib.rs @@ -0,0 +1,7 @@ +extern crate cfg_if; + +cfg_if::cfg_if! { + if #[cfg(target_family = "unix")] { + mod format_me_please; + } +} diff --git a/src/tools/rustfmt/tests/source/issue-4656/lib2.rs b/src/tools/rustfmt/tests/source/issue-4656/lib2.rs new file mode 100644 index 0000000000..b17fffc58e --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4656/lib2.rs @@ -0,0 +1,3 @@ +its_a_macro! { + // Contents +} diff --git a/src/tools/rustfmt/tests/source/issue-510.rs b/src/tools/rustfmt/tests/source/issue-510.rs new file mode 100644 index 0000000000..4c60859e6c --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-510.rs @@ -0,0 +1,37 @@ +impl ISizeAndMarginsComputer for AbsoluteNonReplaced { +fn solve_inline_size_constraints(&self, +block: &mut BlockFlow, +input: &ISizeConstraintInput) +-> ISizeConstraintSolution { +let (inline_start,inline_size,margin_inline_start,margin_inline_end) = +match (inline_startssssssxxxxxxsssssxxxxxxxxxssssssxxx,inline_startssssssxxxxxxsssssxxxxxxxxxssssssxxx) { +(MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { +let margin_start = inline_start_margin.specified_or_zero(); +let margin_end = inline_end_margin.specified_or_zero(); +// Now it is the same situation as inline-start Specified and inline-end +// and inline-size Auto. +// +// Set inline-end to zero to calculate inline-size. +let inline_size = block.get_shrink_to_fit_inline_size(available_inline_size - +(margin_start + margin_end)); +(Au(0), inline_size, margin_start, margin_end) +} +}; + + let (inline_start, inline_size, margin_inline_start, margin_inline_end) = + match (inline_start, inline_end, computed_inline_size) { + (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + // Now it is the same situation as inline-start Specified and inline-end + // and inline-size Auto. + // + // Set inline-end to zero to calculate inline-size. + let inline_size = + block.get_shrink_to_fit_inline_size(available_inline_size - + (margin_start + margin_end)); + (Au(0), inline_size, margin_start, margin_end) + } + }; +} +} diff --git a/src/tools/rustfmt/tests/source/issue-539.rs b/src/tools/rustfmt/tests/source/issue-539.rs new file mode 100644 index 0000000000..d70682e3be --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-539.rs @@ -0,0 +1,5 @@ +// rustfmt-normalize_comments: true +/* + FIXME (#3300): Should allow items to be anonymous. Right now + we just use dummy names for anon items. + */ diff --git a/src/tools/rustfmt/tests/source/issue-683.rs b/src/tools/rustfmt/tests/source/issue-683.rs new file mode 100644 index 0000000000..fd99015ea5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-683.rs @@ -0,0 +1,5 @@ +// rustfmt-normalize_comments: true +/* + * FIXME (#3300): Should allow items to be anonymous. Right now + * we just use dummy names for anon items. + */ diff --git a/src/tools/rustfmt/tests/source/issue-811.rs b/src/tools/rustfmt/tests/source/issue-811.rs new file mode 100644 index 0000000000..b7a89b5d0f --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-811.rs @@ -0,0 +1,19 @@ +trait FooTrait: Sized { + type Bar: BarTrait; +} + +trait BarTrait: Sized { + type Baz; + fn foo(); +} + +type Foo = <>::Bar as BarTrait>::Baz; +type Bar = >::Baz; + +fn some_func, U>() { + <>::Bar as BarTrait>::foo(); +} + +fn some_func>() { + >::foo(); +} diff --git a/src/tools/rustfmt/tests/source/issue-850.rs b/src/tools/rustfmt/tests/source/issue-850.rs new file mode 100644 index 0000000000..c939716a6a --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-850.rs @@ -0,0 +1 @@ +const unsafe fn x() {} diff --git a/src/tools/rustfmt/tests/source/issue-855.rs b/src/tools/rustfmt/tests/source/issue-855.rs new file mode 100644 index 0000000000..8f33fa685a --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-855.rs @@ -0,0 +1,20 @@ +fn main() { + 'running: loop { + for event in event_pump.poll_iter() { + match event { + Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => break 'running, + } + } + } +} + +fn main2() { + 'running: loop { + for event in event_pump.poll_iter() { + match event { + Event::Quit {..} | + Event::KeyDownXXXXXXXXXXXXX { keycode: Some(Keycode::Escape), .. } => break 'running, + } + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-913.rs b/src/tools/rustfmt/tests/source/issue-913.rs new file mode 100644 index 0000000000..25b9d42fd4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-913.rs @@ -0,0 +1,20 @@ +mod client { + impl Client { + fn test(self) -> Result<()> { + let next_state = match self.state { + State::V5(v5::State::Command(v5::coand::State::WriteVersion(ref mut response))) => { + let x = reformat . meeee() ; + } + }; + + let next_state = match self.state { + State::V5(v5::State::Command(v5::comand::State::WriteVersion(ref mut response))) => { + // The pattern cannot be formatted in a way that the match stays + // within the column limit. The rewrite should therefore be + // skipped. + let x = dont . reformat . meeee(); + } + }; + } + } +} diff --git a/src/tools/rustfmt/tests/source/issue-945.rs b/src/tools/rustfmt/tests/source/issue-945.rs new file mode 100644 index 0000000000..37d703c467 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-945.rs @@ -0,0 +1,5 @@ +impl Bar { default const unsafe fn foo() { "hi" } } + +impl Baz { default unsafe extern "C" fn foo() { "hi" } } + +impl Foo for Bar { default fn foo() { "hi" } } diff --git a/src/tools/rustfmt/tests/source/issue-977.rs b/src/tools/rustfmt/tests/source/issue-977.rs new file mode 100644 index 0000000000..fe16387b7b --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-977.rs @@ -0,0 +1,7 @@ +// rustfmt-normalize_comments: true + +trait NameC { /* comment */ } +struct FooC { /* comment */ } +enum MooC { /* comment */ } +mod BarC { /* comment */ } +extern { /* comment */ } diff --git a/src/tools/rustfmt/tests/source/issue_3839.rs b/src/tools/rustfmt/tests/source/issue_3839.rs new file mode 100644 index 0000000000..3933d31ee1 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_3839.rs @@ -0,0 +1,8 @@ +struct Foo { + a: i32, +/* +asd +*/ + // foo +b: i32, +} diff --git a/src/tools/rustfmt/tests/source/issue_3844.rs b/src/tools/rustfmt/tests/source/issue_3844.rs new file mode 100644 index 0000000000..15441b2b05 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_3844.rs @@ -0,0 +1,3 @@ +fn main() { +|| {{}}; +} diff --git a/src/tools/rustfmt/tests/source/issue_3853.rs b/src/tools/rustfmt/tests/source/issue_3853.rs new file mode 100644 index 0000000000..c41309bc78 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_3853.rs @@ -0,0 +1,52 @@ +fn by_ref_with_block_before_ident() { +if let Some(ref /*def*/ state)= foo{ + println!( + "asdfasdfasdf"); } +} + +fn mut_block_before_ident() { +if let Some(mut /*def*/ state ) =foo{ + println!( + "123" ); } +} + +fn ref_and_mut_blocks_before_ident() { +if let Some(ref /*abc*/ + mut /*def*/ state ) = foo { + println!( + "deefefefefefwea" ); } +} + +fn sub_pattern() { + let foo @ /*foo*/ +bar(f) = 42; +} + +fn no_prefix_block_before_ident() { +if let Some( + /*def*/ state ) = foo { + println!( + "129387123123" ); } +} + +fn issue_3853() { +if let Some(ref /*mut*/ state) = foo { + } +} + +fn double_slash_comment_between_lhs_and_rhs() { + if let Some(e) = + // self.foo.bar(e, tx) + packet.transaction.state.committed + { + // body + println!( + "a2304712836123"); + } +} + +fn block_comment_between_lhs_and_rhs() { +if let Some(ref /*def*/ mut /*abc*/ state)= /*abc*/foo{ + println!( + "asdfasdfasdf"); } +} diff --git a/src/tools/rustfmt/tests/source/issue_3868.rs b/src/tools/rustfmt/tests/source/issue_3868.rs new file mode 100644 index 0000000000..6c46c3c9e1 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_3868.rs @@ -0,0 +1,13 @@ +fn foo() { + ; +} + +fn bar() { + for _ in 0..1 { + ; + } +} + +fn baz() { + (); + } \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue_4057.rs b/src/tools/rustfmt/tests/source/issue_4057.rs new file mode 100644 index 0000000000..7cd80734bd --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_4057.rs @@ -0,0 +1,15 @@ +// rustfmt-format_code_in_doc_comments: true + +/// ``` +/// # #[rustversion::since(1.36)] +/// # fn dox() { +/// # use std::pin::Pin; +/// # type Projection<'a> = &'a (); +/// # type ProjectionRef<'a> = &'a (); +/// # trait Dox { +/// fn project_ex (self: Pin<&mut Self>) -> Projection<'_>; +/// fn project_ref(self: Pin<&Self>) -> ProjectionRef<'_>; +/// # } +/// # } +/// ``` +struct Foo; diff --git a/src/tools/rustfmt/tests/source/issue_4086.rs b/src/tools/rustfmt/tests/source/issue_4086.rs new file mode 100644 index 0000000000..ffa6442e93 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_4086.rs @@ -0,0 +1,2 @@ +#[cfg(any())] +extern "C++" {} diff --git a/src/tools/rustfmt/tests/source/issue_4374.rs b/src/tools/rustfmt/tests/source/issue_4374.rs new file mode 100644 index 0000000000..2a45a022e7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_4374.rs @@ -0,0 +1,13 @@ +fn a(_f: F) -> () +where + F: FnOnce() -> (), +{ +} +fn main() { + a(|| { + #[allow(irrefutable_let_patterns)] + while let _ = 0 { + break; + } + }); +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue_4475.rs b/src/tools/rustfmt/tests/source/issue_4475.rs new file mode 100644 index 0000000000..241dc91d7b --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_4475.rs @@ -0,0 +1,27 @@ +fn main() { + #[cfg(debug_assertions)] + { println!("DEBUG"); } +} + +fn main() { + #[cfg(feature = "foo")] + { + /* + let foo = 0 + */ + } +} + +fn main() { + #[cfg(feature = "foo")] + { /* let foo = 0; */ } +} + +fn main() { + #[foo] + #[bar] + #[baz] + { + // let foo = 0; + } +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue_4528.rs b/src/tools/rustfmt/tests/source/issue_4528.rs new file mode 100644 index 0000000000..85f6d8c038 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_4528.rs @@ -0,0 +1,8 @@ +#![allow(clippy::no_effect)] + +extern "C" { + // N.B., mutability can be easily incorrect in FFI calls -- as + // in C, the default is mutable pointers. + fn ffi(c: *mut u8); + fn int_ffi(c: *mut i32); +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue_4584.rs b/src/tools/rustfmt/tests/source/issue_4584.rs new file mode 100644 index 0000000000..695c559055 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_4584.rs @@ -0,0 +1,19 @@ +// rustfmt-indent_style: Visual + +#[derive(Debug,)] +pub enum Case { + Upper, + Lower +} + +#[derive(Debug, Clone, PartialEq, Eq,)] +pub enum Case { + Upper, + Lower +} + +// NB - This formatting looks potentially off the desired state, but is +// consistent with current behavior. Included here to provide a line wrapped +// derive case with the changes applied to resolve issue #4584 +#[derive(Add, Sub, Mul, Div, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Serialize, Mul,)] +struct Foo {} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue_4636.rs b/src/tools/rustfmt/tests/source/issue_4636.rs new file mode 100644 index 0000000000..ea7079f6c7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_4636.rs @@ -0,0 +1,13 @@ +pub trait PrettyPrinter<'tcx>: + Printer< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > + fmt::Write + { + // + } \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue_4675.rs b/src/tools/rustfmt/tests/source/issue_4675.rs new file mode 100644 index 0000000000..66613eed00 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_4675.rs @@ -0,0 +1,8 @@ +macro_rules! foo { + ($s:ident ( $p:pat )) => { + Foo { + name: Name::$s($p), + .. + } + }; +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/item-brace-style-always-next-line.rs b/src/tools/rustfmt/tests/source/item-brace-style-always-next-line.rs new file mode 100644 index 0000000000..38094d67a7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/item-brace-style-always-next-line.rs @@ -0,0 +1,29 @@ +// rustfmt-brace_style: AlwaysNextLine + +mod M { + enum A { + A, + } + + struct B { + b: i32, + } + + // For empty enums and structs, the brace remains on the same line. + enum C {} + + struct D {} + + enum A where T: Copy { + A, + } + + struct B where T: Copy { + b: i32, + } + + // For empty enums and structs, the brace remains on the same line. + enum C where T: Copy {} + + struct D where T: Copy {} +} diff --git a/src/tools/rustfmt/tests/source/item-brace-style-prefer-same-line.rs b/src/tools/rustfmt/tests/source/item-brace-style-prefer-same-line.rs new file mode 100644 index 0000000000..dff89b8b66 --- /dev/null +++ b/src/tools/rustfmt/tests/source/item-brace-style-prefer-same-line.rs @@ -0,0 +1,29 @@ +// rustfmt-brace_style: PreferSameLine + +mod M { + enum A + { + A, + } + + struct B + { + b: i32, + } + + enum C {} + + struct D {} + + enum A where T: Copy { + A, + } + + struct B where T: Copy { + b: i32, + } + + enum C where T: Copy {} + + struct D where T: Copy {} +} diff --git a/src/tools/rustfmt/tests/source/item-brace-style-same-line-where.rs b/src/tools/rustfmt/tests/source/item-brace-style-same-line-where.rs new file mode 100644 index 0000000000..1d034089f6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/item-brace-style-same-line-where.rs @@ -0,0 +1,29 @@ +mod M { + enum A + { + A, + } + + struct B + { + b: i32, + } + + // For empty enums and structs, the brace remains on the same line. + enum C {} + + struct D {} + + enum A where T: Copy { + A, + } + + struct B where T: Copy { + b: i32, + } + + // For empty enums and structs, the brace remains on the same line. + enum C where T: Copy {} + + struct D where T: Copy {} +} diff --git a/src/tools/rustfmt/tests/source/itemized-blocks/no_wrap.rs b/src/tools/rustfmt/tests/source/itemized-blocks/no_wrap.rs new file mode 100644 index 0000000000..a7b6a10a01 --- /dev/null +++ b/src/tools/rustfmt/tests/source/itemized-blocks/no_wrap.rs @@ -0,0 +1,47 @@ +// rustfmt-normalize_comments: true +// rustfmt-format_code_in_doc_comments: true + +//! This is a list: +//! * Outer +//! * Outer +//! * Inner +//! * Inner with lots of text so that it could be reformatted something something something lots of text so that it could be reformatted something something something +//! +//! This example shows how to configure fern to output really nicely colored logs +//! - when the log level is error, the whole line is red +//! - when the log level is warn, the whole line is yellow +//! - when the log level is info, the level name is green and the rest of the line is white +//! - when the log level is debug, the whole line is white +//! - when the log level is trace, the whole line is gray ("bright black") + +/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote +/// theater, i.e., as passed to [`Theater::send`] on the remote actor: +/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means +/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater +/// * `tag` is a tag that identifies the message type +/// * `msg` is the (serialized) message +/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote +/// theater, i.e., as passed to [`Theater::send`] on the remote actor +fn func1() {} + +/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote +/// theater, i.e., as passed to [`Theater::send`] on the remote actor: +/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means +/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater +/// * `tag` is a tag that identifies the message type +/// * `msg` is the (serialized) message +/// ``` +/// let x = 42; +/// ``` +fn func2() {} + +/// Look: +/// +/// ``` +/// let x = 42; +/// ``` +/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means +/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater +/// * `tag` is a tag that identifies the message type +/// * `msg` is the (serialized) message +fn func3() {} diff --git a/src/tools/rustfmt/tests/source/itemized-blocks/rewrite_fail.rs b/src/tools/rustfmt/tests/source/itemized-blocks/rewrite_fail.rs new file mode 100644 index 0000000000..f99c2cc5ff --- /dev/null +++ b/src/tools/rustfmt/tests/source/itemized-blocks/rewrite_fail.rs @@ -0,0 +1,11 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 50 + +// This example shows how to configure fern to output really nicely colored logs +// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +// - when the log level is info, the level name is green and the rest of the line is white +// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +fn func1() {} diff --git a/src/tools/rustfmt/tests/source/itemized-blocks/urls.rs b/src/tools/rustfmt/tests/source/itemized-blocks/urls.rs new file mode 100644 index 0000000000..2eaaafbbc4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/itemized-blocks/urls.rs @@ -0,0 +1,22 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 79 + +//! CMSIS: Cortex Microcontroller Software Interface Standard +//! +//! The version 5 of the standard can be found at: +//! +//! http://arm-software.github.io/CMSIS_5/Core/html/index.html +//! +//! The API reference of the standard can be found at: +//! +//! - example -- http://example.org -- something something something something something something +//! - something something something something something something more -- http://example.org +//! - http://example.org/something/something/something/something/something/something and the rest +//! - Core function access -- http://arm-software.github.io/CMSIS_5/Core/html/group__Core__Register__gr.html +//! - Intrinsic functions for CPU instructions -- http://arm-software.github.io/CMSIS_5/Core/html/group__intrinsic__CPU__gr.html +//! - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum sem lacus, commodo vitae. +//! +//! The reference C implementation used as the base of this Rust port can be +//! found at +//! +//! https://github.com/ARM-software/CMSIS_5/blob/5.3.0/CMSIS/Core/Include/cmsis_gcc.h diff --git a/src/tools/rustfmt/tests/source/itemized-blocks/wrap.rs b/src/tools/rustfmt/tests/source/itemized-blocks/wrap.rs new file mode 100644 index 0000000000..955cc698b7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/itemized-blocks/wrap.rs @@ -0,0 +1,55 @@ +// rustfmt-wrap_comments: true +// rustfmt-format_code_in_doc_comments: true +// rustfmt-max_width: 50 + +//! This is a list: +//! * Outer +//! * Outer +//! * Inner +//! * Inner with lots of text so that it could be reformatted something something something lots of text so that it could be reformatted something something something +//! +//! This example shows how to configure fern to output really nicely colored logs +//! - when the log level is error, the whole line is red +//! - when the log level is warn, the whole line is yellow +//! - when the log level is info, the level name is green and the rest of the line is white +//! - when the log level is debug, the whole line is white +//! - when the log level is trace, the whole line is gray ("bright black") + +// This example shows how to configure fern to output really nicely colored logs +// - when the log level is error, the whole line is red +// - when the log level is warn, the whole line is yellow +// - when the log level is info, the level name is green and the rest of the line is white +// - when the log level is debug, the whole line is white +// - when the log level is trace, the whole line is gray ("bright black") + +/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote +/// theater, i.e., as passed to [`Theater::send`] on the remote actor: +/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means +/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater +/// * `tag` is a tag that identifies the message type +/// * `msg` is the (serialized) message +/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote +/// theater, i.e., as passed to [`Theater::send`] on the remote actor +fn func1() {} + +/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote +/// theater, i.e., as passed to [`Theater::send`] on the remote actor: +/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means +/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater +/// * `tag` is a tag that identifies the message type +/// * `msg` is the (serialized) message +/// ``` +/// let x = 42; +/// ``` +fn func2() {} + +/// Look: +/// +/// ``` +/// let x = 42; +/// ``` +/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means +/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater +/// * `tag` is a tag that identifies the message type +/// * `msg` is the (serialized) message +fn func3() {} diff --git a/src/tools/rustfmt/tests/source/label_break.rs b/src/tools/rustfmt/tests/source/label_break.rs new file mode 100644 index 0000000000..2c79fd35e7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/label_break.rs @@ -0,0 +1,28 @@ +// format with label break value. +fn main() { + +'empty_block: {} + +'block: { + do_thing(); + if condition_not_met() { + break 'block; + } + do_next_thing(); + if condition_not_met() { + break 'block; + } + do_last_thing(); +} + +let result = 'block: { + if foo() { + // comment + break 'block 1; + } + if bar() { /* comment */ + break 'block 2; + } + 3 +}; +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/large-block.rs b/src/tools/rustfmt/tests/source/large-block.rs new file mode 100644 index 0000000000..09e9169f34 --- /dev/null +++ b/src/tools/rustfmt/tests/source/large-block.rs @@ -0,0 +1,5 @@ +fn issue1351() { + std_fmt_Arguments_new_v1_std_rt_begin_panic_fmt_sdfasfasdfasdf({ + static __STATIC_FMTSTR: &'static [&'static str] = &[]; + }); +} diff --git a/src/tools/rustfmt/tests/source/large_vec.rs b/src/tools/rustfmt/tests/source/large_vec.rs new file mode 100644 index 0000000000..34d5bf3995 --- /dev/null +++ b/src/tools/rustfmt/tests/source/large_vec.rs @@ -0,0 +1,29 @@ +// See #1470. + +impl Environment { + pub fn new_root() -> Rc> { + let mut env = Environment::new(); + let builtin_functions = &[("println", + Function::NativeVoid(CallSign { + num_params: 0, + variadic: true, + param_types: vec![], + }, + native_println)), + ("run_http_server", + Function::NativeVoid(CallSign { + num_params: 1, + variadic: false, + param_types: + vec![Some(ConstraintType::Function)], + }, + native_run_http_server)), + ("len", + Function::NativeReturning(CallSign { + num_params: 1, + variadic: false, + param_types: vec![None], + }, + native_len))]; + } +} diff --git a/src/tools/rustfmt/tests/source/lazy_static.rs b/src/tools/rustfmt/tests/source/lazy_static.rs new file mode 100644 index 0000000000..38fefbcbef --- /dev/null +++ b/src/tools/rustfmt/tests/source/lazy_static.rs @@ -0,0 +1,45 @@ +// Format `lazy_static!`. + +lazy_static! { +static ref CONFIG_NAME_REGEX: regex::Regex = +regex::Regex::new(r"^## `([^`]+)`").expect("Failed creating configuration pattern"); +static ref CONFIG_VALUE_REGEX: regex::Regex = regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#) +.expect("Failed creating configuration value pattern"); +} + +// We need to be able to format `lazy_static!` without known syntax. +lazy_static!( + xxx, +yyyy , + zzzzz +); + +lazy_static!{ +} + +// #2354 +lazy_static ! { +pub static ref Sbase64_encode_string : :: lisp :: LispSubrRef = { +let subr = :: remacs_sys :: Lisp_Subr { +header : :: remacs_sys :: Lisp_Vectorlike_Header { +size : ( +( :: remacs_sys :: PseudovecType :: PVEC_SUBR as :: libc :: ptrdiff_t ) << :: +remacs_sys :: PSEUDOVECTOR_AREA_BITS ) , } , function : self :: +Fbase64_encode_string as * const :: libc :: c_void , min_args : 1i16 , +max_args : 2i16 , symbol_name : ( b"base64-encode-string\x00" ) . as_ptr ( ) +as * const :: libc :: c_char , intspec : :: std :: ptr :: null ( ) , doc : :: +std :: ptr :: null ( ) , lang : :: remacs_sys :: Lisp_Subr_Lang_Rust , } ; +unsafe { +let ptr = :: remacs_sys :: xmalloc ( +:: std :: mem :: size_of :: < :: remacs_sys :: Lisp_Subr > ( ) ) as * mut :: +remacs_sys :: Lisp_Subr ; :: std :: ptr :: copy_nonoverlapping ( +& subr , ptr , 1 ) ; :: std :: mem :: forget ( subr ) ; :: lisp :: ExternalPtr +:: new ( ptr ) } } ; } + + +lazy_static! { +static ref FOO: HashMap Result, Either> +),> = HashMap::new(); +} diff --git a/src/tools/rustfmt/tests/source/license-templates/empty_license_path.rs b/src/tools/rustfmt/tests/source/license-templates/empty_license_path.rs new file mode 100644 index 0000000000..d3a91e4231 --- /dev/null +++ b/src/tools/rustfmt/tests/source/license-templates/empty_license_path.rs @@ -0,0 +1,5 @@ +// rustfmt-config: issue-3802.toml + +fn main() { +println!("Hello world!"); +} diff --git a/src/tools/rustfmt/tests/source/license-templates/license.rs b/src/tools/rustfmt/tests/source/license-templates/license.rs new file mode 100644 index 0000000000..6816011c60 --- /dev/null +++ b/src/tools/rustfmt/tests/source/license-templates/license.rs @@ -0,0 +1,6 @@ +// rustfmt-license_template_path: tests/license-template/lt.txt +// Copyright 2019 The rustfmt developers. + +fn main() { +println!("Hello world!"); +} diff --git a/src/tools/rustfmt/tests/source/long-fn-1/version_one.rs b/src/tools/rustfmt/tests/source/long-fn-1/version_one.rs new file mode 100644 index 0000000000..d6832c2af0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/long-fn-1/version_one.rs @@ -0,0 +1,21 @@ +// rustfmt-version: One +// Tests that a function which is almost short enough, but not quite, gets +// formatted correctly. + +impl Foo { + fn some_input(&mut self, input: Input, input_path: Option, ) -> (Input, Option) {} + + fn some_inpu(&mut self, input: Input, input_path: Option) -> (Input, Option) {} +} + +// #1843 +#[allow(non_snake_case)] +pub extern "C" fn Java_com_exonum_binding_storage_indices_ValueSetIndexProxy_nativeContainsByHash() -> bool { + false +} + +// #3009 +impl Something { + fn my_function_name_is_way_to_long_but_used_as_a_case_study_or_an_example_its_fine( +) -> Result< (), String > {} +} diff --git a/src/tools/rustfmt/tests/source/long-fn-1/version_two.rs b/src/tools/rustfmt/tests/source/long-fn-1/version_two.rs new file mode 100644 index 0000000000..f402a26e8b --- /dev/null +++ b/src/tools/rustfmt/tests/source/long-fn-1/version_two.rs @@ -0,0 +1,21 @@ +// rustfmt-version: Two +// Tests that a function which is almost short enough, but not quite, gets +// formatted correctly. + +impl Foo { + fn some_input(&mut self, input: Input, input_path: Option, ) -> (Input, Option) {} + + fn some_inpu(&mut self, input: Input, input_path: Option) -> (Input, Option) {} +} + +// #1843 +#[allow(non_snake_case)] +pub extern "C" fn Java_com_exonum_binding_storage_indices_ValueSetIndexProxy_nativeContainsByHash() -> bool { + false +} + +// #3009 +impl Something { + fn my_function_name_is_way_to_long_but_used_as_a_case_study_or_an_example_its_fine( +) -> Result< (), String > {} +} diff --git a/src/tools/rustfmt/tests/source/long-match-arms-brace-newline.rs b/src/tools/rustfmt/tests/source/long-match-arms-brace-newline.rs new file mode 100644 index 0000000000..927ada0ffb --- /dev/null +++ b/src/tools/rustfmt/tests/source/long-match-arms-brace-newline.rs @@ -0,0 +1,15 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 80 +// rustfmt-control_brace_style: AlwaysNextLine + +fn main() { + match x { + aaaaaaaa::Bbbbb::Ccccccccccccc(_, Some(ref x)) if x == + "aaaaaaaaaaa \ + aaaaaaa \ + aaaaaa" => { + Ok(()) + } + _ => Err(x), + } +} diff --git a/src/tools/rustfmt/tests/source/long-use-statement-issue-3154.rs b/src/tools/rustfmt/tests/source/long-use-statement-issue-3154.rs new file mode 100644 index 0000000000..339382b5bb --- /dev/null +++ b/src/tools/rustfmt/tests/source/long-use-statement-issue-3154.rs @@ -0,0 +1,3 @@ +// rustfmt-reorder_imports: false + +pub use self :: super :: super :: super :: root::mozilla::detail::StringClassFlags as nsTStringRepr_ClassFlags ; diff --git a/src/tools/rustfmt/tests/source/long_field_access.rs b/src/tools/rustfmt/tests/source/long_field_access.rs new file mode 100644 index 0000000000..7aa626221a --- /dev/null +++ b/src/tools/rustfmt/tests/source/long_field_access.rs @@ -0,0 +1,3 @@ +fn f() { + block_flow.base.stacking_relative_position_of_display_port = self.base.stacking_relative_position_of_display_port; +} diff --git a/src/tools/rustfmt/tests/source/loop.rs b/src/tools/rustfmt/tests/source/loop.rs new file mode 100644 index 0000000000..6e92cdc6c2 --- /dev/null +++ b/src/tools/rustfmt/tests/source/loop.rs @@ -0,0 +1,29 @@ + +fn main() { + loop + { return some_val;} + +let x = loop { do_forever(); }; + + 'label : loop { + // Just comments + } + + 'a: while loooooooooooooooooooooooooooooooooong_variable_name + another_value > some_other_value{} + + while aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa > bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb { + } + + while aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa {} + + 'b: for xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx in some_iter(arg1, arg2) { + // do smth + } + + while let Some(i) = x.find('s') + { + x.update(); + continue; + continue 'foo; + } +} diff --git a/src/tools/rustfmt/tests/source/macro_not_expr.rs b/src/tools/rustfmt/tests/source/macro_not_expr.rs new file mode 100644 index 0000000000..d8de4dce38 --- /dev/null +++ b/src/tools/rustfmt/tests/source/macro_not_expr.rs @@ -0,0 +1,7 @@ +macro_rules! test { + ($($t:tt)*) => {} +} + +fn main() { + test!( a : B => c d ); +} diff --git a/src/tools/rustfmt/tests/source/macro_rules.rs b/src/tools/rustfmt/tests/source/macro_rules.rs new file mode 100644 index 0000000000..5aaca0c83f --- /dev/null +++ b/src/tools/rustfmt/tests/source/macro_rules.rs @@ -0,0 +1,301 @@ +// rustfmt-format_macro_matchers: true + +macro_rules! m { + () => (); + ( $ x : ident ) => (); + ( $ m1 : ident , $ m2 : ident , $ x : ident ) => (); + ( $($beginning:ident),*;$middle:ident;$($end:ident),* ) => (); + ( $($beginning: ident),*; $middle: ident; $($end: ident),*; $($beginning: ident),*; $middle: ident; $($end: ident),* ) => {}; + ( $ name : ident ( $ ( $ dol : tt $ var : ident ) * ) $ ( $ body : tt ) * ) => (); + ( $( $ i : ident : $ ty : ty , $def : expr , $stb : expr , $ ( $ dstring : tt ) , + ) ; + $ ( ; ) * + $( $ i : ident : $ ty : ty , $def : expr , $stb : expr , $ ( $ dstring : tt ) , + ) ; + $ ( ; ) * + ) => {}; + ( $foo: tt foo [$ attr : meta] $name: ident ) => {}; + ( $foo: tt [$ attr: meta] $name: ident ) => {}; + ( $foo: tt &'a [$attr : meta] $name: ident ) => {}; + ( $foo: tt foo # [ $attr : meta] $name: ident ) => {}; + ( $foo: tt # [ $attr : meta] $name: ident) => {}; + ( $foo: tt &'a # [ $attr : meta] $name: ident ) => {}; + ( $ x : tt foo bar foo bar foo bar $ y : tt => x*y*z $ z : tt , $ ( $a: tt ) , * ) => {}; +} + + +macro_rules! impl_a_method { + ($n:ident ( $a:ident : $ta:ty ) -> $ret:ty { $body:expr }) => { + fn $n($a:$ta) -> $ret { $body } + macro_rules! $n { ($va:expr) => { $n($va) } } + }; + ($n:ident ( $a:ident : $ta:ty, $b:ident : $tb:ty ) -> $ret:ty { $body:expr }) => { + fn $n($a:$ta, $b:$tb) -> $ret { $body } + macro_rules! $n { ($va:expr, $vb:expr) => { $n($va, $vb) } } + }; + ($n:ident ( $a:ident : $ta:ty, $b:ident : $tb:ty, $c:ident : $tc:ty ) -> $ret:ty { $body:expr }) => { + fn $n($a:$ta, $b:$tb, $c:$tc) -> $ret { $body } + macro_rules! $n { ($va:expr, $vb:expr, $vc:expr) => { $n($va, $vb, $vc) } } + }; + ($n:ident ( $a:ident : $ta:ty, $b:ident : $tb:ty, $c:ident : $tc:ty, $d:ident : $td:ty ) -> $ret:ty { $body:expr }) => { + fn $n($a:$ta, $b:$tb, $c:$tc, $d:$td) -> $ret { $body } + macro_rules! $n { ($va:expr, $vb:expr, $vc:expr, $vd:expr) => { $n($va, $vb, $vc, $vd) } } + }; +} + +macro_rules! m { + // a + ($expr :expr, $( $func : ident ) * ) => { + { + let x = $expr; + $func ( + x + ) + } + }; + + /* b */ + + () => {/* c */}; + + (@tag) => + { + + }; + +// d +( $item:ident ) => { + mod macro_item { struct $item ; } +}; +} + +macro m2 { + // a + ($expr :expr, $( $func : ident ) * ) => { + { + let x = $expr; + $func ( + x + ) + } + } + + /* b */ + + () => {/* c */} + + (@tag) => + { + + } + +// d +( $item:ident ) => { + mod macro_item { struct $item ; } +} +} + +// #2438, #2476 +macro_rules! m { + () => { + fn foo() { + this_line_is_98_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( + ); + } + } +} +macro_rules! m { + () => { + fn foo() { + this_line_is_99_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( +); + } + }; +} +macro_rules! m { + () => { + fn foo() { + this_line_is_100_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( +); + } + }; +} +macro_rules! m { + () => { + fn foo() { + this_line_is_101_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( + ); + } + }; +} + +// #2439 +macro_rules! m { + ($line0_xxxxxxxxxxxxxxxxx: expr, $line1_xxxxxxxxxxxxxxxxx: expr, $line2_xxxxxxxxxxxxxxxxx: expr, $line3_xxxxxxxxxxxxxxxxx: expr,) => {}; +} + +// #2466 +// Skip formatting `macro_rules!` that are not using `{}`. +macro_rules! m ( + () => () +); +macro_rules! m [ + () => () +]; + +// #2470 +macro foo($type_name: ident, $docs: expr) { + #[allow(non_camel_case_types)] + #[doc=$docs] + #[derive(Debug, Clone, Copy)] + pub struct $type_name; +} + +// #2534 +macro_rules! foo { + ($a:ident : $b:ty) => {}; + ($a:ident $b:ident $c:ident) => {}; +} + +// #2538 +macro_rules! add_message_to_notes { + ($msg:expr) => {{ + let mut lines = message.lines(); + notes.push_str(&format!("\n{}: {}", level, lines.next().unwrap())); + for line in lines { + notes.push_str(&format!( + "\n{:indent$}{line}", + "", + indent = level.len() + 2, + line = line, + )); + } + }} +} + +// #2560 +macro_rules! binary { + ($_self:ident,$expr:expr, $lhs:expr,$func:ident) => { + while $_self.matched($expr) { + let op = $_self.get_binary_op()?; + + let rhs = Box::new($_self.$func()?); + + $lhs = Spanned { + span: $lhs.get_span().to(rhs.get_span()), + value: Expression::Binary { + lhs: Box::new($lhs), + op, + rhs, + }, + } + } + }; +} + +// #2558 +macro_rules! m { + ($x:) => {}; + ($($foo:expr)()?) => {}; +} + +// #2749 +macro_rules! foo { + ($(x)* {}) => {}; + ($(x)* ()) => {}; + ($(x)* []) => {}; +} +macro_rules! __wundergraph_expand_sqlite_mutation { + ( $mutation_name:ident $((context = $($context:tt)*))*{ $( $entity_name:ident( $(insert = $insert:ident,)* $(update = $update:ident,)* $(delete = $($delete:tt)+)* ), )* } ) => {}; +} + +// #2607 +macro_rules! bench { + ($ty:ident) => { + criterion_group!( + name = benches; + config = ::common_bench::reduced_samples(); + targets = call, map; + ); + }; +} + +// #2770 +macro_rules! save_regs { + () => { + asm!("push rax + push rcx + push rdx + push rsi + push rdi + push r8 + push r9 + push r10 + push r11" + :::: "intel", "volatile"); + }; +} + +// #2721 +macro_rules! impl_as_byte_slice_arrays { + ($n:expr,) => {}; + ($n:expr, $N:ident, $($NN:ident,)*) => { + impl_as_byte_slice_arrays!($n - 1, $($NN,)*); + + impl AsByteSliceMut for [T; $n] where [T]: AsByteSliceMut { + fn as_byte_slice_mut(&mut self) -> &mut [u8] { + self[..].as_byte_slice_mut() + } + + fn to_le(&mut self) { + self[..].to_le() + } + } + }; + (!div $n:expr,) => {}; + (!div $n:expr, $N:ident, $($NN:ident,)*) => { + impl_as_byte_slice_arrays!(!div $n / 2, $($NN,)*); + + impl AsByteSliceMut for [T; $n] where [T]: AsByteSliceMut { + fn as_byte_slice_mut(&mut self) -> &mut [u8] { + self[..].as_byte_slice_mut() + } + + fn to_le(&mut self) { + self[..].to_le() + } + } + }; +} + +// #2919 +fn foo() { + { + macro_rules! touch_value { + ($func:ident, $value:expr) => {{ + let result = API::get_cached().$func(self, key.as_ptr(), $value, ffi::VSPropAppendMode::paTouch); + let result = API::get_cached().$func(self, key.as_ptr(), $value, ffi::VSPropAppend); + let result = API::get_cached().$func(self, key.as_ptr(), $value, ffi::VSPropAppendM); + let result = APIIIIIIIII::get_cached().$func(self, key.as_ptr(), $value, ffi::VSPropAppendM); + let result = API::get_cached().$func(self, key.as_ptr(), $value, ffi::VSPropAppendMMMMMMMMMM); + debug_assert!(result == 0); + }}; + } + } +} + +// #2642 +macro_rules! template { + ($name: expr) => { + format_args!(r##" +"http://example.com" + +# test +"##, $name) + } +} + +macro_rules! template { + () => { + format_args!(r" +// + +") + } +} diff --git a/src/tools/rustfmt/tests/source/macros.rs b/src/tools/rustfmt/tests/source/macros.rs new file mode 100644 index 0000000000..3b286579ca --- /dev/null +++ b/src/tools/rustfmt/tests/source/macros.rs @@ -0,0 +1,486 @@ +// rustfmt-normalize_comments: true +// rustfmt-format_macro_matchers: true +itemmacro!(this, is.now() .formatted(yay)); + +itemmacro!(really, long.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbb() .is.formatted()); + +itemmacro!{this, is.brace().formatted()} + +fn main() { + foo! ( ); + + foo!(,); + + bar!( a , b , c ); + + bar!( a , b , c , ); + + baz!(1+2+3, quux. kaas()); + + quux!(AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB); + + kaas!(/* comments */ a /* post macro */, b /* another */); + + trailingcomma!( a , b , c , ); + // Preserve trailing comma only when necessary. + ok!(file.seek( + SeekFrom::Start( + table.map(|table| fixture.offset(table)).unwrap_or(0), + ) + )); + + noexpr!( i am not an expression, OK? ); + + vec! [ a , b , c]; + + vec! [AAAAAA, AAAAAA, AAAAAA, AAAAAA, AAAAAA, AAAAAA, AAAAAA, AAAAAA, AAAAAA, + BBBBB, 5, 100-30, 1.33, b, b, b]; + + vec! [a /* comment */]; + + // Trailing spaces after a comma + vec![ + a, + ]; + + vec![a; b]; + vec!(a; b); + vec!{a; b}; + + vec![a, b; c]; + vec![a; b, c]; + + vec![a; (|x| { let y = x + 1; let z = y + 1; z })(2)]; + vec![a; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]; + vec![a; unsafe { + x + 1 + }]; + + unknown_bracket_macro__comma_should_not_be_stripped![ + a, + ]; + + foo(makro!(1, 3)); + + hamkaas!{ () }; + + macrowithbraces! {dont, format, me} + + x!(fn); + + some_macro!( + + ); + + some_macro![ + ]; + + some_macro!{ + // comment + }; + + some_macro!{ + // comment + }; + + some_macro!( + // comment + not function like + ); + + // #1712 + let image = gray_image!( + 00, 01, 02; + 10, 11, 12; + 20, 21, 22); + + // #1092 + chain!(input, a:take!(max_size), || []); + + // #2727 + foo!("bar") +; +} + +impl X { + empty_invoc!{} + empty_invoc! {} +} + +fn issue_1279() { + println!("dsfs"); // a comment +} + +fn issue_1555() { + let hello = &format!("HTTP/1.1 200 OK\r\nServer: {}\r\n\r\n{}", + "65454654654654654654654655464", + "4"); +} + +fn issue1178() { + macro_rules! foo { + (#[$attr:meta] $name:ident) => {} + } + + foo!(#[doc = "bar"] baz); +} + +fn issue1739() { + sql_function!(add_rss_item, + add_rss_item_t, + (a: types::Integer, + b: types::Timestamptz, + c: types::Text, + d: types::Text, + e: types::Text)); + + w.slice_mut(s![.., init_size[1] - extreeeeeeeeeeeeeeeeeeeeeeeem..init_size[1], ..]) + .par_map_inplace(|el| *el = 0.); +} + +fn issue_1885() { + let threads = people.into_iter().map(|name| { + chan_select! { + rx.recv() => {} + } + }).collect::>(); +} + +fn issue_1917() { + mod x { + quickcheck! { + fn test(a: String, s: String, b: String) -> TestResult { + if a.find(&s).is_none() { + + TestResult::from_bool(true) + } else { + TestResult::discard() + } + } + } + } +} + +fn issue_1921() { + // Macro with tabs. + lazy_static! { + static ref ONE: u32 = 1; + static ref TWO: u32 = 2; + static ref THREE: u32 = 3; + static ref FOUR: u32 = { + let mut acc = 1; + acc += 1; + acc += 2; + acc + } +} +} + +// #1577 +fn issue1577() { + let json = json!({ + "foo": "bar", + }); +} + +// #3174 +fn issue_3174() { + let data = + if let Some(debug) = error.debug_info() { + json!({ + "errorKind": format!("{:?}", error.err_kind()), + "debugMessage": debug.message, + }) + } else { + json!({"errorKind": format!("{:?}", error.err_kind())}) + }; +} + +gfx_pipeline!(pipe { + vbuf: gfx::VertexBuffer = (), + out: gfx::RenderTarget = "Target0", +}); + +// #1919 +#[test] +fn __bindgen_test_layout_HandleWithDtor_open0_int_close0_instantiation() { + assert_eq!( + ::std::mem::size_of::>(), + 8usize, + concat!( + "Size of template specialization: ", + stringify ! ( HandleWithDtor < :: std :: os :: raw :: c_int > ) + ) + ); + assert_eq ! ( :: std :: mem :: align_of :: < HandleWithDtor < :: std :: os :: raw :: c_int > > ( ) , 8usize , concat ! ( "Alignment of template specialization: " , stringify ! ( HandleWithDtor < :: std :: os :: raw :: c_int > ) ) ); +} + +// #878 +macro_rules! try_opt { + ($expr:expr) => (match $expr { + Some(val) => val, + + None => { return None; } + }) +} + +// #2214 +// macro call whose argument is an array with trailing comma. +fn issue2214() { +make_test!(str_searcher_ascii_haystack, "bb", "abbcbbd", [ + Reject(0, 1), + Match (1, 3), + Reject(3, 4), + Match (4, 6), + Reject(6, 7), +]); +} + +fn special_case_macros() { + let p = eprint!(); + let q = eprint!("{}", 1); + let r = eprint!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + let s = eprint!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + let q = eprintln!("{}", 1); + let r = eprintln!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + let s = eprintln!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + let q = format!("{}", 1); + let r = format!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + let s = format!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + let q = format_args!("{}", 1); + let r = format_args!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + let s = format_args!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + let q = print!("{}", 1); + let r = print!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + let s = print!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + let q = println!("{}", 1); + let r = println!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + let s = println!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + let q = unreachable!("{}", 1); + let r = unreachable!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + let s = unreachable!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + debug!("{}", 1); + debug!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + debug!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + error!("{}", 1); + error!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + error!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + info!("{}", 1); + info!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + info!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + panic!("{}", 1); + panic!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + panic!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + warn!("{}", 1); + warn!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + warn!("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + assert!(); + assert!(result == 42); + assert!(result == 42, "Ahoy there, {}!", target); + assert!(result == 42, "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", result, input, expected); + assert!(result == 42, "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + assert_eq!(); + assert_eq!(left); + assert_eq!(left, right); + assert_eq!(left, right, "Ahoy there, {}!", target); + assert_eq!(left, right, "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", result, input, expected); + assert_eq!(first_realllllllllllly_long_variable_that_doesnt_fit_one_one_line, second_reallllllllllly_long_variable_that_doesnt_fit_one_one_line, "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", result, input, expected); + assert_eq!(left + 42, right, "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", result, input, expected); + assert_eq!(left, right, "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + write!(&mut s, "Ahoy there, {}!", target); + write!(&mut s, "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", result, input, expected); + write!(&mut s, "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + + writeln!(&mut s, "Ahoy there, {}!", target); + writeln!(&mut s, "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", result, input, expected); + writeln!(&mut s, "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); +} + +// #1209 +impl Foo { + /// foo + pub fn foo(&self) -> Bar {} +} + +// #819 +fn macro_in_pattern_position () { + let x = match y { + foo!( ) => (), + bar!( a, b, + c) => (), + bar!(a + , b + , c + ,) => (), + baz!( 1 + 2 + 3, quux.kaas( ) + ) => (), + quux!(AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB) => (), + }; +} + +macro foo() { + + +} + +pub macro bar($x:ident+$y:expr; ) { + fn foo($x: Foo) { + long_function(a_long_argument_to_a_long_function_is_what_this_is(AAAAAAAAAAAAAAAAAAAAAAAAAAAA), + $x.bar($y)); + } +} + +macro foo() { + // a comment + fn foo() { + // another comment + bar(); + } +} + +// #2574 +macro_rules! test { + () => {{}} +} + +macro lex_err($kind: ident $(, $body: expr)*) { + Err(QlError::LexError(LexError::$kind($($body,)*))) +} + +// Preserve trailing comma on item-level macro with `()` or `[]`. +methods![ get, post, delete, ]; +methods!( get, post, delete, ); + +// #2588 +macro_rules! m { + () => { + r#" + test + "# + }; +} +fn foo() { + f!{r#" + test + "#}; +} + +// #2591 +fn foo() { + match 0u32 { + 0 => (), + _ => unreachable!(/* obviously */), + } +} + +fn foo() { + let _ = column!(/* here */); +} + +// #2616 +// Preserve trailing comma when using mixed layout for macro call. +fn foo() { + foo!(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); + foo!(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,); +} + +// #2830 +// Preserve trailing comma-less/ness inside nested macro. +named!( + do_parse_gsv, + map_res!( + do_parse!( + number_of_sentences: map_res!(digit, parse_num::) + >> char!(',') + >> sentence_index: map_res!(digit, parse_num::) + >> char!(',') + >> total_number_of_sats: map_res!(digit, parse_num::) + >> char!(',') + >> sat0: opt!(complete!(parse_gsv_sat_info)) + >> sat1: opt!(complete!(parse_gsv_sat_info)) + >> sat2: opt!(complete!(parse_gsv_sat_info)) + >> sat3: opt!(complete!(parse_gsv_sat_info)) + >> ( + number_of_sentences, + sentence_index, + total_number_of_sats, + sat0, + sat1, + sat2, + sat3 + ) + ), + construct_gsv_data + ) +); + +// #2857 +convert_args!(vec!(1, 2, 3)); + +// #3031 +thread_local!( +/// TLV Holds a set of JSTraceables that need to be rooted + static ROOTED_TRACEABLES: RefCell = + RefCell::new(RootedTraceableSet::new()) ; +) ; + +thread_local![ + /// TLV Holds a set of JSTraceables that need to be rooted + static ROOTED_TRACEABLES: RefCell = + RefCell::new(RootedTraceableSet::new()) ; + + /// TLV Holds a set of JSTraceables that need to be rooted + static ROOTED_TRACEABLES: RefCell = + RefCell::new(RootedTraceableSet::new(0)) ; + + /// TLV Holds a set of JSTraceables that need to be rooted + static ROOTED_TRACEABLES: RefCell = + RefCell::new(RootedTraceableSet::new(), xxx, yyy) ; + + /// TLV Holds a set of JSTraceables that need to be rooted +static ROOTED_TRACEABLES: RefCell = + RefCell::new(RootedTraceableSet::new(1234)) ; + +] ; + +fn issue3004() { + foo!(|_| { ( ) }); + stringify!(( foo+ )); +} + +// #3331 +pub fn fold_abi(_visitor: &mut V, _i: Abi) -> Abi { + Abi { + extern_token: Token ! [ extern ](tokens_helper(_visitor, &_i.extern_token.span)), + name: (_i.name).map(|it| _visitor.fold_lit_str(it)), + } +} + +// #3463 +x ! {()} + +// #3746 +f!(match a { + 4 => + &[ + (3, false), // Missing + (4, true) // I-frame + ] [..], +}); + +// #3583 +foo!(|x = y|); diff --git a/src/tools/rustfmt/tests/source/markdown-comment-with-options.rs b/src/tools/rustfmt/tests/source/markdown-comment-with-options.rs new file mode 100644 index 0000000000..2c4d6a5cc2 --- /dev/null +++ b/src/tools/rustfmt/tests/source/markdown-comment-with-options.rs @@ -0,0 +1,17 @@ +// rustfmt-wrap_comments: true + +// Preserve two trailing whitespaces in doc comment, +// but trim any whitespaces in normal comment. + +//! hello world +//! hello world + +/// hello world +/// hello world +/// hello world +fn foo() { + // hello world + // hello world + let x = 3; + println!("x = {}", x); +} diff --git a/src/tools/rustfmt/tests/source/markdown-comment.rs b/src/tools/rustfmt/tests/source/markdown-comment.rs new file mode 100644 index 0000000000..1ec26562fe --- /dev/null +++ b/src/tools/rustfmt/tests/source/markdown-comment.rs @@ -0,0 +1,15 @@ +// Preserve two trailing whitespaces in doc comment, +// but trim any whitespaces in normal comment. + +//! hello world +//! hello world + +/// hello world +/// hello world +/// hello world +fn foo() { + // hello world + // hello world + let x = 3; + println!("x = {}", x); +} diff --git a/src/tools/rustfmt/tests/source/match-block-trailing-comma.rs b/src/tools/rustfmt/tests/source/match-block-trailing-comma.rs new file mode 100644 index 0000000000..e9daac13bf --- /dev/null +++ b/src/tools/rustfmt/tests/source/match-block-trailing-comma.rs @@ -0,0 +1,14 @@ +// rustfmt-match_block_trailing_comma: true +// Match expressions, no unwrapping of block arms or wrapping of multiline +// expressions. + +fn foo() { + match x { + a => { + "line1"; + "line2" + } + b => (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb), + } +} diff --git a/src/tools/rustfmt/tests/source/match-flattening.rs b/src/tools/rustfmt/tests/source/match-flattening.rs new file mode 100644 index 0000000000..935ece53b8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/match-flattening.rs @@ -0,0 +1,21 @@ +fn main() { + match option { + None => if condition { + true + } else { + false + }, + } +} + +fn main() { + match option { + None => { + if condition { + true + } else { + false + } + } + } +} diff --git a/src/tools/rustfmt/tests/source/match-nowrap-trailing-comma.rs b/src/tools/rustfmt/tests/source/match-nowrap-trailing-comma.rs new file mode 100644 index 0000000000..134d2fdf9a --- /dev/null +++ b/src/tools/rustfmt/tests/source/match-nowrap-trailing-comma.rs @@ -0,0 +1,15 @@ +// rustfmt-match_arm_blocks: false +// rustfmt-match_block_trailing_comma: true +// Match expressions, no unwrapping of block arms or wrapping of multiline +// expressions. + +fn foo() { + match x { + a => { + "line1"; + "line2" + } + b => (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb), + } +} diff --git a/src/tools/rustfmt/tests/source/match-nowrap.rs b/src/tools/rustfmt/tests/source/match-nowrap.rs new file mode 100644 index 0000000000..db22cd9f01 --- /dev/null +++ b/src/tools/rustfmt/tests/source/match-nowrap.rs @@ -0,0 +1,12 @@ +// rustfmt-match_arm_blocks: false +// Match expressions, no unwrapping of block arms or wrapping of multiline +// expressions. + +fn foo() { + match x { + a => { foo() } + b => + (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb), + } +} diff --git a/src/tools/rustfmt/tests/source/match.rs b/src/tools/rustfmt/tests/source/match.rs new file mode 100644 index 0000000000..f54082f92e --- /dev/null +++ b/src/tools/rustfmt/tests/source/match.rs @@ -0,0 +1,570 @@ +// rustfmt-normalize_comments: true +// Match expressions. + +fn foo() { + // A match expression. + match x { + // Some comment. + a => foo(), + b if 0 < 42 => foo(), + c => { // Another comment. + // Comment. + an_expression; + foo() + } + Foo(ref bar) => + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + Pattern1 | Pattern2 | Pattern3 => false, + Paternnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn | + Paternnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn => { + blah + } + Patternnnnnnnnnnnnnnnnnnn | + Patternnnnnnnnnnnnnnnnnnn | + Patternnnnnnnnnnnnnnnnnnn | + Patternnnnnnnnnnnnnnnnnnn => meh, + + Patternnnnnnnnnnnnnnnnnnn | + Patternnnnnnnnnnnnnnnnnnn if looooooooooooooooooong_guard => meh, + + Patternnnnnnnnnnnnnnnnnnnnnnnnn | + Patternnnnnnnnnnnnnnnnnnnnnnnnn if looooooooooooooooooooooooooooooooooooooooong_guard => + meh, + + // Test that earlier patterns can take the guard space + (aaaa, bbbbb, ccccccc, aaaaa, bbbbbbbb, cccccc, aaaa, bbbbbbbb, cccccc, dddddd) | + Patternnnnnnnnnnnnnnnnnnnnnnnnn if loooooooooooooooooooooooooooooooooooooooooong_guard => {} + + _ => {} + ast::PathParameters::AngleBracketedParameters(ref data) if data.lifetimes.len() > 0 || + data.types.len() > 0 || + data.bindings.len() > 0 => {} + } + + let whatever = match something { + /// DOC COMMENT! + Some(_) => 42, + // Comment on an attribute. + #[an_attribute] + // Comment after an attribute. + None => 0, + #[rustfmt::skip] + Blurb => { } + }; +} + +// Test that a match on an overflow line is laid out properly. +fn main() { + let sub_span = + match xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx { + Some(sub_span) => Some(sub_span), + None => sub_span, + }; +} + +// Test that one-line bodies align. +fn main() { + match r { + Variableeeeeeeeeeeeeeeeee => ( "variable", + vec!("id", "name", "qualname", + "value", "type", "scopeid"), + true, + true), + Enummmmmmmmmmmmmmmmmmmmm => ("enum", + vec!("id","qualname","scopeid","value"), + true, + true), + Variantttttttttttttttttttttttt => ("variant", + vec!("id", + "name", + "qualname", + "type", + "value", + "scopeid"), + true, + true), + }; + + match x{ + y=>{/*Block with comment. Preserve me.*/ } + z=>{stmt();} } +} + +fn matches() { + match 1 { + -1 => 10, + 1 => 1, // foo + 2 => 2, + // bar + 3 => 3, + _ => 0 // baz + } +} + +fn match_skip() { + let _ = match Some(1) { + #[rustfmt::skip] + Some( n ) => n, + None => 1, + }; +} + +fn issue339() { + match a { + b => {} + c => { } + d => { + } + e => { + + + + } + // collapsing here is safe + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff => { + } + // collapsing here exceeds line length + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffg => { + } + h => { // comment above block + } + i => { + } // comment below block + j => { + // comment inside block + } + j2 => { + // comments inside... + } // ... and after + // TODO uncomment when vertical whitespace is handled better + // k => { + // + // // comment with WS above + // } + // l => { + // // comment with ws below + // + // } + m => { + } n => { } o => + { + + } + p => { // Don't collapse me + } q => { } r => + { + + } + s => 0, // s comment + // t comment + t => 1, + u => 2, + v => { + } /* funky block + * comment */ + // final comment + } +} + +fn issue355() { + match mac { + a => println!("a", b), + b => vec!(1, 2), + c => vec!(3; 4), + d => { + println!("a", b) + } + e => { + vec!(1, 2) + } + f => { + vec!(3; 4) + } + h => println!("a", b), // h comment + i => vec!(1, 2), // i comment + j => vec!(3; 4), // j comment + // k comment + k => println!("a", b), + // l comment + l => vec!(1, 2), + // m comment + m => vec!(3; 4), + // Rewrite splits macro + nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn => println!("a", b), + // Rewrite splits macro + oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo => vec!(1, 2), + // Macro support fails to recognise this macro as splittable + // We push the whole expr to a new line, TODO split this macro as well + pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp => vec!(3; 4), + // q, r and s: Rewrite splits match arm + qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq => println!("a", b), + rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr => vec!(1, 2), + ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss => vec!(3; 4), + // Funky bracketing styles + t => println!{"a", b}, + u => vec!{1, 2}, + v => vec!{3; 4}, + w => println!["a", b], + x => vec![1, 2], + y =>vec![3; 4], + // Brackets with comments + tc => println!{"a", b}, // comment + uc => vec!{1, 2}, // comment + vc =>vec!{3; 4}, // comment + wc =>println!["a", b], // comment + xc => vec![1,2], // comment + yc => vec![3; 4], // comment + yd => + looooooooooooooooooooooooooooooooooooooooooooooooooooooooong_func(aaaaaaaaaa, + bbbbbbbbbb, + cccccccccc, + dddddddddd), + } +} + +fn issue280() { + { + match x { + CompressionMode::DiscardNewline | CompressionMode::CompressWhitespaceNewline => ch == + '\n', + ast::ItemConst(ref typ, ref expr) => self.process_static_or_const_item(item, + &typ, + &expr), + } + } +} + +fn issue383() { + match resolution.last_private {LastImport{..} => false, _ => true}; +} + +fn issue507() { + match 1 { + 1 => unsafe { std::intrinsics::abort() }, + _ => (), + } +} + +fn issue508() { + match s.type_id() { + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLCanvasElement))) => true, + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLObjectElement))) => s.has_object_data(), + Some(NodeTypeId::Element(_)) => false, + } +} + +fn issue496() {{{{ + match def { + def::DefConst(def_id) | def::DefAssociatedConst(def_id) => + match const_eval::lookup_const_by_id(cx.tcx, def_id, Some(self.pat.id)) { + Some(const_expr) => { x }}}}}}} + +fn issue494() { + { + match stmt.node { + hir::StmtExpr(ref expr, id) | hir::StmtSemi(ref expr, id) => + result.push( + StmtRef::Mirror( + Box::new(Stmt { span: stmt.span, + kind: StmtKind::Expr { + scope: cx.tcx.region_maps.node_extent(id), + expr: expr.to_ref() } }))), + } + } +} + +fn issue386() { + match foo { + BiEq | BiLt | BiLe | BiNe | BiGt | BiGe => + true, + BiAnd | BiOr | BiAdd | BiSub | BiMul | BiDiv | BiRem | + BiBitXor | BiBitAnd | BiBitOr | BiShl | BiShr => + false, + } +} + +fn guards() { + match foo { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if foooooooooooooo && barrrrrrrrrrrr => {} + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if foooooooooooooo && barrrrrrrrrrrr => {} + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + if fooooooooooooooooooooo && + (bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb || cccccccccccccccccccccccccccccccccccccccc) => {} + } +} + +fn issue1371() { + Some(match type_ { + sfEvtClosed => Closed, + sfEvtResized => { + let e = unsafe { *event.size.as_ref() }; + + Resized { + width: e.width, + height: e.height, + } + } + sfEvtLostFocus => LostFocus, + sfEvtGainedFocus => GainedFocus, + sfEvtTextEntered => { + TextEntered { + unicode: unsafe { + ::std::char::from_u32((*event.text.as_ref()).unicode) + .expect("Invalid unicode encountered on TextEntered event") + }, + } + } + sfEvtKeyPressed => { + let e = unsafe { event.key.as_ref() }; + + KeyPressed { + code: unsafe { ::std::mem::transmute(e.code) }, + alt: e.alt.to_bool(), + ctrl: e.control.to_bool(), + shift: e.shift.to_bool(), + system: e.system.to_bool(), + } + } + sfEvtKeyReleased => { + let e = unsafe { event.key.as_ref() }; + + KeyReleased { + code: unsafe { ::std::mem::transmute(e.code) }, + alt: e.alt.to_bool(), + ctrl: e.control.to_bool(), + shift: e.shift.to_bool(), + system: e.system.to_bool(), + } + } + }) +} + +fn issue1395() { + let bar = Some(true); + let foo = Some(true); + let mut x = false; + bar.and_then(|_| { + match foo { + None => None, + Some(b) => { + x = true; + Some(b) + } + } + }); +} + +fn issue1456() { + Ok(Recording { + artists: match reader.evaluate(".//mb:recording/mb:artist-credit/mb:name-credit")? { + Nodeset(nodeset) => { + let res: Result, ReadError> = nodeset + .iter() + .map(|node| { + XPathNodeReader::new(node, &context).and_then(|r| ArtistRef::from_xml(&r)) + }) + .collect(); + res? + } + _ => Vec::new(), + }, + }) +} + +fn issue1460() { + let _ = match foo { + REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT => "internal_spec_insert_internal_spec_insert_internal_spec_insert", + _ => "reorder_something", + }; +} + +fn issue525() { + foobar(f, "{}", match *self { + TaskState::Started => "started", + TaskState::Success => "success", + TaskState::Failed => "failed", + }); +} + +// #1838, #1839 +fn match_with_near_max_width() { + let (this_line_uses_99_characters_and_is_formatted_properly, x012345) = match some_expression { + _ => unimplemented!(), + }; + + let (should_be_formatted_like_the_line_above_using_100_characters, x0) = match some_expression { + _ => unimplemented!(), + }; + + let (should_put_the_brace_on_the_next_line_using_101_characters, x0000) = match some_expression + { + _ => unimplemented!(), + }; + match m { + Variant::Tag | Variant::Tag2 | Variant::Tag3 | Variant::Tag4 | Variant::Tag5 | Variant::Tag6 => + {} + } +} + +fn match_with_trailing_spaces() { + match x { + #![allow(simple_match)] + Some(..) => 0, + None => 1, + } +} + +fn issue_2099() { + let a = match x { +}; + let b = match x { + + }; + + match x {} +} + +// #2021 +impl<'tcx> Const<'tcx> { + pub fn from_constval<'a>() -> Const<'tcx> { + let val = match *cv { + ConstVal::Variant(_) | ConstVal::Aggregate(..) | ConstVal::Unevaluated(..) => bug!("MIR must not use `{:?}` (aggregates are expanded to MIR rvalues)", cv), + }; + } +} + +// #2151 +fn issue_2151() { + match either { + x => { + + }y => () + } +} + +// #2152 +fn issue_2152() { + match m { + "aaaaaaaaaaaaa" | "bbbbbbbbbbbbb" | "cccccccccccccccccccccccccccccccccccccccccccc" if true => {} + "bind" | "writev" | "readv" | "sendmsg" | "recvmsg" if android && (aarch64 || x86_64) => true, + } +} + +// #2376 +// Preserve block around expressions with condition. +fn issue_2376() { + let mut x = None; + match x { + Some(0) => { + for i in 1..11 { + x = Some(i); + } + } + Some(ref mut y) => { + while *y < 10 { + *y += 1; + } + } + None => { + while let None = x { + x = Some(10); + } + } + } +} + +// #2621 +// Strip leading `|` in match arm patterns +fn issue_2621() { + let x = Foo::A; + match x { + Foo::A => println!("No vert single condition"), + Foo::B | Foo::C => println!("Center vert two conditions"), + | Foo::D => println!("Preceding vert single condition"), + | Foo::E + | Foo::F => println!("Preceding vert over two lines"), + Foo::G | + Foo::H => println!("Trailing vert over two lines"), + // Comment on its own line + | Foo::I => println!("With comment"), // Comment after line + } +} + +fn issue_2377() { + match tok { + Tok::Not + | Tok::BNot + | Tok::Plus + | Tok::Minus + | Tok::PlusPlus + | Tok::MinusMinus + | Tok::Void + | Tok::Delete if prec <= 16 => { + // code here... + } + Tok::TypeOf if prec <= 16 => {} + } +} + +// #3040 +fn issue_3040() { + { + match foo { + DevtoolScriptControlMsg::WantsLiveNotifications(id, to_send) => { + match documents.find_window(id) { + Some(window) => devtools::handle_wants_live_notifications(window.upcast(), to_send), + None => return warn!("Message sent to closed pipeline {}.", id), + } + } + } + } +} + +// #3030 +fn issue_3030() { + match input.trim().parse::() { + Ok(val) + if !( + // A valid number is the same as what rust considers to be valid, + // except for +1., NaN, and Infinity. + val.is_infinite() || val + .is_nan() || input.ends_with(".") || input.starts_with("+") + ) + => { + } + } +} + +fn issue_3005() { + match *token { + Token::Dimension { + value, ref unit, .. + } if num_context.is_ok(context.parsing_mode, value) => + { + return NoCalcLength::parse_dimension(context, value, unit) + .map(LengthOrPercentage::Length) + .map_err(|()| location.new_unexpected_token_error(token.clone())); + }, + } +} + +// #3774 +fn issue_3774() { + { + { + { + match foo { + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => unreachab(), + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => unreacha!(), + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => unreachabl(), + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => unreachae!(), + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => unreachable(), + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => unreachable!(), + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => rrunreachable!(), + } + } + } + } +} diff --git a/src/tools/rustfmt/tests/source/match_overflow_expr.rs b/src/tools/rustfmt/tests/source/match_overflow_expr.rs new file mode 100644 index 0000000000..91275a8942 --- /dev/null +++ b/src/tools/rustfmt/tests/source/match_overflow_expr.rs @@ -0,0 +1,53 @@ +// rustfmt-overflow_delimited_expr: true + +fn main() { + println!( + "Foobar: {}", + match "input" { + "a" => "", + "b" => "", + "c" => "", + "d" => "", + "e" => "", + "f" => "", + "g" => "", + "h" => "", + "i" => "", + "j" => "", + "k" => "", + "l" => "", + "m" => "", + "n" => "", + "o" => "", + "p" => "", + "q" => "", + "r" => "Rust", + } + ); +} + +fn main() { + println!( + "Very Long Input String Which Makes It Impossible To Fit On The Same Line: {}", + match "input" { + "a" => "", + "b" => "", + "c" => "", + "d" => "", + "e" => "", + "f" => "", + "g" => "", + "h" => "", + "i" => "", + "j" => "", + "k" => "", + "l" => "", + "m" => "", + "n" => "", + "o" => "", + "p" => "", + "q" => "", + "r" => "Rust", + } + ); +} diff --git a/src/tools/rustfmt/tests/source/max-line-length-in-chars.rs b/src/tools/rustfmt/tests/source/max-line-length-in-chars.rs new file mode 100644 index 0000000000..d49fbb7e30 --- /dev/null +++ b/src/tools/rustfmt/tests/source/max-line-length-in-chars.rs @@ -0,0 +1,4 @@ +// rustfmt-max_width: 25 + +// абвгдеёжзийклмнопрст +fn main() {} diff --git a/src/tools/rustfmt/tests/source/merge_imports_true_compat.rs b/src/tools/rustfmt/tests/source/merge_imports_true_compat.rs new file mode 100644 index 0000000000..bcea943512 --- /dev/null +++ b/src/tools/rustfmt/tests/source/merge_imports_true_compat.rs @@ -0,0 +1,4 @@ +// rustfmt-merge_imports: true + +use a::b; +use a::c; \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/mod-1.rs b/src/tools/rustfmt/tests/source/mod-1.rs new file mode 100644 index 0000000000..427a355b6b --- /dev/null +++ b/src/tools/rustfmt/tests/source/mod-1.rs @@ -0,0 +1,29 @@ +// Deeply indented modules. + + mod foo { mod bar { mod baz {} } } + +mod foo { + mod bar { + mod baz { + fn foo() { bar() } + } + } + + mod qux { + + } +} + +mod boxed { pub use std::boxed::{Box, HEAP}; } + +pub mod x { + pub fn freopen(filename: *const c_char, + mode: *const c_char, + mode2: *const c_char, + mode3: *const c_char, + file: *mut FILE) + -> *mut FILE{} +} + + mod y { // sup boooooiiii + } diff --git a/src/tools/rustfmt/tests/source/mod-2.rs b/src/tools/rustfmt/tests/source/mod-2.rs new file mode 100644 index 0000000000..7202e00203 --- /dev/null +++ b/src/tools/rustfmt/tests/source/mod-2.rs @@ -0,0 +1,4 @@ +// Some nested mods + +#[cfg(test)] mod nestedmod ; +pub mod no_new_line_beginning; diff --git a/src/tools/rustfmt/tests/source/mod_skip_child.rs b/src/tools/rustfmt/tests/source/mod_skip_child.rs new file mode 100644 index 0000000000..d48c4a37e8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/mod_skip_child.rs @@ -0,0 +1,2 @@ +// rustfmt-skip_children: true +mod nested_skipped; diff --git a/src/tools/rustfmt/tests/source/multiple.rs b/src/tools/rustfmt/tests/source/multiple.rs new file mode 100644 index 0000000000..f89f4f68da --- /dev/null +++ b/src/tools/rustfmt/tests/source/multiple.rs @@ -0,0 +1,134 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-format_strings: true +// Test of lots of random stuff. +// FIXME split this into multiple, self-contained tests. + + +#[attr1] extern crate foo; +#[attr2] #[attr3] extern crate foo; +#[attr1]extern crate foo; +#[attr2]#[attr3]extern crate foo; + +use std::cell::*; +use std::{any, ascii, self, borrow, boxed, char, borrow, boxed, char, borrow, borrow, boxed, char, borrow, boxed, char, borrow, boxed, char, borrow, boxed, char, borrow, boxed, char, borrow, boxed, char, borrow, boxed, char, borrow, boxed, char}; + +mod doc; +mod other; + + +// sfdgfffffffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffff + + fn foo(a: isize, + b: u32, /* blah blah */ + c: f64) { + +} + +fn foo()->Box where 'a: 'b, for<'a> D<'b>: 'a { + hello!() +} + +fn baz<'a: 'b /* comment on 'a */, T: SomsssssssssssssssssssssssssssssssssssssssssssssssssssssseType /* comment on T */>(a: A, b: B /* comment on b */, c: C) -> Bob { + #[attr1] extern crate foo; + #[attr2] #[attr3] extern crate foo; + #[attr1]extern crate foo; + #[attr2]#[attr3]extern crate foo; +} + +#[rustfmt::skip] +fn qux(a: dadsfa, // Comment 1 + b: sdfasdfa, // Comment 2 + c: dsfdsafa) // Comment 3 +{ + +} + +/// Blah blah blah. +impl Bar { + fn foo(&mut self, a: sdfsdfcccccccccccccccccccccccccccccccccccccccccccccccccc, // comment on a + b: sdfasdfsdfasfs /*closing comment*/ ) -> isize {} + + /// Blah blah blah. + pub fn f2(self) { + (foo, bar) + } + + #[an_attribute] + fn f3(self) -> Dog { + } +} + +/// The `nodes` and `edges` method each return instantiations of +/// `Cow<[T]>` to leave implementers the freedom to create + +/// entirely new vectors or to pass back slices into internally owned +/// vectors. +pub trait GraphWalk<'a, N, E> { + /// Returns all the nodes in this graph. + fn nodes(&'a self) -> Nodes<'a, N>; + /// Returns all of the edges in this graph. + fn edges(&'a self) -> Edges<'a, E>; + /// The source node for `edge`. + fn source(&'a self, edge: &E) -> N; + /// The target node for `edge`. + fn target(&'a self, edge: &E) -> N; +} + +/// A Doc comment +#[AnAttribute] +pub struct Foo { + #[rustfmt::skip] + f : SomeType, // Comment beside a field + f : SomeType, // Comment beside a field + // Comment on a field + g: SomeOtherType, + /// A doc comment on a field + h: AThirdType,} + +struct Bar; + +// With a where-clause and generics. +pub struct Foo<'a, Y: Baz> + where X: Whatever +{ + f: SomeType, // Comment beside a field +} + +fn foo(ann: &'a (PpAnn+'a)) {} + +fn main() { + for i in 0i32..4 { + println!("{}", i); + } + + + while true { + hello(); + } + + let rc = Cell::new(42usize,42usize, Cell::new(42usize, remaining_widthremaining_widthremaining_widthremaining_width), 42usize); + let rc = RefCell::new(42usize,remaining_width, remaining_width); // a comment + let x = "Hello!!!!!!!!! abcd abcd abcd abcd abcd abcd\n abcd abcd abcd abcd abcd abcd abcd abcd abcd \ + abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd \ + abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd"; + let s = expand(a + , + b); } + +fn deconstruct() -> (SocketAddr, Method, Headers, + RequestUri, HttpVersion, + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) { +} + +fn deconstruct(foo: Bar) -> (SocketAddr, Method, Headers, + RequestUri, HttpVersion, + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) { +} + +#[rustfmt::skip] +mod a{ +fn foo(x: T) { + let x: T = dfasdf; +} +} diff --git a/src/tools/rustfmt/tests/source/negative-impl.rs b/src/tools/rustfmt/tests/source/negative-impl.rs new file mode 100644 index 0000000000..da242d4f3d --- /dev/null +++ b/src/tools/rustfmt/tests/source/negative-impl.rs @@ -0,0 +1,7 @@ +impl ! Display for JoinHandle { } + +impl ! Box < JoinHandle > { } + +impl ! std :: fmt :: Display for JoinHandle < T : std :: future :: Future + std :: marker :: Send + std :: marker :: Sync > { } + +impl ! JoinHandle < T : std :: future :: Future < Output > + std :: marker :: Send + std :: marker :: Sync + 'static > + 'static { } diff --git a/src/tools/rustfmt/tests/source/nested-if-else.rs b/src/tools/rustfmt/tests/source/nested-if-else.rs new file mode 100644 index 0000000000..9a54789ddc --- /dev/null +++ b/src/tools/rustfmt/tests/source/nested-if-else.rs @@ -0,0 +1,11 @@ +fn issue1518() { + Some(Object { + field: if a { + a_thing + } else if b { + b_thing + } else { + c_thing + }, + }) +} diff --git a/src/tools/rustfmt/tests/source/nested_skipped/mod.rs b/src/tools/rustfmt/tests/source/nested_skipped/mod.rs new file mode 100644 index 0000000000..44b25ca879 --- /dev/null +++ b/src/tools/rustfmt/tests/source/nested_skipped/mod.rs @@ -0,0 +1,3 @@ +fn ugly() { +92; +} diff --git a/src/tools/rustfmt/tests/source/nestedmod/mod.rs b/src/tools/rustfmt/tests/source/nestedmod/mod.rs new file mode 100644 index 0000000000..d04e49570a --- /dev/null +++ b/src/tools/rustfmt/tests/source/nestedmod/mod.rs @@ -0,0 +1,13 @@ + +mod mod2a; +mod mod2b; + +mod mymod1 { + use mod2a::{Foo,Bar}; +mod mod3a; +} + +#[path="mod2c.rs"] +mod mymod2; + +mod submod2; diff --git a/src/tools/rustfmt/tests/source/nestedmod/mod2a.rs b/src/tools/rustfmt/tests/source/nestedmod/mod2a.rs new file mode 100644 index 0000000000..5df457a831 --- /dev/null +++ b/src/tools/rustfmt/tests/source/nestedmod/mod2a.rs @@ -0,0 +1,4 @@ +// This is an empty file containing only +// comments + +// ................... diff --git a/src/tools/rustfmt/tests/source/nestedmod/mod2b.rs b/src/tools/rustfmt/tests/source/nestedmod/mod2b.rs new file mode 100644 index 0000000000..f128e2da6d --- /dev/null +++ b/src/tools/rustfmt/tests/source/nestedmod/mod2b.rs @@ -0,0 +1,3 @@ + +#[path="mod2a.rs"] +mod c; diff --git a/src/tools/rustfmt/tests/source/nestedmod/mod2c.rs b/src/tools/rustfmt/tests/source/nestedmod/mod2c.rs new file mode 100644 index 0000000000..eda6b233e4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/nestedmod/mod2c.rs @@ -0,0 +1,3 @@ +// A standard mod + +fn a( ) {} diff --git a/src/tools/rustfmt/tests/source/nestedmod/mymod1/mod3a.rs b/src/tools/rustfmt/tests/source/nestedmod/mymod1/mod3a.rs new file mode 100644 index 0000000000..f28bde5e56 --- /dev/null +++ b/src/tools/rustfmt/tests/source/nestedmod/mymod1/mod3a.rs @@ -0,0 +1,2 @@ +// Another mod +fn a( ) { } diff --git a/src/tools/rustfmt/tests/source/nestedmod/submod2/a.rs b/src/tools/rustfmt/tests/source/nestedmod/submod2/a.rs new file mode 100644 index 0000000000..0eaf08f0d2 --- /dev/null +++ b/src/tools/rustfmt/tests/source/nestedmod/submod2/a.rs @@ -0,0 +1,6 @@ +// Yet Another mod +// Nested + +use c::a; + +fn foo( ) { } diff --git a/src/tools/rustfmt/tests/source/nestedmod/submod2/mod.rs b/src/tools/rustfmt/tests/source/nestedmod/submod2/mod.rs new file mode 100644 index 0000000000..52f8be9102 --- /dev/null +++ b/src/tools/rustfmt/tests/source/nestedmod/submod2/mod.rs @@ -0,0 +1,5 @@ +// Another mod + +mod a; + +use a::a; diff --git a/src/tools/rustfmt/tests/source/no_arg_with_commnet.rs b/src/tools/rustfmt/tests/source/no_arg_with_commnet.rs new file mode 100644 index 0000000000..ea4ee0f1ee --- /dev/null +++ b/src/tools/rustfmt/tests/source/no_arg_with_commnet.rs @@ -0,0 +1,2 @@ +fn foo( /* cooment */ +) {} diff --git a/src/tools/rustfmt/tests/source/no_new_line_beginning.rs b/src/tools/rustfmt/tests/source/no_new_line_beginning.rs new file mode 100644 index 0000000000..f79c691f08 --- /dev/null +++ b/src/tools/rustfmt/tests/source/no_new_line_beginning.rs @@ -0,0 +1,2 @@ +fn main() { +} diff --git a/src/tools/rustfmt/tests/source/normalize_doc_attributes_should_not_imply_format_doc_comments.rs b/src/tools/rustfmt/tests/source/normalize_doc_attributes_should_not_imply_format_doc_comments.rs new file mode 100644 index 0000000000..a97705bfb3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/normalize_doc_attributes_should_not_imply_format_doc_comments.rs @@ -0,0 +1,15 @@ +// rustfmt-normalize_doc_attributes: true + +/// Foo +/// +/// # Example +/// ``` +/// # #![cfg_attr(not(dox), feature(cfg_target_feature, target_feature, stdsimd))] +/// # #![cfg_attr(not(dox), no_std)] +/// fn foo() { } +/// ``` +/// +fn foo() {} + +#[doc = "Bar documents"] +fn bar() {} diff --git a/src/tools/rustfmt/tests/source/normalize_multiline_doc_attribute.rs b/src/tools/rustfmt/tests/source/normalize_multiline_doc_attribute.rs new file mode 100644 index 0000000000..3564e3e7ad --- /dev/null +++ b/src/tools/rustfmt/tests/source/normalize_multiline_doc_attribute.rs @@ -0,0 +1,12 @@ +// rustfmt-unstable: true +// rustfmt-normalize_doc_attributes: true + +#[doc = "This comment +is split +on multiple lines"] +fn foo() {} + +#[doc = " B1"] +#[doc = ""] +#[doc = " A1"] +fn bar() {} diff --git a/src/tools/rustfmt/tests/source/one_line_if_v1.rs b/src/tools/rustfmt/tests/source/one_line_if_v1.rs new file mode 100644 index 0000000000..d3dcbe6787 --- /dev/null +++ b/src/tools/rustfmt/tests/source/one_line_if_v1.rs @@ -0,0 +1,42 @@ +// rustfmt-version: One + +fn plain_if(x: bool) -> u8 { + if x { + 0 + } else { + 1 + } +} + +fn paren_if(x: bool) -> u8 { + (if x { 0 } else { 1 }) +} + +fn let_if(x: bool) -> u8 { + let x = if x { + foo() + } else { + bar() + }; + x +} + +fn return_if(x: bool) -> u8 { + return if x { + 0 + } else { + 1 + }; +} + +fn multi_if() { + use std::io; + if x { foo() } else { bar() } + if x { foo() } else { bar() } +} + +fn middle_if() { + use std::io; + if x { foo() } else { bar() } + let x = 1; +} diff --git a/src/tools/rustfmt/tests/source/one_line_if_v2.rs b/src/tools/rustfmt/tests/source/one_line_if_v2.rs new file mode 100644 index 0000000000..40c834959f --- /dev/null +++ b/src/tools/rustfmt/tests/source/one_line_if_v2.rs @@ -0,0 +1,42 @@ +// rustfmt-version: Two + +fn plain_if(x: bool) -> u8 { + if x { + 0 + } else { + 1 + } +} + +fn paren_if(x: bool) -> u8 { + (if x { 0 } else { 1 }) +} + +fn let_if(x: bool) -> u8 { + let x = if x { + foo() + } else { + bar() + }; + x +} + +fn return_if(x: bool) -> u8 { + return if x { + 0 + } else { + 1 + }; +} + +fn multi_if() { + use std::io; + if x { foo() } else { bar() } + if x { foo() } else { bar() } +} + +fn middle_if() { + use std::io; + if x { foo() } else { bar() } + let x = 1; +} diff --git a/src/tools/rustfmt/tests/source/other.rs b/src/tools/rustfmt/tests/source/other.rs new file mode 100644 index 0000000000..dfce84fcdc --- /dev/null +++ b/src/tools/rustfmt/tests/source/other.rs @@ -0,0 +1,5 @@ +// Part of multiple.rs + +fn bob() { + println!("hello other!"); +} diff --git a/src/tools/rustfmt/tests/source/paren.rs b/src/tools/rustfmt/tests/source/paren.rs new file mode 100644 index 0000000000..04e5ab7a55 --- /dev/null +++ b/src/tools/rustfmt/tests/source/paren.rs @@ -0,0 +1,6 @@ +fn main() { + let x = (((1))); + let y = (/* comment */((2))); + let z = (((3)/* comment */)); + let a = (((4/* comment */))); +} diff --git a/src/tools/rustfmt/tests/source/path_clarity/foo.rs b/src/tools/rustfmt/tests/source/path_clarity/foo.rs new file mode 100644 index 0000000000..cd247fabfe --- /dev/null +++ b/src/tools/rustfmt/tests/source/path_clarity/foo.rs @@ -0,0 +1,2 @@ +// rustfmt-edition: 2018 +mod bar; diff --git a/src/tools/rustfmt/tests/source/path_clarity/foo/bar.rs b/src/tools/rustfmt/tests/source/path_clarity/foo/bar.rs new file mode 100644 index 0000000000..8c1be504c0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/path_clarity/foo/bar.rs @@ -0,0 +1,3 @@ +pub fn fn_in_bar( ) { + println!( "foo/bar.rs" ); +} diff --git a/src/tools/rustfmt/tests/source/paths.rs b/src/tools/rustfmt/tests/source/paths.rs new file mode 100644 index 0000000000..ebc26f146e --- /dev/null +++ b/src/tools/rustfmt/tests/source/paths.rs @@ -0,0 +1,25 @@ +// rustfmt-normalize_comments: true + +fn main() { + let constellation_chan = Constellation:: ::start( + compositor_proxy, + resource_task, + image_cache_task,font_cache_task, + time_profiler_chan, + mem_profiler_chan, + devtools_chan, + storage_task, + supports_clipboard + ); + + Quux::::some_func(); + + < *mut JSObject >:: relocate(entry); + + let x: Foo
; + let x: Foo/*::*/; +} + +fn op(foo: Bar, key : &[u8], upd : Fn(Option<&memcache::Item> , Baz ) -> Result) -> MapResult {} diff --git a/src/tools/rustfmt/tests/source/pattern-condense-wildcards.rs b/src/tools/rustfmt/tests/source/pattern-condense-wildcards.rs new file mode 100644 index 0000000000..69c3fa3cba --- /dev/null +++ b/src/tools/rustfmt/tests/source/pattern-condense-wildcards.rs @@ -0,0 +1,12 @@ +// rustfmt-normalize_comments: true +// rustfmt-condense_wildcard_suffixes: true + +fn main() { + match x { + Butt (_,_) => "hah", + Tup (_) => "nah", + Quad (_,_, x,_) => " also no rewrite", + Quad (x, _, _, _) => "condense me pls", + Weird (x, _, _, /* don't condense before */ _, _, _) => "pls work", + } +} diff --git a/src/tools/rustfmt/tests/source/pattern.rs b/src/tools/rustfmt/tests/source/pattern.rs new file mode 100644 index 0000000000..f06d03cadf --- /dev/null +++ b/src/tools/rustfmt/tests/source/pattern.rs @@ -0,0 +1,90 @@ +// rustfmt-normalize_comments: true +#![feature(exclusive_range_pattern)] +use core::u8::MAX; + +fn main() { + let z = match x { + "pat1" => 1, + ( ref x, ref mut y /*comment*/) => 2, + }; + + if let < T as Trait > :: CONST = ident { + do_smth(); + } + + let Some ( ref xyz /* comment! */) = opt; + + if let None = opt2 { panic!("oh noes"); } + + let foo@bar (f) = 42; + let a::foo ( ..) = 42; + let [ ] = 42; + let [a, b,c ] = 42; + let [ a,b,c ] = 42; + let [a, b, c, d,e,f, g] = 42; + let foo { } = 42; + let foo {..} = 42; + let foo { x, y: ref foo, .. } = 42; + let foo { x, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: ref foo, .. } = 42; + let foo { x, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: ref foo, } = 42; + let foo { x, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: ref foo, .. }; + let foo { x, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: ref foo, }; + + match b"12" { + [0, + 1..MAX + ] => {} + _ => {} + } +} + +impl<'a,'b> ResolveGeneratedContentFragmentMutator<'a,'b> { + fn mutate_fragment(&mut self, fragment: &mut Fragment) { + match **info { + GeneratedContentInfo::ContentItem( + ContentItem::Counter( + ref counter_name, + counter_style + ) + ) => {}}} +} + +fn issue_1319() { + if let (Event { .. }, .. ) = ev_state {} +} + +fn issue_1874() { + if let Some(()) = x { +y + } +} + +fn combine_patterns() { + let x = match y { + Some( + Some( + Foo { + z: Bar(..), + a: Bar(..), + b: Bar(..), + }, + ), + ) => z, + _ => return, + }; +} + +fn slice_patterns() { + match b"123" { + [0, ..] => {} + [0, foo] => {} + _ => {} + } +} + +fn issue3728() { + let foo = | + (c,) + | c; + foo((1,)); +} diff --git a/src/tools/rustfmt/tests/source/preserves_carriage_return_for_unix.rs b/src/tools/rustfmt/tests/source/preserves_carriage_return_for_unix.rs new file mode 100644 index 0000000000..e5e0b28659 --- /dev/null +++ b/src/tools/rustfmt/tests/source/preserves_carriage_return_for_unix.rs @@ -0,0 +1,2 @@ +// rustfmt-newline_style: Unix +// Foo Bar diff --git a/src/tools/rustfmt/tests/source/preserves_carriage_return_for_windows.rs b/src/tools/rustfmt/tests/source/preserves_carriage_return_for_windows.rs new file mode 100644 index 0000000000..1085360ee5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/preserves_carriage_return_for_windows.rs @@ -0,0 +1,2 @@ +// rustfmt-newline_style: Windows +// Foo Bar diff --git a/src/tools/rustfmt/tests/source/pub-restricted.rs b/src/tools/rustfmt/tests/source/pub-restricted.rs new file mode 100644 index 0000000000..30051fa72e --- /dev/null +++ b/src/tools/rustfmt/tests/source/pub-restricted.rs @@ -0,0 +1,64 @@ +pub( super ) enum WriteState { + WriteId { + id: U64Writer, + size: U64Writer, + payload: Option>, + }, + WriteSize { + size: U64Writer, + payload: Option>, + }, + WriteData(Writer), +} + +pub( crate ) enum WriteState { + WriteId { + id: U64Writer, + size: U64Writer, + payload: Option>, + }, + WriteSize { + size: U64Writer, + payload: Option>, + }, + WriteData(Writer), +} + + crate enum WriteState { + WriteId { + id: U64Writer, + size: U64Writer, + payload: Option>, + }, + WriteSize { + size: U64Writer, + payload: Option>, + }, + WriteData(Writer), +} + +pub(in ::global:: path :: to::some_mod ) enum WriteState { + WriteId { + id: U64Writer, + size: U64Writer, + payload: Option>, + }, + WriteSize { + size: U64Writer, + payload: Option>, + }, + WriteData(Writer), +} + +pub( in local:: path :: to::some_mod ) enum WriteState { + WriteId { + id: U64Writer, + size: U64Writer, + payload: Option>, + }, + WriteSize { + size: U64Writer, + payload: Option>, + }, + WriteData(Writer), +} diff --git a/src/tools/rustfmt/tests/source/remove_blank_lines.rs b/src/tools/rustfmt/tests/source/remove_blank_lines.rs new file mode 100644 index 0000000000..43733ce763 --- /dev/null +++ b/src/tools/rustfmt/tests/source/remove_blank_lines.rs @@ -0,0 +1,44 @@ +fn main() { + + + + + let x = 1; + + + let y = 2; + + + println!("x + y = {}", x + y); + + + +} + + +fn foo() { + + #![attribute] + + let x = 1; + + // comment + + +} +// comment after item + + +// comment before item +fn bar() { + let x = 1; + // comment after statement + + + // comment before statement + let y = 2; + let z = 3; + + + println!("x + y + z = {}", x + y + z); +} diff --git a/src/tools/rustfmt/tests/source/reorder-impl-items.rs b/src/tools/rustfmt/tests/source/reorder-impl-items.rs new file mode 100644 index 0000000000..16efff55b0 --- /dev/null +++ b/src/tools/rustfmt/tests/source/reorder-impl-items.rs @@ -0,0 +1,15 @@ +// rustfmt-reorder_impl_items: true + +// The ordering of the following impl items should be idempotent. +impl<'a> Command<'a> { + pub fn send_to(&self, w: &mut io::Write) -> io::Result<()> { + match self { + &Command::Data(ref c) => c.send_to(w), + &Command::Vrfy(ref c) => c.send_to(w), + } + } + + pub fn parse(arg: &[u8]) -> Result { + nom_to_result(command(arg)) + } +} diff --git a/src/tools/rustfmt/tests/source/single-line-if-else.rs b/src/tools/rustfmt/tests/source/single-line-if-else.rs new file mode 100644 index 0000000000..bcde390d11 --- /dev/null +++ b/src/tools/rustfmt/tests/source/single-line-if-else.rs @@ -0,0 +1,49 @@ + +// Format if-else expressions on a single line, when possible. + +fn main() { + let a = if 1 > 2 { + unreachable!() + } else { + 10 + }; + + let a = if x { 1 } else if y { 2 } else { 3 }; + + let b = if cond() { + 5 + } else { + // Brief comment. + 10 + }; + + let c = if cond() { + statement(); + + 5 + } else { + 10 + }; + + let d = if let Some(val) = turbo + { "cool" } else { + "beans" }; + + if cond() { statement(); } else { other_statement(); } + + if true { + do_something() + } + + let x = if veeeeeeeeery_loooooong_condition() { aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa } else { bbbbbbbbbb }; + + let x = if veeeeeeeeery_loooooong_condition() { aaaaaaaaaaaaaaaaaaaaaaaaa } else { + bbbbbbbbbb }; + + funk(if test() { + 1 + } else { + 2 + }, + arg2); +} diff --git a/src/tools/rustfmt/tests/source/single-line-macro/v1.rs b/src/tools/rustfmt/tests/source/single-line-macro/v1.rs new file mode 100644 index 0000000000..a3aa631ed4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/single-line-macro/v1.rs @@ -0,0 +1,10 @@ +// rustfmt-version: One + +// #2652 +// Preserve trailing comma inside macro, even if it looks an array. +macro_rules! bar { + ($m:ident) => { + $m!([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,]); + $m!([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z]); + }; +} diff --git a/src/tools/rustfmt/tests/source/single-line-macro/v2.rs b/src/tools/rustfmt/tests/source/single-line-macro/v2.rs new file mode 100644 index 0000000000..51a665f756 --- /dev/null +++ b/src/tools/rustfmt/tests/source/single-line-macro/v2.rs @@ -0,0 +1,10 @@ +// rustfmt-version: Two + +// #2652 +// Preserve trailing comma inside macro, even if it looks an array. +macro_rules! bar { + ($m:ident) => { + $m!([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,]); + $m!([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z]); + }; +} diff --git a/src/tools/rustfmt/tests/source/soft-wrapping.rs b/src/tools/rustfmt/tests/source/soft-wrapping.rs new file mode 100644 index 0000000000..b0682d4db3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/soft-wrapping.rs @@ -0,0 +1,15 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 80 +// Soft wrapping for comments. + +// #535, soft wrapping for comments +// Compare the lowest `f32` of both inputs for greater than or equal. The +// lowest 32 bits of the result will be `0xffffffff` if `a.extract(0)` is +// ggreater than or equal `b.extract(0)`, or `0` otherwise. The upper 96 bits off +// the result are the upper 96 bits of `a`. + +/// Compares the lowest `f32` of both inputs for greater than or equal. The +/// lowest 32 bits of the result will be `0xffffffff` if `a.extract(0)` is +/// greater than or equal `b.extract(0)`, or `0` otherwise. The upper 96 bits off +/// the result are the upper 96 bits of `a`. +fn foo() {} diff --git a/src/tools/rustfmt/tests/source/space-not-before-newline.rs b/src/tools/rustfmt/tests/source/space-not-before-newline.rs new file mode 100644 index 0000000000..2a1e185690 --- /dev/null +++ b/src/tools/rustfmt/tests/source/space-not-before-newline.rs @@ -0,0 +1,8 @@ +struct Foo { + a: (), + // spaces ^^^ to be removed +} +enum Foo { + Bar, + // spaces ^^^ to be removed +} diff --git a/src/tools/rustfmt/tests/source/spaces-around-ranges.rs b/src/tools/rustfmt/tests/source/spaces-around-ranges.rs new file mode 100644 index 0000000000..1936b5e161 --- /dev/null +++ b/src/tools/rustfmt/tests/source/spaces-around-ranges.rs @@ -0,0 +1,15 @@ +// rustfmt-spaces_around_ranges: true + +fn bar(v: &[u8]) {} + +fn foo() { + let a = vec![0; 20]; + for j in 0..=20 { + for i in 0..3 { + bar(a[i..j]); + bar(a[i..]); + bar(a[..j]); + bar(a[..=(j + 1)]); + } + } +} diff --git a/src/tools/rustfmt/tests/source/statements.rs b/src/tools/rustfmt/tests/source/statements.rs new file mode 100644 index 0000000000..c840b8ce10 --- /dev/null +++ b/src/tools/rustfmt/tests/source/statements.rs @@ -0,0 +1,43 @@ +// FIXME(calebcartwright) - Hopefully one day we can +// elide these redundant semis like we do in other contexts. +fn redundant_item_semis() { + impl Foo { + fn get(&self) -> usize { + 5 + } + }; + + impl Bar { + fn get(&self) -> usize { + 5 + } + } /*asdfsf*/; + + + impl Baz { + fn get(&self) -> usize { + 5 + } + } /*asdfsf*/ + + // why would someone do this + ; + + + impl Qux { + fn get(&self) -> usize { + 5 + } + } + + // why + ; + + impl Lorem { + fn get(&self) -> usize { + 5 + } + } + // oh why + ; +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/static.rs b/src/tools/rustfmt/tests/source/static.rs new file mode 100644 index 0000000000..970786381c --- /dev/null +++ b/src/tools/rustfmt/tests/source/static.rs @@ -0,0 +1,23 @@ +const FILE_GENERIC_READ: DWORD = + STANDARD_RIGHTS_READ | FILE_READ_DATA | + FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE; + +static boolnames: &'static[&'static str] = &["bw", "am", "xsb", "xhp", "xenl", "eo", + "gn", "hc", "km", "hs", "in", "db", "da", "mir", "msgr", "os", "eslok", "xt", "hz", "ul", "xon", + "nxon", "mc5i", "chts", "nrrmc", "npc", "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy", + "xvpa", "sam", "cpix", "lpix", "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"]; + +static mut name: SomeType = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; + + pub static count : u8 = 10 ; + +pub const test: &Type = &val; + +impl Color { + pub const WHITE: u32 = 10; +} + +// #1391 +pub const XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: NTSTATUS = 0 as usize; + +pub const XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: Yyyyyyyyyyyyyyyyyyyyyyyyyyyy = 1; diff --git a/src/tools/rustfmt/tests/source/string-lit-2.rs b/src/tools/rustfmt/tests/source/string-lit-2.rs new file mode 100644 index 0000000000..6b95e25a05 --- /dev/null +++ b/src/tools/rustfmt/tests/source/string-lit-2.rs @@ -0,0 +1,25 @@ +fn main() -> &'static str { + let too_many_lines = "Hello"; + let leave_me = "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss\ + s + jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj"; +} + +fn issue_1237() { + let msg = "eedadn\n\ + drvtee\n\ + eandsr\n\ + raavrd\n\ + atevrs\n\ + tsrnev\n\ + sdttsa\n\ + rasrtv\n\ + nssdts\n\ + ntnada\n\ + svetve\n\ + tesnvt\n\ + vntsnd\n\ + vrdear\n\ + dvrsen\n\ + enarar"; +} diff --git a/src/tools/rustfmt/tests/source/string-lit.rs b/src/tools/rustfmt/tests/source/string-lit.rs new file mode 100644 index 0000000000..7719e76ffe --- /dev/null +++ b/src/tools/rustfmt/tests/source/string-lit.rs @@ -0,0 +1,61 @@ +// rustfmt-format_strings: true +// Long string literals + +fn main() -> &'static str { + let str = "AAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAaAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAa"; + let str = "AAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAa"; + let str = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + + let too_many_lines = "H\ + e\ + l\ + l\ + o"; + + // Make sure we don't break after an escape character. + let odd_length_name = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; + let even_length_name = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; + + let really_long_variable_name = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + + let raw_string = r#"Do +not +remove +formatting"#; + + filename.replace(" ", "\\" ); + + let xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = + funktion("yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"); + + let unicode = "a̐éö̲\r\n"; + let unicode2 = "Löwe 老虎 Léopard"; + let unicode3 = "中华Việt Nam"; + let unicode4 = "☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃"; + + "stuffin'" +} + +fn issue682() { + let a = "hello \\ o/"; + let b = a.replace("\\ ", "\\"); +} + +fn issue716() { + println!("forall x. mult(e(), x) = x /\\ + forall x. mult(x, x) = e()"); +} + +fn issue_1282() { + { + match foo { + Permission::AndroidPermissionAccessLocationExtraCommands => { + "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" + } + } + } +} + +// #1987 +#[link_args = "-s NO_FILESYSTEM=1 -s NO_EXIT_RUNTIME=1 -s EXPORTED_RUNTIME_METHODS=[\"_malloc\"] -s NO_DYNAMIC_EXECUTION=1 -s ELIMINATE_DUPLICATE_FUNCTIONS=1 -s EVAL_CTORS=1"] +extern "C" {} diff --git a/src/tools/rustfmt/tests/source/string_punctuation.rs b/src/tools/rustfmt/tests/source/string_punctuation.rs new file mode 100644 index 0000000000..552c461ed3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/string_punctuation.rs @@ -0,0 +1,9 @@ +// rustfmt-format_strings: true + +fn main() { + println!("ThisIsAReallyLongStringWithNoSpaces.It_should_prefer_to_break_onpunctuation:Likethisssssssssssss"); + format!("{}__{}__{}ItShouldOnlyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyNoticeSemicolonsPeriodsColonsAndCommasAndResortToMid-CharBreaksAfterPunctuation{}{}",x,y,z,a,b); + println!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaalhijalfhiigjapdighjapdigjapdighdapighapdighpaidhg;adopgihadoguaadbadgad,qeoihapethae8t0aet8haetadbjtaeg;ooeouthaoeutgadlgajduabgoiuadogabudogubaodugbadgadgadga;adoughaoeugbaouea"); + println!("sentuhaesnuthaesnutheasunteahusnaethuseantuihaesntdiastnidaetnuhaideuhsenathe。WeShouldSupportNonAsciiPunctuations§ensuhatheasunteahsuneathusneathuasnuhaesnuhaesnuaethusnaetuheasnuth"); + println!("ThisIsASampleOfCJKString.祇園精舍の鐘の声、諸行無常の響きあり。娑羅双樹の花の色、盛者必衰の理をあらはす。奢れる人も久しからず、ただ春の夜の夢のごとし。猛き者もつひにはほろびぬ、ひとへに風の前の塵に同じ。"); +} diff --git a/src/tools/rustfmt/tests/source/struct-field-attributes.rs b/src/tools/rustfmt/tests/source/struct-field-attributes.rs new file mode 100644 index 0000000000..76d6eda885 --- /dev/null +++ b/src/tools/rustfmt/tests/source/struct-field-attributes.rs @@ -0,0 +1,52 @@ +// #1535 +#![feature(struct_field_attributes)] + +struct Foo { + bar: u64, + + #[cfg(test)] + qux: u64, +} + +fn do_something() -> Foo { + Foo { + bar: 0, + + #[cfg(test)] + qux: 1, + } +} + +fn main() { + do_something(); +} + +// #1462 +struct Foo { + foo: usize, + #[cfg(feature="include-bar")] + bar: usize, +} + +fn new_foo() -> Foo { + Foo { + foo: 0, + #[cfg(feature="include-bar")] + bar: 0, + } +} + +// #2044 +pub enum State { + Closure(#[cfg_attr(feature = "serde_derive", serde(state_with = "::serialization::closure"))] GcPtr), +} + +struct Fields( + #[cfg_attr(feature = "serde_derive", serde(state_with = "::base::serialization::shared"))] Arc>, +); + +// #2309 +pub struct A { +#[doc="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"] +pub foos:Vec +} diff --git a/src/tools/rustfmt/tests/source/struct_lits.rs b/src/tools/rustfmt/tests/source/struct_lits.rs new file mode 100644 index 0000000000..c5aaf7ef88 --- /dev/null +++ b/src/tools/rustfmt/tests/source/struct_lits.rs @@ -0,0 +1,143 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// Struct literal expressions. + +fn main() { + let x = Bar; + + // Comment + let y = Foo {a: x }; + + Foo { a: foo() /* comment*/, /* comment*/ b: bar(), ..something }; + + Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: f(), b: b(), }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: f(), b: b(), }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + // Comment + a: foo(), // Comment + // Comment + b: bar(), // Comment + }; + + Foo { a:Bar, + b:f() }; + + Quux { x: if cond { bar(); }, y: baz() }; + + A { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. + first: item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + second: Item + }; + + Some(Data::MethodCallData(MethodCallData { + span: sub_span.unwrap(), + scope: self.enclosing_scope(id), + ref_id: def_id, + decl_id: Some(decl_id), + })); + + Diagram { /* o This graph demonstrates how + * / \ significant whitespace is + * o o preserved. + * /|\ \ + * o o o o */ + graph: G, } +} + +fn matcher() { + TagTerminatedByteMatcher { + matcher: ByteMatcher { + pattern: b" { memb: T } + let foo = Foo:: { memb: 10 }; +} + +fn issue201() { + let s = S{a:0, .. b}; +} + +fn issue201_2() { + let s = S{a: S2{ .. c}, .. b}; +} + +fn issue278() { + let s = S { + a: 0, + // + b: 0, + }; + let s1 = S { + a: 0, + // foo + // + // bar + b: 0, + }; +} + +fn struct_exprs() { + Foo + { a : 1, b:f( 2)}; + Foo{a:1,b:f(2),..g(3)}; + LoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongStruct { ..base }; + IntrinsicISizesContribution { content_intrinsic_sizes: IntrinsicISizes { minimum_inline_size: 0, }, }; +} + +fn issue123() { + Foo { a: b, c: d, e: f }; + + Foo { a: bb, c: dd, e: ff }; + + Foo { a: ddddddddddddddddddddd, b: cccccccccccccccccccccccccccccccccccccc }; +} + +fn issue491() { + Foo { + guard: None, + arm: 0, // Comment + }; + + Foo { + arm: 0, // Comment + }; + + Foo { a: aaaaaaaaaa, b: bbbbbbbb, c: cccccccccc, d: dddddddddd, /* a comment */ + e: eeeeeeeee }; +} + +fn issue698() { + Record { + ffffffffffffffffffffffffffields: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + }; + Record { + ffffffffffffffffffffffffffields: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + } +} + +fn issue835() { + MyStruct {}; + MyStruct { /* a comment */ }; + MyStruct { + // Another comment + }; + MyStruct {} +} + +fn field_init_shorthand() { + MyStruct { x, y, z }; + MyStruct { x, y, z, .. base }; + Foo { aaaaaaaaaa, bbbbbbbb, cccccccccc, dddddddddd, /* a comment */ + eeeeeeeee }; + Record { ffffffffffffffffffffffffffieldsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa }; +} diff --git a/src/tools/rustfmt/tests/source/struct_lits_multiline.rs b/src/tools/rustfmt/tests/source/struct_lits_multiline.rs new file mode 100644 index 0000000000..256ba1bbda --- /dev/null +++ b/src/tools/rustfmt/tests/source/struct_lits_multiline.rs @@ -0,0 +1,81 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-struct_lit_single_line: false + +// Struct literal expressions. + +fn main() { + let x = Bar; + + // Comment + let y = Foo {a: x }; + + Foo { a: foo() /* comment*/, /* comment*/ b: bar(), ..something }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(), b: bar(), }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(), b: bar(), }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + // Comment + a: foo(), // Comment + // Comment + b: bar(), // Comment + }; + + Foo { a:Bar, + b:foo() }; + + Quux { x: if cond { bar(); }, y: baz() }; + + A { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. + first: item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + second: Item + }; + + Some(Data::MethodCallData(MethodCallData { + span: sub_span.unwrap(), + scope: self.enclosing_scope(id), + ref_id: def_id, + decl_id: Some(decl_id), + })); + + Diagram { /* o This graph demonstrates how + * / \ significant whitespace is + * o o preserved. + * /|\ \ + * o o o o */ + graph: G, } +} + +fn matcher() { + TagTerminatedByteMatcher { + matcher: ByteMatcher { + pattern: b" { memb: T } + let foo = Foo:: { memb: 10 }; +} + +fn issue201() { + let s = S{a:0, .. b}; +} + +fn issue201_2() { + let s = S{a: S2{ .. c}, .. b}; +} + +fn issue491() { + Foo { + guard: None, + arm: 0, // Comment + }; +} diff --git a/src/tools/rustfmt/tests/source/struct_lits_visual.rs b/src/tools/rustfmt/tests/source/struct_lits_visual.rs new file mode 100644 index 0000000000..e84652e9ea --- /dev/null +++ b/src/tools/rustfmt/tests/source/struct_lits_visual.rs @@ -0,0 +1,46 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-indent_style: Visual + +// Struct literal expressions. + +fn main() { + let x = Bar; + + // Comment + let y = Foo {a: x }; + + Foo { a: foo() /* comment*/, /* comment*/ b: bar(), ..something }; + + Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: f(), b: b(), }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + // Comment + a: foo(), // Comment + // Comment + b: bar(), // Comment + }; + + Foo { a:Bar, + b:f() }; + + Quux { x: if cond { bar(); }, y: baz() }; + + Baz { x: yxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, z: zzzzz // test + }; + + A { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. + first: item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + second: Item + }; + + Diagram { /* o This graph demonstrates how + * / \ significant whitespace is + * o o preserved. + * /|\ \ + * o o o o */ + graph: G, } +} diff --git a/src/tools/rustfmt/tests/source/struct_lits_visual_multiline.rs b/src/tools/rustfmt/tests/source/struct_lits_visual_multiline.rs new file mode 100644 index 0000000000..d2990f8da3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/struct_lits_visual_multiline.rs @@ -0,0 +1,44 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-indent_style: Visual +// rustfmt-struct_lit_single_line: false + +// Struct literal expressions. + +fn main() { + let x = Bar; + + // Comment + let y = Foo {a: x }; + + Foo { a: foo() /* comment*/, /* comment*/ b: bar(), ..something }; + + Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(), b: bar(), }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + // Comment + a: foo(), // Comment + // Comment + b: bar(), // Comment + }; + + Foo { a:Bar, + b:foo() }; + + Quux { x: if cond { bar(); }, y: baz() }; + + A { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. + first: item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + second: Item + }; + + Diagram { /* o This graph demonstrates how + * / \ significant whitespace is + * o o preserved. + * /|\ \ + * o o o o */ + graph: G, } +} diff --git a/src/tools/rustfmt/tests/source/struct_tuple_visual.rs b/src/tools/rustfmt/tests/source/struct_tuple_visual.rs new file mode 100644 index 0000000000..f95f3fe4fd --- /dev/null +++ b/src/tools/rustfmt/tests/source/struct_tuple_visual.rs @@ -0,0 +1,36 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-indent_style: Visual +fn foo() { + Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(f(), b()); + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(// Comment + foo(), /* Comment */ + // Comment + bar() /* Comment */); + + Foo(Bar, f()); + + Quux(if cond { + bar(); + }, + baz()); + + Baz(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + zzzzz /* test */); + + A(// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit + // amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante + // hendrerit. Donec et mollis dolor. + item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + Item); + + Diagram(// o This graph demonstrates how + // / \ significant whitespace is + // o o preserved. + // /|\ \ + // o o o o + G) +} diff --git a/src/tools/rustfmt/tests/source/structs.rs b/src/tools/rustfmt/tests/source/structs.rs new file mode 100644 index 0000000000..537151b276 --- /dev/null +++ b/src/tools/rustfmt/tests/source/structs.rs @@ -0,0 +1,298 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true + + /// A Doc comment +#[AnAttribute] +pub struct Foo { + #[rustfmt::skip] + f : SomeType, // Comment beside a field + f: SomeType, // Comment beside a field + // Comment on a field + #[AnAttribute] + g: SomeOtherType, + /// A doc comment on a field + h: AThirdType, + pub i: TypeForPublicField +} + +// Destructuring +fn foo() { + S { x: 5, + ..}; + Struct {..} = Struct { a: 1, b: 4 }; + Struct { a, .. } = Struct { a: 1, b: 2, c: 3}; + TupleStruct(a,.., b) = TupleStruct(1, 2); + TupleStruct( ..) = TupleStruct(3, 4); + TupleStruct(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, .., bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) = TupleStruct(1, 2); +} + +// #1095 +struct S { + t: T, +} + +// #1029 +pub struct Foo { + #[doc(hidden)] + // This will NOT get deleted! + bar: String, // hi +} + +// #1029 +struct X { + // `x` is an important number. + #[allow(unused)] // TODO: use + x: u32, +} + +// #410 +#[allow(missing_docs)] +pub struct Writebatch { + #[allow(dead_code)] //only used for holding the internal pointer + writebatch: RawWritebatch, + marker: PhantomData, +} + +struct Bar; + +struct NewType(Type, OtherType); + +struct +NewInt (pub i32, SomeType /* inline comment */, T /* sup */ + + + ); + +struct Qux<'a, + N: Clone + 'a, + E: Clone + 'a, + G: Labeller<'a, N, E> + GraphWalk<'a, N, E>, + W: Write + Copy> +( + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, // Comment + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, + #[AnAttr] + // Comment + /// Testdoc + G, + pub W, +); + +struct Tuple(/*Comment 1*/ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + /* Comment 2 */ BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB,); + +// With a where-clause and generics. +pub struct Foo<'a, Y: Baz> + where X: Whatever +{ + f: SomeType, // Comment beside a field +} + +struct Baz { + + a: A, // Comment A + b: B, // Comment B + c: C, // Comment C + +} + +struct Baz { + a: A, // Comment A + + b: B, // Comment B + + + + + c: C, // Comment C +} + +struct Baz { + + a: A, + + b: B, + c: C, + + + + + d: D + +} + +struct Baz +{ + // Comment A + a: A, + + // Comment B +b: B, + // Comment C + c: C,} + +// Will this be a one-liner? +struct Tuple( + A, //Comment + B +); + +pub struct State time::Timespec> { now: F } + +pub struct State ()> { now: F } + +pub struct State { now: F } + +struct Palette { /// A map of indices in the palette to a count of pixels in approximately that color + foo: i32} + +// Splitting a single line comment into a block previously had a misalignment +// when the field had attributes +struct FieldsWithAttributes { + // Pre Comment + #[rustfmt::skip] pub host:String, // Post comment BBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBB BBBBBBBBBBB + //Another pre comment + #[attr1] + #[attr2] pub id: usize // CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCC CCCCCCCCCCCC +} + +struct Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: node::Handle>, + Type, + NodeType>, +} + +struct Foo(T); +struct Foo(T) where T: Copy, T: Eq; +struct Foo(TTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUUUUUU, TTTTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUU); +struct Foo(TTTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUUUUUU, TTTTTTTTTTTTTTTTTTT) where T: PartialEq; +struct Foo(TTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUUUUUU, TTTTTTTTTTTTTTTTTTTTT) where T: PartialEq; +struct Foo(TTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUUUUUUU, TTTTTTTTTTTTTTTTTTT, UUUUUUUUUUUUUUUUUUU) where T: PartialEq; +struct Foo(TTTTTTTTTTTTTTTTT, // Foo + UUUUUUUUUUUUUUUUUUUUUUUU /* Bar */, + // Baz + TTTTTTTTTTTTTTTTTTT, + // Qux (FIXME #572 - doc comment) + UUUUUUUUUUUUUUUUUUU); + +mod m { + struct X where T: Sized { + a: T, + } +} + +struct Foo(TTTTTTTTTTTTTTTTTTT, + /// Qux + UUUUUUUUUUUUUUUUUUU); + +struct Issue677 { + pub ptr: *const libc::c_void, + pub trace: fn( obj: + *const libc::c_void, tracer : *mut JSTracer ), +} + +struct Foo {} +struct Foo { + } +struct Foo { + // comment + } +struct Foo { + // trailing space -> + + + } +struct Foo { /* comment */ } +struct Foo( /* comment */ ); + +struct LongStruct { + a: A, + the_quick_brown_fox_jumps_over_the_lazy_dog:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +} + +struct Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: node::Handle>, + Type, + NodeType>, +} + +struct Foo(String); + +// #1364 +fn foo() { + convex_shape.set_point(0, &Vector2f { x: 400.0, y: 100.0 }); + convex_shape.set_point(1, &Vector2f { x: 500.0, y: 70.0 }); + convex_shape.set_point(2, &Vector2f { x: 450.0, y: 100.0 }); + convex_shape.set_point(3, &Vector2f { x: 580.0, y: 150.0 }); +} + +// Vertical alignment +struct Foo { + aaaaa: u32, // a + + b: u32, // b + cc: u32, // cc + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 1 + yy: u32, // comment2 + zzz: u32, // comment3 + + aaaaaa: u32, // comment4 + bb: u32, // comment5 + // separate + dd: u32, // comment7 + c: u32, // comment6 + + aaaaaaa: u32, /* multi + * line + * comment + */ + b: u32, // hi + + do_not_push_this_comment1: u32, // comment1 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + please_do_not_push_this_comment3: u32, // comment3 + + do_not_push_this_comment1: u32, // comment1 + // separate + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + please_do_not_push_this_comment3: u32, // comment3 + + do_not_push_this_comment1: u32, // comment1 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + // separate + please_do_not_push_this_comment3: u32, // comment3 +} + +// structs with long identifier +struct Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong {} +struct Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong {} +struct Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong {} +struct Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong { x: i32 } + +// structs with visibility, do not duplicate visibility (#2110). +pub(in self) struct Foo{} +pub(super) struct Foo{} +pub(crate) struct Foo{} +pub(in self) struct Foo(); +pub(super) struct Foo(); +pub(crate) struct Foo(); + +// #2125 +pub struct ReadinessCheckRegistry(Mutex, Box ReadinessCheck + Sync + Send>>>); + +// #2144 unit struct with generics +struct MyBox; +struct MyBoxx where T: ?Sized, S: Clone; + +// #2208 +struct Test { + /// foo + #[serde(default)] + pub join: Vec, + #[serde(default)] pub tls: bool, +} + +// #2818 +struct Paren((i32)) where i32: Trait; +struct Parens((i32, i32)) where i32: Trait; diff --git a/src/tools/rustfmt/tests/source/trailing-comma-never.rs b/src/tools/rustfmt/tests/source/trailing-comma-never.rs new file mode 100644 index 0000000000..c74267cd17 --- /dev/null +++ b/src/tools/rustfmt/tests/source/trailing-comma-never.rs @@ -0,0 +1,45 @@ +// rustfmt-trailing_comma: Never + +enum X { + A, + B, +} + +enum Y { + A, + B +} + +enum TupX { + A(u32), + B(i32, u16), +} + +enum TupY { + A(u32), + B(i32, u16) +} + +enum StructX { + A { + s: u16, + }, + B { + u: u32, + i: i32, + }, +} + +enum StructY { + A { + s: u16, + }, + B { + u: u32, + i: i32, + } +} + +static XXX: [i8; 64] = [ +1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, +]; diff --git a/src/tools/rustfmt/tests/source/trailing_commas.rs b/src/tools/rustfmt/tests/source/trailing_commas.rs new file mode 100644 index 0000000000..3e5fcc8080 --- /dev/null +++ b/src/tools/rustfmt/tests/source/trailing_commas.rs @@ -0,0 +1,47 @@ +// rustfmt-match_block_trailing_comma: true +// rustfmt-trailing_comma: Always + +fn main() { + match foo { + x => {} + y => { + foo(); + } + _ => x + } +} + +fn f(x: T, y: S) -> T where T: P, S: Q +{ + x +} + +impl Trait for T where T: P +{ + fn f(x: T) -> T where T: Q + R + { + x + } +} + +struct Pair where T: P, S: P + Q { + a: T, + b: S +} + +struct TupPair (S, T) where T: P, S: P + Q; + +enum E where S: P, T: P { + A {a: T}, +} + +type Double where T: P, T: Q = Pair; + +extern "C" { + fn f(x: T, y: S) -> T where T: P, S: Q; +} + +trait Q where T: P, S: R +{ + fn f(self, x: T, y: S, z: U) -> Self where U: P, V: P; +} diff --git a/src/tools/rustfmt/tests/source/trailing_comments/hard_tabs.rs b/src/tools/rustfmt/tests/source/trailing_comments/hard_tabs.rs new file mode 100644 index 0000000000..88249aa5fb --- /dev/null +++ b/src/tools/rustfmt/tests/source/trailing_comments/hard_tabs.rs @@ -0,0 +1,21 @@ +// rustfmt-version: Two +// rustfmt-wrap_comments: true +// rustfmt-hard_tabs: true + +impl Foo { + fn foo() { + bar(); // comment 1 + // comment 2 + // comment 3 + baz(); + } +} + +fn lorem_ipsum() { + let f = bar(); // Donec consequat mi. Quisque vitae dolor. Integer lobortis. Maecenas id nulla. Lorem. + // Id turpis. Nam posuere lectus vitae nibh. Etiam tortor orci, sagittis malesuada, rhoncus quis, hendrerit eget, libero. Quisque commodo nulla at nunc. Mauris consequat, enim vitae venenatis sollicitudin, dolor orci bibendum enim, a sagittis nulla nunc quis elit. Phasellus augue. Nunc suscipit, magna tincidunt lacinia faucibus, lacus tellus ornare purus, a pulvinar lacus orci eget nibh. Maecenas sed nibh non lacus tempor faucibus. In hac habitasse platea dictumst. Vivamus a orci at nulla tristique condimentum. Donec arcu quam, dictum accumsan, convallis accumsan, cursus sit amet, ipsum. In pharetra sagittis nunc. + let b = baz(); + + let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0] + // TODO(emilio): It may make sense to make this range [.01, 10.0], to align with css-fonts-4's range of [1, 1000]. +} diff --git a/src/tools/rustfmt/tests/source/trailing_comments/soft_tabs.rs b/src/tools/rustfmt/tests/source/trailing_comments/soft_tabs.rs new file mode 100644 index 0000000000..7845f713b8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/trailing_comments/soft_tabs.rs @@ -0,0 +1,21 @@ +// rustfmt-version: Two +// rustfmt-wrap_comments: true + +pub const IFF_MULTICAST: ::c_int = 0x0000000800; // Supports multicast +// Multicast using broadcst. add. + +pub const SQ_CRETAB: u16 = 0x000e; // CREATE TABLE +pub const SQ_DRPTAB: u16 = 0x000f; // DROP TABLE +pub const SQ_CREIDX: u16 = 0x0010; // CREATE INDEX +//const SQ_DRPIDX: u16 = 0x0011; // DROP INDEX +//const SQ_GRANT: u16 = 0x0012; // GRANT +//const SQ_REVOKE: u16 = 0x0013; // REVOKE + +fn foo() { + let f = bar(); // Donec consequat mi. Quisque vitae dolor. Integer lobortis. Maecenas id nulla. Lorem. + // Id turpis. Nam posuere lectus vitae nibh. Etiam tortor orci, sagittis malesuada, rhoncus quis, hendrerit eget, libero. Quisque commodo nulla at nunc. Mauris consequat, enim vitae venenatis sollicitudin, dolor orci bibendum enim, a sagittis nulla nunc quis elit. Phasellus augue. Nunc suscipit, magna tincidunt lacinia faucibus, lacus tellus ornare purus, a pulvinar lacus orci eget nibh. Maecenas sed nibh non lacus tempor faucibus. In hac habitasse platea dictumst. Vivamus a orci at nulla tristique condimentum. Donec arcu quam, dictum accumsan, convallis accumsan, cursus sit amet, ipsum. In pharetra sagittis nunc. + let b = baz(); + + let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0] + // TODO(emilio): It may make sense to make this range [.01, 10.0], to align with css-fonts-4's range of [1, 1000]. +} diff --git a/src/tools/rustfmt/tests/source/trait.rs b/src/tools/rustfmt/tests/source/trait.rs new file mode 100644 index 0000000000..80ee0188a6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/trait.rs @@ -0,0 +1,176 @@ +// Test traits + +trait Foo { + fn bar(x: i32 ) -> Baz< U> { Baz::new() + } + + fn baz(a: AAAAAAAAAAAAAAAAAAAAAA, +b: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB) +-> RetType; + + fn foo(a: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, // Another comment +b: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB) + -> RetType ; // Some comment + + fn baz(&mut self ) -> i32 ; + +fn increment(& mut self, x: i32 ); + + fn read(&mut self, x: BufReader /* Used to be MemReader */) + where R: Read; +} + +pub trait WriteMessage { + fn write_message (&mut self, &FrontendMessage) -> io::Result<()>; +} + +trait Runnable { + fn handler(self: & Runnable ); +} + +trait TraitWithExpr { + fn fn_with_expr(x: [i32; 1]); +} + +trait Test { + fn read_struct(&mut self, s_name: &str, len: usize, f: F) -> Result where F: FnOnce(&mut Self) -> Result; +} + +trait T<> {} + +trait Foo { type Bar: Baz; type Inner: Foo = Box< Foo >; } + +trait ConstCheck:Foo where T: Baz { + const J: i32; +} + +trait Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttt + where T: Foo {} + +trait Ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt where T: Foo {} + +trait FooBar : Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt where J: Bar { fn test(); } + +trait WhereList where T: Foo, J: Bar {} + +trait X /* comment */ {} +trait Y // comment +{ +} + +// #2055 +pub trait Foo: +// A and C +A + C +// and B + + B +{} + +// #2158 +trait Foo { + type ItRev = > as UntypedTimeSeries>::IterRev; + type IteRev = > as UntypedTimeSeries>::IterRev; +} + +// #2331 +trait MyTrait { + fn foo() {} +} + +// Trait aliases +trait FooBar = + Foo + + Bar; +trait FooBar = + Foo + + Bar; +pub trait FooBar = + Foo + + Bar; +pub trait FooBar = + Foo + + Bar; +trait AAAAAAAAAAAAAAAAAA = BBBBBBBBBBBBBBBBBBB + CCCCCCCCCCCCCCCCCCCCCCCCCCCCC + DDDDDDDDDDDDDDDDDD; +pub trait AAAAAAAAAAAAAAAAAA = BBBBBBBBBBBBBBBBBBB + CCCCCCCCCCCCCCCCCCCCCCCCCCCCC + DDDDDDDDDDDDDDDDDD; +trait AAAAAAAAAAAAAAAAAAA = BBBBBBBBBBBBBBBBBBB + CCCCCCCCCCCCCCCCCCCCCCCCCCCCC + DDDDDDDDDDDDDDDDDD; +trait AAAAAAAAAAAAAAAAAA = BBBBBBBBBBBBBBBBBBB + CCCCCCCCCCCCCCCCCCCCCCCCCCCCC + DDDDDDDDDDDDDDDDDDD; +trait AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA = FooBar; +trait AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA = FooBar; +#[rustfmt::skip] +trait FooBar = Foo + + Bar; + +// #2637 +auto trait Example {} +pub auto trait PubExample {} +pub unsafe auto trait PubUnsafeExample {} + +// #3006 +trait Foo<'a> { + type Bar< 'a >; +} + +impl<'a> Foo<'a> for i32 { + type Bar< 'a > = i32; +} + +// #3092 +pub mod test { + pub trait ATraitWithALooongName {} + pub trait ATrait + :ATraitWithALooongName + ATraitWithALooongName + ATraitWithALooongName + ATraitWithALooongName +{ +} +} + +// Trait aliases with where clauses. +trait A = where for<'b> &'b Self: Send; + +trait B = where for<'b> &'b Self: Send + Clone + Copy + SomeTrait + AAAAAAAA + BBBBBBB + CCCCCCCCCC; +trait B = where for<'b> &'b Self: Send + Clone + Copy + SomeTrait + AAAAAAAA + BBBBBBB + CCCCCCCCCCC; +trait B = where + for<'b> &'b Self: +Send + Clone + Copy + SomeTrait + AAAAAAAA + BBBBBBB + CCCCCCCCCCCCCCCCCCCCCCC; +trait B = where + for<'b> &'b Self: +Send + Clone + Copy + SomeTrait + AAAAAAAA + BBBBBBB + CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC; + +trait B = where + for<'b> &'b Self: +Send + + Clone + + Copy + + SomeTrait + + AAAAAAAA + + BBBBBBB + + CCCCCCCCC + + DDDDDDD + + DDDDDDDD + + DDDDDDDDD + + EEEEEEE; + +trait A<'a, 'b, 'c> = Debug + Foo where for<'b> &'b Self: Send; + +trait B<'a, 'b, 'c> = Debug +Foo +where for<'b> &'b Self: +Send + + Clone + + Copy + + SomeTrait + + AAAAAAAA + + BBBBBBB + + CCCCCCCCC + + DDDDDDD; + +trait B<'a, 'b, 'c,T> = Debug<'a, T> where for<'b> &'b Self: +Send + + Clone + + Copy + + SomeTrait + + AAAAAAAA + + BBBBBBB + + CCCCCCCCC + + DDDDDDD + + DDDDDDDD + + DDDDDDDDD + + EEEEEEE; diff --git a/src/tools/rustfmt/tests/source/try-conversion.rs b/src/tools/rustfmt/tests/source/try-conversion.rs new file mode 100644 index 0000000000..ed83ee9e10 --- /dev/null +++ b/src/tools/rustfmt/tests/source/try-conversion.rs @@ -0,0 +1,18 @@ +// rustfmt-use_try_shorthand: true + +fn main() { + let x = try!(some_expr()); + + let y = try!(a.very.loooooooooooooooooooooooooooooooooooooong().chain().inside().weeeeeeeeeeeeeee()).test().0.x; +} + +fn test() { + a? +} + +fn issue1291() { + try!(fs::create_dir_all(&gitfiledir).chain_err(|| { + format!("failed to create the {} submodule directory for the workarea", + name) + })); +} diff --git a/src/tools/rustfmt/tests/source/try_block.rs b/src/tools/rustfmt/tests/source/try_block.rs new file mode 100644 index 0000000000..2e8d61f7e6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/try_block.rs @@ -0,0 +1,30 @@ +// rustfmt-edition: 2018 + +fn main() -> Result<(), !> { + let _x: Option<_> = try { + 4 + }; + + try {} +} + +fn baz() -> Option { + if (1 == 1) { + return try { + 5 + }; + } + + // test + let x: Option<()> = try { + // try blocks are great + }; + + let y: Option = try { + 6 + }; // comment + + let x: Option = try { baz()?; baz()?; baz()?; 7 }; + + return None; +} diff --git a/src/tools/rustfmt/tests/source/tuple.rs b/src/tools/rustfmt/tests/source/tuple.rs new file mode 100644 index 0000000000..9a0f979fbc --- /dev/null +++ b/src/tools/rustfmt/tests/source/tuple.rs @@ -0,0 +1,63 @@ +// Test tuple litterals + +fn foo() { + let a = (a, a, a, a, a); + let aaaaaaaaaaaaaaaa = (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaa, aaaaaaaaaaaaaa); + let aaaaaaaaaaaaaaaaaaaaaa = (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + aaaaaaaaaaaaaaaaaaaaaaaaa, + aaaa); + let a = (a,); + + let b = (// This is a comment + b, // Comment + b /* Trailing comment */); + + // #1063 + foo(x.0 .0); +} + +fn a() { + ((aaaaaaaa, + aaaaaaaaaaaaa, + aaaaaaaaaaaaaaaaa, + aaaaaaaaaaaaaa, + aaaaaaaaaaaaaaaa, + aaaaaaaaaaaaaa),) +} + +fn b() { + ((bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb), + bbbbbbbbbbbbbbbbbb) +} + +fn issue550() { + self.visitor.visit_volume(self.level.sector_id(sector), (floor_y, + if is_sky_flat(ceil_tex) {from_wad_height(self.height_range.1)} else {ceil_y})); +} + +fn issue775() { + if indent { + let a = mk_object(&[("a".to_string(), Boolean(true)), + ("b".to_string(), + Array(vec![mk_object(&[("c".to_string(), + String("\x0c\r".to_string()))]), + mk_object(&[("d".to_string(), String("".to_string()))])]))]); + } +} + +fn issue1725() { + bench_antialiased_lines!(bench_draw_antialiased_line_segment_diagonal, (10, 10), (450, 450)); + bench_antialiased_lines!(bench_draw_antialiased_line_segment_shallow, (10, 10), (450, 80)); +} + +fn issue_4355() { + let _ = ((1,),).0.0; +} + +// https://github.com/rust-lang/rustfmt/issues/4410 +impl Drop for LockGuard { + fn drop(&mut self) { + LockMap::unlock(&self.0.0, &self.0.1); + } +} diff --git a/src/tools/rustfmt/tests/source/tuple_v2.rs b/src/tools/rustfmt/tests/source/tuple_v2.rs new file mode 100644 index 0000000000..9223033832 --- /dev/null +++ b/src/tools/rustfmt/tests/source/tuple_v2.rs @@ -0,0 +1,5 @@ +// rustfmt-version: Two + +fn issue_4355() { + let _ = ((1,),).0 .0; +} diff --git a/src/tools/rustfmt/tests/source/type-ascription.rs b/src/tools/rustfmt/tests/source/type-ascription.rs new file mode 100644 index 0000000000..4874094ccc --- /dev/null +++ b/src/tools/rustfmt/tests/source/type-ascription.rs @@ -0,0 +1,10 @@ + +fn main() { + let xxxxxxxxxxx = yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy : SomeTrait; + + let xxxxxxxxxxxxxxx = yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; + + let z = funk(yyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzz, wwwwww): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; + + x : u32 - 1u32 / 10f32 : u32 +} diff --git a/src/tools/rustfmt/tests/source/type.rs b/src/tools/rustfmt/tests/source/type.rs new file mode 100644 index 0000000000..57f31dc901 --- /dev/null +++ b/src/tools/rustfmt/tests/source/type.rs @@ -0,0 +1,174 @@ +// rustfmt-normalize_comments: true +fn types() { + let x: [ Vec < _ > ] = []; + let y: * mut [ SomeType ; konst_funk() ] = expr(); + let z: (/*#digits*/ usize, /*exp*/ i16) = funk(); + let z: ( usize /*#digits*/ , i16 /*exp*/ ) = funk(); +} + +struct F { + f: extern "C" fn(x: u8, ... /* comment */), + g: extern "C" fn(x: u8,/* comment */ ...), + h: extern "C" fn(x: u8, ... ), + i: extern "C" fn(x: u8, /* comment 4*/ y: String, // comment 3 + z: Foo, /* comment */ .../* comment 2*/ ), +} + +fn issue_1006(def_id_to_string: for<'a, 'b> unsafe fn(TyCtxt<'b, 'tcx, 'tcx>, DefId) -> String) {} + +fn impl_trait_fn_1() -> impl Fn(i32) -> Option {} + +fn impl_trait_fn_2() -> impl Future {} + +fn issue_1234() { + do_parse!(name: take_while1!(is_token) >> (Header)) +} + +// #2510 +impl CombineTypes { + pub fn pop_callback( + &self, + query_id: Uuid, + ) -> Option< + ( + ProjectId, + Box () + Sync + Send>, + ), + > { + self.query_callbacks()(&query_id) + } +} + +// #2859 +pub fn do_something<'a, T: Trait1 + Trait2 + 'a>(&fooo: u32) -> impl Future< + Item = ( + impl Future + 'a, + impl Future + 'a, +impl Future + 'a, + ), + Error = SomeError, + > + + + 'a { +} + +pub fn do_something<'a, T: Trait1 + Trait2 + 'a>( &fooo: u32, +) -> impl Future< + Item = ( +impl Future + 'a, + impl Future + 'a, + impl Future + 'a, + ), + Error = SomeError, + > + + Future< + Item = ( + impl Future + 'a, +impl Future + 'a, + impl Future + 'a, + ), + Error = SomeError, + > + + Future< + Item = ( + impl Future + 'a, + impl Future + 'a, + impl Future + 'a, + ), + Error = SomeError, + > + + + 'a + 'b + + 'c { +} + +// #3051 +token![impl]; +token![ impl ]; + +// #3060 +macro_rules! foo { + ($foo_api: ty) => { + type Target = ( $foo_api ) + 'static; + } +} + +type Target = ( FooAPI ) + 'static; + +// #3137 +fn foo(t: T) +where + T: ( FnOnce() -> () ) + Clone, + U: ( FnOnce() -> () ) + 'static, +{ +} + +// #3117 +fn issue3117() { + { + { + { + { + { + { + { + { + let opt: &mut Option = + unsafe { &mut *self.future.get() }; + } + } + } + } + } + } + } + } +} + +// #3139 +fn issue3139() { + assert_eq!( + to_json_value(&None :: ).unwrap(), + json!( { "test": None :: } ) + ); +} + +// #3180 +fn foo(a: SomeLongComplexType, b: SomeOtherLongComplexType) -> Box> { +} + +type MyFn = fn(a: SomeLongComplexType, b: SomeOtherLongComplexType,) -> Box>; + +// Const opt-out + +trait T: ? const Super {} + +const fn maybe_const() -> i32 { ::CONST } + +struct S(std::marker::PhantomData); + +impl ? const T {} + +fn trait_object() -> &'static dyn ? const T { &S } + +fn i(_: impl IntoIterator>) {} + +fn apit(_: impl ?const T) {} + +fn rpit() -> impl ? const T { S } + +pub struct Foo(T); +impl Foo { + fn new(t: T) -> Self { + // not calling methods on `t`, so we opt out of requiring + // `` to have const methods via `?const` + Self(t) + } +} + +// #4357 +type T = typeof( +1); +impl T for .. { +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/type_alias.rs b/src/tools/rustfmt/tests/source/type_alias.rs new file mode 100644 index 0000000000..58c807f402 --- /dev/null +++ b/src/tools/rustfmt/tests/source/type_alias.rs @@ -0,0 +1,34 @@ +// rustfmt-normalize_comments: true + +type PrivateTest<'a, I> = (Box + 'a>, Box + 'a>); + +pub type PublicTest<'a, I, O> = Result, Box + 'a>, Box + 'a>>; + +pub type LongGenericListTest<'a, 'b, 'c, 'd, LONGPARAMETERNAME, LONGPARAMETERNAME, LONGPARAMETERNAME, A, B, C> = Option>; + +pub type Exactly100CharsTest<'a, 'b, 'c, 'd, LONGPARAMETERNAME, LONGPARAMETERNAME, A, B> = Vec; + +pub type Exactly101CharsTest<'a, 'b, 'c, 'd, LONGPARAMETERNAME, LONGPARAMETERNAME, A, B> = Vec; + +pub type Exactly100CharsToEqualTest<'a, 'b, 'c, 'd, LONGPARAMETERNAME, LONGPARAMETERNAME, A, B, C> = Vec; + +pub type GenericsFitButNotEqualTest<'a, 'b, 'c, 'd, LONGPARAMETERNAME, LONGPARAMETERNAME, A1, B, C> = Vec; + +pub type CommentTest< /* Lifetime */ 'a + , + // Type + T + > = (); + + +pub type WithWhereClause where T: Clone, LONGPARAMETERNAME: Clone + Eq + OtherTrait = Option; + +pub type Exactly100CharstoEqualWhereTest where T: Clone + Ord + Eq + SomeOtherTrait = Option; + +pub type Exactly101CharstoEqualWhereTest where T: Clone + Ord + Eq + SomeOtherTrait = Option; + +type RegisterPlugin = unsafe fn(pt: *const c_char, plugin: *mut c_void, data: *mut CallbackData); + +// #1683 +pub type Between = super::operators::Between, AsExpr>>; +pub type NotBetween = super::operators::NotBetween, AsExpr>>; diff --git a/src/tools/rustfmt/tests/source/unicode.rs b/src/tools/rustfmt/tests/source/unicode.rs new file mode 100644 index 0000000000..4c2119af57 --- /dev/null +++ b/src/tools/rustfmt/tests/source/unicode.rs @@ -0,0 +1,33 @@ +// rustfmt-wrap_comments: true + +fn foo() { + let s = "this line goes to 100: ͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶ"; + let s = 42; + + // a comment of length 80, with the starting sigil: ҘҘҘҘҘҘҘҘҘҘ ҘҘҘҘҘҘҘҘҘҘҘҘҘҘ + let s = 42; +} + +pub fn bar(config: &Config) { + let csv = RefCell::new(create_csv(config, "foo")); + { + let mut csv = csv.borrow_mut(); + for (i1, i2, i3) in iproduct!(0..2, 0..3, 0..3) { + csv.write_field(format!("γ[{}.{}.{}]", i1, i2, i3)) + .unwrap(); + csv.write_field(format!("d[{}.{}.{}]", i1, i2, i3)) + .unwrap(); + csv.write_field(format!("i[{}.{}.{}]", i1, i2, i3)) + .unwrap(); + } + csv.write_record(None::<&[u8]>).unwrap(); + } +} + +// The NotUnicode line is below 100 wrt chars but over it wrt String::len +fn baz() { + let our_error_b = result_b_from_func.or_else(|e| match e { + NotPresent => Err(e).chain_err(|| "env var wasn't provided"), + NotUnicode(_) => Err(e).chain_err(|| "env var was very very very bork文字化ã"), + }); +} diff --git a/src/tools/rustfmt/tests/source/unions.rs b/src/tools/rustfmt/tests/source/unions.rs new file mode 100644 index 0000000000..53630788fe --- /dev/null +++ b/src/tools/rustfmt/tests/source/unions.rs @@ -0,0 +1,195 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true + + /// A Doc comment +#[AnAttribute] +pub union Foo { + #[rustfmt::skip] + f : SomeType, // Comment beside a field + f: SomeType, // Comment beside a field + // Comment on a field + #[AnAttribute] + g: SomeOtherType, + /// A doc comment on a field + h: AThirdType, + pub i: TypeForPublicField +} + +// #1029 +pub union Foo { + #[doc(hidden)] + // This will NOT get deleted! + bar: String, // hi +} + +// #1029 +union X { + // `x` is an important number. + #[allow(unused)] // TODO: use + x: u32, +} + +// #410 +#[allow(missing_docs)] +pub union Writebatch { + #[allow(dead_code)] //only used for holding the internal pointer + writebatch: RawWritebatch, + marker: PhantomData, +} + +// With a where-clause and generics. +pub union Foo<'a, Y: Baz> + where X: Whatever +{ + f: SomeType, // Comment beside a field +} + +union Baz { + + a: A, // Comment A + b: B, // Comment B + c: C, // Comment C + +} + +union Baz { + a: A, // Comment A + + b: B, // Comment B + + + + + c: C, // Comment C +} + +union Baz { + + a: A, + + b: B, + c: C, + + + + + d: D + +} + +union Baz +{ + // Comment A + a: A, + + // Comment B +b: B, + // Comment C + c: C,} + +pub union State time::Timespec> { now: F } + +pub union State ()> { now: F } + +pub union State { now: F } + +union Palette { /// A map of indices in the palette to a count of pixels in approximately that color + foo: i32} + +// Splitting a single line comment into a block previously had a misalignment +// when the field had attributes +union FieldsWithAttributes { + // Pre Comment + #[rustfmt::skip] pub host:String, // Post comment BBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBB BBBBBBBBBBB + //Another pre comment + #[attr1] + #[attr2] pub id: usize // CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCC CCCCCCCCCCCC +} + +union Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: node::Handle>, + Type, + NodeType>, +} + +mod m { + union X where T: Sized { + a: T, + } +} + +union Issue677 { + pub ptr: *const libc::c_void, + pub trace: fn( obj: + *const libc::c_void, tracer : *mut JSTracer ), +} + +union Foo {} +union Foo { + } +union Foo { + // comment + } +union Foo { + // trailing space -> + + + } +union Foo { /* comment */ } + +union LongUnion { + a: A, + the_quick_brown_fox_jumps_over_the_lazy_dog:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +} + +union Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: node::Handle>, + Type, + NodeType>, +} + +// #1364 +fn foo() { + convex_shape.set_point(0, &Vector2f { x: 400.0, y: 100.0 }); + convex_shape.set_point(1, &Vector2f { x: 500.0, y: 70.0 }); + convex_shape.set_point(2, &Vector2f { x: 450.0, y: 100.0 }); + convex_shape.set_point(3, &Vector2f { x: 580.0, y: 150.0 }); +} + +// Vertical alignment +union Foo { + aaaaa: u32, // a + + b: u32, // b + cc: u32, // cc + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 1 + yy: u32, // comment2 + zzz: u32, // comment3 + + aaaaaa: u32, // comment4 + bb: u32, // comment5 + // separate + dd: u32, // comment7 + c: u32, // comment6 + + aaaaaaa: u32, /* multi + * line + * comment + */ + b: u32, // hi + + do_not_push_this_comment1: u32, // comment1 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + please_do_not_push_this_comment3: u32, // comment3 + + do_not_push_this_comment1: u32, // comment1 + // separate + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + please_do_not_push_this_comment3: u32, // comment3 + + do_not_push_this_comment1: u32, // comment1 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + // separate + please_do_not_push_this_comment3: u32, // comment3 +} diff --git a/src/tools/rustfmt/tests/source/unsafe-mod.rs b/src/tools/rustfmt/tests/source/unsafe-mod.rs new file mode 100644 index 0000000000..9996b06271 --- /dev/null +++ b/src/tools/rustfmt/tests/source/unsafe-mod.rs @@ -0,0 +1,7 @@ +// These are supported by rustc syntactically but not semantically. + +#[cfg(any())] +unsafe mod m { } + +#[cfg(any())] +unsafe extern "C++" { } diff --git a/src/tools/rustfmt/tests/source/visibility.rs b/src/tools/rustfmt/tests/source/visibility.rs new file mode 100644 index 0000000000..1c5919ccff --- /dev/null +++ b/src/tools/rustfmt/tests/source/visibility.rs @@ -0,0 +1,8 @@ +// #2398 +pub mod outer_mod { + pub mod inner_mod { + pub ( in outer_mod ) fn outer_mod_visible_fn() {} + pub ( super ) fn super_mod_visible_fn() {} + pub ( self ) fn inner_mod_visible_fn() {} + } +} diff --git a/src/tools/rustfmt/tests/source/visual-fn-type.rs b/src/tools/rustfmt/tests/source/visual-fn-type.rs new file mode 100644 index 0000000000..67dad5fa46 --- /dev/null +++ b/src/tools/rustfmt/tests/source/visual-fn-type.rs @@ -0,0 +1,10 @@ +// rustfmt-indent_style: Visual +type CNodeSetAtts = unsafe extern "C" fn(node: *const RsvgNode, + node_impl: *const RsvgCNodeImpl, + handle: *const RsvgHandle, + pbag: *const PropertyBag) + ; +type CNodeDraw = unsafe extern "C" fn(node: *const RsvgNode, + node_impl: *const RsvgCNodeImpl, + draw_ctx: *const RsvgDrawingCtx, + dominate: i32); diff --git a/src/tools/rustfmt/tests/source/where-clause-rfc.rs b/src/tools/rustfmt/tests/source/where-clause-rfc.rs new file mode 100644 index 0000000000..219a9bddb1 --- /dev/null +++ b/src/tools/rustfmt/tests/source/where-clause-rfc.rs @@ -0,0 +1,73 @@ +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) where T: FOo, U: Bar { + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) where T: FOo { + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape, shape: &Shape) where T: FOo, U: Bar { + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape, shape: &Shape) where T: FOo { + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) -> Option where T: FOo, U: Bar { + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) -> Option where T: FOo { + let mut effects = HashMap::new(); +} + +pub trait Test { + fn very_long_method_name(self, f: F) -> MyVeryLongReturnType where F: FnMut(Self::Item) -> bool; + + fn exactly_100_chars1(self, f: F) -> MyVeryLongReturnType where F: FnMut(Self::Item) -> bool; +} + +fn very_long_function_name(very_long_argument: F) -> MyVeryLongReturnType where F: FnMut(Self::Item) -> bool { } + +struct VeryLongTupleStructName(LongLongTypename, LongLongTypename, i32, i32) where A: LongTrait; + +struct Exactly100CharsToSemicolon + (LongLongTypename, i32, i32) + where A: LongTrait1234; + +struct AlwaysOnNextLine where A: LongTrait { + x: i32 +} + +pub trait SomeTrait + where + T: Something + Sync + Send + Display + Debug + Copy + Hash + Debug + Display + Write + Read + FromStr +{ +} + +// #2020 +impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> { + fn elaborate_bounds(&mut self, bounds: &[ty::PolyTraitRef<'tcx>], mut mk_cand: F) + where F: for<'b> FnMut(&mut ProbeContext<'b, 'gcx, 'tcx>, ty::PolyTraitRef<'tcx>, ty::AssociatedItem), + { + // ... + } +} + +// #2497 +fn handle_update<'a, Tab, Conn, R, C>(executor: &Executor>>, change_set: &'a C) -> ExecutionResult +where &'a C: Identifiable + AsChangeset + HasTable, + <&'a C as AsChangeset>::Changeset: QueryFragment, + Tab: Table + HasTable
, + Tab::PrimaryKey: EqAll<<&'a C as Identifiable>::Id>, + Tab::FromClause: QueryFragment, + Tab: FindDsl<<&'a C as Identifiable>::Id>, + Find::Id>: IntoUpdateTarget
, + ::Id> as IntoUpdateTarget>::WhereClause: QueryFragment, + Tab::Query: FilterDsl<::Id>>::Output>, + Filter::Id>>::Output>: LimitDsl, + Limit::Id>>::Output>>: QueryDsl + BoxedDsl< 'a, Conn::Backend, Output = BoxedSelectStatement<'a, R::SqlType, Tab, Conn::Backend>>, + R: LoadingHandler + GraphQLType, { + unimplemented!() +} diff --git a/src/tools/rustfmt/tests/source/where-clause.rs b/src/tools/rustfmt/tests/source/where-clause.rs new file mode 100644 index 0000000000..2a91608254 --- /dev/null +++ b/src/tools/rustfmt/tests/source/where-clause.rs @@ -0,0 +1,58 @@ +// rustfmt-indent_style: Visual + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) where T: FOo, U: Bar { + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) where T: FOo { + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape, shape: &Shape) where T: FOo, U: Bar { + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape, shape: &Shape) where T: FOo { + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) -> Option where T: FOo, U: Bar { + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) -> Option where T: FOo { + let mut effects = HashMap::new(); +} + +pub trait Test { + fn very_long_method_name(self, f: F) -> MyVeryLongReturnType where F: FnMut(Self::Item) -> bool; + + fn exactly_100_chars1(self, f: F) -> MyVeryLongReturnType where F: FnMut(Self::Item) -> bool; +} + +fn very_long_function_name(very_long_argument: F) -> MyVeryLongReturnType where F: FnMut(Self::Item) -> bool { } + +struct VeryLongTupleStructName(LongLongTypename, LongLongTypename, i32, i32) where A: LongTrait; + +struct Exactly100CharsToSemicolon + (LongLongTypename, i32, i32) + where A: LongTrait1234; + +struct AlwaysOnNextLine where A: LongTrait { + x: i32 +} + +pub trait SomeTrait + where + T: Something + Sync + Send + Display + Debug + Copy + Hash + Debug + Display + Write + Read + FromStr +{ +} + +// #2020 +impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> { + fn elaborate_bounds(&mut self, bounds: &[ty::PolyTraitRef<'tcx>], mut mk_cand: F) + where F: for<'b> FnMut(&mut ProbeContext<'b, 'gcx, 'tcx>, ty::PolyTraitRef<'tcx>, ty::AssociatedItem), + { + // ... + } +} diff --git a/src/tools/rustfmt/tests/source/width-heuristics.rs b/src/tools/rustfmt/tests/source/width-heuristics.rs new file mode 100644 index 0000000000..a591218b4c --- /dev/null +++ b/src/tools/rustfmt/tests/source/width-heuristics.rs @@ -0,0 +1,28 @@ +// rustfmt-max_width: 120 + +// elems on multiple lines for max_width 100, but same line for max_width 120 +fn foo(e: Enum) { + match e { + Enum::Var { + elem1, + elem2, + elem3, + } => { + return; + } + } +} + +// elems not on same line for either max_width 100 or 120 +fn bar(e: Enum) { + match e { + Enum::Var { + elem1, + elem2, + elem3, + elem4, + } => { + return; + } + } +} diff --git a/src/tools/rustfmt/tests/source/wrap_comments_should_not_imply_format_doc_comments.rs b/src/tools/rustfmt/tests/source/wrap_comments_should_not_imply_format_doc_comments.rs new file mode 100644 index 0000000000..78b3ce146f --- /dev/null +++ b/src/tools/rustfmt/tests/source/wrap_comments_should_not_imply_format_doc_comments.rs @@ -0,0 +1,16 @@ +// rustfmt-wrap_comments: true + +/// Foo +/// +/// # Example +/// ``` +/// # #![cfg_attr(not(dox), feature(cfg_target_feature, target_feature, stdsimd))] +/// # #![cfg_attr(not(dox), no_std)] +/// fn foo() { } +/// ``` +/// +fn foo() {} + +/// A long commment for wrapping +/// This is a long long long long long long long long long long long long long long long long long long long long sentence. +fn bar() {} diff --git a/src/tools/rustfmt/tests/target/alignment_2633/block_style.rs b/src/tools/rustfmt/tests/target/alignment_2633/block_style.rs new file mode 100644 index 0000000000..f13e8a8760 --- /dev/null +++ b/src/tools/rustfmt/tests/target/alignment_2633/block_style.rs @@ -0,0 +1,10 @@ +// rustfmt-struct_field_align_threshold: 50 + +fn func() { + Ok(ServerInformation { + name: unwrap_message_string(items.get(0)), + vendor: unwrap_message_string(items.get(1)), + version: unwrap_message_string(items.get(2)), + spec_version: unwrap_message_string(items.get(3)), + }); +} diff --git a/src/tools/rustfmt/tests/target/alignment_2633/horizontal_tactic.rs b/src/tools/rustfmt/tests/target/alignment_2633/horizontal_tactic.rs new file mode 100644 index 0000000000..a381945fd8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/alignment_2633/horizontal_tactic.rs @@ -0,0 +1,13 @@ +// rustfmt-struct_field_align_threshold: 5 + +#[derive(Fail, Debug, Clone)] +pub enum BuildError { + LineTooLong { length: usize, limit: usize }, + DisallowedByte { b: u8, pos: usize }, + ContainsNewLine { pos: usize }, +} + +enum Foo { + A { a: usize, bbbbb: () }, + B { a: (), bbbbb: () }, +} diff --git a/src/tools/rustfmt/tests/target/alignment_2633/visual_style.rs b/src/tools/rustfmt/tests/target/alignment_2633/visual_style.rs new file mode 100644 index 0000000000..7d21b599af --- /dev/null +++ b/src/tools/rustfmt/tests/target/alignment_2633/visual_style.rs @@ -0,0 +1,9 @@ +// rustfmt-struct_field_align_threshold: 50 +// rustfmt-indent_style: Visual + +fn func() { + Ok(ServerInformation { name: unwrap_message_string(items.get(0)), + vendor: unwrap_message_string(items.get(1)), + version: unwrap_message_string(items.get(2)), + spec_version: unwrap_message_string(items.get(3)), }); +} diff --git a/src/tools/rustfmt/tests/target/array_comment.rs b/src/tools/rustfmt/tests/target/array_comment.rs new file mode 100644 index 0000000000..93e1f5f403 --- /dev/null +++ b/src/tools/rustfmt/tests/target/array_comment.rs @@ -0,0 +1,18 @@ +// Issue 2842 +// The comment should not make the last line shorter + +static XXX: [i8; 64] = [ + 1, // Comment + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; + +static XXX: [i8; 64] = [ + 1, // Comment + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; + +static XXX: [i8; 64] = [ + 1, // Comment + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, +]; diff --git a/src/tools/rustfmt/tests/target/assignment.rs b/src/tools/rustfmt/tests/target/assignment.rs new file mode 100644 index 0000000000..1a70d84813 --- /dev/null +++ b/src/tools/rustfmt/tests/target/assignment.rs @@ -0,0 +1,39 @@ +// Test assignment + +fn main() { + let some_var: Type; + + let mut mutable; + + let variable = + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::BBBBBBBBBBBBBBBBBBBBBB::CCCCCCCCCCCCCCCCCCCCCC::EEEEEE; + + variable = + LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONG; + + let single_line_fit = DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD; + + single_line_fit = 5; + single_lit_fit >>= 10; + + // #2791 + let x = 2; +} + +fn break_meee() { + { + ( + block_start, + block_size, + margin_block_start, + margin_block_end, + ) = match (block_start, block_end, block_size) { + x => 1, + _ => 2, + }; + } +} + +// #2018 +pub const EXPLAIN_UNSIZED_TUPLE_COERCION: &'static str = + "Unsized tuple coercion is not stable enough for use and is subject to change"; diff --git a/src/tools/rustfmt/tests/target/associated-items.rs b/src/tools/rustfmt/tests/target/associated-items.rs new file mode 100644 index 0000000000..1b0a828d02 --- /dev/null +++ b/src/tools/rustfmt/tests/target/associated-items.rs @@ -0,0 +1,3 @@ +fn main() { + println!("{}", ::default()); +} diff --git a/src/tools/rustfmt/tests/target/associated-types-bounds-wrapping.rs b/src/tools/rustfmt/tests/target/associated-types-bounds-wrapping.rs new file mode 100644 index 0000000000..8aaeee3b16 --- /dev/null +++ b/src/tools/rustfmt/tests/target/associated-types-bounds-wrapping.rs @@ -0,0 +1,6 @@ +// Test proper wrapping of long associated type bounds + +pub trait HttpService { + type WsService: 'static + + Service; +} diff --git a/src/tools/rustfmt/tests/target/associated_type_bounds.rs b/src/tools/rustfmt/tests/target/associated_type_bounds.rs new file mode 100644 index 0000000000..2dcbd65f86 --- /dev/null +++ b/src/tools/rustfmt/tests/target/associated_type_bounds.rs @@ -0,0 +1,13 @@ +// See #3657 - https://github.com/rust-lang/rustfmt/issues/3657 + +#![feature(associated_type_bounds)] + +fn f>() {} + +fn g>() {} + +fn h>() {} + +fn i>() {} + +fn j>() {} diff --git a/src/tools/rustfmt/tests/target/associated_type_defaults.rs b/src/tools/rustfmt/tests/target/associated_type_defaults.rs new file mode 100644 index 0000000000..d0a0813372 --- /dev/null +++ b/src/tools/rustfmt/tests/target/associated_type_defaults.rs @@ -0,0 +1,4 @@ +#![feature(associated_type_defaults)] +trait Foo { + type Bar = (); +} diff --git a/src/tools/rustfmt/tests/target/async_block.rs b/src/tools/rustfmt/tests/target/async_block.rs new file mode 100644 index 0000000000..258dd937a6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/async_block.rs @@ -0,0 +1,25 @@ +// rustfmt-edition: 2018 + +fn main() { + let x = async { Ok(()) }; +} + +fn baz() { + // test + let x = async { + // async blocks are great + Ok(()) + }; + + let y = async { Ok(()) }; // comment + + spawn(a, async move { + action(); + Ok(()) + }); + + spawn(a, async move || { + action(); + Ok(()) + }); +} diff --git a/src/tools/rustfmt/tests/target/async_closure.rs b/src/tools/rustfmt/tests/target/async_closure.rs new file mode 100644 index 0000000000..9364e7dcc6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/async_closure.rs @@ -0,0 +1,22 @@ +// rustfmt-edition: 2018 + +fn main() { + let async_closure = async { + let x = 3; + x + }; + + let f = async /* comment */ { + let x = 3; + x + }; + + let g = async /* comment */ move { + let x = 3; + x + }; + + let f = |x| async { + println!("hello, world"); + }; +} diff --git a/src/tools/rustfmt/tests/target/async_fn.rs b/src/tools/rustfmt/tests/target/async_fn.rs new file mode 100644 index 0000000000..ac151dddb2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/async_fn.rs @@ -0,0 +1,24 @@ +// rustfmt-edition: 2018 + +async fn bar() -> Result<(), ()> { + Ok(()) +} + +pub async fn baz() -> Result<(), ()> { + Ok(()) +} + +async unsafe fn foo() { + async move { Ok(()) } +} + +async unsafe fn rust() { + async move { + // comment + Ok(()) + } +} + +async fn await_try() { + something.await?; +} diff --git a/src/tools/rustfmt/tests/target/attrib-block-expr.rs b/src/tools/rustfmt/tests/target/attrib-block-expr.rs new file mode 100644 index 0000000000..1e9557dc03 --- /dev/null +++ b/src/tools/rustfmt/tests/target/attrib-block-expr.rs @@ -0,0 +1,58 @@ +fn issue_2073() { + let x = { + #![my_attr] + do_something() + }; + + let x = #[my_attr] + { + do_something() + }; + + let x = #[my_attr] + {}; + + { + #![just_an_attribute] + }; + + let z = #[attr1] + #[attr2] + { + body() + }; + + x = |y| { + #![inner] + }; + + x = |y| #[outer] + {}; + + x = |y| { + //! ynot + }; + + x = |y| #[outer] + unsafe {}; + + let x = unsafe { + #![my_attr] + do_something() + }; + + let x = #[my_attr] + unsafe { + do_something() + }; + + // This is a dumb but possible case + let x = #[my_attr] + unsafe {}; + + x = |y| #[outer] + #[outer2] + unsafe { + //! Comment + }; +} diff --git a/src/tools/rustfmt/tests/target/attrib-extern-crate.rs b/src/tools/rustfmt/tests/target/attrib-extern-crate.rs new file mode 100644 index 0000000000..ed64a0aeb0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/attrib-extern-crate.rs @@ -0,0 +1,17 @@ +// Attributes on extern crate. + +#[Attr1] +extern crate Bar; +#[Attr2] +#[Attr2] +extern crate Baz; +extern crate Foo; + +fn foo() { + #[Attr1] + extern crate Bar; + #[Attr2] + #[Attr2] + extern crate Baz; + extern crate Foo; +} diff --git a/src/tools/rustfmt/tests/target/attrib.rs b/src/tools/rustfmt/tests/target/attrib.rs new file mode 100644 index 0000000000..7e61f68d76 --- /dev/null +++ b/src/tools/rustfmt/tests/target/attrib.rs @@ -0,0 +1,271 @@ +// rustfmt-wrap_comments: true +// Test attributes and doc comments are preserved. +#![doc( + html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://doc.rust-lang.org/favicon.ico", + html_root_url = "https://doc.rust-lang.org/nightly/", + html_playground_url = "https://play.rust-lang.org/", + test(attr(deny(warnings))) +)] + +//! Doc comment + +#![attribute] + +//! Crate doc comment + +// Comment + +// Comment on attribute +#![the(attribute)] + +// Another comment + +/// Blah blah blah. +/// Blah blah blah. +/// Blah blah blah. +/// Blah blah blah. + +/// Blah blah blah. +impl Bar { + /// Blah blah blooo. + /// Blah blah blooo. + /// Blah blah blooo. + /// Blah blah blooo. + #[an_attribute] + #[doc = "an attribute that shouldn't be normalized to a doc comment"] + fn foo(&mut self) -> isize {} + + /// Blah blah bing. + /// Blah blah bing. + /// Blah blah bing. + + /// Blah blah bing. + /// Blah blah bing. + /// Blah blah bing. + pub fn f2(self) { + (foo, bar) + } + + #[another_attribute] + fn f3(self) -> Dog {} + + /// Blah blah bing. + + #[attrib1] + /// Blah blah bing. + #[attrib2] + // Another comment that needs rewrite because it's tooooooooooooooooooooooooooooooo + // loooooooooooong. + /// Blah blah bing. + fn f4(self) -> Cat {} + + // We want spaces around `=` + #[cfg(feature = "nightly")] + fn f5(self) -> Monkey {} +} + +// #984 +struct Foo { + #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] + foo: usize, +} + +// #1668 + +/// Default path (*nix) +#[cfg(all( + unix, + not(target_os = "macos"), + not(target_os = "ios"), + not(target_os = "android") +))] +fn foo() { + #[cfg(target_os = "freertos")] + match port_id { + 'a' | 'A' => GpioPort { + port_address: GPIO_A, + }, + 'b' | 'B' => GpioPort { + port_address: GPIO_B, + }, + _ => panic!(), + } + + #[cfg_attr(not(target_os = "freertos"), allow(unused_variables))] + let x = 3; +} + +// #1777 +#[test] +#[should_panic(expected = "(")] +#[should_panic(expected = /* ( */ "(")] +#[should_panic(/* ((((( */expected /* ((((( */= /* ((((( */ "("/* ((((( */)] +#[should_panic( + /* (((((((( *//* + (((((((((()(((((((( */ + expected = "(" + // (((((((( +)] +fn foo() {} + +// #1799 +fn issue_1799() { + #[allow(unreachable_code)] // https://github.com/rust-lang/rust/issues/43336 + Some(Err(error)); + + #[allow(unreachable_code)] + // https://github.com/rust-lang/rust/issues/43336 + Some(Err(error)); +} + +// Formatting inner attributes +fn inner_attributes() { + #![this_is_an_inner_attribute(foo)] + + foo(); +} + +impl InnerAttributes() { + #![this_is_an_inner_attribute(foo)] + + fn foo() {} +} + +mod InnerAttributes { + #![this_is_an_inner_attribute(foo)] +} + +fn attributes_on_statements() { + // Local + #[attr(on(local))] + let x = 3; + + // Item + #[attr(on(item))] + use foo; + + // Expr + #[attr(on(expr))] + {} + + // Semi + #[attr(on(semi))] + foo(); + + // Mac + #[attr(on(mac))] + foo!(); +} + +// Large derives +#[derive( + Add, Sub, Mul, Div, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Serialize, Mul, +)] + +/// Foo bar baz + +#[derive( + Add, + Sub, + Mul, + Div, + Clone, + Copy, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Hash, + Serialize, + Deserialize, +)] +pub struct HP(pub u8); + +// Long `#[doc = "..."]` +struct A { + #[doc = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] + b: i32, +} + +// #2647 +#[cfg( + feature = "this_line_is_101_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +)] +pub fn foo() {} + +// path attrs +#[clippy::bar] +#[clippy::bar(a, b, c)] +pub fn foo() {} + +mod issue_2620 { + #[derive(Debug, StructOpt)] + #[structopt(about = "Display information about the character on FF Logs")] + pub struct Params { + #[structopt(help = "The server the character is on")] + server: String, + #[structopt(help = "The character's first name")] + first_name: String, + #[structopt(help = "The character's last name")] + last_name: String, + #[structopt( + short = "j", + long = "job", + help = "The job to look at", + parse(try_from_str) + )] + job: Option, + } +} + +// #2969 +#[cfg(not(all( + feature = "std", + any( + target_os = "linux", + target_os = "android", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "haiku", + target_os = "emscripten", + target_os = "solaris", + target_os = "cloudabi", + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", + target_os = "redox", + target_os = "fuchsia", + windows, + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), + ) +)))] +type Os = NoSource; + +// #3313 +fn stmt_expr_attributes() { + let foo; + #[must_use] + foo = false; +} + +// #3509 +fn issue3509() { + match MyEnum { + MyEnum::Option1 if cfg!(target_os = "windows") => + #[cfg(target_os = "windows")] + { + 1 + } + } + match MyEnum { + MyEnum::Option1 if cfg!(target_os = "windows") => + { + #[cfg(target_os = "windows")] + 1 + } + } +} diff --git a/src/tools/rustfmt/tests/target/big-impl-block.rs b/src/tools/rustfmt/tests/target/big-impl-block.rs new file mode 100644 index 0000000000..e3728caba3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/big-impl-block.rs @@ -0,0 +1,82 @@ +// #1357 +impl<'a, Select, From, Distinct, Where, Order, Limit, Offset, Groupby, DB> InternalBoxedDsl<'a, DB> + for SelectStatement +where + DB: Backend, + Select: QueryFragment + SelectableExpression + 'a, + Distinct: QueryFragment + 'a, + Where: Into + 'a>>>, + Order: QueryFragment + 'a, + Limit: QueryFragment + 'a, + Offset: QueryFragment + 'a, +{ + type Output = BoxedSelectStatement<'a, Select::SqlTypeForSelect, From, DB>; + + fn internal_into_boxed(self) -> Self::Output { + BoxedSelectStatement::new( + Box::new(self.select), + self.from, + Box::new(self.distinct), + self.where_clause.into(), + Box::new(self.order), + Box::new(self.limit), + Box::new(self.offset), + ) + } +} + +// #1369 +impl Foo + for Bar +{ + fn foo() {} +} +impl Foo + for Bar +{ + fn foo() {} +} +impl + Foo + for Bar +{ + fn foo() {} +} +impl Foo + for Bar< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, + > +{ + fn foo() {} +} +impl Foo + for Bar< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, + > +{ + fn foo() {} +} +impl + Foo + for Bar< + ExcessivelyLongGenericName, + ExcessivelyLongGenericName, + AnotherExcessivelyLongGenericName, + > +{ + fn foo() {} +} + +// #1689 +impl SubSelectDirect +where + M: select::Selector, + S: event::Stream, + F: for<'t> FnMut(transform::Api<'t, Stream>>) -> transform::Api<'t, X>, + X: event::Stream, +{ +} diff --git a/src/tools/rustfmt/tests/target/big-impl-visual.rs b/src/tools/rustfmt/tests/target/big-impl-visual.rs new file mode 100644 index 0000000000..04b0a83fd9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/big-impl-visual.rs @@ -0,0 +1,65 @@ +// rustfmt-indent_style: Visual + +// #1357 +impl<'a, Select, From, Distinct, Where, Order, Limit, Offset, Groupby, DB> InternalBoxedDsl<'a, DB> + for SelectStatement + where DB: Backend, + Select: QueryFragment + SelectableExpression + 'a, + Distinct: QueryFragment + 'a, + Where: Into + 'a>>>, + Order: QueryFragment + 'a, + Limit: QueryFragment + 'a, + Offset: QueryFragment + 'a +{ + type Output = BoxedSelectStatement<'a, Select::SqlTypeForSelect, From, DB>; + + fn internal_into_boxed(self) -> Self::Output { + BoxedSelectStatement::new(Box::new(self.select), + self.from, + Box::new(self.distinct), + self.where_clause.into(), + Box::new(self.order), + Box::new(self.limit), + Box::new(self.offset)) + } +} + +// #1369 +impl Foo + for Bar +{ + fn foo() {} +} +impl Foo + for Bar +{ + fn foo() {} +} +impl + Foo + for Bar +{ + fn foo() {} +} +impl Foo + for Bar +{ + fn foo() {} +} +impl Foo + for Bar +{ + fn foo() {} +} +impl + Foo + for Bar +{ + fn foo() {} +} diff --git a/src/tools/rustfmt/tests/target/binary-expr.rs b/src/tools/rustfmt/tests/target/binary-expr.rs new file mode 100644 index 0000000000..93115b282c --- /dev/null +++ b/src/tools/rustfmt/tests/target/binary-expr.rs @@ -0,0 +1,16 @@ +// Binary expressions + +fn foo() { + // 100 + let x = aaaaaaaaaa || bbbbbbbbbb || cccccccccc || dddddddddd && eeeeeeeeee || ffffffffff || ggg; + // 101 + let x = + aaaaaaaaaa || bbbbbbbbbb || cccccccccc || dddddddddd && eeeeeeeeee || ffffffffff || gggg; + // 104 + let x = aaaaaaaaaa + || bbbbbbbbbb + || cccccccccc + || dddddddddd && eeeeeeeeee + || ffffffffff + || gggggggg; +} diff --git a/src/tools/rustfmt/tests/target/break-and-continue.rs b/src/tools/rustfmt/tests/target/break-and-continue.rs new file mode 100644 index 0000000000..c01d8a0784 --- /dev/null +++ b/src/tools/rustfmt/tests/target/break-and-continue.rs @@ -0,0 +1,23 @@ +// break and continue formatting + +#![feature(loop_break_value)] + +fn main() { + 'a: loop { + break 'a; + } + + let mut done = false; + 'b: while !done { + done = true; + continue 'b; + } + + let x = loop { + break 5; + }; + + let x = 'c: loop { + break 'c 5; + }; +} diff --git a/src/tools/rustfmt/tests/target/catch.rs b/src/tools/rustfmt/tests/target/catch.rs new file mode 100644 index 0000000000..ffe694f8e7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/catch.rs @@ -0,0 +1,22 @@ +// rustfmt-edition: 2018 +#![feature(try_blocks)] + +fn main() { + let x = try { foo()? }; + + let x = try /* Invisible comment */ { foo()? }; + + let x = try { unsafe { foo()? } }; + + let y = match (try { foo()? }) { + _ => (), + }; + + try { + foo()?; + }; + + try { + // Regular try block + }; +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/arch/aarch64.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/aarch64.rs new file mode 100644 index 0000000000..91c51ed89e --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/aarch64.rs @@ -0,0 +1,98 @@ +//! Aarch64 run-time features. + +/// Checks if `aarch64` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal, stdsimd)] +macro_rules! is_aarch64_feature_detected { + ("neon") => { + // FIXME: this should be removed once we rename Aarch64 neon to asimd + cfg!(target_feature = "neon") || $crate::detect::check_for($crate::detect::Feature::asimd) + }; + ("asimd") => { + cfg!(target_feature = "neon") || $crate::detect::check_for($crate::detect::Feature::asimd) + }; + ("pmull") => { + cfg!(target_feature = "pmull") || $crate::detect::check_for($crate::detect::Feature::pmull) + }; + ("fp") => { + cfg!(target_feature = "fp") || $crate::detect::check_for($crate::detect::Feature::fp) + }; + ("fp16") => { + cfg!(target_feature = "fp16") || $crate::detect::check_for($crate::detect::Feature::fp16) + }; + ("sve") => { + cfg!(target_feature = "sve") || $crate::detect::check_for($crate::detect::Feature::sve) + }; + ("crc") => { + cfg!(target_feature = "crc") || $crate::detect::check_for($crate::detect::Feature::crc) + }; + ("crypto") => { + cfg!(target_feature = "crypto") + || $crate::detect::check_for($crate::detect::Feature::crypto) + }; + ("lse") => { + cfg!(target_feature = "lse") || $crate::detect::check_for($crate::detect::Feature::lse) + }; + ("rdm") => { + cfg!(target_feature = "rdm") || $crate::detect::check_for($crate::detect::Feature::rdm) + }; + ("rcpc") => { + cfg!(target_feature = "rcpc") || $crate::detect::check_for($crate::detect::Feature::rcpc) + }; + ("dotprod") => { + cfg!(target_feature = "dotprod") + || $crate::detect::check_for($crate::detect::Feature::dotprod) + }; + ("ras") => { + compile_error!("\"ras\" feature cannot be detected at run-time") + }; + ("v8.1a") => { + compile_error!("\"v8.1a\" feature cannot be detected at run-time") + }; + ("v8.2a") => { + compile_error!("\"v8.2a\" feature cannot be detected at run-time") + }; + ("v8.3a") => { + compile_error!("\"v8.3a\" feature cannot be detected at run-time") + }; + ($t:tt,) => { + is_aarch64_feature_detected!($t); + }; + ($t:tt) => { + compile_error!(concat!("unknown aarch64 target feature: ", $t)) + }; +} + +/// ARM Aarch64 CPU Feature enum. Each variant denotes a position in a bitset +/// for a particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// ARM Advanced SIMD (ASIMD) + asimd, + /// Polynomial Multiply + pmull, + /// Floating point support + fp, + /// Half-float support. + fp16, + /// Scalable Vector Extension (SVE) + sve, + /// CRC32 (Cyclic Redundancy Check) + crc, + /// Crypto: AES + PMULL + SHA1 + SHA2 + crypto, + /// Atomics (Large System Extension) + lse, + /// Rounding Double Multiply (ASIMDRDM) + rdm, + /// Release consistent Processor consistent (RcPc) + rcpc, + /// Vector Dot-Product (ASIMDDP) + dotprod, +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/arch/arm.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/arm.rs new file mode 100644 index 0000000000..90c61fed8a --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/arm.rs @@ -0,0 +1,47 @@ +//! Run-time feature detection on ARM Aarch32. + +/// Checks if `arm` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal, stdsimd)] +macro_rules! is_arm_feature_detected { + ("neon") => { + cfg!(target_feature = "neon") || $crate::detect::check_for($crate::detect::Feature::neon) + }; + ("pmull") => { + cfg!(target_feature = "pmull") || $crate::detect::check_for($crate::detect::Feature::pmull) + }; + ("v7") => { + compile_error!("\"v7\" feature cannot be detected at run-time") + }; + ("vfp2") => { + compile_error!("\"vfp2\" feature cannot be detected at run-time") + }; + ("vfp3") => { + compile_error!("\"vfp3\" feature cannot be detected at run-time") + }; + ("vfp4") => { + compile_error!("\"vfp4\" feature cannot be detected at run-time") + }; + ($t:tt,) => { + is_arm_feature_detected!($t); + }; + ($t:tt) => { + compile_error!(concat!("unknown arm target feature: ", $t)) + }; +} + +/// ARM CPU Feature enum. Each variant denotes a position in a bitset for a +/// particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// ARM Advanced SIMD (NEON) - Aarch32 + neon, + /// Polynomial Multiply + pmull, +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/arch/mips.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/mips.rs new file mode 100644 index 0000000000..2397a09060 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/mips.rs @@ -0,0 +1,30 @@ +//! Run-time feature detection on MIPS. + +/// Checks if `mips` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal, stdsimd)] +macro_rules! is_mips_feature_detected { + ("msa") => { + cfg!(target_feature = "msa") || $crate::detect::check_for($crate::detect::Feature::msa) + }; + ($t:tt,) => { + is_mips_feature_detected!($t); + }; + ($t:tt) => { + compile_error!(concat!("unknown mips target feature: ", $t)) + }; +} + +/// MIPS CPU Feature enum. Each variant denotes a position in a bitset for a +/// particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// MIPS SIMD Architecture (MSA) + msa, +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/arch/mips64.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/mips64.rs new file mode 100644 index 0000000000..d378defc5d --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/mips64.rs @@ -0,0 +1,30 @@ +//! Run-time feature detection on MIPS64. + +/// Checks if `mips64` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal, stdsimd)] +macro_rules! is_mips64_feature_detected { + ("msa") => { + cfg!(target_feature = "msa") || $crate::detect::check_for($crate::detect::Feature::msa) + }; + ($t:tt,) => { + is_mips64_feature_detected!($t); + }; + ($t:tt) => { + compile_error!(concat!("unknown mips64 target feature: ", $t)) + }; +} + +/// MIPS64 CPU Feature enum. Each variant denotes a position in a bitset +/// for a particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// MIPS SIMD Architecture (MSA) + msa, +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/arch/powerpc.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/powerpc.rs new file mode 100644 index 0000000000..e7a9daac68 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/powerpc.rs @@ -0,0 +1,42 @@ +//! Run-time feature detection on PowerPC. + +/// Checks if `powerpc` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal, stdsimd)] +macro_rules! is_powerpc_feature_detected { + ("altivec") => { + cfg!(target_feature = "altivec") + || $crate::detect::check_for($crate::detect::Feature::altivec) + }; + ("vsx") => { + cfg!(target_feature = "vsx") || $crate::detect::check_for($crate::detect::Feature::vsx) + }; + ("power8") => { + cfg!(target_feature = "power8") + || $crate::detect::check_for($crate::detect::Feature::power8) + }; + ($t:tt,) => { + is_powerpc_feature_detected!($t); + }; + ($t:tt) => { + compile_error!(concat!("unknown powerpc target feature: ", $t)) + }; +} + +/// PowerPC CPU Feature enum. Each variant denotes a position in a bitset +/// for a particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// Altivec + altivec, + /// VSX + vsx, + /// Power8 + power8, +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/arch/powerpc64.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/powerpc64.rs new file mode 100644 index 0000000000..c102202695 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/powerpc64.rs @@ -0,0 +1,42 @@ +//! Run-time feature detection on PowerPC64. + +/// Checks if `powerpc64` feature is enabled. +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +#[allow_internal_unstable(stdsimd_internal, stdsimd)] +macro_rules! is_powerpc64_feature_detected { + ("altivec") => { + cfg!(target_feature = "altivec") + || $crate::detect::check_for($crate::detect::Feature::altivec) + }; + ("vsx") => { + cfg!(target_feature = "vsx") || $crate::detect::check_for($crate::detect::Feature::vsx) + }; + ("power8") => { + cfg!(target_feature = "power8") + || $crate::detect::check_for($crate::detect::Feature::power8) + }; + ($t:tt,) => { + is_powerpc64_feature_detected!($t); + }; + ($t:tt) => { + compile_error!(concat!("unknown powerpc64 target feature: ", $t)) + }; +} + +/// PowerPC64 CPU Feature enum. Each variant denotes a position in a bitset +/// for a particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// Altivec + altivec, + /// VSX + vsx, + /// Power8 + power8, +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/arch/x86.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/x86.rs new file mode 100644 index 0000000000..9219a4a577 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/x86.rs @@ -0,0 +1,333 @@ +//! This module implements minimal run-time feature detection for x86. +//! +//! The features are detected using the `detect_features` function below. +//! This function uses the CPUID instruction to read the feature flags from the +//! CPU and encodes them in an `usize` where each bit position represents +//! whether a feature is available (bit is set) or unavaiable (bit is cleared). +//! +//! The enum `Feature` is used to map bit positions to feature names, and the +//! the `__crate::detect::check_for!` macro is used to map string literals (e.g., +//! "avx") to these bit positions (e.g., `Feature::avx`). +//! +//! The run-time feature detection is performed by the +//! `__crate::detect::check_for(Feature) -> bool` function. On its first call, +//! this functions queries the CPU for the available features and stores them +//! in a global `AtomicUsize` variable. The query is performed by just checking +//! whether the feature bit in this global variable is set or cleared. + +/// A macro to test at *runtime* whether a CPU feature is available on +/// x86/x86-64 platforms. +/// +/// This macro is provided in the standard library and will detect at runtime +/// whether the specified CPU feature is detected. This does **not** resolve at +/// compile time unless the specified feature is already enabled for the entire +/// crate. Runtime detection currently relies mostly on the `cpuid` instruction. +/// +/// This macro only takes one argument which is a string literal of the feature +/// being tested for. The feature names supported are the lowercase versions of +/// the ones defined by Intel in [their documentation][docs]. +/// +/// ## Supported arguments +/// +/// This macro supports the same names that `#[target_feature]` supports. Unlike +/// `#[target_feature]`, however, this macro does not support names separated +/// with a comma. Instead testing for multiple features must be done through +/// separate macro invocations for now. +/// +/// Supported arguments are: +/// +/// * `"aes"` +/// * `"pclmulqdq"` +/// * `"rdrand"` +/// * `"rdseed"` +/// * `"tsc"` +/// * `"mmx"` +/// * `"sse"` +/// * `"sse2"` +/// * `"sse3"` +/// * `"ssse3"` +/// * `"sse4.1"` +/// * `"sse4.2"` +/// * `"sse4a"` +/// * `"sha"` +/// * `"avx"` +/// * `"avx2"` +/// * `"avx512f"` +/// * `"avx512cd"` +/// * `"avx512er"` +/// * `"avx512pf"` +/// * `"avx512bw"` +/// * `"avx512dq"` +/// * `"avx512vl"` +/// * `"avx512ifma"` +/// * `"avx512vbmi"` +/// * `"avx512vpopcntdq"` +/// * `"f16c"` +/// * `"fma"` +/// * `"bmi1"` +/// * `"bmi2"` +/// * `"abm"` +/// * `"lzcnt"` +/// * `"tbm"` +/// * `"popcnt"` +/// * `"fxsr"` +/// * `"xsave"` +/// * `"xsaveopt"` +/// * `"xsaves"` +/// * `"xsavec"` +/// * `"adx"` +/// * `"rtm"` +/// +/// [docs]: https://software.intel.com/sites/landingpage/IntrinsicsGuide +#[macro_export] +#[stable(feature = "simd_x86", since = "1.27.0")] +#[allow_internal_unstable(stdsimd_internal, stdsimd)] +macro_rules! is_x86_feature_detected { + ("aes") => { + cfg!(target_feature = "aes") || $crate::detect::check_for($crate::detect::Feature::aes) + }; + ("pclmulqdq") => { + cfg!(target_feature = "pclmulqdq") + || $crate::detect::check_for($crate::detect::Feature::pclmulqdq) + }; + ("rdrand") => { + cfg!(target_feature = "rdrand") + || $crate::detect::check_for($crate::detect::Feature::rdrand) + }; + ("rdseed") => { + cfg!(target_feature = "rdseed") + || $crate::detect::check_for($crate::detect::Feature::rdseed) + }; + ("tsc") => { + cfg!(target_feature = "tsc") || $crate::detect::check_for($crate::detect::Feature::tsc) + }; + ("mmx") => { + cfg!(target_feature = "mmx") || $crate::detect::check_for($crate::detect::Feature::mmx) + }; + ("sse") => { + cfg!(target_feature = "sse") || $crate::detect::check_for($crate::detect::Feature::sse) + }; + ("sse2") => { + cfg!(target_feature = "sse2") || $crate::detect::check_for($crate::detect::Feature::sse2) + }; + ("sse3") => { + cfg!(target_feature = "sse3") || $crate::detect::check_for($crate::detect::Feature::sse3) + }; + ("ssse3") => { + cfg!(target_feature = "ssse3") || $crate::detect::check_for($crate::detect::Feature::ssse3) + }; + ("sse4.1") => { + cfg!(target_feature = "sse4.1") + || $crate::detect::check_for($crate::detect::Feature::sse4_1) + }; + ("sse4.2") => { + cfg!(target_feature = "sse4.2") + || $crate::detect::check_for($crate::detect::Feature::sse4_2) + }; + ("sse4a") => { + cfg!(target_feature = "sse4a") || $crate::detect::check_for($crate::detect::Feature::sse4a) + }; + ("sha") => { + cfg!(target_feature = "sha") || $crate::detect::check_for($crate::detect::Feature::sha) + }; + ("avx") => { + cfg!(target_feature = "avx") || $crate::detect::check_for($crate::detect::Feature::avx) + }; + ("avx2") => { + cfg!(target_feature = "avx2") || $crate::detect::check_for($crate::detect::Feature::avx2) + }; + ("avx512f") => { + cfg!(target_feature = "avx512f") + || $crate::detect::check_for($crate::detect::Feature::avx512f) + }; + ("avx512cd") => { + cfg!(target_feature = "avx512cd") + || $crate::detect::check_for($crate::detect::Feature::avx512cd) + }; + ("avx512er") => { + cfg!(target_feature = "avx512er") + || $crate::detect::check_for($crate::detect::Feature::avx512er) + }; + ("avx512pf") => { + cfg!(target_feature = "avx512pf") + || $crate::detect::check_for($crate::detect::Feature::avx512pf) + }; + ("avx512bw") => { + cfg!(target_feature = "avx512bw") + || $crate::detect::check_for($crate::detect::Feature::avx512bw) + }; + ("avx512dq") => { + cfg!(target_feature = "avx512dq") + || $crate::detect::check_for($crate::detect::Feature::avx512dq) + }; + ("avx512vl") => { + cfg!(target_Feature = "avx512vl") + || $crate::detect::check_for($crate::detect::Feature::avx512vl) + }; + ("avx512ifma") => { + cfg!(target_feature = "avx512ifma") + || $crate::detect::check_for($crate::detect::Feature::avx512_ifma) + }; + ("avx512vbmi") => { + cfg!(target_feature = "avx512vbmi") + || $crate::detect::check_for($crate::detect::Feature::avx512_vbmi) + }; + ("avx512vpopcntdq") => { + cfg!(target_feature = "avx512vpopcntdq") + || $crate::detect::check_for($crate::detect::Feature::avx512_vpopcntdq) + }; + ("f16c") => { + cfg!(target_feature = "f16c") || $crate::detect::check_for($crate::detect::Feature::f16c) + }; + ("fma") => { + cfg!(target_feature = "fma") || $crate::detect::check_for($crate::detect::Feature::fma) + }; + ("bmi1") => { + cfg!(target_feature = "bmi1") || $crate::detect::check_for($crate::detect::Feature::bmi) + }; + ("bmi2") => { + cfg!(target_feature = "bmi2") || $crate::detect::check_for($crate::detect::Feature::bmi2) + }; + ("abm") => { + cfg!(target_feature = "abm") || $crate::detect::check_for($crate::detect::Feature::abm) + }; + ("lzcnt") => { + cfg!(target_feature = "lzcnt") || $crate::detect::check_for($crate::detect::Feature::abm) + }; + ("tbm") => { + cfg!(target_feature = "tbm") || $crate::detect::check_for($crate::detect::Feature::tbm) + }; + ("popcnt") => { + cfg!(target_feature = "popcnt") + || $crate::detect::check_for($crate::detect::Feature::popcnt) + }; + ("fxsr") => { + cfg!(target_feature = "fxsr") || $crate::detect::check_for($crate::detect::Feature::fxsr) + }; + ("xsave") => { + cfg!(target_feature = "xsave") || $crate::detect::check_for($crate::detect::Feature::xsave) + }; + ("xsaveopt") => { + cfg!(target_feature = "xsaveopt") + || $crate::detect::check_for($crate::detect::Feature::xsaveopt) + }; + ("xsaves") => { + cfg!(target_feature = "xsaves") + || $crate::detect::check_for($crate::detect::Feature::xsaves) + }; + ("xsavec") => { + cfg!(target_feature = "xsavec") + || $crate::detect::check_for($crate::detect::Feature::xsavec) + }; + ("cmpxchg16b") => { + cfg!(target_feature = "cmpxchg16b") + || $crate::detect::check_for($crate::detect::Feature::cmpxchg16b) + }; + ("adx") => { + cfg!(target_feature = "adx") || $crate::detect::check_for($crate::detect::Feature::adx) + }; + ("rtm") => { + cfg!(target_feature = "rtm") || $crate::detect::check_for($crate::detect::Feature::rtm) + }; + ($t:tt,) => { + is_x86_feature_detected!($t); + }; + ($t:tt) => { + compile_error!(concat!("unknown target feature: ", $t)) + }; +} + +/// X86 CPU Feature enum. Each variant denotes a position in a bitset for a +/// particular feature. +/// +/// This is an unstable implementation detail subject to change. +#[allow(non_camel_case_types)] +#[repr(u8)] +#[doc(hidden)] +#[unstable(feature = "stdsimd_internal", issue = "0")] +pub enum Feature { + /// AES (Advanced Encryption Standard New Instructions AES-NI) + aes, + /// CLMUL (Carry-less Multiplication) + pclmulqdq, + /// RDRAND + rdrand, + /// RDSEED + rdseed, + /// TSC (Time Stamp Counter) + tsc, + /// MMX + mmx, + /// SSE (Streaming SIMD Extensions) + sse, + /// SSE2 (Streaming SIMD Extensions 2) + sse2, + /// SSE3 (Streaming SIMD Extensions 3) + sse3, + /// SSSE3 (Supplemental Streaming SIMD Extensions 3) + ssse3, + /// SSE4.1 (Streaming SIMD Extensions 4.1) + sse4_1, + /// SSE4.2 (Streaming SIMD Extensions 4.2) + sse4_2, + /// SSE4a (Streaming SIMD Extensions 4a) + sse4a, + /// SHA + sha, + /// AVX (Advanced Vector Extensions) + avx, + /// AVX2 (Advanced Vector Extensions 2) + avx2, + /// AVX-512 F (Foundation) + avx512f, + /// AVX-512 CD (Conflict Detection Instructions) + avx512cd, + /// AVX-512 ER (Exponential and Reciprocal Instructions) + avx512er, + /// AVX-512 PF (Prefetch Instructions) + avx512pf, + /// AVX-512 BW (Byte and Word Instructions) + avx512bw, + /// AVX-512 DQ (Doubleword and Quadword) + avx512dq, + /// AVX-512 VL (Vector Length Extensions) + avx512vl, + /// AVX-512 IFMA (Integer Fused Multiply Add) + avx512_ifma, + /// AVX-512 VBMI (Vector Byte Manipulation Instructions) + avx512_vbmi, + /// AVX-512 VPOPCNTDQ (Vector Population Count Doubleword and + /// Quadword) + avx512_vpopcntdq, + /// F16C (Conversions between IEEE-754 `binary16` and `binary32` formats) + f16c, + /// FMA (Fused Multiply Add) + fma, + /// BMI1 (Bit Manipulation Instructions 1) + bmi, + /// BMI1 (Bit Manipulation Instructions 2) + bmi2, + /// ABM (Advanced Bit Manipulation) on AMD / LZCNT (Leading Zero + /// Count) on Intel + abm, + /// TBM (Trailing Bit Manipulation) + tbm, + /// POPCNT (Population Count) + popcnt, + /// FXSR (Floating-point context fast save and restor) + fxsr, + /// XSAVE (Save Processor Extended States) + xsave, + /// XSAVEOPT (Save Processor Extended States Optimized) + xsaveopt, + /// XSAVES (Save Processor Extended States Supervisor) + xsaves, + /// XSAVEC (Save Processor Extended States Compacted) + xsavec, + /// CMPXCH16B, a 16-byte compare-and-swap instruction + cmpxchg16b, + /// ADX, Intel ADX (Multi-Precision Add-Carry Instruction Extensions) + adx, + /// RTM, Intel (Restricted Transactional Memory) + rtm, +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/bit.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/bit.rs new file mode 100644 index 0000000000..578f0b16b7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/bit.rs @@ -0,0 +1,9 @@ +//! Bit manipulation utilities. + +/// Tests the `bit` of `x`. +#[allow(dead_code)] +#[inline] +pub(crate) fn test(x: usize, bit: u32) -> bool { + debug_assert!(bit < 32, "bit index out-of-bounds"); + x & (1 << bit) != 0 +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/cache.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/cache.rs new file mode 100644 index 0000000000..92bc4b58d1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/cache.rs @@ -0,0 +1,164 @@ +//! Caches run-time feature detection so that it only needs to be computed +//! once. + +#![allow(dead_code)] // not used on all platforms + +use crate::sync::atomic::Ordering; + +#[cfg(target_pointer_width = "64")] +use crate::sync::atomic::AtomicU64; + +#[cfg(target_pointer_width = "32")] +use crate::sync::atomic::AtomicU32; + +/// Sets the `bit` of `x`. +#[inline] +const fn set_bit(x: u64, bit: u32) -> u64 { + x | 1 << bit +} + +/// Tests the `bit` of `x`. +#[inline] +const fn test_bit(x: u64, bit: u32) -> bool { + x & (1 << bit) != 0 +} + +/// Maximum number of features that can be cached. +const CACHE_CAPACITY: u32 = 63; + +/// This type is used to initialize the cache +#[derive(Copy, Clone)] +pub(crate) struct Initializer(u64); + +#[allow(clippy::use_self)] +impl Default for Initializer { + fn default() -> Self { + Initializer(0) + } +} + +impl Initializer { + /// Tests the `bit` of the cache. + #[allow(dead_code)] + #[inline] + pub(crate) fn test(self, bit: u32) -> bool { + // FIXME: this way of making sure that the cache is large enough is + // brittle. + debug_assert!( + bit < CACHE_CAPACITY, + "too many features, time to increase the cache size!" + ); + test_bit(self.0, bit) + } + + /// Sets the `bit` of the cache. + #[inline] + pub(crate) fn set(&mut self, bit: u32) { + // FIXME: this way of making sure that the cache is large enough is + // brittle. + debug_assert!( + bit < CACHE_CAPACITY, + "too many features, time to increase the cache size!" + ); + let v = self.0; + self.0 = set_bit(v, bit); + } +} + +/// This global variable is a cache of the features supported by the CPU. +static CACHE: Cache = Cache::uninitialized(); + +/// Feature cache with capacity for `CACHE_CAPACITY` features. +/// +/// Note: the last feature bit is used to represent an +/// uninitialized cache. +#[cfg(target_pointer_width = "64")] +struct Cache(AtomicU64); + +#[cfg(target_pointer_width = "64")] +#[allow(clippy::use_self)] +impl Cache { + /// Creates an uninitialized cache. + #[allow(clippy::declare_interior_mutable_const)] + const fn uninitialized() -> Self { + Cache(AtomicU64::new(u64::max_value())) + } + /// Is the cache uninitialized? + #[inline] + pub(crate) fn is_uninitialized(&self) -> bool { + self.0.load(Ordering::Relaxed) == u64::max_value() + } + + /// Is the `bit` in the cache set? + #[inline] + pub(crate) fn test(&self, bit: u32) -> bool { + test_bit(CACHE.0.load(Ordering::Relaxed), bit) + } + + /// Initializes the cache. + #[inline] + pub(crate) fn initialize(&self, value: Initializer) { + self.0.store(value.0, Ordering::Relaxed); + } +} + +/// Feature cache with capacity for `CACHE_CAPACITY` features. +/// +/// Note: the last feature bit is used to represent an +/// uninitialized cache. +#[cfg(target_pointer_width = "32")] +struct Cache(AtomicU32, AtomicU32); + +#[cfg(target_pointer_width = "32")] +impl Cache { + /// Creates an uninitialized cache. + const fn uninitialized() -> Self { + Cache( + AtomicU32::new(u32::max_value()), + AtomicU32::new(u32::max_value()), + ) + } + /// Is the cache uninitialized? + #[inline] + pub(crate) fn is_uninitialized(&self) -> bool { + self.1.load(Ordering::Relaxed) == u32::max_value() + } + + /// Is the `bit` in the cache set? + #[inline] + pub(crate) fn test(&self, bit: u32) -> bool { + if bit < 32 { + test_bit(CACHE.0.load(Ordering::Relaxed) as u64, bit) + } else { + test_bit(CACHE.1.load(Ordering::Relaxed) as u64, bit - 32) + } + } + + /// Initializes the cache. + #[inline] + pub(crate) fn initialize(&self, value: Initializer) { + let lo: u32 = value.0 as u32; + let hi: u32 = (value.0 >> 32) as u32; + self.0.store(lo, Ordering::Relaxed); + self.1.store(hi, Ordering::Relaxed); + } +} + +/// Tests the `bit` of the storage. If the storage has not been initialized, +/// initializes it with the result of `f()`. +/// +/// On its first invocation, it detects the CPU features and caches them in the +/// `CACHE` global variable as an `AtomicU64`. +/// +/// It uses the `Feature` variant to index into this variable as a bitset. If +/// the bit is set, the feature is enabled, and otherwise it is disabled. +#[inline] +pub(crate) fn test(bit: u32, f: F) -> bool +where + F: FnOnce() -> Initializer, +{ + if CACHE.is_uninitialized() { + CACHE.initialize(f()); + } + CACHE.test(bit) +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/error_macros.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/error_macros.rs new file mode 100644 index 0000000000..6769757ed9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/error_macros.rs @@ -0,0 +1,150 @@ +//! The `is_{target_arch}_feature_detected!` macro are only available on their +//! architecture. These macros provide a better error messages when the user +//! attempts to call them in a different architecture. + +/// Prevents compilation if `is_x86_feature_detected` is used somewhere +/// else than `x86` and `x86_64` targets. +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_x86_feature_detected { + ($t: tt) => { + compile_error!( + r#" + is_x86_feature_detected can only be used on x86 and x86_64 targets. + You can prevent it from being used in other architectures by + guarding it behind a cfg(target_arch) as follows: + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + if is_x86_feature_detected(...) { ... } + } + "# + ) + }; +} + +/// Prevents compilation if `is_arm_feature_detected` is used somewhere else +/// than `ARM` targets. +#[cfg(not(target_arch = "arm"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_arm_feature_detected { + ($t:tt) => { + compile_error!( + r#" + is_arm_feature_detected can only be used on ARM targets. + You can prevent it from being used in other architectures by + guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "arm")] { + if is_arm_feature_detected(...) { ... } + } + "# + ) + }; +} + +/// Prevents compilation if `is_aarch64_feature_detected` is used somewhere else +/// than `aarch64` targets. +#[cfg(not(target_arch = "aarch64"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_aarch64_feature_detected { + ($t: tt) => { + compile_error!( + r#" + is_aarch64_feature_detected can only be used on AArch64 targets. + You can prevent it from being used in other architectures by + guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "aarch64")] { + if is_aarch64_feature_detected(...) { ... } + } + "# + ) + }; +} + +/// Prevents compilation if `is_powerpc_feature_detected` is used somewhere else +/// than `PowerPC` targets. +#[cfg(not(target_arch = "powerpc"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_powerpc_feature_detected { + ($t:tt) => { + compile_error!( + r#" +is_powerpc_feature_detected can only be used on PowerPC targets. +You can prevent it from being used in other architectures by +guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "powerpc")] { + if is_powerpc_feature_detected(...) { ... } + } +"# + ) + }; +} + +/// Prevents compilation if `is_powerpc64_feature_detected` is used somewhere +/// else than `PowerPC64` targets. +#[cfg(not(target_arch = "powerpc64"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_powerpc64_feature_detected { + ($t:tt) => { + compile_error!( + r#" +is_powerpc64_feature_detected can only be used on PowerPC64 targets. +You can prevent it from being used in other architectures by +guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "powerpc64")] { + if is_powerpc64_feature_detected(...) { ... } + } +"# + ) + }; +} + +/// Prevents compilation if `is_mips_feature_detected` is used somewhere else +/// than `MIPS` targets. +#[cfg(not(target_arch = "mips"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_mips_feature_detected { + ($t:tt) => { + compile_error!( + r#" + is_mips_feature_detected can only be used on MIPS targets. + You can prevent it from being used in other architectures by + guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "mips")] { + if is_mips_feature_detected(...) { ... } + } + "# + ) + }; +} + +/// Prevents compilation if `is_mips64_feature_detected` is used somewhere else +/// than `MIPS64` targets. +#[cfg(not(target_arch = "mips64"))] +#[macro_export] +#[unstable(feature = "stdsimd", issue = "27731")] +macro_rules! is_mips64_feature_detected { + ($t:tt) => { + compile_error!( + r#" + is_mips64_feature_detected can only be used on MIPS64 targets. + You can prevent it from being used in other architectures by + guarding it behind a cfg(target_arch) as follows: + + #[cfg(target_arch = "mips64")] { + if is_mips64_feature_detected(...) { ... } + } + "# + ) + }; +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/mod.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/mod.rs new file mode 100644 index 0000000000..f446e88eed --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/mod.rs @@ -0,0 +1,85 @@ +//! This module implements run-time feature detection. +//! +//! The `is_{arch}_feature_detected!("feature-name")` macros take the name of a +//! feature as a string-literal, and return a boolean indicating whether the +//! feature is enabled at run-time or not. +//! +//! These macros do two things: +//! * map the string-literal into an integer stored as a `Feature` enum, +//! * call a `os::check_for(x: Feature)` function that returns `true` if the +//! feature is enabled. +//! +//! The `Feature` enums are also implemented in the `arch/{target_arch}.rs` +//! modules. +//! +//! The `check_for` functions are, in general, Operating System dependent. Most +//! architectures do not allow user-space programs to query the feature bits +//! due to security concerns (x86 is the big exception). These functions are +//! implemented in the `os/{target_os}.rs` modules. + +#[macro_use] +mod error_macros; + +cfg_if! { + if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + #[path = "arch/x86.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "arm")] { + #[path = "arch/arm.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "aarch64")] { + #[path = "arch/aarch64.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "powerpc")] { + #[path = "arch/powerpc.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "powerpc64")] { + #[path = "arch/powerpc64.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "mips")] { + #[path = "arch/mips.rs"] + #[macro_use] + mod arch; + } else if #[cfg(target_arch = "mips64")] { + #[path = "arch/mips64.rs"] + #[macro_use] + mod arch; + } else { + // Unimplemented architecture: + mod arch { + pub enum Feature { + Null + } + } + } +} +pub use self::arch::Feature; + +mod bit; +mod cache; + +cfg_if! { + if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + // On x86/x86_64 no OS specific functionality is required. + #[path = "os/x86.rs"] + mod os; + } else if #[cfg(all(target_os = "linux", feature = "use_std"))] { + #[path = "os/linux/mod.rs"] + mod os; + } else if #[cfg(target_os = "freebsd")] { + #[cfg(target_arch = "aarch64")] + #[path = "os/aarch64.rs"] + mod aarch64; + #[path = "os/freebsd/mod.rs"] + mod os; + } else { + #[path = "os/other.rs"] + mod os; + } +} +pub use self::os::check_for; diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/aarch64.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/aarch64.rs new file mode 100644 index 0000000000..9adc938a26 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/aarch64.rs @@ -0,0 +1,88 @@ +//! Run-time feature detection for Aarch64 on any OS that emulates the mrs instruction. +//! +//! On FreeBSD >= 12.0, Linux >= 4.11 and other operating systems, it is possible to use +//! privileged system registers from userspace to check CPU feature support. +//! +//! AArch64 system registers ID_AA64ISAR0_EL1, ID_AA64PFR0_EL1, ID_AA64ISAR1_EL1 +//! have bits dedicated to features like AdvSIMD, CRC32, AES, atomics (LSE), etc. +//! Each part of the register indicates the level of support for a certain feature, e.g. +//! when ID_AA64ISAR0_EL1\[7:4\] is >= 1, AES is supported; when it's >= 2, PMULL is supported. +//! +//! For proper support of [SoCs where different cores have different capabilities](https://medium.com/@jadr2ddude/a-big-little-problem-a-tale-of-big-little-gone-wrong-e7778ce744bb), +//! the OS has to always report only the features supported by all cores, like [FreeBSD does](https://reviews.freebsd.org/D17137#393947). +//! +//! References: +//! +//! - [Zircon implementation](https://fuchsia.googlesource.com/zircon/+/master/kernel/arch/arm64/feature.cpp) +//! - [Linux documentation](https://www.kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt) + +use crate::detect::{cache, Feature}; + +/// Try to read the features from the system registers. +/// +/// This will cause SIGILL if the current OS is not trapping the mrs instruction. +pub(crate) fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + + { + let mut enable_feature = |f, enable| { + if enable { + value.set(f as u32); + } + }; + + // ID_AA64ISAR0_EL1 - Instruction Set Attribute Register 0 + let aa64isar0: u64; + unsafe { + asm!("mrs $0, ID_AA64ISAR0_EL1" : "=r"(aa64isar0)); + } + + let aes = bits_shift(aa64isar0, 7, 4) >= 1; + let pmull = bits_shift(aa64isar0, 7, 4) >= 2; + let sha1 = bits_shift(aa64isar0, 11, 8) >= 1; + let sha2 = bits_shift(aa64isar0, 15, 12) >= 1; + enable_feature(Feature::pmull, pmull); + // Crypto is specified as AES + PMULL + SHA1 + SHA2 per LLVM/hosts.cpp + enable_feature(Feature::crypto, aes && pmull && sha1 && sha2); + enable_feature(Feature::lse, bits_shift(aa64isar0, 23, 20) >= 1); + enable_feature(Feature::crc, bits_shift(aa64isar0, 19, 16) >= 1); + + // ID_AA64PFR0_EL1 - Processor Feature Register 0 + let aa64pfr0: u64; + unsafe { + asm!("mrs $0, ID_AA64PFR0_EL1" : "=r"(aa64pfr0)); + } + + let fp = bits_shift(aa64pfr0, 19, 16) < 0xF; + let fphp = bits_shift(aa64pfr0, 19, 16) >= 1; + let asimd = bits_shift(aa64pfr0, 23, 20) < 0xF; + let asimdhp = bits_shift(aa64pfr0, 23, 20) >= 1; + enable_feature(Feature::fp, fp); + enable_feature(Feature::fp16, fphp); + // SIMD support requires float support - if half-floats are + // supported, it also requires half-float support: + enable_feature(Feature::asimd, fp && asimd && (!fphp | asimdhp)); + // SIMD extensions require SIMD support: + enable_feature(Feature::rdm, asimd && bits_shift(aa64isar0, 31, 28) >= 1); + enable_feature( + Feature::dotprod, + asimd && bits_shift(aa64isar0, 47, 44) >= 1, + ); + enable_feature(Feature::sve, asimd && bits_shift(aa64pfr0, 35, 32) >= 1); + + // ID_AA64ISAR1_EL1 - Instruction Set Attribute Register 1 + let aa64isar1: u64; + unsafe { + asm!("mrs $0, ID_AA64ISAR1_EL1" : "=r"(aa64isar1)); + } + + enable_feature(Feature::rcpc, bits_shift(aa64isar1, 23, 20) >= 1); + } + + value +} + +#[inline] +fn bits_shift(x: u64, high: usize, low: usize) -> u64 { + (x >> low) & ((1 << (high - low + 1)) - 1) +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/aarch64.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/aarch64.rs new file mode 100644 index 0000000000..97fe40f80a --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/aarch64.rs @@ -0,0 +1,28 @@ +//! Run-time feature detection for Aarch64 on FreeBSD. + +use super::super::aarch64::detect_features; +use crate::detect::{cache, Feature}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +#[cfg(test)] +mod tests { + #[test] + fn dump() { + println!("asimd: {:?}", is_aarch64_feature_detected!("asimd")); + println!("pmull: {:?}", is_aarch64_feature_detected!("pmull")); + println!("fp: {:?}", is_aarch64_feature_detected!("fp")); + println!("fp16: {:?}", is_aarch64_feature_detected!("fp16")); + println!("sve: {:?}", is_aarch64_feature_detected!("sve")); + println!("crc: {:?}", is_aarch64_feature_detected!("crc")); + println!("crypto: {:?}", is_aarch64_feature_detected!("crypto")); + println!("lse: {:?}", is_aarch64_feature_detected!("lse")); + println!("rdm: {:?}", is_aarch64_feature_detected!("rdm")); + println!("rcpc: {:?}", is_aarch64_feature_detected!("rcpc")); + println!("dotprod: {:?}", is_aarch64_feature_detected!("dotprod")); + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/arm.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/arm.rs new file mode 100644 index 0000000000..7aa040075e --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/arm.rs @@ -0,0 +1,27 @@ +//! Run-time feature detection for ARM on FreeBSD + +use super::auxvec; +use crate::detect::{cache, Feature}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Try to read the features from the auxiliary vector +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + let enable_feature = |value: &mut cache::Initializer, f, enable| { + if enable { + value.set(f as u32); + } + }; + + if let Ok(auxv) = auxvec::auxv() { + enable_feature(&mut value, Feature::neon, auxv.hwcap & 0x00001000 != 0); + enable_feature(&mut value, Feature::pmull, auxv.hwcap2 & 0x00000002 != 0); + return value; + } + value +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/auxvec.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/auxvec.rs new file mode 100644 index 0000000000..c595ec459b --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/auxvec.rs @@ -0,0 +1,94 @@ +//! Parses ELF auxiliary vectors. +#![cfg_attr(any(target_arch = "arm", target_arch = "powerpc64"), allow(dead_code))] + +/// Key to access the CPU Hardware capabilities bitfield. +pub(crate) const AT_HWCAP: usize = 25; +/// Key to access the CPU Hardware capabilities 2 bitfield. +pub(crate) const AT_HWCAP2: usize = 26; + +/// Cache HWCAP bitfields of the ELF Auxiliary Vector. +/// +/// If an entry cannot be read all the bits in the bitfield are set to zero. +/// This should be interpreted as all the features being disabled. +#[derive(Debug, Copy, Clone)] +pub(crate) struct AuxVec { + pub hwcap: usize, + pub hwcap2: usize, +} + +/// ELF Auxiliary Vector +/// +/// The auxiliary vector is a memory region in a running ELF program's stack +/// composed of (key: usize, value: usize) pairs. +/// +/// The keys used in the aux vector are platform dependent. For FreeBSD, they are +/// defined in [sys/elf_common.h][elf_common_h]. The hardware capabilities of a given +/// CPU can be queried with the `AT_HWCAP` and `AT_HWCAP2` keys. +/// +/// Note that run-time feature detection is not invoked for features that can +/// be detected at compile-time. +/// +/// [elf_common.h]: https://svnweb.freebsd.org/base/release/12.0.0/sys/sys/elf_common.h?revision=341707 +pub(crate) fn auxv() -> Result { + if let Ok(hwcap) = archauxv(AT_HWCAP) { + if let Ok(hwcap2) = archauxv(AT_HWCAP2) { + if hwcap != 0 && hwcap2 != 0 { + return Ok(AuxVec { hwcap, hwcap2 }); + } + } + } + Err(()) +} + +/// Tries to read the `key` from the auxiliary vector. +fn archauxv(key: usize) -> Result { + use crate::mem; + + #[derive(Copy, Clone)] + #[repr(C)] + pub struct Elf_Auxinfo { + pub a_type: usize, + pub a_un: unnamed, + } + #[derive(Copy, Clone)] + #[repr(C)] + pub union unnamed { + pub a_val: libc::c_long, + pub a_ptr: *mut libc::c_void, + pub a_fcn: Option ()>, + } + + let mut auxv: [Elf_Auxinfo; 27] = [Elf_Auxinfo { + a_type: 0, + a_un: unnamed { a_val: 0 }, + }; 27]; + + let mut len: libc::c_uint = mem::size_of_val(&auxv) as libc::c_uint; + + unsafe { + let mut mib = [ + libc::CTL_KERN, + libc::KERN_PROC, + libc::KERN_PROC_AUXV, + libc::getpid(), + ]; + + let ret = libc::sysctl( + mib.as_mut_ptr(), + mib.len() as u32, + &mut auxv as *mut _ as *mut _, + &mut len as *mut _ as *mut _, + 0 as *mut libc::c_void, + 0, + ); + + if ret != -1 { + for i in 0..auxv.len() { + if auxv[i].a_type == key { + return Ok(auxv[i].a_un.a_val as usize); + } + } + } + } + return Ok(0); +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/mod.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/mod.rs new file mode 100644 index 0000000000..1a5338a355 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/mod.rs @@ -0,0 +1,22 @@ +//! Run-time feature detection on FreeBSD + +mod auxvec; + +cfg_if! { + if #[cfg(target_arch = "aarch64")] { + mod aarch64; + pub use self::aarch64::check_for; + } else if #[cfg(target_arch = "arm")] { + mod arm; + pub use self::arm::check_for; + } else if #[cfg(target_arch = "powerpc64")] { + mod powerpc; + pub use self::powerpc::check_for; + } else { + use crate::arch::detect::Feature; + /// Performs run-time feature detection. + pub fn check_for(_x: Feature) -> bool { + false + } + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/powerpc.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/powerpc.rs new file mode 100644 index 0000000000..203e5cd7f2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/freebsd/powerpc.rs @@ -0,0 +1,27 @@ +//! Run-time feature detection for PowerPC on FreeBSD. + +use super::auxvec; +use crate::detect::{cache, Feature}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + let enable_feature = |value: &mut cache::Initializer, f, enable| { + if enable { + value.set(f as u32); + } + }; + + if let Ok(auxv) = auxvec::auxv() { + enable_feature(&mut value, Feature::altivec, auxv.hwcap & 0x10000000 != 0); + enable_feature(&mut value, Feature::vsx, auxv.hwcap & 0x00000080 != 0); + enable_feature(&mut value, Feature::power8, auxv.hwcap2 & 0x80000000 != 0); + return value; + } + value +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/aarch64.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/aarch64.rs new file mode 100644 index 0000000000..8d874f2280 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/aarch64.rs @@ -0,0 +1,160 @@ +//! Run-time feature detection for Aarch64 on Linux. + +use super::{auxvec, cpuinfo}; +use crate::detect::{bit, cache, Feature}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Try to read the features from the auxiliary vector, and if that fails, try +/// to read them from /proc/cpuinfo. +fn detect_features() -> cache::Initializer { + if let Ok(auxv) = auxvec::auxv() { + let hwcap: AtHwcap = auxv.into(); + return hwcap.cache(); + } + if let Ok(c) = cpuinfo::CpuInfo::new() { + let hwcap: AtHwcap = c.into(); + return hwcap.cache(); + } + cache::Initializer::default() +} + +/// These values are part of the platform-specific [asm/hwcap.h][hwcap] . +/// +/// [hwcap]: https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/hwcap.h +struct AtHwcap { + fp: bool, // 0 + asimd: bool, // 1 + // evtstrm: bool, // 2 + aes: bool, // 3 + pmull: bool, // 4 + sha1: bool, // 5 + sha2: bool, // 6 + crc32: bool, // 7 + atomics: bool, // 8 + fphp: bool, // 9 + asimdhp: bool, // 10 + // cpuid: bool, // 11 + asimdrdm: bool, // 12 + // jscvt: bool, // 13 + // fcma: bool, // 14 + lrcpc: bool, // 15 + // dcpop: bool, // 16 + // sha3: bool, // 17 + // sm3: bool, // 18 + // sm4: bool, // 19 + asimddp: bool, // 20 + // sha512: bool, // 21 + sve: bool, // 22 +} + +impl From for AtHwcap { + /// Reads AtHwcap from the auxiliary vector. + fn from(auxv: auxvec::AuxVec) -> Self { + AtHwcap { + fp: bit::test(auxv.hwcap, 0), + asimd: bit::test(auxv.hwcap, 1), + // evtstrm: bit::test(auxv.hwcap, 2), + aes: bit::test(auxv.hwcap, 3), + pmull: bit::test(auxv.hwcap, 4), + sha1: bit::test(auxv.hwcap, 5), + sha2: bit::test(auxv.hwcap, 6), + crc32: bit::test(auxv.hwcap, 7), + atomics: bit::test(auxv.hwcap, 8), + fphp: bit::test(auxv.hwcap, 9), + asimdhp: bit::test(auxv.hwcap, 10), + // cpuid: bit::test(auxv.hwcap, 11), + asimdrdm: bit::test(auxv.hwcap, 12), + // jscvt: bit::test(auxv.hwcap, 13), + // fcma: bit::test(auxv.hwcap, 14), + lrcpc: bit::test(auxv.hwcap, 15), + // dcpop: bit::test(auxv.hwcap, 16), + // sha3: bit::test(auxv.hwcap, 17), + // sm3: bit::test(auxv.hwcap, 18), + // sm4: bit::test(auxv.hwcap, 19), + asimddp: bit::test(auxv.hwcap, 20), + // sha512: bit::test(auxv.hwcap, 21), + sve: bit::test(auxv.hwcap, 22), + } + } +} + +impl From for AtHwcap { + /// Reads AtHwcap from /proc/cpuinfo . + fn from(c: cpuinfo::CpuInfo) -> Self { + let f = &c.field("Features"); + AtHwcap { + // 64-bit names. FIXME: In 32-bit compatibility mode /proc/cpuinfo will + // map some of the 64-bit names to some 32-bit feature names. This does not + // cover that yet. + fp: f.has("fp"), + asimd: f.has("asimd"), + // evtstrm: f.has("evtstrm"), + aes: f.has("aes"), + pmull: f.has("pmull"), + sha1: f.has("sha1"), + sha2: f.has("sha2"), + crc32: f.has("crc32"), + atomics: f.has("atomics"), + fphp: f.has("fphp"), + asimdhp: f.has("asimdhp"), + // cpuid: f.has("cpuid"), + asimdrdm: f.has("asimdrdm"), + // jscvt: f.has("jscvt"), + // fcma: f.has("fcma"), + lrcpc: f.has("lrcpc"), + // dcpop: f.has("dcpop"), + // sha3: f.has("sha3"), + // sm3: f.has("sm3"), + // sm4: f.has("sm4"), + asimddp: f.has("asimddp"), + // sha512: f.has("sha512"), + sve: f.has("sve"), + } + } +} + +impl AtHwcap { + /// Initializes the cache from the feature -bits. + /// + /// The features are enabled approximately like in LLVM host feature detection: + /// https://github.com/llvm-mirror/llvm/blob/master/lib/Support/Host.cpp#L1273 + fn cache(self) -> cache::Initializer { + let mut value = cache::Initializer::default(); + { + let mut enable_feature = |f, enable| { + if enable { + value.set(f as u32); + } + }; + + enable_feature(Feature::fp, self.fp); + // Half-float support requires float support + enable_feature(Feature::fp16, self.fp && self.fphp); + enable_feature(Feature::pmull, self.pmull); + enable_feature(Feature::crc, self.crc32); + enable_feature(Feature::lse, self.atomics); + enable_feature(Feature::rcpc, self.lrcpc); + + // SIMD support requires float support - if half-floats are + // supported, it also requires half-float support: + let asimd = self.fp && self.asimd && (!self.fphp | self.asimdhp); + enable_feature(Feature::asimd, asimd); + // SIMD extensions require SIMD support: + enable_feature(Feature::rdm, self.asimdrdm && asimd); + enable_feature(Feature::dotprod, self.asimddp && asimd); + enable_feature(Feature::sve, self.sve && asimd); + + // Crypto is specified as AES + PMULL + SHA1 + SHA2 per LLVM/hosts.cpp + enable_feature( + Feature::crypto, + self.aes && self.pmull && self.sha1 && self.sha2, + ); + } + value + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/arm.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/arm.rs new file mode 100644 index 0000000000..9c89500cc1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/arm.rs @@ -0,0 +1,52 @@ +//! Run-time feature detection for ARM on Linux. + +use super::{auxvec, cpuinfo}; +use crate::detect::{bit, cache, Feature}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Try to read the features from the auxiliary vector, and if that fails, try +/// to read them from /proc/cpuinfo. +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + let enable_feature = |value: &mut cache::Initializer, f, enable| { + if enable { + value.set(f as u32); + } + }; + + // The values are part of the platform-specific [asm/hwcap.h][hwcap] + // + // [hwcap]: https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/hwcap.h + if let Ok(auxv) = auxvec::auxv() { + enable_feature(&mut value, Feature::neon, bit::test(auxv.hwcap, 12)); + enable_feature(&mut value, Feature::pmull, bit::test(auxv.hwcap2, 1)); + return value; + } + + if let Ok(c) = cpuinfo::CpuInfo::new() { + enable_feature( + &mut value, + Feature::neon, + c.field("Features").has("neon") && !has_broken_neon(&c), + ); + enable_feature(&mut value, Feature::pmull, c.field("Features").has("pmull")); + return value; + } + value +} + +/// Is the CPU known to have a broken NEON unit? +/// +/// See https://crbug.com/341598. +fn has_broken_neon(cpuinfo: &cpuinfo::CpuInfo) -> bool { + cpuinfo.field("CPU implementer") == "0x51" + && cpuinfo.field("CPU architecture") == "7" + && cpuinfo.field("CPU variant") == "0x1" + && cpuinfo.field("CPU part") == "0x04d" + && cpuinfo.field("CPU revision") == "0" +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/auxvec.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/auxvec.rs new file mode 100644 index 0000000000..6ebae67fbf --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/auxvec.rs @@ -0,0 +1,304 @@ +//! Parses ELF auxiliary vectors. +#![cfg_attr(not(target_arch = "aarch64"), allow(dead_code))] + +#[cfg(feature = "std_detect_file_io")] +use crate::{fs::File, io::Read}; + +/// Key to access the CPU Hardware capabilities bitfield. +pub(crate) const AT_HWCAP: usize = 16; +/// Key to access the CPU Hardware capabilities 2 bitfield. +#[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] +pub(crate) const AT_HWCAP2: usize = 26; + +/// Cache HWCAP bitfields of the ELF Auxiliary Vector. +/// +/// If an entry cannot be read all the bits in the bitfield are set to zero. +/// This should be interpreted as all the features being disabled. +#[derive(Debug, Copy, Clone)] +pub(crate) struct AuxVec { + pub hwcap: usize, + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + pub hwcap2: usize, +} + +/// ELF Auxiliary Vector +/// +/// The auxiliary vector is a memory region in a running ELF program's stack +/// composed of (key: usize, value: usize) pairs. +/// +/// The keys used in the aux vector are platform dependent. For Linux, they are +/// defined in [linux/auxvec.h][auxvec_h]. The hardware capabilities of a given +/// CPU can be queried with the `AT_HWCAP` and `AT_HWCAP2` keys. +/// +/// There is no perfect way of reading the auxiliary vector. +/// +/// - If the `std_detect_dlsym_getauxval` cargo feature is enabled, this will use +/// `getauxval` if its linked to the binary, and otherwise proceed to a fallback implementation. +/// When `std_detect_dlsym_getauxval` is disabled, this will assume that `getauxval` is +/// linked to the binary - if that is not the case the behavior is undefined. +/// - Otherwise, if the `std_detect_file_io` cargo feature is enabled, it will +/// try to read `/proc/self/auxv`. +/// - If that fails, this function returns an error. +/// +/// Note that run-time feature detection is not invoked for features that can +/// be detected at compile-time. Also note that if this function returns an +/// error, cpuinfo still can (and will) be used to try to perform run-time +/// feature detecton on some platforms. +/// +/// For more information about when `getauxval` is available check the great +/// [`auxv` crate documentation][auxv_docs]. +/// +/// [auxvec_h]: https://github.com/torvalds/linux/blob/master/include/uapi/linux/auxvec.h +/// [auxv_docs]: https://docs.rs/auxv/0.3.3/auxv/ +pub(crate) fn auxv() -> Result { + #[cfg(feature = "std_detect_dlsym_getauxval")] + { + // Try to call a dynamically-linked getauxval function. + if let Ok(hwcap) = getauxval(AT_HWCAP) { + // Targets with only AT_HWCAP: + #[cfg(any(target_arch = "aarch64", target_arch = "mips", target_arch = "mips64"))] + { + if hwcap != 0 { + return Ok(AuxVec { hwcap }); + } + } + + // Targets with AT_HWCAP and AT_HWCAP2: + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + { + if let Ok(hwcap2) = getauxval(AT_HWCAP2) { + if hwcap != 0 && hwcap2 != 0 { + return Ok(AuxVec { hwcap, hwcap2 }); + } + } + } + drop(hwcap); + } + #[cfg(feature = "std_detect_file_io")] + { + // If calling getauxval fails, try to read the auxiliary vector from + // its file: + auxv_from_file("/proc/self/auxv") + } + #[cfg(not(feature = "std_detect_file_io"))] + { + Err(()) + } + } + + #[cfg(not(feature = "std_detect_dlsym_getauxval"))] + { + let hwcap = unsafe { ffi_getauxval(AT_HWCAP) }; + + // Targets with only AT_HWCAP: + #[cfg(any(target_arch = "aarch64", target_arch = "mips", target_arch = "mips64"))] + { + if hwcap != 0 { + return Ok(AuxVec { hwcap }); + } + } + + // Targets with AT_HWCAP and AT_HWCAP2: + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + { + let hwcap2 = unsafe { ffi_getauxval(AT_HWCAP2) }; + if hwcap != 0 && hwcap2 != 0 { + return Ok(AuxVec { hwcap, hwcap2 }); + } + } + } +} + +/// Tries to read the `key` from the auxiliary vector by calling the +/// dynamically-linked `getauxval` function. If the function is not linked, +/// this function return `Err`. +#[cfg(feature = "std_detect_dlsym_getauxval")] +fn getauxval(key: usize) -> Result { + use libc; + pub type F = unsafe extern "C" fn(usize) -> usize; + unsafe { + let ptr = libc::dlsym(libc::RTLD_DEFAULT, "getauxval\0".as_ptr() as *const _); + if ptr.is_null() { + return Err(()); + } + + let ffi_getauxval: F = mem::transmute(ptr); + Ok(ffi_getauxval(key)) + } +} + +/// Tries to read the auxiliary vector from the `file`. If this fails, this +/// function returns `Err`. +#[cfg(feature = "std_detect_file_io")] +fn auxv_from_file(file: &str) -> Result { + let mut file = File::open(file).map_err(|_| ())?; + + // See . + // + // The auxiliary vector contains at most 32 (key,value) fields: from + // `AT_EXECFN = 31` to `AT_NULL = 0`. That is, a buffer of + // 2*32 `usize` elements is enough to read the whole vector. + let mut buf = [0_usize; 64]; + { + let raw: &mut [u8; 64 * mem::size_of::()] = unsafe { mem::transmute(&mut buf) }; + file.read(raw).map_err(|_| ())?; + } + auxv_from_buf(&buf) +} + +/// Tries to interpret the `buffer` as an auxiliary vector. If that fails, this +/// function returns `Err`. +#[cfg(feature = "std_detect_file_io")] +fn auxv_from_buf(buf: &[usize; 64]) -> Result { + // Targets with only AT_HWCAP: + #[cfg(any(target_arch = "aarch64", target_arch = "mips", target_arch = "mips64"))] + { + for el in buf.chunks(2) { + match el[0] { + AT_HWCAP => return Ok(AuxVec { hwcap: el[1] }), + _ => (), + } + } + } + // Targets with AT_HWCAP and AT_HWCAP2: + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + { + let mut hwcap = None; + let mut hwcap2 = None; + for el in buf.chunks(2) { + match el[0] { + AT_HWCAP => hwcap = Some(el[1]), + AT_HWCAP2 => hwcap2 = Some(el[1]), + _ => (), + } + } + + if let (Some(hwcap), Some(hwcap2)) = (hwcap, hwcap2) { + return Ok(AuxVec { hwcap, hwcap2 }); + } + } + drop(buf); + Err(()) +} + +#[cfg(test)] +mod tests { + extern crate auxv as auxv_crate; + use super::*; + + // Reads the Auxiliary Vector key from /proc/self/auxv + // using the auxv crate. + #[cfg(feature = "std_detect_file_io")] + fn auxv_crate_getprocfs(key: usize) -> Option { + use self::auxv_crate::procfs::search_procfs_auxv; + use self::auxv_crate::AuxvType; + let k = key as AuxvType; + match search_procfs_auxv(&[k]) { + Ok(v) => Some(v[&k] as usize), + Err(_) => None, + } + } + + // Reads the Auxiliary Vector key from getauxval() + // using the auxv crate. + #[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] + fn auxv_crate_getauxval(key: usize) -> Option { + use self::auxv_crate::getauxval::Getauxval; + use self::auxv_crate::AuxvType; + let q = auxv_crate::getauxval::NativeGetauxval {}; + match q.getauxval(key as AuxvType) { + Ok(v) => Some(v as usize), + Err(_) => None, + } + } + + // FIXME: on mips/mips64 getauxval returns 0, and /proc/self/auxv + // does not always contain the AT_HWCAP key under qemu. + #[cfg(not(any(target_arch = "mips", target_arch = "mips64", target_arch = "powerpc")))] + #[test] + fn auxv_crate() { + let v = auxv(); + if let Some(hwcap) = auxv_crate_getauxval(AT_HWCAP) { + let rt_hwcap = v.expect("failed to find hwcap key").hwcap; + assert_eq!(rt_hwcap, hwcap); + } + + // Targets with AT_HWCAP and AT_HWCAP2: + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + { + if let Some(hwcap2) = auxv_crate_getauxval(AT_HWCAP2) { + let rt_hwcap2 = v.expect("failed to find hwcap2 key").hwcap2; + assert_eq!(rt_hwcap2, hwcap2); + } + } + } + + #[test] + fn auxv_dump() { + if let Ok(auxvec) = auxv() { + println!("{:?}", auxvec); + } else { + println!("both getauxval() and reading /proc/self/auxv failed!"); + } + } + + #[cfg(feature = "std_detect_file_io")] + cfg_if! { + if #[cfg(target_arch = "arm")] { + #[test] + fn linux_rpi3() { + let file = concat!(env!("CARGO_MANIFEST_DIR"), "/src/detect/test_data/linux-rpi3.auxv"); + println!("file: {}", file); + let v = auxv_from_file(file).unwrap(); + assert_eq!(v.hwcap, 4174038); + assert_eq!(v.hwcap2, 16); + } + + #[test] + #[should_panic] + fn linux_macos_vb() { + let file = concat!(env!("CARGO_MANIFEST_DIR"), "/src/detect/test_data/macos-virtualbox-linux-x86-4850HQ.auxv"); + println!("file: {}", file); + let v = auxv_from_file(file).unwrap(); + // this file is incomplete (contains hwcap but not hwcap2), we + // want to fall back to /proc/cpuinfo in this case, so + // reading should fail. assert_eq!(v.hwcap, 126614527); + // assert_eq!(v.hwcap2, 0); + } + } else if #[cfg(target_arch = "aarch64")] { + #[test] + fn linux_x64() { + let file = concat!(env!("CARGO_MANIFEST_DIR"), "/src/detect/test_data/linux-x64-i7-6850k.auxv"); + println!("file: {}", file); + let v = auxv_from_file(file).unwrap(); + assert_eq!(v.hwcap, 3219913727); + } + } + } + + #[test] + #[cfg(feature = "std_detect_file_io")] + fn auxv_dump_procfs() { + if let Ok(auxvec) = auxv_from_file("/proc/self/auxv") { + println!("{:?}", auxvec); + } else { + println!("reading /proc/self/auxv failed!"); + } + } + + #[test] + fn auxv_crate_procfs() { + let v = auxv(); + if let Some(hwcap) = auxv_crate_getprocfs(AT_HWCAP) { + assert_eq!(v.unwrap().hwcap, hwcap); + } + + // Targets with AT_HWCAP and AT_HWCAP2: + #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] + { + if let Some(hwcap2) = auxv_crate_getprocfs(AT_HWCAP2) { + assert_eq!(v.unwrap().hwcap2, hwcap2); + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/cpuinfo.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/cpuinfo.rs new file mode 100644 index 0000000000..f76c48a4b1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/cpuinfo.rs @@ -0,0 +1,300 @@ +//! Parses /proc/cpuinfo +#![cfg_attr(not(target_arch = "arm"), allow(dead_code))] + +extern crate std; +use self::std::{fs::File, io, io::Read, prelude::v1::*}; + +/// cpuinfo +pub(crate) struct CpuInfo { + raw: String, +} + +impl CpuInfo { + /// Reads /proc/cpuinfo into CpuInfo. + pub(crate) fn new() -> Result { + let mut file = File::open("/proc/cpuinfo")?; + let mut cpui = Self { raw: String::new() }; + file.read_to_string(&mut cpui.raw)?; + Ok(cpui) + } + /// Returns the value of the cpuinfo `field`. + pub(crate) fn field(&self, field: &str) -> CpuInfoField { + for l in self.raw.lines() { + if l.trim().starts_with(field) { + return CpuInfoField::new(l.split(": ").nth(1)); + } + } + CpuInfoField(None) + } + + /// Returns the `raw` contents of `/proc/cpuinfo` + #[cfg(test)] + fn raw(&self) -> &String { + &self.raw + } + + #[cfg(test)] + fn from_str(other: &str) -> Result { + Ok(Self { + raw: String::from(other), + }) + } +} + +/// Field of cpuinfo +#[derive(Debug)] +pub(crate) struct CpuInfoField<'a>(Option<&'a str>); + +impl<'a> PartialEq<&'a str> for CpuInfoField<'a> { + fn eq(&self, other: &&'a str) -> bool { + match self.0 { + None => other.is_empty(), + Some(f) => f == other.trim(), + } + } +} + +impl<'a> CpuInfoField<'a> { + pub(crate) fn new<'b>(v: Option<&'b str>) -> CpuInfoField<'b> { + match v { + None => CpuInfoField::<'b>(None), + Some(f) => CpuInfoField::<'b>(Some(f.trim())), + } + } + /// Does the field exist? + #[cfg(test)] + pub(crate) fn exists(&self) -> bool { + self.0.is_some() + } + /// Does the field contain `other`? + pub(crate) fn has(&self, other: &str) -> bool { + match self.0 { + None => other.is_empty(), + Some(f) => { + let other = other.trim(); + for v in f.split(' ') { + if v == other { + return true; + } + } + false + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn raw_dump() { + let cpuinfo = CpuInfo::new().unwrap(); + if cpuinfo.field("vendor_id") == "GenuineIntel" { + assert!(cpuinfo.field("flags").exists()); + assert!(!cpuinfo.field("vendor33_id").exists()); + assert!(cpuinfo.field("flags").has("sse")); + assert!(!cpuinfo.field("flags").has("avx314")); + } + println!("{}", cpuinfo.raw()); + } + + const CORE_DUO_T6500: &str = r"processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 23 +model name : Intel(R) Core(TM)2 Duo CPU T6500 @ 2.10GHz +stepping : 10 +microcode : 0xa0b +cpu MHz : 1600.000 +cache size : 2048 KB +physical id : 0 +siblings : 2 +core id : 0 +cpu cores : 2 +apicid : 0 +initial apicid : 0 +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 13 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm dtherm +bogomips : 4190.43 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: +"; + + #[test] + fn core_duo_t6500() { + let cpuinfo = CpuInfo::from_str(CORE_DUO_T6500).unwrap(); + assert_eq!(cpuinfo.field("vendor_id"), "GenuineIntel"); + assert_eq!(cpuinfo.field("cpu family"), "6"); + assert_eq!(cpuinfo.field("model"), "23"); + assert_eq!( + cpuinfo.field("model name"), + "Intel(R) Core(TM)2 Duo CPU T6500 @ 2.10GHz" + ); + assert_eq!( + cpuinfo.field("flags"), + "fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm dtherm" + ); + assert!(cpuinfo.field("flags").has("fpu")); + assert!(cpuinfo.field("flags").has("dtherm")); + assert!(cpuinfo.field("flags").has("sse2")); + assert!(!cpuinfo.field("flags").has("avx")); + } + + const ARM_CORTEX_A53: &str = r"Processor : AArch64 Processor rev 3 (aarch64) + processor : 0 + processor : 1 + processor : 2 + processor : 3 + processor : 4 + processor : 5 + processor : 6 + processor : 7 + Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 + CPU implementer : 0x41 + CPU architecture: AArch64 + CPU variant : 0x0 + CPU part : 0xd03 + CPU revision : 3 + + Hardware : HiKey Development Board + "; + + #[test] + fn arm_cortex_a53() { + let cpuinfo = CpuInfo::from_str(ARM_CORTEX_A53).unwrap(); + assert_eq!( + cpuinfo.field("Processor"), + "AArch64 Processor rev 3 (aarch64)" + ); + assert_eq!( + cpuinfo.field("Features"), + "fp asimd evtstrm aes pmull sha1 sha2 crc32" + ); + assert!(cpuinfo.field("Features").has("pmull")); + assert!(!cpuinfo.field("Features").has("neon")); + assert!(cpuinfo.field("Features").has("asimd")); + } + + const ARM_CORTEX_A57: &str = r"Processor : Cortex A57 Processor rev 1 (aarch64) +processor : 0 +processor : 1 +processor : 2 +processor : 3 +Features : fp asimd aes pmull sha1 sha2 crc32 wp half thumb fastmult vfp edsp neon vfpv3 tlsi vfpv4 idiva idivt +CPU implementer : 0x41 +CPU architecture: 8 +CPU variant : 0x1 +CPU part : 0xd07 +CPU revision : 1"; + + #[test] + fn arm_cortex_a57() { + let cpuinfo = CpuInfo::from_str(ARM_CORTEX_A57).unwrap(); + assert_eq!( + cpuinfo.field("Processor"), + "Cortex A57 Processor rev 1 (aarch64)" + ); + assert_eq!( + cpuinfo.field("Features"), + "fp asimd aes pmull sha1 sha2 crc32 wp half thumb fastmult vfp edsp neon vfpv3 tlsi vfpv4 idiva idivt" + ); + assert!(cpuinfo.field("Features").has("pmull")); + assert!(cpuinfo.field("Features").has("neon")); + assert!(cpuinfo.field("Features").has("asimd")); + } + + const POWER8E_POWERKVM: &str = r"processor : 0 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +processor : 1 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +processor : 2 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +processor : 3 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +timebase : 512000000 +platform : pSeries +model : IBM pSeries (emulated by qemu) +machine : CHRP IBM pSeries (emulated by qemu)"; + + #[test] + fn power8_powerkvm() { + let cpuinfo = CpuInfo::from_str(POWER8E_POWERKVM).unwrap(); + assert_eq!(cpuinfo.field("cpu"), "POWER8E (raw), altivec supported"); + + assert!(cpuinfo.field("cpu").has("altivec")); + } + + const POWER5P: &str = r"processor : 0 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 1 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 2 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 3 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 4 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 5 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 6 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 7 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +timebase : 237331000 +platform : pSeries +machine : CHRP IBM,9133-55A"; + + #[test] + fn power5p() { + let cpuinfo = CpuInfo::from_str(POWER5P).unwrap(); + assert_eq!(cpuinfo.field("cpu"), "POWER5+ (gs)"); + + assert!(!cpuinfo.field("cpu").has("altivec")); + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/mips.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/mips.rs new file mode 100644 index 0000000000..46a47fb7b7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/mips.rs @@ -0,0 +1,31 @@ +//! Run-time feature detection for MIPS on Linux. + +use super::auxvec; +use crate::detect::{bit, cache, Feature}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Try to read the features from the auxiliary vector, and if that fails, try +/// to read them from `/proc/cpuinfo`. +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + let enable_feature = |value: &mut cache::Initializer, f, enable| { + if enable { + value.set(f as u32); + } + }; + + // The values are part of the platform-specific [asm/hwcap.h][hwcap] + // + // [hwcap]: https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/hwcap.h + if let Ok(auxv) = auxvec::auxv() { + enable_feature(&mut value, Feature::msa, bit::test(auxv.hwcap, 1)); + return value; + } + // TODO: fall back via `cpuinfo`. + value +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/mod.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/mod.rs new file mode 100644 index 0000000000..e02d5e6dcd --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/mod.rs @@ -0,0 +1,28 @@ +//! Run-time feature detection on Linux + +mod auxvec; + +#[cfg(feature = "std_detect_file_io")] +mod cpuinfo; + +cfg_if! { + if #[cfg(target_arch = "aarch64")] { + mod aarch64; + pub use self::aarch64::check_for; + } else if #[cfg(target_arch = "arm")] { + mod arm; + pub use self::arm::check_for; + } else if #[cfg(any(target_arch = "mips", target_arch = "mips64"))] { + mod mips; + pub use self::mips::check_for; + } else if #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] { + mod powerpc; + pub use self::powerpc::check_for; + } else { + use crate::detect::Feature; + /// Performs run-time feature detection. + pub fn check_for(_x: Feature) -> bool { + false + } + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/powerpc.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/powerpc.rs new file mode 100644 index 0000000000..dc19bc8eda --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/linux/powerpc.rs @@ -0,0 +1,41 @@ +//! Run-time feature detection for PowerPC on Linux. + +use super::{auxvec, cpuinfo}; +use crate::detect::{cache, Feature}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Try to read the features from the auxiliary vector, and if that fails, try +/// to read them from /proc/cpuinfo. +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + let enable_feature = |value: &mut cache::Initializer, f, enable| { + if enable { + value.set(f as u32); + } + }; + + // The values are part of the platform-specific [asm/cputable.h][cputable] + // + // [cputable]: https://github.com/torvalds/linux/blob/master/arch/powerpc/include/uapi/asm/cputable.h + if let Ok(auxv) = auxvec::auxv() { + // note: the PowerPC values are the mask to do the test (instead of the + // index of the bit to test like in ARM and Aarch64) + enable_feature(&mut value, Feature::altivec, auxv.hwcap & 0x10000000 != 0); + enable_feature(&mut value, Feature::vsx, auxv.hwcap & 0x00000080 != 0); + enable_feature(&mut value, Feature::power8, auxv.hwcap2 & 0x80000000 != 0); + return value; + } + + // PowerPC's /proc/cpuinfo lacks a proper Feature field, + // but `altivec` support is indicated in the `cpu` field. + if let Ok(c) = cpuinfo::CpuInfo::new() { + enable_feature(&mut value, Feature::altivec, c.field("cpu").has("altivec")); + return value; + } + value +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/other.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/other.rs new file mode 100644 index 0000000000..23e399ea79 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/other.rs @@ -0,0 +1,9 @@ +//! Other operating systems + +use crate::detect::Feature; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(_x: Feature) -> bool { + false +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/os/x86.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/os/x86.rs new file mode 100644 index 0000000000..2e228aa374 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/os/x86.rs @@ -0,0 +1,367 @@ +//! x86 run-time feature detection is OS independent. + +#[cfg(target_arch = "x86")] +use crate::arch::x86::*; +#[cfg(target_arch = "x86_64")] +use crate::arch::x86_64::*; + +use crate::mem; + +use crate::detect::{bit, cache, Feature}; + +/// Performs run-time feature detection. +#[inline] +pub fn check_for(x: Feature) -> bool { + cache::test(x as u32, detect_features) +} + +/// Run-time feature detection on x86 works by using the CPUID instruction. +/// +/// The [CPUID Wikipedia page][wiki_cpuid] contains +/// all the information about which flags to set to query which values, and in +/// which registers these are reported. +/// +/// The definitive references are: +/// - [Intel 64 and IA-32 Architectures Software Developer's Manual Volume 2: +/// Instruction Set Reference, A-Z][intel64_ref]. +/// - [AMD64 Architecture Programmer's Manual, Volume 3: General-Purpose and +/// System Instructions][amd64_ref]. +/// +/// [wiki_cpuid]: https://en.wikipedia.org/wiki/CPUID +/// [intel64_ref]: http://www.intel.de/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf +/// [amd64_ref]: http://support.amd.com/TechDocs/24594.pdf +#[allow(clippy::similar_names)] +fn detect_features() -> cache::Initializer { + let mut value = cache::Initializer::default(); + + // If the x86 CPU does not support the CPUID instruction then it is too + // old to support any of the currently-detectable features. + if !has_cpuid() { + return value; + } + + // Calling `__cpuid`/`__cpuid_count` from here on is safe because the CPU + // has `cpuid` support. + + // 0. EAX = 0: Basic Information: + // - EAX returns the "Highest Function Parameter", that is, the maximum + // leaf value for subsequent calls of `cpuinfo` in range [0, + // 0x8000_0000]. - The vendor ID is stored in 12 u8 ascii chars, + // returned in EBX, EDX, and ECX (in that order): + let (max_basic_leaf, vendor_id) = unsafe { + let CpuidResult { + eax: max_basic_leaf, + ebx, + ecx, + edx, + } = __cpuid(0); + let vendor_id: [[u8; 4]; 3] = [ + mem::transmute(ebx), + mem::transmute(edx), + mem::transmute(ecx), + ]; + let vendor_id: [u8; 12] = mem::transmute(vendor_id); + (max_basic_leaf, vendor_id) + }; + + if max_basic_leaf < 1 { + // Earlier Intel 486, CPUID not implemented + return value; + } + + // EAX = 1, ECX = 0: Queries "Processor Info and Feature Bits"; + // Contains information about most x86 features. + let CpuidResult { + ecx: proc_info_ecx, + edx: proc_info_edx, + .. + } = unsafe { __cpuid(0x0000_0001_u32) }; + + // EAX = 7, ECX = 0: Queries "Extended Features"; + // Contains information about bmi,bmi2, and avx2 support. + let (extended_features_ebx, extended_features_ecx) = if max_basic_leaf >= 7 { + let CpuidResult { ebx, ecx, .. } = unsafe { __cpuid(0x0000_0007_u32) }; + (ebx, ecx) + } else { + (0, 0) // CPUID does not support "Extended Features" + }; + + // EAX = 0x8000_0000, ECX = 0: Get Highest Extended Function Supported + // - EAX returns the max leaf value for extended information, that is, + // `cpuid` calls in range [0x8000_0000; u32::MAX]: + let CpuidResult { + eax: extended_max_basic_leaf, + .. + } = unsafe { __cpuid(0x8000_0000_u32) }; + + // EAX = 0x8000_0001, ECX=0: Queries "Extended Processor Info and Feature + // Bits" + let extended_proc_info_ecx = if extended_max_basic_leaf >= 1 { + let CpuidResult { ecx, .. } = unsafe { __cpuid(0x8000_0001_u32) }; + ecx + } else { + 0 + }; + + { + // borrows value till the end of this scope: + let mut enable = |r, rb, f| { + if bit::test(r as usize, rb) { + value.set(f as u32); + } + }; + + enable(proc_info_ecx, 0, Feature::sse3); + enable(proc_info_ecx, 1, Feature::pclmulqdq); + enable(proc_info_ecx, 9, Feature::ssse3); + enable(proc_info_ecx, 13, Feature::cmpxchg16b); + enable(proc_info_ecx, 19, Feature::sse4_1); + enable(proc_info_ecx, 20, Feature::sse4_2); + enable(proc_info_ecx, 23, Feature::popcnt); + enable(proc_info_ecx, 25, Feature::aes); + enable(proc_info_ecx, 29, Feature::f16c); + enable(proc_info_ecx, 30, Feature::rdrand); + enable(extended_features_ebx, 18, Feature::rdseed); + enable(extended_features_ebx, 19, Feature::adx); + enable(extended_features_ebx, 11, Feature::rtm); + enable(proc_info_edx, 4, Feature::tsc); + enable(proc_info_edx, 23, Feature::mmx); + enable(proc_info_edx, 24, Feature::fxsr); + enable(proc_info_edx, 25, Feature::sse); + enable(proc_info_edx, 26, Feature::sse2); + enable(extended_features_ebx, 29, Feature::sha); + + enable(extended_features_ebx, 3, Feature::bmi); + enable(extended_features_ebx, 8, Feature::bmi2); + + // `XSAVE` and `AVX` support: + let cpu_xsave = bit::test(proc_info_ecx as usize, 26); + if cpu_xsave { + // 0. Here the CPU supports `XSAVE`. + + // 1. Detect `OSXSAVE`, that is, whether the OS is AVX enabled and + // supports saving the state of the AVX/AVX2 vector registers on + // context-switches, see: + // + // - [intel: is avx enabled?][is_avx_enabled], + // - [mozilla: sse.cpp][mozilla_sse_cpp]. + // + // [is_avx_enabled]: https://software.intel.com/en-us/blogs/2011/04/14/is-avx-enabled + // [mozilla_sse_cpp]: https://hg.mozilla.org/mozilla-central/file/64bab5cbb9b6/mozglue/build/SSE.cpp#l190 + let cpu_osxsave = bit::test(proc_info_ecx as usize, 27); + + if cpu_osxsave { + // 2. The OS must have signaled the CPU that it supports saving and + // restoring the: + // + // * SSE -> `XCR0.SSE[1]` + // * AVX -> `XCR0.AVX[2]` + // * AVX-512 -> `XCR0.AVX-512[7:5]`. + // + // by setting the corresponding bits of `XCR0` to `1`. + // + // This is safe because the CPU supports `xsave` + // and the OS has set `osxsave`. + let xcr0 = unsafe { _xgetbv(0) }; + // Test `XCR0.SSE[1]` and `XCR0.AVX[2]` with the mask `0b110 == 6`: + let os_avx_support = xcr0 & 6 == 6; + // Test `XCR0.AVX-512[7:5]` with the mask `0b1110_0000 == 224`: + let os_avx512_support = xcr0 & 224 == 224; + + // Only if the OS and the CPU support saving/restoring the AVX + // registers we enable `xsave` support: + if os_avx_support { + // See "13.3 ENABLING THE XSAVE FEATURE SET AND XSAVE-ENABLED + // FEATURES" in the "Intel® 64 and IA-32 Architectures Software + // Developer’s Manual, Volume 1: Basic Architecture": + // + // "Software enables the XSAVE feature set by setting + // CR4.OSXSAVE[bit 18] to 1 (e.g., with the MOV to CR4 + // instruction). If this bit is 0, execution of any of XGETBV, + // XRSTOR, XRSTORS, XSAVE, XSAVEC, XSAVEOPT, XSAVES, and XSETBV + // causes an invalid-opcode exception (#UD)" + // + enable(proc_info_ecx, 26, Feature::xsave); + + // For `xsaveopt`, `xsavec`, and `xsaves` we need to query: + // Processor Extended State Enumeration Sub-leaf (EAX = 0DH, + // ECX = 1): + if max_basic_leaf >= 0xd { + let CpuidResult { + eax: proc_extended_state1_eax, + .. + } = unsafe { __cpuid_count(0xd_u32, 1) }; + enable(proc_extended_state1_eax, 0, Feature::xsaveopt); + enable(proc_extended_state1_eax, 1, Feature::xsavec); + enable(proc_extended_state1_eax, 3, Feature::xsaves); + } + + // FMA (uses 256-bit wide registers): + enable(proc_info_ecx, 12, Feature::fma); + + // And AVX/AVX2: + enable(proc_info_ecx, 28, Feature::avx); + enable(extended_features_ebx, 5, Feature::avx2); + + // For AVX-512 the OS also needs to support saving/restoring + // the extended state, only then we enable AVX-512 support: + if os_avx512_support { + enable(extended_features_ebx, 16, Feature::avx512f); + enable(extended_features_ebx, 17, Feature::avx512dq); + enable(extended_features_ebx, 21, Feature::avx512_ifma); + enable(extended_features_ebx, 26, Feature::avx512pf); + enable(extended_features_ebx, 27, Feature::avx512er); + enable(extended_features_ebx, 28, Feature::avx512cd); + enable(extended_features_ebx, 30, Feature::avx512bw); + enable(extended_features_ebx, 31, Feature::avx512vl); + enable(extended_features_ecx, 1, Feature::avx512_vbmi); + enable(extended_features_ecx, 14, Feature::avx512_vpopcntdq); + } + } + } + } + + // This detects ABM on AMD CPUs and LZCNT on Intel CPUs. + // On intel CPUs with popcnt, lzcnt implements the + // "missing part" of ABM, so we map both to the same + // internal feature. + // + // The `is_x86_feature_detected!("lzcnt")` macro then + // internally maps to Feature::abm. + enable(extended_proc_info_ecx, 5, Feature::abm); + // As Hygon Dhyana originates from AMD technology and shares most of the architecture with + // AMD's family 17h, but with different CPU Vendor ID("HygonGenuine")/Family series + // number(Family 18h). + // + // For CPUID feature bits, Hygon Dhyana(family 18h) share the same definition with AMD + // family 17h. + // + // Related AMD CPUID specification is https://www.amd.com/system/files/TechDocs/25481.pdf. + // Related Hygon kernel patch can be found on + // http://lkml.kernel.org/r/5ce86123a7b9dad925ac583d88d2f921040e859b.1538583282.git.puwen@hygon.cn + if vendor_id == *b"AuthenticAMD" || vendor_id == *b"HygonGenuine" { + // These features are available on AMD arch CPUs: + enable(extended_proc_info_ecx, 6, Feature::sse4a); + enable(extended_proc_info_ecx, 21, Feature::tbm); + } + } + + value +} + +#[cfg(test)] +mod tests { + extern crate cupid; + + #[test] + fn dump() { + println!("aes: {:?}", is_x86_feature_detected!("aes")); + println!("pclmulqdq: {:?}", is_x86_feature_detected!("pclmulqdq")); + println!("rdrand: {:?}", is_x86_feature_detected!("rdrand")); + println!("rdseed: {:?}", is_x86_feature_detected!("rdseed")); + println!("tsc: {:?}", is_x86_feature_detected!("tsc")); + println!("sse: {:?}", is_x86_feature_detected!("sse")); + println!("sse2: {:?}", is_x86_feature_detected!("sse2")); + println!("sse3: {:?}", is_x86_feature_detected!("sse3")); + println!("ssse3: {:?}", is_x86_feature_detected!("ssse3")); + println!("sse4.1: {:?}", is_x86_feature_detected!("sse4.1")); + println!("sse4.2: {:?}", is_x86_feature_detected!("sse4.2")); + println!("sse4a: {:?}", is_x86_feature_detected!("sse4a")); + println!("sha: {:?}", is_x86_feature_detected!("sha")); + println!("avx: {:?}", is_x86_feature_detected!("avx")); + println!("avx2: {:?}", is_x86_feature_detected!("avx2")); + println!("avx512f {:?}", is_x86_feature_detected!("avx512f")); + println!("avx512cd {:?}", is_x86_feature_detected!("avx512cd")); + println!("avx512er {:?}", is_x86_feature_detected!("avx512er")); + println!("avx512pf {:?}", is_x86_feature_detected!("avx512pf")); + println!("avx512bw {:?}", is_x86_feature_detected!("avx512bw")); + println!("avx512dq {:?}", is_x86_feature_detected!("avx512dq")); + println!("avx512vl {:?}", is_x86_feature_detected!("avx512vl")); + println!("avx512_ifma {:?}", is_x86_feature_detected!("avx512ifma")); + println!("avx512_vbmi {:?}", is_x86_feature_detected!("avx512vbmi")); + println!( + "avx512_vpopcntdq {:?}", + is_x86_feature_detected!("avx512vpopcntdq") + ); + println!("fma: {:?}", is_x86_feature_detected!("fma")); + println!("abm: {:?}", is_x86_feature_detected!("abm")); + println!("bmi: {:?}", is_x86_feature_detected!("bmi1")); + println!("bmi2: {:?}", is_x86_feature_detected!("bmi2")); + println!("tbm: {:?}", is_x86_feature_detected!("tbm")); + println!("popcnt: {:?}", is_x86_feature_detected!("popcnt")); + println!("lzcnt: {:?}", is_x86_feature_detected!("lzcnt")); + println!("fxsr: {:?}", is_x86_feature_detected!("fxsr")); + println!("xsave: {:?}", is_x86_feature_detected!("xsave")); + println!("xsaveopt: {:?}", is_x86_feature_detected!("xsaveopt")); + println!("xsaves: {:?}", is_x86_feature_detected!("xsaves")); + println!("xsavec: {:?}", is_x86_feature_detected!("xsavec")); + println!("cmpxchg16b: {:?}", is_x86_feature_detected!("cmpxchg16b")); + println!("adx: {:?}", is_x86_feature_detected!("adx")); + println!("rtm: {:?}", is_x86_feature_detected!("rtm")); + } + + #[test] + fn compare_with_cupid() { + let information = cupid::master().unwrap(); + assert_eq!(is_x86_feature_detected!("aes"), information.aesni()); + assert_eq!( + is_x86_feature_detected!("pclmulqdq"), + information.pclmulqdq() + ); + assert_eq!(is_x86_feature_detected!("rdrand"), information.rdrand()); + assert_eq!(is_x86_feature_detected!("rdseed"), information.rdseed()); + assert_eq!(is_x86_feature_detected!("tsc"), information.tsc()); + assert_eq!(is_x86_feature_detected!("sse"), information.sse()); + assert_eq!(is_x86_feature_detected!("sse2"), information.sse2()); + assert_eq!(is_x86_feature_detected!("sse3"), information.sse3()); + assert_eq!(is_x86_feature_detected!("ssse3"), information.ssse3()); + assert_eq!(is_x86_feature_detected!("sse4.1"), information.sse4_1()); + assert_eq!(is_x86_feature_detected!("sse4.2"), information.sse4_2()); + assert_eq!(is_x86_feature_detected!("sse4a"), information.sse4a()); + assert_eq!(is_x86_feature_detected!("sha"), information.sha()); + assert_eq!(is_x86_feature_detected!("avx"), information.avx()); + assert_eq!(is_x86_feature_detected!("avx2"), information.avx2()); + assert_eq!(is_x86_feature_detected!("avx512f"), information.avx512f()); + assert_eq!(is_x86_feature_detected!("avx512cd"), information.avx512cd()); + assert_eq!(is_x86_feature_detected!("avx512er"), information.avx512er()); + assert_eq!(is_x86_feature_detected!("avx512pf"), information.avx512pf()); + assert_eq!(is_x86_feature_detected!("avx512bw"), information.avx512bw()); + assert_eq!(is_x86_feature_detected!("avx512dq"), information.avx512dq()); + assert_eq!(is_x86_feature_detected!("avx512vl"), information.avx512vl()); + assert_eq!( + is_x86_feature_detected!("avx512ifma"), + information.avx512_ifma() + ); + assert_eq!( + is_x86_feature_detected!("avx512vbmi"), + information.avx512_vbmi() + ); + assert_eq!( + is_x86_feature_detected!("avx512vpopcntdq"), + information.avx512_vpopcntdq() + ); + assert_eq!(is_x86_feature_detected!("fma"), information.fma()); + assert_eq!(is_x86_feature_detected!("bmi1"), information.bmi1()); + assert_eq!(is_x86_feature_detected!("bmi2"), information.bmi2()); + assert_eq!(is_x86_feature_detected!("popcnt"), information.popcnt()); + assert_eq!(is_x86_feature_detected!("abm"), information.lzcnt()); + assert_eq!(is_x86_feature_detected!("tbm"), information.tbm()); + assert_eq!(is_x86_feature_detected!("lzcnt"), information.lzcnt()); + assert_eq!(is_x86_feature_detected!("xsave"), information.xsave()); + assert_eq!(is_x86_feature_detected!("xsaveopt"), information.xsaveopt()); + assert_eq!( + is_x86_feature_detected!("xsavec"), + information.xsavec_and_xrstor() + ); + assert_eq!( + is_x86_feature_detected!("xsaves"), + information.xsaves_xrstors_and_ia32_xss() + ); + assert_eq!( + is_x86_feature_detected!("cmpxchg16b"), + information.cmpxchg16b(), + ); + assert_eq!(is_x86_feature_detected!("adx"), information.adx(),); + assert_eq!(is_x86_feature_detected!("rtm"), information.rtm(),); + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_if/lib.rs b/src/tools/rustfmt/tests/target/cfg_if/lib.rs new file mode 100644 index 0000000000..8b3bb304f1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/lib.rs @@ -0,0 +1,49 @@ +//! Run-time feature detection for the Rust standard library. +//! +//! To detect whether a feature is enabled in the system running the binary +//! use one of the appropriate macro for the target: +//! +//! * `x86` and `x86_64`: [`is_x86_feature_detected`] +//! * `arm`: [`is_arm_feature_detected`] +//! * `aarch64`: [`is_aarch64_feature_detected`] +//! * `mips`: [`is_mips_feature_detected`] +//! * `mips64`: [`is_mips64_feature_detected`] +//! * `powerpc`: [`is_powerpc_feature_detected`] +//! * `powerpc64`: [`is_powerpc64_feature_detected`] + +#![unstable(feature = "stdsimd", issue = "27731")] +#![feature(const_fn, staged_api, stdsimd, doc_cfg, allow_internal_unstable)] +#![allow(clippy::shadow_reuse)] +#![deny(clippy::missing_inline_in_public_items)] +#![cfg_attr(target_os = "linux", feature(linkage))] +#![cfg_attr(all(target_os = "freebsd", target_arch = "aarch64"), feature(asm))] +#![cfg_attr(stdsimd_strict, deny(warnings))] +#![cfg_attr(test, allow(unused_imports))] +#![no_std] + +#[macro_use] +extern crate cfg_if; + +cfg_if! { + if #[cfg(feature = "std_detect_file_io")] { + #[cfg_attr(test, macro_use(println))] + extern crate std; + + #[allow(unused_imports)] + use std::{arch, fs, io, mem, sync}; + } else { + #[cfg(test)] + #[macro_use(println)] + extern crate std; + + #[allow(unused_imports)] + use core::{arch, mem, sync}; + } +} + +#[cfg(feature = "std_detect_dlsym_getauxval")] +extern crate libc; + +#[doc(hidden)] +#[unstable(feature = "stdsimd", issue = "27731")] +pub mod detect; diff --git a/src/tools/rustfmt/tests/target/cfg_if/mod.rs b/src/tools/rustfmt/tests/target/cfg_if/mod.rs new file mode 100644 index 0000000000..b630e7ff38 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_if/mod.rs @@ -0,0 +1,5 @@ +//! `std_detect` + +#[doc(hidden)] // unstable implementation detail +#[unstable(feature = "stdsimd", issue = "27731")] +pub mod detect; diff --git a/src/tools/rustfmt/tests/target/cfg_mod/bar.rs b/src/tools/rustfmt/tests/target/cfg_mod/bar.rs new file mode 100644 index 0000000000..20dc5b4a08 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_mod/bar.rs @@ -0,0 +1,3 @@ +fn bar() -> &str { + "bar" +} diff --git a/src/tools/rustfmt/tests/target/cfg_mod/dir/dir1/dir2/wasm32.rs b/src/tools/rustfmt/tests/target/cfg_mod/dir/dir1/dir2/wasm32.rs new file mode 100644 index 0000000000..ac437e4225 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_mod/dir/dir1/dir2/wasm32.rs @@ -0,0 +1,3 @@ +fn wasm32() -> &str { + "wasm32" +} diff --git a/src/tools/rustfmt/tests/target/cfg_mod/dir/dir1/dir3/wasm32.rs b/src/tools/rustfmt/tests/target/cfg_mod/dir/dir1/dir3/wasm32.rs new file mode 100644 index 0000000000..ac437e4225 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_mod/dir/dir1/dir3/wasm32.rs @@ -0,0 +1,3 @@ +fn wasm32() -> &str { + "wasm32" +} diff --git a/src/tools/rustfmt/tests/target/cfg_mod/foo.rs b/src/tools/rustfmt/tests/target/cfg_mod/foo.rs new file mode 100644 index 0000000000..053c8e6f3e --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_mod/foo.rs @@ -0,0 +1,3 @@ +fn foo() -> &str { + "foo" +} diff --git a/src/tools/rustfmt/tests/target/cfg_mod/mod.rs b/src/tools/rustfmt/tests/target/cfg_mod/mod.rs new file mode 100644 index 0000000000..45ba86f11b --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_mod/mod.rs @@ -0,0 +1,10 @@ +#[cfg_attr(feature = "foo", path = "foo.rs")] +#[cfg_attr(not(feture = "foo"), path = "bar.rs")] +mod sub_mod; + +#[cfg_attr(target_arch = "wasm32", path = "dir/dir1/dir2/wasm32.rs")] +#[cfg_attr(not(target_arch = "wasm32"), path = "dir/dir1/dir3/wasm32.rs")] +mod wasm32; + +#[some_attr(path = "somewhere.rs")] +mod other; diff --git a/src/tools/rustfmt/tests/target/cfg_mod/other.rs b/src/tools/rustfmt/tests/target/cfg_mod/other.rs new file mode 100644 index 0000000000..5929b8dcf5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_mod/other.rs @@ -0,0 +1,3 @@ +fn other() -> &str { + "other" +} diff --git a/src/tools/rustfmt/tests/target/cfg_mod/wasm32.rs b/src/tools/rustfmt/tests/target/cfg_mod/wasm32.rs new file mode 100644 index 0000000000..ac437e4225 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_mod/wasm32.rs @@ -0,0 +1,3 @@ +fn wasm32() -> &str { + "wasm32" +} diff --git a/src/tools/rustfmt/tests/target/chains-visual.rs b/src/tools/rustfmt/tests/target/chains-visual.rs new file mode 100644 index 0000000000..76ef99a4b5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/chains-visual.rs @@ -0,0 +1,158 @@ +// rustfmt-indent_style: Visual +// Test chain formatting. + +fn main() { + // Don't put chains on a single line if it wasn't so in source. + let a = b.c.d.1.foo(|x| x + 1); + + bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc + .ddddddddddddddddddddddddddd(); + + bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc + .ddddddddddddddddddddddddddd + .eeeeeeee(); + + // Test case where first chain element isn't a path, but is shorter than + // the size of a tab. + x().y(|| match cond() { + true => (), + false => (), + }); + + loong_func().quux(move || if true { 1 } else { 2 }); + + some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }) + .method_call_b(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + fffffffffffffffffffffffffffffffffff(a, { + SCRIPT_TASK_ROOT.with(|root| { + *root.borrow_mut() = Some(&script_task); + }); + }); + + let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = + xxxxxxx.map(|x| x + 5) + .map(|x| x / 2) + .fold(0, |acc, x| acc + x); + + aaaaaaaaaaaaaaaa.map(|x| { + x += 1; + x + }) + .filter(some_mod::some_filter) +} + +fn floaters() { + let z = Foo { field1: val1, + field2: val2 }; + + let x = Foo { field1: val1, + field2: val2 }.method_call() + .method_call(); + + let y = if cond { val1 } else { val2 }.method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }].clone()); + } + } + } + + if cond { + some(); + } else { + none(); + }.bar() + .baz(); + + Foo { x: val }.baz(|| { + force(); + multiline(); + }) + .quux(); + + Foo { y: i_am_multi_line, + z: ok }.baz(|| { + force(); + multiline(); + }) + .quux(); + + a + match x { + true => "yay!", + false => "boo!", + }.bar() +} + +fn is_replaced_content() -> bool { + constellat.send(ConstellationMsg::ViewportConstrained(self.id, constraints)) + .unwrap(); +} + +fn issue587() { + a.b::<()>(c); + + std::mem::transmute(dl.symbol::<()>("init").unwrap()) +} + +fn issue_1389() { + let names = String::from_utf8(names)?.split('|') + .map(str::to_owned) + .collect(); +} + +fn issue1217() -> Result { + let random_chars: String = OsRng::new()?.gen_ascii_chars() + .take(self.bit_length) + .collect(); + + Ok(Mnemonic::new(&random_chars)) +} + +fn issue1236(options: Vec) -> Result> { + let process = Command::new("dmenu").stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .chain_err(|| "failed to spawn dmenu")?; +} + +fn issue1434() { + for _ in 0..100 { + let prototype_id = + PrototypeIdData::from_reader::<_, B>(&mut self.file_cursor).chain_err(|| { + format!("could not read prototype ID at offset {:#010x}", + current_offset) + })?; + } +} + +fn issue2264() { + { + something.function() + .map(|| { + if let a_very_very_very_very_very_very_very_very_long_variable = + compute_this_variable() + { + println!("Hello"); + } + }) + .collect(); + } +} diff --git a/src/tools/rustfmt/tests/target/chains.rs b/src/tools/rustfmt/tests/target/chains.rs new file mode 100644 index 0000000000..292da29819 --- /dev/null +++ b/src/tools/rustfmt/tests/target/chains.rs @@ -0,0 +1,306 @@ +// rustfmt-use_small_heuristics: Off +// Test chain formatting. + +fn main() { + let a = b.c.d.1.foo(|x| x + 1); + + bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd(); + + bbbbbbbbbbbbbbbbbbb + .ccccccccccccccccccccccccccccccccccccc + .ddddddddddddddddddddddddddd + .eeeeeeee(); + + let f = fooooooooooooooooooooooooooooooooooooooooooooooooooo + .baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar; + + // Test case where first chain element isn't a path, but is shorter than + // the size of a tab. + x().y(|| match cond() { + true => (), + false => (), + }); + + loong_func().quux(move || { + if true { + 1 + } else { + 2 + } + }); + + some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + some_fuuuuuuuuunction() + .method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }) + .method_call_b(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + fffffffffffffffffffffffffffffffffff(a, { + SCRIPT_TASK_ROOT.with(|root| { + *root.borrow_mut() = Some(&script_task); + }); + }); + + let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = + xxxxxxx.map(|x| x + 5).map(|x| x / 2).fold(0, |acc, x| acc + x); + + body.fold(Body::new(), |mut body, chunk| { + body.extend(chunk); + Ok(body) + }) + .and_then(move |body| { + let req = Request::from_parts(parts, body); + f(req).map_err(|_| io::Error::new(io::ErrorKind::Other, "")) + }); + + aaaaaaaaaaaaaaaa + .map(|x| { + x += 1; + x + }) + .filter(some_mod::some_filter) +} + +fn floaters() { + let z = Foo { + field1: val1, + field2: val2, + }; + + let x = Foo { + field1: val1, + field2: val2, + } + .method_call() + .method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push( + mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone(), + ); + } + } + } + + if cond { + some(); + } else { + none(); + } + .bar() + .baz(); + + Foo { + x: val, + } + .baz(|| { + force(); + multiline(); + }) + .quux(); + + Foo { + y: i_am_multi_line, + z: ok, + } + .baz(|| { + force(); + multiline(); + }) + .quux(); + + a + match x { + true => "yay!", + false => "boo!", + } + .bar() +} + +fn is_replaced_content() -> bool { + constellat.send(ConstellationMsg::ViewportConstrained(self.id, constraints)).unwrap(); +} + +fn issue587() { + a.b::<()>(c); + + std::mem::transmute(dl.symbol::<()>("init").unwrap()) +} + +fn try_shorthand() { + let x = expr?; + let y = expr.kaas()?.test(); + let loooooooooooooooooooooooooooooooooooooooooong = + does_this?.look?.good?.should_we_break?.after_the_first_question_mark?; + let yyyy = expr?.another?.another?.another?.another?.another?.another?.another?.another?.test(); + let zzzz = expr?.another?.another?.another?.another?; + let aaa = x??????????????????????????????????????????????????????????????????????????; + + let y = a + .very + .loooooooooooooooooooooooooooooooooooooong() + .chain() + .inside() + .weeeeeeeeeeeeeee()? + .test() + .0 + .x; + + parameterized(f, substs, def_id, Ns::Value, &[], |tcx| tcx.lookup_item_type(def_id).generics)?; + fooooooooooooooooooooooooooo()? + .bar()? + .baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz()?; +} + +fn issue_1004() { + match *self { + ty::ImplOrTraitItem::MethodTraitItem(ref i) => write!(f, "{:?}", i), + ty::ImplOrTraitItem::ConstTraitItem(ref i) => write!(f, "{:?}", i), + ty::ImplOrTraitItem::TypeTraitItem(ref i) => write!(f, "{:?}", i), + }?; + + ty::tls::with(|tcx| { + let tap = ty::Binder(TraitAndProjections(principal, projections)); + in_binder(f, tcx, &ty::Binder(""), Some(tap)) + })?; +} + +fn issue1392() { + test_method( + r#" + if foo { + a(); + } + else { + b(); + } + "# + .trim(), + ); +} + +// #2067 +impl Settings { + fn save(&self) -> Result<()> { + let mut file = File::create(&settings_path) + .chain_err(|| ErrorKind::WriteError(settings_path.clone()))?; + } +} + +fn issue2126() { + { + { + { + { + { + let x = self + .span_from(sub_span.expect("No span found for struct arant variant")); + self.sspanpan_from_span( + sub_span.expect("No span found for struct variant"), + ); + let x = self.spanpan_from_span( + sub_span.expect("No span found for struct variant"), + )?; + } + } + } + } + } +} + +// #2200 +impl Foo { + pub fn from_ast(diagnostic: &::errors::Handler, attrs: &[ast::Attribute]) -> Attributes { + let other_attrs = attrs + .iter() + .filter_map(|attr| { + attr.with_desugared_doc(|attr| { + if attr.check_name("doc") { + if let Some(mi) = attr.meta() { + if let Some(value) = mi.value_str() { + doc_strings.push(DocFragment::Include( + line, attr.span, filename, contents, + )); + } + } + } + }) + }) + .collect(); + } +} + +// #2415 +// Avoid orphan in chain +fn issue2415() { + let base_url = (|| { + // stuff + + Ok((|| { + // stuff + Some(value.to_string()) + })() + .ok_or("")?) + })() + .unwrap_or_else(|_: Box<::std::error::Error>| String::from("")); +} + +impl issue_2786 { + fn thing(&self) { + foo(|a| { + println!("a"); + println!("b"); + }) + .bar(|c| { + println!("a"); + println!("b"); + }) + .baz(|c| { + println!("a"); + println!("b"); + }) + } +} + +fn issue_2773() { + let bar = Some(0); + bar.or_else(|| { + // do stuff + None + }) + .or_else(|| { + // do other stuff + None + }) + .and_then(|val| { + // do this stuff + None + }); +} + +fn issue_3034() { + disallowed_headers.iter().any(|header| *header == name) + || disallowed_header_prefixes.iter().any(|prefix| name.starts_with(prefix)) +} diff --git a/src/tools/rustfmt/tests/target/chains_with_comment.rs b/src/tools/rustfmt/tests/target/chains_with_comment.rs new file mode 100644 index 0000000000..522d70713b --- /dev/null +++ b/src/tools/rustfmt/tests/target/chains_with_comment.rs @@ -0,0 +1,137 @@ +// Chains with comment. + +fn main() { + let x = y // comment + .z; + + foo // foo + // comment after parent + .x + .y + // comment 1 + .bar() // comment after bar() + // comment 2 + .foobar + // comment after + // comment 3 + .baz(x, y, z); + + self.rev_dep_graph + .iter() + // Remove nodes that are not dirty + .filter(|&(unit, _)| dirties.contains(&unit)) + // Retain only dirty dependencies of the ones that are dirty + .map(|(k, deps)| { + ( + k.clone(), + deps.iter() + .cloned() + .filter(|d| dirties.contains(&d)) + .collect(), + ) + }); + + let y = expr /* comment */ + .kaas()? + // comment + .test(); + let loooooooooooooooooooooooooooooooooooooooooong = does_this? + .look? + .good? + .should_we_break? + .after_the_first_question_mark?; + let zzzz = expr? // comment after parent + // comment 0 + .another??? // comment 1 + .another???? // comment 2 + .another? // comment 3 + .another?; + + let y = a + .very + .loooooooooooooooooooooooooooooooooooooong() /* comment */ + .chain() + .inside() /* comment */ + .weeeeeeeeeeeeeee()? + .test() + .0 + .x; + + parameterized(f, substs, def_id, Ns::Value, &[], |tcx| { + tcx.lookup_item_type(def_id).generics + })?; + fooooooooooooooooooooooooooo()? + .bar()? + .baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz()?; + + // #2559 + App::new("cargo-cache") + .version(crate_version!()) + .bin_name("cargo") + .about("Manage cargo cache") + .author("matthiaskrgr") + .subcommand( + SubCommand::with_name("cache") + .version(crate_version!()) + .bin_name("cargo-cache") + .about("Manage cargo cache") + .author("matthiaskrgr") + .arg(&list_dirs) + .arg(&remove_dir) + .arg(&gc_repos) + .arg(&info) + .arg(&keep_duplicate_crates) + .arg(&dry_run) + .arg(&auto_clean) + .arg(&auto_clean_expensive), + ) // subcommand + .arg(&list_dirs); +} + +// #2177 +impl Foo { + fn dirty_rev_dep_graph( + &self, + dirties: &HashSet, + ) -> HashMap> { + let dirties = self.transitive_dirty_units(dirties); + trace!("transitive_dirty_units: {:?}", dirties); + + self.rev_dep_graph + .iter() + // Remove nodes that are not dirty + .filter(|&(unit, _)| dirties.contains(&unit)) + // Retain only dirty dependencies of the ones that are dirty + .map(|(k, deps)| { + ( + k.clone(), + deps.iter() + .cloned() + .filter(|d| dirties.contains(&d)) + .collect(), + ) + }) + } +} + +// #2907 +fn foo() { + let x = foo + .bar??? // comment + .baz; + let x = foo + .bar??? + // comment + .baz; + let x = foo + .bar??? // comment + // comment + .baz; + let x = foo + .bar??????????????? // comment + // comment + // comment + // comment + // comment + .baz; +} diff --git a/src/tools/rustfmt/tests/target/closure-block-inside-macro.rs b/src/tools/rustfmt/tests/target/closure-block-inside-macro.rs new file mode 100644 index 0000000000..b3ddfb5126 --- /dev/null +++ b/src/tools/rustfmt/tests/target/closure-block-inside-macro.rs @@ -0,0 +1,9 @@ +// #1547 +fuzz_target!(|data: &[u8]| if let Some(first) = data.first() { + let index = *first as usize; + if index >= ENCODINGS.len() { + return; + } + let encoding = ENCODINGS[index]; + dispatch_test(encoding, &data[1..]); +}); diff --git a/src/tools/rustfmt/tests/target/closure.rs b/src/tools/rustfmt/tests/target/closure.rs new file mode 100644 index 0000000000..f3107d19c2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/closure.rs @@ -0,0 +1,250 @@ +// rustfmt-normalize_comments: true +// Closures + +fn main() { + let square = (|i: i32| i * i); + + let commented = |// first + a, // argument + // second + b: WithType, // argument + // ignored + _| { + ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + ) + }; + + let block_body = move |xxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + ref yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy| { + xxxxxxxxxxxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + }; + + let loooooooooooooong_name = |field| { + // format comments. + if field.node.attrs.len() > 0 { + field.node.attrs[0].span.lo() + } else { + field.span.lo() + } + }; + + let unblock_me = |trivial| closure(); + + let empty = |arg| {}; + + let simple = |arg| { + // comment formatting + foo(arg) + }; + + let test = || { + do_something(); + do_something_else(); + }; + + let arg_test = + |big_argument_name, test123| looooooooooooooooooong_function_naaaaaaaaaaaaaaaaame(); + + let arg_test = + |big_argument_name, test123| looooooooooooooooooong_function_naaaaaaaaaaaaaaaaame(); + + let simple_closure = move || -> () {}; + + let closure = |input: Ty| -> Option { foo() }; + + let closure_with_return_type = + |aaaaaaaaaaaaaaaaaaaaaaarg1, aaaaaaaaaaaaaaaaaaaaaaarg2| -> Strong { "sup".to_owned() }; + + |arg1, arg2, _, _, arg3, arg4| { + let temp = arg4 + arg3; + arg2 * arg1 - temp + }; + + let block_body_with_comment = args.iter().map(|a| { + // Emitting only dep-info is possible only for final crate type, as + // as others may emit required metadata for dependent crate types + if a.starts_with("--emit") && is_final_crate_type && !self.workspace_mode { + "--emit=dep-info" + } else { + a + } + }); +} + +fn issue311() { + let func = |x| println!("{}", x); + + (func)(0.0); +} + +fn issue863() { + let closure = |x| match x { + 0 => true, + _ => false, + } == true; +} + +fn issue934() { + let hash: &Fn(&&Block) -> u64 = &|block| -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_block(block); + h.finish() + }; + + let hash: &Fn(&&Block) -> u64 = &|block| -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_block(block); + h.finish(); + }; +} + +impl<'a, 'tcx: 'a> SpanlessEq<'a, 'tcx> { + pub fn eq_expr(&self, left: &Expr, right: &Expr) -> bool { + match (&left.node, &right.node) { + (&ExprBinary(l_op, ref ll, ref lr), &ExprBinary(r_op, ref rl, ref rr)) => { + l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr) + || swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| { + l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr) + }) + } + } + } +} + +fn foo() { + lifetimes_iter___map(|lasdfasfd| { + let hi = if l.bounds.is_empty() { + l.lifetime.span.hi() + }; + }); +} + +fn issue1405() { + open_raw_fd(fd, b'r').and_then(|file| { + Capture::new_raw(None, |_, err| unsafe { raw::pcap_fopen_offline(file, err) }) + }); +} + +fn issue1466() { + let vertex_buffer = frame.scope(|ctx| { + let buffer = ctx.create_host_visible_buffer::>(&vertices); + ctx.create_device_local_buffer(buffer) + }); +} + +fn issue470() { + { + { + { + let explicit_arg_decls = + explicit_arguments + .into_iter() + .enumerate() + .map(|(index, (ty, pattern))| { + let lvalue = Lvalue::Arg(index as u32); + block = this.pattern( + block, + argument_extent, + hair::PatternRef::Hair(pattern), + &lvalue, + ); + ArgDecl { ty: ty } + }); + } + } + } +} + +// #1509 +impl Foo { + pub fn bar(&self) { + Some(SomeType { + push_closure_out_to_100_chars: iter(otherwise_it_works_ok.into_iter().map(|f| Ok(f))), + }) + } +} + +fn issue1329() { + aaaaaaaaaaaaaaaa + .map(|x| { + x += 1; + x + }) + .filter +} + +fn issue325() { + let f = + || unsafe { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }; +} + +fn issue1697() { + Test.func_a( + A_VERY_LONG_CONST_VARIABLE_NAME, + move |arg1, arg2, arg3, arg4| arg1 + arg2 + arg3 + arg4, + ) +} + +fn issue1694() { + foooooo( + |_referencefffffffff: _, _target_reference: _, _oid: _, _target_oid: _| { + format!("refs/pull/{}/merge", pr_id) + }, + ) +} + +fn issue1713() { + rayon::join( + || recurse(left, is_less, pred, limit), + || recurse(right, is_less, Some(pivot), limit), + ); + + rayon::join( + 1, + || recurse(left, is_less, pred, limit), + 2, + || recurse(right, is_less, Some(pivot), limit), + ); +} + +fn issue2063() { + |ctx: Ctx<(String, String)>| -> io::Result { + Ok(Response::new().with_body(ctx.params.0)) + } +} + +fn issue1524() { + let f = |x| x; + let f = |x| x; + let f = |x| x; + let f = |x| x; + let f = |x| x; +} + +fn issue2171() { + foo(|| unsafe { + if PERIPHERALS { + loop {} + } else { + PERIPHERALS = true; + } + }) +} + +fn issue2207() { + a.map(|_| { + unsafe { a_very_very_very_very_very_very_very_long_function_name_or_anything_else() } + .to_string() + }) +} + +fn issue2262() { + result + .init(&mut result.slave.borrow_mut(), &mut (result.strategy)()) + .map_err(|factory| Error { + factory, + slave: None, + })?; +} diff --git a/src/tools/rustfmt/tests/target/comment-inside-const.rs b/src/tools/rustfmt/tests/target/comment-inside-const.rs new file mode 100644 index 0000000000..f847f2c69d --- /dev/null +++ b/src/tools/rustfmt/tests/target/comment-inside-const.rs @@ -0,0 +1,9 @@ +fn issue982() { + const SOME_CONSTANT: u32 = + // Explanation why SOME_CONSTANT needs FLAG_A to be set. + FLAG_A | + // Explanation why SOME_CONSTANT needs FLAG_B to be set. + FLAG_B | + // Explanation why SOME_CONSTANT needs FLAG_C to be set. + FLAG_C; +} diff --git a/src/tools/rustfmt/tests/target/comment-not-disappear.rs b/src/tools/rustfmt/tests/target/comment-not-disappear.rs new file mode 100644 index 0000000000..b1fa0ff6fe --- /dev/null +++ b/src/tools/rustfmt/tests/target/comment-not-disappear.rs @@ -0,0 +1,38 @@ +// All the comments here should not disappear. + +fn a() { + match x { + X | + // A comment + Y => {} + }; +} + +fn b() { + match x { + X => + // A comment + { + y + } + } +} + +fn c() { + a() /* ... */; +} + +fn foo() -> Vec { + (0..11) + .map(|x| + // This comment disappears. + if x % 2 == 0 { x } else { x * 2 }) + .collect() +} + +fn calc_page_len(prefix_len: usize, sofar: usize) -> usize { + 2 // page type and flags + + 1 // stored depth + + 2 // stored count + + prefix_len + sofar // sum of size of all the actual items +} diff --git a/src/tools/rustfmt/tests/target/comment.rs b/src/tools/rustfmt/tests/target/comment.rs new file mode 100644 index 0000000000..b987c8a44f --- /dev/null +++ b/src/tools/rustfmt/tests/target/comment.rs @@ -0,0 +1,93 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true + +//! Doc comment +fn test() { + //! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam + //! lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam + + // comment + // comment2 + + code(); // leave this comment alone! + // ok? + + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a + // diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam + // viverra nec consectetur ante hendrerit. Donec et mollis dolor. + // Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam + // tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut + // libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit + // amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis + // felis, pulvinar a semper sed, adipiscing id dolor. + + // Very looooooooooooooooooooooooooooooooooooooooooooooooooooooooong comment + // that should be split + + // println!("{:?}", rewrite_comment(subslice, + // false, + // comment_width, + // self.block_indent, + // self.config) + // .unwrap()); + + funk(); // dontchangeme + // or me + + // #1388 + const EXCEPTION_PATHS: &'static [&'static str] = &[ + // std crates + "src/libstd/sys/", // Platform-specific code for std lives here. + "src/bootstrap", + ]; +} + +/// test123 +fn doc_comment() {} + +fn chains() { + foo.bar(|| { + let x = 10; + // comment + x + }) +} + +fn issue_1086() { + // +} + +// random comment + +fn main() { // Test +} + +// #1643 +fn some_fn() // some comment +{ +} + +fn some_fn1() +// some comment +{ +} + +fn some_fn2() // some comment +{ +} + +fn some_fn3() // some comment some comment some comment some comment some comment some comment so +{ +} + +fn some_fn4() +// some comment some comment some comment some comment some comment some comment some comment +{ +} + +// #1603 +pub enum Foo { + A, // `/** **/` + B, // `/*!` + C, +} diff --git a/src/tools/rustfmt/tests/target/comment2.rs b/src/tools/rustfmt/tests/target/comment2.rs new file mode 100644 index 0000000000..04f84a15c9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/comment2.rs @@ -0,0 +1,5 @@ +// rustfmt-wrap_comments: true + +/// This is a long line that angers rustfmt. Rustfmt shall deal with it swiftly +/// and justly. +pub mod foo {} diff --git a/src/tools/rustfmt/tests/target/comment3.rs b/src/tools/rustfmt/tests/target/comment3.rs new file mode 100644 index 0000000000..3a810590d7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/comment3.rs @@ -0,0 +1,6 @@ +// rustfmt-wrap_comments: true + +//! This is a long line that angers rustfmt. Rustfmt shall deal with it swiftly +//! and justly. + +pub mod foo {} diff --git a/src/tools/rustfmt/tests/target/comment4.rs b/src/tools/rustfmt/tests/target/comment4.rs new file mode 100644 index 0000000000..e2ef7de978 --- /dev/null +++ b/src/tools/rustfmt/tests/target/comment4.rs @@ -0,0 +1,51 @@ +#![allow(dead_code)] // bar + +//! Doc comment +fn test() { + // comment + // comment2 + + code(); /* leave this comment alone! + * ok? */ + + /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a + * diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam + * viverra nec consectetur ante hendrerit. Donec et mollis dolor. + * Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam + * tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut + * libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit + * amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis + * felis, pulvinar a semper sed, adipiscing id dolor. */ + + // Very loooooooooooooooooooooooooooooooooooooooooooooooooooooooong comment that should be split + + // println!("{:?}", rewrite_comment(subslice, + // false, + // comment_width, + // self.block_indent, + // self.config) + // .unwrap()); + + funk(); //dontchangeme + // or me +} + +/// test123 +fn doc_comment() {} + +/* +Regression test for issue #956 + +(some very important text) +*/ + +/* +fn debug_function() { + println!("hello"); +} +// */ + +#[link_section=".vectors"] +#[no_mangle] // Test this attribute is preserved. +#[cfg_attr(rustfmt, rustfmt::skip)] +pub static ISSUE_1284: [i32; 16] = []; diff --git a/src/tools/rustfmt/tests/target/comment5.rs b/src/tools/rustfmt/tests/target/comment5.rs new file mode 100644 index 0000000000..82d171e6f6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/comment5.rs @@ -0,0 +1,16 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true + +//@ special comment +//@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec adiam lectus. +//@ Sed sit amet ipsum mauris. Maecenas congue ligula ac quam +//@ +//@ foo +fn test() {} + +//@@@ another special comment +//@@@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec adiam +//@@@ lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam +//@@@ +//@@@ foo +fn bar() {} diff --git a/src/tools/rustfmt/tests/target/comment6.rs b/src/tools/rustfmt/tests/target/comment6.rs new file mode 100644 index 0000000000..565fee632f --- /dev/null +++ b/src/tools/rustfmt/tests/target/comment6.rs @@ -0,0 +1,14 @@ +// rustfmt-wrap_comments: true + +// Pendant la nuit du 9 mars 1860, les nuages, se confondant avec la mer, +// limitaient à quelques brasses la portée de la vue. Sur cette mer démontée, +// dont les lames déferlaient en projetant des lueurs livides, un léger bâtiment +// fuyait presque à sec de toile. + +pub mod foo {} + +// ゆく河の流れは絶えずして、しかももとの水にあらず。淀みに浮かぶうたかたは、 +// かつ消えかつ結びて、久しくとどまりたるためしなし。世の中にある人とすみかと、 +// またかくのごとし。 + +pub mod bar {} diff --git a/src/tools/rustfmt/tests/target/comment_crlf_newline.rs b/src/tools/rustfmt/tests/target/comment_crlf_newline.rs new file mode 100644 index 0000000000..aab9e94d9e --- /dev/null +++ b/src/tools/rustfmt/tests/target/comment_crlf_newline.rs @@ -0,0 +1,4 @@ +// rustfmt-normalize_comments: true +// Block comments followed by CRLF newlines should not an extra newline at the end + +// Something else diff --git a/src/tools/rustfmt/tests/target/comments-fn.rs b/src/tools/rustfmt/tests/target/comments-fn.rs new file mode 100644 index 0000000000..1f43bd93bb --- /dev/null +++ b/src/tools/rustfmt/tests/target/comments-fn.rs @@ -0,0 +1,38 @@ +// Test comments on functions are preserved. + +// Comment on foo. +fn foo( + a: aaaaaaaaaaaaa, // A comment + b: bbbbbbbbbbbbb, // a second comment + c: ccccccccccccc, + // Newline comment + d: ddddddddddddd, + // A multi line comment + // between args. + e: eeeeeeeeeeeee, /* comment before paren */ +) -> bar +where + F: Foo, // COmment after where-clause + G: Goo, // final comment +{ +} + +fn bar() {} + +fn baz() -> Baz /* Comment after return type */ {} + +fn some_fn() +where + T: Eq, // some comment +{ +} + +fn issue458(a: &str, f: F) +// comment1 +where + // comment2 + F: FnOnce(&str) -> bool, +{ + f(a); + () +} diff --git a/src/tools/rustfmt/tests/target/configs/blank_lines_lower_bound/1.rs b/src/tools/rustfmt/tests/target/configs/blank_lines_lower_bound/1.rs new file mode 100644 index 0000000000..9706699dc7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/blank_lines_lower_bound/1.rs @@ -0,0 +1,16 @@ +// rustfmt-blank_lines_lower_bound: 1 + +fn foo() {} + +fn bar() {} + +// comment +fn foobar() {} + +fn foo1() {} + +fn bar1() {} + +// comment + +fn foobar1() {} diff --git a/src/tools/rustfmt/tests/target/configs/brace_style/fn_always_next_line.rs b/src/tools/rustfmt/tests/target/configs/brace_style/fn_always_next_line.rs new file mode 100644 index 0000000000..2755a26468 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/brace_style/fn_always_next_line.rs @@ -0,0 +1,19 @@ +// rustfmt-brace_style: AlwaysNextLine +// Function brace style + +fn lorem() +{ + // body +} + +fn lorem(ipsum: usize) +{ + // body +} + +fn lorem(ipsum: T) +where + T: Add + Sub + Mul + Div, +{ + // body +} diff --git a/src/tools/rustfmt/tests/target/configs/brace_style/fn_prefer_same_line.rs b/src/tools/rustfmt/tests/target/configs/brace_style/fn_prefer_same_line.rs new file mode 100644 index 0000000000..23f98b6dd7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/brace_style/fn_prefer_same_line.rs @@ -0,0 +1,16 @@ +// rustfmt-brace_style: PreferSameLine +// Function brace style + +fn lorem() { + // body +} + +fn lorem(ipsum: usize) { + // body +} + +fn lorem(ipsum: T) +where + T: Add + Sub + Mul + Div, { + // body +} diff --git a/src/tools/rustfmt/tests/target/configs/brace_style/fn_same_line_where.rs b/src/tools/rustfmt/tests/target/configs/brace_style/fn_same_line_where.rs new file mode 100644 index 0000000000..2afe59943a --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/brace_style/fn_same_line_where.rs @@ -0,0 +1,17 @@ +// rustfmt-brace_style: SameLineWhere +// Function brace style + +fn lorem() { + // body +} + +fn lorem(ipsum: usize) { + // body +} + +fn lorem(ipsum: T) +where + T: Add + Sub + Mul + Div, +{ + // body +} diff --git a/src/tools/rustfmt/tests/target/configs/brace_style/item_always_next_line.rs b/src/tools/rustfmt/tests/target/configs/brace_style/item_always_next_line.rs new file mode 100644 index 0000000000..c13018630b --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/brace_style/item_always_next_line.rs @@ -0,0 +1,25 @@ +// rustfmt-brace_style: AlwaysNextLine +// Item brace style + +enum Foo {} + +struct Bar {} + +struct Lorem +{ + ipsum: bool, +} + +struct Dolor +where + T: Eq, +{ + sit: T, +} + +#[cfg(test)] +mod tests +{ + #[test] + fn it_works() {} +} diff --git a/src/tools/rustfmt/tests/target/configs/brace_style/item_prefer_same_line.rs b/src/tools/rustfmt/tests/target/configs/brace_style/item_prefer_same_line.rs new file mode 100644 index 0000000000..5143d7517c --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/brace_style/item_prefer_same_line.rs @@ -0,0 +1,18 @@ +// rustfmt-brace_style: PreferSameLine +// Item brace style + +struct Lorem { + ipsum: bool, +} + +struct Dolor +where + T: Eq, { + sit: T, +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() {} +} diff --git a/src/tools/rustfmt/tests/target/configs/brace_style/item_same_line_where.rs b/src/tools/rustfmt/tests/target/configs/brace_style/item_same_line_where.rs new file mode 100644 index 0000000000..8a3b285265 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/brace_style/item_same_line_where.rs @@ -0,0 +1,19 @@ +// rustfmt-brace_style: SameLineWhere +// Item brace style + +struct Lorem { + ipsum: bool, +} + +struct Dolor +where + T: Eq, +{ + sit: T, +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() {} +} diff --git a/src/tools/rustfmt/tests/target/configs/combine_control_expr/false.rs b/src/tools/rustfmt/tests/target/configs/combine_control_expr/false.rs new file mode 100644 index 0000000000..5ada9b1dd1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/combine_control_expr/false.rs @@ -0,0 +1,134 @@ +// rustfmt-indent_style: Block +// rustfmt-combine_control_expr: false + +// Combining openings and closings. See rust-lang/fmt-rfcs#61. + +fn main() { + // Call + foo(bar( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // Mac + foo(foo!( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // MethodCall + foo(x.foo::( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // Block + foo!({ + foo(); + bar(); + }); + + // Closure + foo(|x| { + let y = x + 1; + y + }); + + // Match + foo(match opt { + Some(x) => x, + None => y, + }); + + // Struct + foo(Bar { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + }); + + // If + foo!( + if x { + foo(); + } else { + bar(); + } + ); + + // IfLet + foo!( + if let Some(..) = x { + foo(); + } else { + bar(); + } + ); + + // While + foo!( + while x { + foo(); + bar(); + } + ); + + // WhileLet + foo!( + while let Some(..) = x { + foo(); + bar(); + } + ); + + // ForLoop + foo!( + for x in y { + foo(); + bar(); + } + ); + + // Loop + foo!( + loop { + foo(); + bar(); + } + ); + + // Tuple + foo(( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // AddrOf + foo(&bar( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // Box + foo(box Bar { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + }); + + // Unary + foo(!bar( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // Try + foo(bar( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )?); + + // Cast + foo(Bar { + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + } as i64); +} diff --git a/src/tools/rustfmt/tests/target/configs/combine_control_expr/true.rs b/src/tools/rustfmt/tests/target/configs/combine_control_expr/true.rs new file mode 100644 index 0000000000..52acd26492 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/combine_control_expr/true.rs @@ -0,0 +1,122 @@ +// rustfmt-indent_style: Block +// rustfmt-combine_control_expr: true + +// Combining openings and closings. See rust-lang/fmt-rfcs#61. + +fn main() { + // Call + foo(bar( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // Mac + foo(foo!( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // MethodCall + foo(x.foo::( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // Block + foo!({ + foo(); + bar(); + }); + + // Closure + foo(|x| { + let y = x + 1; + y + }); + + // Match + foo(match opt { + Some(x) => x, + None => y, + }); + + // Struct + foo(Bar { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + }); + + // If + foo!(if x { + foo(); + } else { + bar(); + }); + + // IfLet + foo!(if let Some(..) = x { + foo(); + } else { + bar(); + }); + + // While + foo!(while x { + foo(); + bar(); + }); + + // WhileLet + foo!(while let Some(..) = x { + foo(); + bar(); + }); + + // ForLoop + foo!(for x in y { + foo(); + bar(); + }); + + // Loop + foo!(loop { + foo(); + bar(); + }); + + // Tuple + foo(( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // AddrOf + foo(&bar( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // Box + foo(box Bar { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + }); + + // Unary + foo(!bar( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )); + + // Try + foo(bar( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + )?); + + // Cast + foo(Bar { + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + } as i64); +} diff --git a/src/tools/rustfmt/tests/target/configs/comment_width/above.rs b/src/tools/rustfmt/tests/target/configs/comment_width/above.rs new file mode 100644 index 0000000000..ddfecda65c --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/comment_width/above.rs @@ -0,0 +1,8 @@ +// rustfmt-comment_width: 40 +// rustfmt-wrap_comments: true +// Comment width + +fn main() { + // Lorem ipsum dolor sit amet, + // consectetur adipiscing elit. +} diff --git a/src/tools/rustfmt/tests/target/configs/comment_width/below.rs b/src/tools/rustfmt/tests/target/configs/comment_width/below.rs new file mode 100644 index 0000000000..abbc5930c4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/comment_width/below.rs @@ -0,0 +1,7 @@ +// rustfmt-comment_width: 80 +// rustfmt-wrap_comments: true +// Comment width + +fn main() { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. +} diff --git a/src/tools/rustfmt/tests/target/configs/comment_width/ignore.rs b/src/tools/rustfmt/tests/target/configs/comment_width/ignore.rs new file mode 100644 index 0000000000..c86e71c289 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/comment_width/ignore.rs @@ -0,0 +1,7 @@ +// rustfmt-comment_width: 40 +// rustfmt-wrap_comments: false +// Comment width + +fn main() { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. +} diff --git a/src/tools/rustfmt/tests/target/configs/condense_wildcard_suffixes/false.rs b/src/tools/rustfmt/tests/target/configs/condense_wildcard_suffixes/false.rs new file mode 100644 index 0000000000..3b967f35a8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/condense_wildcard_suffixes/false.rs @@ -0,0 +1,6 @@ +// rustfmt-condense_wildcard_suffixes: false +// Condense wildcard suffixes + +fn main() { + let (lorem, ipsum, _, _) = (1, 2, 3, 4); +} diff --git a/src/tools/rustfmt/tests/target/configs/condense_wildcard_suffixes/true.rs b/src/tools/rustfmt/tests/target/configs/condense_wildcard_suffixes/true.rs new file mode 100644 index 0000000000..4f880abe80 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/condense_wildcard_suffixes/true.rs @@ -0,0 +1,6 @@ +// rustfmt-condense_wildcard_suffixes: true +// Condense wildcard suffixes + +fn main() { + let (lorem, ipsum, ..) = (1, 2, 3, 4); +} diff --git a/src/tools/rustfmt/tests/target/configs/control_brace_style/always_next_line.rs b/src/tools/rustfmt/tests/target/configs/control_brace_style/always_next_line.rs new file mode 100644 index 0000000000..7dc06f207f --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/control_brace_style/always_next_line.rs @@ -0,0 +1,18 @@ +// rustfmt-control_brace_style: AlwaysNextLine +// Control brace style + +fn main() { + if lorem + { + println!("ipsum!"); + } + else + { + println!("dolor!"); + } + match magi + { + Homura => "Akemi", + Madoka => "Kaname", + } +} diff --git a/src/tools/rustfmt/tests/target/configs/control_brace_style/always_same_line.rs b/src/tools/rustfmt/tests/target/configs/control_brace_style/always_same_line.rs new file mode 100644 index 0000000000..993b6b681f --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/control_brace_style/always_same_line.rs @@ -0,0 +1,14 @@ +// rustfmt-control_brace_style: AlwaysSameLine +// Control brace style + +fn main() { + if lorem { + println!("ipsum!"); + } else { + println!("dolor!"); + } + match magi { + Homura => "Akemi", + Madoka => "Kaname", + } +} diff --git a/src/tools/rustfmt/tests/target/configs/control_brace_style/closing_next_line.rs b/src/tools/rustfmt/tests/target/configs/control_brace_style/closing_next_line.rs new file mode 100644 index 0000000000..013852ee79 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/control_brace_style/closing_next_line.rs @@ -0,0 +1,15 @@ +// rustfmt-control_brace_style: ClosingNextLine +// Control brace style + +fn main() { + if lorem { + println!("ipsum!"); + } + else { + println!("dolor!"); + } + match magi { + Homura => "Akemi", + Madoka => "Kaname", + } +} diff --git a/src/tools/rustfmt/tests/target/configs/disable_all_formatting/false.rs b/src/tools/rustfmt/tests/target/configs/disable_all_formatting/false.rs new file mode 100644 index 0000000000..1a0477ddb3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/disable_all_formatting/false.rs @@ -0,0 +1,10 @@ +// rustfmt-disable_all_formatting: false +// Disable all formatting + +fn main() { + if lorem { + println!("ipsum!"); + } else { + println!("dolor!"); + } +} diff --git a/src/tools/rustfmt/tests/target/configs/disable_all_formatting/true.rs b/src/tools/rustfmt/tests/target/configs/disable_all_formatting/true.rs new file mode 100644 index 0000000000..736ccf5694 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/disable_all_formatting/true.rs @@ -0,0 +1,6 @@ +// rustfmt-disable_all_formatting: true +// Disable all formatting + +fn main() { + if lorem{println!("ipsum!");}else{println!("dolor!");} +} diff --git a/src/tools/rustfmt/tests/target/configs/empty_item_single_line/false.rs b/src/tools/rustfmt/tests/target/configs/empty_item_single_line/false.rs new file mode 100644 index 0000000000..174fe330a8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/empty_item_single_line/false.rs @@ -0,0 +1,14 @@ +// rustfmt-empty_item_single_line: false +// Empty impl on single line + +impl Lorem { +} + +impl Ipsum { +} + +fn lorem() { +} + +fn lorem() { +} diff --git a/src/tools/rustfmt/tests/target/configs/empty_item_single_line/true.rs b/src/tools/rustfmt/tests/target/configs/empty_item_single_line/true.rs new file mode 100644 index 0000000000..0755485fea --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/empty_item_single_line/true.rs @@ -0,0 +1,10 @@ +// rustfmt-empty_item_single_line: true +// Empty impl on single line + +impl Lorem {} + +impl Ipsum {} + +fn lorem() {} + +fn lorem() {} diff --git a/src/tools/rustfmt/tests/target/configs/enum_discrim_align_threshold/40.rs b/src/tools/rustfmt/tests/target/configs/enum_discrim_align_threshold/40.rs new file mode 100644 index 0000000000..3ed66039c9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/enum_discrim_align_threshold/40.rs @@ -0,0 +1,34 @@ +// rustfmt-enum_discrim_align_threshold: 40 + +enum Standard { + A = 1, + Bcdef = 2, +} + +enum NoDiscrims { + ThisIsAFairlyLongEnumVariantWithoutDiscrimLongerThan40, + A = 1, + ThisIsAnotherFairlyLongEnumVariantWithoutDiscrimLongerThan40, + Bcdef = 2, +} + +enum TooLong { + ThisOneHasDiscrimAaaaaaaaaaaaaaaaaaaaaaChar40 = 10, + A = 1, + Bcdef = 2, +} + +enum Borderline { + ThisOneHasDiscrimAaaaaaaaaaaaaaaaaaaaaa = 10, + A = 1, + Bcdef = 2, +} + +// Live specimen from #1686 +enum LongWithSmallDiff { + SceneColorimetryEstimates = 0x73636F65, + SceneAppearanceEstimates = 0x73617065, + FocalPlaneColorimetryEstimates = 0x66706365, + ReflectionHardcopyOriginalColorimetry = 0x72686F63, + ReflectionPrintOutputColorimetry = 0x72706F63, +} diff --git a/src/tools/rustfmt/tests/target/configs/error_on_line_overflow/false.rs b/src/tools/rustfmt/tests/target/configs/error_on_line_overflow/false.rs new file mode 100644 index 0000000000..fa70ae7835 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/error_on_line_overflow/false.rs @@ -0,0 +1,6 @@ +// rustfmt-error_on_line_overflow: false +// Error on line overflow + +fn main() { + let lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit; +} diff --git a/src/tools/rustfmt/tests/target/configs/error_on_unformatted/false.rs b/src/tools/rustfmt/tests/target/configs/error_on_unformatted/false.rs new file mode 100644 index 0000000000..6a78374e2a --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/error_on_unformatted/false.rs @@ -0,0 +1,12 @@ +// rustfmt-error_on_unformatted: false +// Error on line overflow comment or string literals. + +// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +fn main() { + // aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + let x = " "; + let a = " + +"; +} diff --git a/src/tools/rustfmt/tests/target/configs/fn_args_layout/compressed.rs b/src/tools/rustfmt/tests/target/configs/fn_args_layout/compressed.rs new file mode 100644 index 0000000000..f189446e25 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/fn_args_layout/compressed.rs @@ -0,0 +1,22 @@ +// rustfmt-fn_args_layout: Compressed +// Function arguments density + +trait Lorem { + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { + // body + } + + fn lorem( + ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, + adipiscing: Adipiscing, elit: Elit, + ); + + fn lorem( + ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, + adipiscing: Adipiscing, elit: Elit, + ) { + // body + } +} diff --git a/src/tools/rustfmt/tests/target/configs/fn_args_layout/tall.rs b/src/tools/rustfmt/tests/target/configs/fn_args_layout/tall.rs new file mode 100644 index 0000000000..20f308973a --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/fn_args_layout/tall.rs @@ -0,0 +1,32 @@ +// rustfmt-fn_args_layout: Tall +// Function arguments density + +trait Lorem { + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { + // body + } + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: onsectetur, + adipiscing: Adipiscing, + elit: Elit, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: onsectetur, + adipiscing: Adipiscing, + elit: Elit, + ) { + // body + } +} diff --git a/src/tools/rustfmt/tests/target/configs/fn_args_layout/vertical.rs b/src/tools/rustfmt/tests/target/configs/fn_args_layout/vertical.rs new file mode 100644 index 0000000000..6c695a75df --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/fn_args_layout/vertical.rs @@ -0,0 +1,42 @@ +// rustfmt-fn_args_layout: Vertical +// Function arguments density + +trait Lorem { + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + ) { + // body + } + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: onsectetur, + adipiscing: Adipiscing, + elit: Elit, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: onsectetur, + adipiscing: Adipiscing, + elit: Elit, + ) { + // body + } +} diff --git a/src/tools/rustfmt/tests/target/configs/fn_single_line/false.rs b/src/tools/rustfmt/tests/target/configs/fn_single_line/false.rs new file mode 100644 index 0000000000..3d092f0c0b --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/fn_single_line/false.rs @@ -0,0 +1,11 @@ +// rustfmt-fn_single_line: false +// Single-expression function on single line + +fn lorem() -> usize { + 42 +} + +fn lorem() -> usize { + let ipsum = 42; + ipsum +} diff --git a/src/tools/rustfmt/tests/target/configs/fn_single_line/true.rs b/src/tools/rustfmt/tests/target/configs/fn_single_line/true.rs new file mode 100644 index 0000000000..10d94e02f1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/fn_single_line/true.rs @@ -0,0 +1,9 @@ +// rustfmt-fn_single_line: true +// Single-expression function on single line + +fn lorem() -> usize { 42 } + +fn lorem() -> usize { + let ipsum = 42; + ipsum +} diff --git a/src/tools/rustfmt/tests/target/configs/force_explicit_abi/false.rs b/src/tools/rustfmt/tests/target/configs/force_explicit_abi/false.rs new file mode 100644 index 0000000000..3c48f8e0c7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/force_explicit_abi/false.rs @@ -0,0 +1,6 @@ +// rustfmt-force_explicit_abi: false +// Force explicit abi + +extern { + pub static lorem: c_int; +} diff --git a/src/tools/rustfmt/tests/target/configs/force_explicit_abi/true.rs b/src/tools/rustfmt/tests/target/configs/force_explicit_abi/true.rs new file mode 100644 index 0000000000..90f5a8c4ec --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/force_explicit_abi/true.rs @@ -0,0 +1,6 @@ +// rustfmt-force_explicit_abi: true +// Force explicit abi + +extern "C" { + pub static lorem: c_int; +} diff --git a/src/tools/rustfmt/tests/target/configs/force_multiline_block/false.rs b/src/tools/rustfmt/tests/target/configs/force_multiline_block/false.rs new file mode 100644 index 0000000000..7cb4cac1d6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/force_multiline_block/false.rs @@ -0,0 +1,20 @@ +// rustfmt-force_multiline_blocks: false +// Option forces multiline match arm and closure bodies to be wrapped in a block + +fn main() { + match lorem { + Lorem::Ipsum => { + if ipsum { + println!("dolor"); + } + } + Lorem::Dolor => println!("amet"), + } +} + +fn main() { + result.and_then(|maybe_value| match maybe_value { + None => Err("oops"), + Some(value) => Ok(1), + }); +} diff --git a/src/tools/rustfmt/tests/target/configs/force_multiline_block/true.rs b/src/tools/rustfmt/tests/target/configs/force_multiline_block/true.rs new file mode 100644 index 0000000000..aec50afe5a --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/force_multiline_block/true.rs @@ -0,0 +1,22 @@ +// rustfmt-force_multiline_blocks: true +// Option forces multiline match arm and closure bodies to be wrapped in a block + +fn main() { + match lorem { + Lorem::Ipsum => { + if ipsum { + println!("dolor"); + } + } + Lorem::Dolor => println!("amet"), + } +} + +fn main() { + result.and_then(|maybe_value| { + match maybe_value { + None => Err("oops"), + Some(value) => Ok(1), + } + }); +} diff --git a/src/tools/rustfmt/tests/target/configs/format_macro_bodies/false.rs b/src/tools/rustfmt/tests/target/configs/format_macro_bodies/false.rs new file mode 100644 index 0000000000..ec871b25bf --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/format_macro_bodies/false.rs @@ -0,0 +1,6 @@ +// rustfmt-format_macro_bodies: false + +macro_rules! foo { + ($a: ident : $b: ty) => { $a(42): $b; }; + ($a: ident $b: ident $c: ident) => { $a=$b+$c; }; +} diff --git a/src/tools/rustfmt/tests/target/configs/format_macro_bodies/true.rs b/src/tools/rustfmt/tests/target/configs/format_macro_bodies/true.rs new file mode 100644 index 0000000000..9dc2524c38 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/format_macro_bodies/true.rs @@ -0,0 +1,10 @@ +// rustfmt-format_macro_bodies: true + +macro_rules! foo { + ($a: ident : $b: ty) => { + $a(42): $b; + }; + ($a: ident $b: ident $c: ident) => { + $a = $b + $c; + }; +} diff --git a/src/tools/rustfmt/tests/target/configs/format_macro_matchers/false.rs b/src/tools/rustfmt/tests/target/configs/format_macro_matchers/false.rs new file mode 100644 index 0000000000..3966d21be7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/format_macro_matchers/false.rs @@ -0,0 +1,10 @@ +// rustfmt-format_macro_matchers: false + +macro_rules! foo { + ($a: ident : $b: ty) => { + $a(42): $b; + }; + ($a: ident $b: ident $c: ident) => { + $a = $b + $c; + }; +} diff --git a/src/tools/rustfmt/tests/target/configs/format_macro_matchers/true.rs b/src/tools/rustfmt/tests/target/configs/format_macro_matchers/true.rs new file mode 100644 index 0000000000..e113af96f2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/format_macro_matchers/true.rs @@ -0,0 +1,10 @@ +// rustfmt-format_macro_matchers: true + +macro_rules! foo { + ($a:ident : $b:ty) => { + $a(42): $b; + }; + ($a:ident $b:ident $c:ident) => { + $a = $b + $c; + }; +} diff --git a/src/tools/rustfmt/tests/target/configs/format_strings/false.rs b/src/tools/rustfmt/tests/target/configs/format_strings/false.rs new file mode 100644 index 0000000000..ecca0d7d1f --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/format_strings/false.rs @@ -0,0 +1,8 @@ +// rustfmt-format_strings: false +// rustfmt-max_width: 50 +// rustfmt-error_on_line_overflow: false +// Force format strings + +fn main() { + let lorem = "ipsum dolor sit amet consectetur adipiscing elit lorem ipsum dolor sit"; +} diff --git a/src/tools/rustfmt/tests/target/configs/format_strings/true.rs b/src/tools/rustfmt/tests/target/configs/format_strings/true.rs new file mode 100644 index 0000000000..fdd5ab2c97 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/format_strings/true.rs @@ -0,0 +1,9 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 50 +// Force format strings + +fn main() { + let lorem = "ipsum dolor sit amet \ + consectetur adipiscing elit \ + lorem ipsum dolor sit"; +} diff --git a/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate-merge_imports.rs b/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate-merge_imports.rs new file mode 100644 index 0000000000..5e4064dd81 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate-merge_imports.rs @@ -0,0 +1,16 @@ +// rustfmt-group_imports: StdExternalCrate +// rustfmt-imports_granularity: Crate +use alloc::{alloc::Layout, vec::Vec}; +use core::f32; +use std::sync::Arc; + +use broker::database::PooledConnection; +use chrono::Utc; +use juniper::{FieldError, FieldResult}; +use uuid::Uuid; + +use super::{ + schema::{Context, Payload}, + update::convert_publish_payload, +}; +use crate::models::Event; diff --git a/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate-nested.rs b/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate-nested.rs new file mode 100644 index 0000000000..daf23375c0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate-nested.rs @@ -0,0 +1,7 @@ +// rustfmt-group_imports: StdExternalCrate +mod test { + use std::path; + + use crate::foo::bar; + use crate::foo::bar2; +} diff --git a/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate-no_reorder.rs b/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate-no_reorder.rs new file mode 100644 index 0000000000..76d3d6ccb9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate-no_reorder.rs @@ -0,0 +1,15 @@ +// rustfmt-group_imports: StdExternalCrate +// rustfmt-reorder_imports: false + +use alloc::alloc::Layout; +use std::sync::Arc; +use core::f32; + +use chrono::Utc; +use juniper::{FieldError, FieldResult}; +use uuid::Uuid; +use broker::database::PooledConnection; + +use super::update::convert_publish_payload; +use super::schema::{Context, Payload}; +use crate::models::Event; diff --git a/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate.rs b/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate.rs new file mode 100644 index 0000000000..0802579689 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/group_imports/StdExternalCrate.rs @@ -0,0 +1,13 @@ +// rustfmt-group_imports: StdExternalCrate +use alloc::alloc::Layout; +use core::f32; +use std::sync::Arc; + +use broker::database::PooledConnection; +use chrono::Utc; +use juniper::{FieldError, FieldResult}; +use uuid::Uuid; + +use super::schema::{Context, Payload}; +use super::update::convert_publish_payload; +use crate::models::Event; diff --git a/src/tools/rustfmt/tests/target/configs/hard_tabs/false.rs b/src/tools/rustfmt/tests/target/configs/hard_tabs/false.rs new file mode 100644 index 0000000000..ccfb53d8c8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/hard_tabs/false.rs @@ -0,0 +1,6 @@ +// rustfmt-hard_tabs: false +// Hard tabs + +fn lorem() -> usize { + 42 // spaces before 42 +} diff --git a/src/tools/rustfmt/tests/target/configs/hard_tabs/true.rs b/src/tools/rustfmt/tests/target/configs/hard_tabs/true.rs new file mode 100644 index 0000000000..3ed4e4f20a --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/hard_tabs/true.rs @@ -0,0 +1,6 @@ +// rustfmt-hard_tabs: true +// Hard tabs + +fn lorem() -> usize { + 42 // spaces before 42 +} diff --git a/src/tools/rustfmt/tests/target/configs/imports_indent/block.rs b/src/tools/rustfmt/tests/target/configs/imports_indent/block.rs new file mode 100644 index 0000000000..84c3b26bd6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/imports_indent/block.rs @@ -0,0 +1,7 @@ +// rustfmt-imports_indent: Block + +use lists::{ + definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape, + struct_lit_tactic, write_list, DefinitiveListTactic, ListFormatting, ListItem, ListTactic, + SeparatorTactic, +}; diff --git a/src/tools/rustfmt/tests/target/configs/imports_layout/horizontal_vertical.rs b/src/tools/rustfmt/tests/target/configs/imports_layout/horizontal_vertical.rs new file mode 100644 index 0000000000..4a63556d45 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/imports_layout/horizontal_vertical.rs @@ -0,0 +1,18 @@ +// rustfmt-imports_indent: Block +// rustfmt-imports_layout: HorizontalVertical + +use comment::{contains_comment, recover_comment_removed, rewrite_comment, FindUncommented}; +use lists::{ + definitive_tactic, + itemize_list, + shape_for_tactic, + struct_lit_formatting, + struct_lit_shape, + struct_lit_tactic, + write_list, + DefinitiveListTactic, + ListFormatting, + ListItem, + ListTactic, + SeparatorTactic, +}; diff --git a/src/tools/rustfmt/tests/target/configs/imports_layout/merge_mixed.rs b/src/tools/rustfmt/tests/target/configs/imports_layout/merge_mixed.rs new file mode 100644 index 0000000000..bc0da92fff --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/imports_layout/merge_mixed.rs @@ -0,0 +1,5 @@ +// rustfmt-imports_indent: Block +// rustfmt-imports_granularity: Crate +// rustfmt-imports_layout: Mixed + +use std::{fmt, io, str, str::FromStr}; diff --git a/src/tools/rustfmt/tests/target/configs/imports_layout/mixed.rs b/src/tools/rustfmt/tests/target/configs/imports_layout/mixed.rs new file mode 100644 index 0000000000..5d3349a01b --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/imports_layout/mixed.rs @@ -0,0 +1,9 @@ +// rustfmt-imports_indent: Block +// rustfmt-imports_layout: Mixed + +use comment::{contains_comment, recover_comment_removed, rewrite_comment, FindUncommented}; +use lists::{ + definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape, + struct_lit_tactic, write_list, DefinitiveListTactic, ListFormatting, ListItem, ListTactic, + SeparatorTactic, +}; diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/block_args.rs b/src/tools/rustfmt/tests/target/configs/indent_style/block_args.rs new file mode 100644 index 0000000000..80f4e13335 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/block_args.rs @@ -0,0 +1,47 @@ +// rustfmt-indent_style: Block +// Function arguments layout + +fn lorem() {} + +fn lorem(ipsum: usize) {} + +fn lorem( + ipsum: usize, + dolor: usize, + sit: usize, + amet: usize, + consectetur: usize, + adipiscing: usize, + elit: usize, +) { + // body +} + +// #1441 +extern "system" { + pub fn GetConsoleHistoryInfo( + console_history_info: *mut ConsoleHistoryInfo, + ) -> Boooooooooooooool; +} + +// rustfmt should not add trailing comma for variadic function. See #1623. +extern "C" { + pub fn variadic_fn( + first_parameter: FirstParameterType, + second_parameter: SecondParameterType, + ... + ); +} + +// #1652 +fn deconstruct( + foo: Bar, +) -> ( + SocketAddr, + Header, + Method, + RequestUri, + HttpVersion, + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +) { +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/block_array.rs b/src/tools/rustfmt/tests/target/configs/indent_style/block_array.rs new file mode 100644 index 0000000000..5d458248c0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/block_array.rs @@ -0,0 +1,14 @@ +// rustfmt-indent_style: Block +// Array layout + +fn main() { + let lorem = vec![ + "ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipiscing", + "elit", + ]; +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/block_call.rs b/src/tools/rustfmt/tests/target/configs/indent_style/block_call.rs new file mode 100644 index 0000000000..19c44dc019 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/block_call.rs @@ -0,0 +1,151 @@ +// rustfmt-indent_style: Block +// Function call style + +fn main() { + lorem( + "lorem", + "ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipiscing", + "elit", + ); + // #1501 + let hyper = Arc::new(Client::with_connector( + HttpsConnector::new(TlsClient::new()), + )); + + // chain + let x = yooooooooooooo + .fooooooooooooooo + .baaaaaaaaaaaaar(hello, world); + + // #1380 + { + { + let creds = self + .client + .client_credentials(&self.config.auth.oauth2.id, &self.config.auth.oauth2.secret)?; + } + } + + // nesting macro and function call + try!(foo( + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + )); + try!(foo(try!( + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + ))); +} + +// #1521 +impl Foo { + fn map_pixel_to_coords(&self, point: &Vector2i, view: &View) -> Vector2f { + unsafe { + Vector2f::from_raw(ffi::sfRenderTexture_mapPixelToCoords( + self.render_texture, + point.raw(), + view.raw(), + )) + } + } +} + +fn issue1420() { + given( + r#" + # Getting started + ... + "#, + ) + .running(waltz) +} + +// #1563 +fn query(conn: &Connection) -> Result<()> { + conn.query_row( + r#" + SELECT title, date + FROM posts, + WHERE DATE(date) = $1 + "#, + &[], + |row| Post { + title: row.get(0), + date: row.get(1), + }, + )?; + + Ok(()) +} + +// #1449 +fn future_rayon_wait_1_thread() { + // run with only 1 worker thread; this would deadlock if we couldn't make progress + let mut result = None; + ThreadPool::new(Configuration::new().num_threads(1)) + .unwrap() + .install(|| { + scope(|s| { + use std::sync::mpsc::channel; + let (tx, rx) = channel(); + let a = s.spawn_future(lazy(move || Ok::(rx.recv().unwrap()))); + // ^^^^ FIXME: why is this needed? + let b = s.spawn_future(a.map(|v| v + 1)); + let c = s.spawn_future(b.map(|v| v + 1)); + s.spawn(move |_| tx.send(20).unwrap()); + result = Some(c.rayon_wait().unwrap()); + }); + }); + assert_eq!(result, Some(22)); +} + +// #1494 +impl Cursor { + fn foo() { + self.cur_type() + .num_template_args() + .or_else(|| { + let n: c_int = unsafe { clang_Cursor_getNumTemplateArguments(self.x) }; + + if n >= 0 { + Some(n as u32) + } else { + debug_assert_eq!(n, -1); + None + } + }) + .or_else(|| { + let canonical = self.canonical(); + if canonical != *self { + canonical.num_template_args() + } else { + None + } + }); + } +} + +fn issue1581() { + bootstrap.checks.register("PERSISTED_LOCATIONS", move || { + if locations2.0.inner_mut.lock().poisoned { + Check::new( + State::Error, + "Persisted location storage is poisoned due to a write failure", + ) + } else { + Check::new(State::Healthy, "Persisted location storage is healthy") + } + }); +} + +fn issue1651() { + { + let type_list: Vec<_> = + try_opt!(types.iter().map(|ty| ty.rewrite(context, shape)).collect()); + } +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/block_chain.rs b/src/tools/rustfmt/tests/target/configs/indent_style/block_chain.rs new file mode 100644 index 0000000000..23340a4aba --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/block_chain.rs @@ -0,0 +1,12 @@ +// rustfmt-indent_style: Block +// Chain indent + +fn main() { + let lorem = ipsum + .dolor() + .sit() + .amet() + .consectetur() + .adipiscing() + .elite(); +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/block_generic.rs b/src/tools/rustfmt/tests/target/configs/indent_style/block_generic.rs new file mode 100644 index 0000000000..c4fcaaf65d --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/block_generic.rs @@ -0,0 +1,22 @@ +// rustfmt-indent_style: Block +// Generics indent + +fn lorem< + Ipsum: Eq = usize, + Dolor: Eq = usize, + Sit: Eq = usize, + Amet: Eq = usize, + Adipiscing: Eq = usize, + Consectetur: Eq = usize, + Elit: Eq = usize, +>( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + adipiscing: Adipiscing, + consectetur: Consectetur, + elit: Elit, +) -> T { + // body +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/block_struct_lit.rs b/src/tools/rustfmt/tests/target/configs/indent_style/block_struct_lit.rs new file mode 100644 index 0000000000..656b562266 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/block_struct_lit.rs @@ -0,0 +1,9 @@ +// rustfmt-indent_style: Block +// Struct literal-style + +fn main() { + let lorem = Lorem { + ipsum: dolor, + sit: amet, + }; +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/block_tab_spaces_call.rs b/src/tools/rustfmt/tests/target/configs/indent_style/block_tab_spaces_call.rs new file mode 100644 index 0000000000..5531e61ddc --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/block_tab_spaces_call.rs @@ -0,0 +1,14 @@ +// rustfmt-indent_style: Block +// rustfmt-max_width: 80 +// rustfmt-tab_spaces: 2 + +// #1427 +fn main() { + exceptaions::config(move || { + ( + NmiConfig {}, + HardFaultConfig {}, + SysTickConfig { gpio_sbsrr }, + ) + }); +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/block_trailing_comma_call/one.rs b/src/tools/rustfmt/tests/target/configs/indent_style/block_trailing_comma_call/one.rs new file mode 100644 index 0000000000..6b9489bef5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/block_trailing_comma_call/one.rs @@ -0,0 +1,12 @@ +// rustfmt-version: One +// rustfmt-error_on_line_overflow: false +// rustfmt-indent_style: Block + +// rustfmt should not add trailing comma when rewriting macro. See #1528. +fn a() { + panic!("this is a long string that goes past the maximum line length causing rustfmt to insert a comma here:"); + foo( + a, + oooptoptoptoptptooptoptoptoptptooptoptoptoptptoptoptoptoptpt(), + ); +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/block_trailing_comma_call/two.rs b/src/tools/rustfmt/tests/target/configs/indent_style/block_trailing_comma_call/two.rs new file mode 100644 index 0000000000..4f4292e5f4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/block_trailing_comma_call/two.rs @@ -0,0 +1,14 @@ +// rustfmt-version: Two +// rustfmt-error_on_line_overflow: false +// rustfmt-indent_style: Block + +// rustfmt should not add trailing comma when rewriting macro. See #1528. +fn a() { + panic!( + "this is a long string that goes past the maximum line length causing rustfmt to insert a comma here:" + ); + foo( + a, + oooptoptoptoptptooptoptoptoptptooptoptoptoptptoptoptoptoptpt(), + ); +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/block_where_pred.rs b/src/tools/rustfmt/tests/target/configs/indent_style/block_where_pred.rs new file mode 100644 index 0000000000..ad7e0b8f36 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/block_where_pred.rs @@ -0,0 +1,12 @@ +// rustfmt-indent_style: Block +// Where predicate indent + +fn lorem() -> T +where + Ipsum: Eq, + Dolor: Eq, + Sit: Eq, + Amet: Eq, +{ + // body +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/default.rs b/src/tools/rustfmt/tests/target/configs/indent_style/default.rs new file mode 100644 index 0000000000..a8f0902b34 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/default.rs @@ -0,0 +1,11 @@ +// rustfmt-indent_style: Visual +// Where style + +fn lorem() -> T + where Ipsum: Eq, + Dolor: Eq, + Sit: Eq, + Amet: Eq +{ + // body +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/rfc_control.rs b/src/tools/rustfmt/tests/target/configs/indent_style/rfc_control.rs new file mode 100644 index 0000000000..6619d8b263 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/rfc_control.rs @@ -0,0 +1,39 @@ +// rustfmt-indent_style: Block + +// #1618 +fn main() { + loop { + if foo { + if ((right_paddle_speed < 0.) && (right_paddle.position().y - paddle_size.y / 2. > 5.)) + || ((right_paddle_speed > 0.) + && (right_paddle.position().y + paddle_size.y / 2. < game_height as f32 - 5.)) + { + foo + } + if ai_timer.elapsed_time().as_microseconds() > ai_time.as_microseconds() { + if ball.position().y + ball_radius > right_paddle.position().y + paddle_size.y / 2. + { + foo + } + } + } + } +} + +fn issue1656() { + { + { + match rewrite { + Some(ref body_str) + if (!body_str.contains('\n') && body_str.len() <= arm_shape.width) + || !context.config.match_arm_blocks() + || (extend && first_line_width(body_str) <= arm_shape.width) + || is_block => + { + return None; + } + _ => {} + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/rfc_where.rs b/src/tools/rustfmt/tests/target/configs/indent_style/rfc_where.rs new file mode 100644 index 0000000000..a7b9a4f02b --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/rfc_where.rs @@ -0,0 +1,12 @@ +// rustfmt-indent_style: Block +// Where style + +fn lorem() -> T +where + Ipsum: Eq, + Dolor: Eq, + Sit: Eq, + Amet: Eq, +{ + // body +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/visual_args.rs b/src/tools/rustfmt/tests/target/configs/indent_style/visual_args.rs new file mode 100644 index 0000000000..04c2eaee3c --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/visual_args.rs @@ -0,0 +1,40 @@ +// rustfmt-indent_style: Visual +// Function arguments layout + +fn lorem() {} + +fn lorem(ipsum: usize) {} + +fn lorem(ipsum: usize, + dolor: usize, + sit: usize, + amet: usize, + consectetur: usize, + adipiscing: usize, + elit: usize) { + // body +} + +// #1922 +extern "C" { + pub fn LAPACKE_csytrs_rook_work(matrix_layout: c_int, + uplo: c_char, + n: lapack_int, + nrhs: lapack_int, + a: *const lapack_complex_float, + lda: lapack_int, + ipiv: *const lapack_int, + b: *mut lapack_complex_float, + ldb: lapack_int) + -> lapack_int; + + pub fn LAPACKE_csytrs_rook_work(matrix_layout: c_int, + uplo: c_char, + n: lapack_int, + nrhs: lapack_int, + lda: lapack_int, + ipiv: *const lapack_int, + b: *mut lapack_complex_float, + ldb: lapack_int) + -> lapack_int; +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/visual_array.rs b/src/tools/rustfmt/tests/target/configs/indent_style/visual_array.rs new file mode 100644 index 0000000000..1da6ff2373 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/visual_array.rs @@ -0,0 +1,12 @@ +// rustfmt-indent_style: Visual +// Array layout + +fn main() { + let lorem = vec!["ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipiscing", + "elit"]; +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/visual_call.rs b/src/tools/rustfmt/tests/target/configs/indent_style/visual_call.rs new file mode 100644 index 0000000000..5454c44ef9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/visual_call.rs @@ -0,0 +1,13 @@ +// rustfmt-indent_style: Visual +// Function call style + +fn main() { + lorem("lorem", + "ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipiscing", + "elit"); +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/visual_chain.rs b/src/tools/rustfmt/tests/target/configs/indent_style/visual_chain.rs new file mode 100644 index 0000000000..569f3d8b81 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/visual_chain.rs @@ -0,0 +1,11 @@ +// rustfmt-indent_style: Visual +// Chain indent + +fn main() { + let lorem = ipsum.dolor() + .sit() + .amet() + .consectetur() + .adipiscing() + .elite(); +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/visual_generics.rs b/src/tools/rustfmt/tests/target/configs/indent_style/visual_generics.rs new file mode 100644 index 0000000000..491075a146 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/visual_generics.rs @@ -0,0 +1,20 @@ +// rustfmt-indent_style: Visual +// Generics indent + +fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + adipiscing: Adipiscing, + consectetur: Consectetur, + elit: Elit) + -> T { + // body +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/visual_struct_lit.rs b/src/tools/rustfmt/tests/target/configs/indent_style/visual_struct_lit.rs new file mode 100644 index 0000000000..ec49021d33 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/visual_struct_lit.rs @@ -0,0 +1,7 @@ +// rustfmt-indent_style: Visual +// Struct literal-style + +fn main() { + let lorem = Lorem { ipsum: dolor, + sit: amet }; +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/visual_trailing_comma.rs b/src/tools/rustfmt/tests/target/configs/indent_style/visual_trailing_comma.rs new file mode 100644 index 0000000000..9738d397db --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/visual_trailing_comma.rs @@ -0,0 +1,7 @@ +// rustfmt-error_on_line_overflow: false +// rustfmt-indent_style: Visual + +// rustfmt should not add trailing comma when rewriting macro. See #1528. +fn a() { + panic!("this is a long string that goes past the maximum line length causing rustfmt to insert a comma here:"); +} diff --git a/src/tools/rustfmt/tests/target/configs/indent_style/visual_where_pred.rs b/src/tools/rustfmt/tests/target/configs/indent_style/visual_where_pred.rs new file mode 100644 index 0000000000..45799dcd5f --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/indent_style/visual_where_pred.rs @@ -0,0 +1,11 @@ +// rustfmt-indent_style: Visual +// Where predicate indent + +fn lorem() -> T + where Ipsum: Eq, + Dolor: Eq, + Sit: Eq, + Amet: Eq +{ + // body +} diff --git a/src/tools/rustfmt/tests/target/configs/match_arm_blocks/false.rs b/src/tools/rustfmt/tests/target/configs/match_arm_blocks/false.rs new file mode 100644 index 0000000000..7a9834168c --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/match_arm_blocks/false.rs @@ -0,0 +1,12 @@ +// rustfmt-match_arm_blocks: false +// Wrap match-arms + +fn main() { + match lorem { + true => + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + false => { + println!("{}", sit) + } + } +} diff --git a/src/tools/rustfmt/tests/target/configs/match_arm_blocks/true.rs b/src/tools/rustfmt/tests/target/configs/match_arm_blocks/true.rs new file mode 100644 index 0000000000..eb9e34059c --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/match_arm_blocks/true.rs @@ -0,0 +1,13 @@ +// rustfmt-match_arm_blocks: true +// Wrap match-arms + +fn main() { + match lorem { + true => { + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x) + } + false => { + println!("{}", sit) + } + } +} diff --git a/src/tools/rustfmt/tests/target/configs/match_arm_leading_pipes/always.rs b/src/tools/rustfmt/tests/target/configs/match_arm_leading_pipes/always.rs new file mode 100644 index 0000000000..f2af81eac3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/match_arm_leading_pipes/always.rs @@ -0,0 +1,27 @@ +// rustfmt-match_arm_leading_pipes: Always + +fn foo() { + match foo { + | "foo" | "bar" => {} + | "baz" + | "something relatively long" + | "something really really really realllllllllllllly long" => println!("x"), + | "qux" => println!("y"), + | _ => {} + } +} + +fn issue_3973() { + match foo { + | "foo" | "bar" => {} + | _ => {} + } +} + +fn bar() { + match baz { + | "qux" => {} + | "foo" | "bar" => {} + | _ => {} + } +} diff --git a/src/tools/rustfmt/tests/target/configs/match_arm_leading_pipes/never.rs b/src/tools/rustfmt/tests/target/configs/match_arm_leading_pipes/never.rs new file mode 100644 index 0000000000..345014e4b4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/match_arm_leading_pipes/never.rs @@ -0,0 +1,27 @@ +// rustfmt-match_arm_leading_pipes: Never + +fn foo() { + match foo { + "foo" | "bar" => {} + "baz" + | "something relatively long" + | "something really really really realllllllllllllly long" => println!("x"), + "qux" => println!("y"), + _ => {} + } +} + +fn issue_3973() { + match foo { + "foo" | "bar" => {} + _ => {} + } +} + +fn bar() { + match baz { + "qux" => {} + "foo" | "bar" => {} + _ => {} + } +} diff --git a/src/tools/rustfmt/tests/target/configs/match_arm_leading_pipes/preserve.rs b/src/tools/rustfmt/tests/target/configs/match_arm_leading_pipes/preserve.rs new file mode 100644 index 0000000000..2beb1f5d81 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/match_arm_leading_pipes/preserve.rs @@ -0,0 +1,27 @@ +// rustfmt-match_arm_leading_pipes: Preserve + +fn foo() { + match foo { + | "foo" | "bar" => {} + | "baz" + | "something relatively long" + | "something really really really realllllllllllllly long" => println!("x"), + | "qux" => println!("y"), + _ => {} + } +} + +fn issue_3973() { + match foo { + | "foo" | "bar" => {} + _ => {} + } +} + +fn bar() { + match baz { + "qux" => {} + "foo" | "bar" => {} + _ => {} + } +} diff --git a/src/tools/rustfmt/tests/target/configs/match_block_trailing_comma/false.rs b/src/tools/rustfmt/tests/target/configs/match_block_trailing_comma/false.rs new file mode 100644 index 0000000000..70e02955fb --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/match_block_trailing_comma/false.rs @@ -0,0 +1,11 @@ +// rustfmt-match_block_trailing_comma: false +// Match block trailing comma + +fn main() { + match lorem { + Lorem::Ipsum => { + println!("ipsum"); + } + Lorem::Dolor => println!("dolor"), + } +} diff --git a/src/tools/rustfmt/tests/target/configs/match_block_trailing_comma/true.rs b/src/tools/rustfmt/tests/target/configs/match_block_trailing_comma/true.rs new file mode 100644 index 0000000000..b78b046dc1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/match_block_trailing_comma/true.rs @@ -0,0 +1,11 @@ +// rustfmt-match_block_trailing_comma: true +// Match block trailing comma + +fn main() { + match lorem { + Lorem::Ipsum => { + println!("ipsum"); + }, + Lorem::Dolor => println!("dolor"), + } +} diff --git a/src/tools/rustfmt/tests/target/configs/merge_derives/true.rs b/src/tools/rustfmt/tests/target/configs/merge_derives/true.rs new file mode 100644 index 0000000000..4d0148b1c6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/merge_derives/true.rs @@ -0,0 +1,40 @@ +// rustfmt-merge_derives: true +// Merge multiple derives to a single one. + +#[bar] +#[derive(Eq, PartialEq)] +#[foo] +#[derive(Debug)] +#[foobar] +#[derive(Copy, Clone)] +pub enum Foo {} + +#[derive(Eq, PartialEq, Debug)] +#[foobar] +#[derive(Copy, Clone)] +pub enum Bar {} + +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +pub enum FooBar {} + +mod foo { + #[bar] + #[derive(Eq, PartialEq)] + #[foo] + #[derive(Debug)] + #[foobar] + #[derive(Copy, Clone)] + pub enum Foo {} +} + +mod bar { + #[derive(Eq, PartialEq, Debug)] + #[foobar] + #[derive(Copy, Clone)] + pub enum Bar {} +} + +mod foobar { + #[derive(Eq, PartialEq, Debug, Copy, Clone)] + pub enum FooBar {} +} diff --git a/src/tools/rustfmt/tests/target/configs/normalize_comments/false.rs b/src/tools/rustfmt/tests/target/configs/normalize_comments/false.rs new file mode 100644 index 0000000000..488962ed93 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/normalize_comments/false.rs @@ -0,0 +1,13 @@ +// rustfmt-normalize_comments: false +// Normalize comments + +// Lorem ipsum: +fn dolor() -> usize {} + +/* sit amet: */ +fn adipiscing() -> usize {} + +// #652 +//////////////////////////////////////////////////////////////////////////////// +// Basic slice extension methods +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tools/rustfmt/tests/target/configs/normalize_comments/true.rs b/src/tools/rustfmt/tests/target/configs/normalize_comments/true.rs new file mode 100644 index 0000000000..0bdbe08ab4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/normalize_comments/true.rs @@ -0,0 +1,13 @@ +// rustfmt-normalize_comments: true +// Normalize comments + +// Lorem ipsum: +fn dolor() -> usize {} + +// sit amet: +fn adipiscing() -> usize {} + +// #652 +//////////////////////////////////////////////////////////////////////////////// +// Basic slice extension methods +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tools/rustfmt/tests/target/configs/normalize_doc_attributes/false.rs b/src/tools/rustfmt/tests/target/configs/normalize_doc_attributes/false.rs new file mode 100644 index 0000000000..f8eb64273c --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/normalize_doc_attributes/false.rs @@ -0,0 +1,13 @@ +// rustfmt-normalize_doc_attributes: false +// Normalize doc attributes + +#![doc = " Example documentation"] + +#[doc = " Example item documentation"] +pub enum Foo {} + +#[doc = " Lots of space"] +pub enum Bar {} + +#[doc = "no leading space"] +pub mod FooBar {} diff --git a/src/tools/rustfmt/tests/target/configs/normalize_doc_attributes/true.rs b/src/tools/rustfmt/tests/target/configs/normalize_doc_attributes/true.rs new file mode 100644 index 0000000000..fadab985be --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/normalize_doc_attributes/true.rs @@ -0,0 +1,13 @@ +// rustfmt-normalize_doc_attributes: true +// Normalize doc attributes + +//! Example documentation + +/// Example item documentation +pub enum Foo {} + +/// Lots of space +pub enum Bar {} + +///no leading space +pub mod FooBar {} diff --git a/src/tools/rustfmt/tests/target/configs/remove_nested_parens/remove_nested_parens.rs b/src/tools/rustfmt/tests/target/configs/remove_nested_parens/remove_nested_parens.rs new file mode 100644 index 0000000000..d896042c33 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/remove_nested_parens/remove_nested_parens.rs @@ -0,0 +1,5 @@ +// rustfmt-remove_nested_parens: true + +fn main() { + (foo()); +} diff --git a/src/tools/rustfmt/tests/target/configs/reorder_impl_items/false.rs b/src/tools/rustfmt/tests/target/configs/reorder_impl_items/false.rs new file mode 100644 index 0000000000..beb99f0fb8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/reorder_impl_items/false.rs @@ -0,0 +1,11 @@ +// rustfmt-reorder_impl_items: false + +struct Dummy; + +impl Iterator for Dummy { + fn next(&mut self) -> Option { + None + } + + type Item = i32; +} diff --git a/src/tools/rustfmt/tests/target/configs/reorder_impl_items/true.rs b/src/tools/rustfmt/tests/target/configs/reorder_impl_items/true.rs new file mode 100644 index 0000000000..f2294412a9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/reorder_impl_items/true.rs @@ -0,0 +1,11 @@ +// rustfmt-reorder_impl_items: true + +struct Dummy; + +impl Iterator for Dummy { + type Item = i32; + + fn next(&mut self) -> Option { + None + } +} diff --git a/src/tools/rustfmt/tests/target/configs/reorder_imports/false.rs b/src/tools/rustfmt/tests/target/configs/reorder_imports/false.rs new file mode 100644 index 0000000000..4b85684dc0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/reorder_imports/false.rs @@ -0,0 +1,7 @@ +// rustfmt-reorder_imports: false +// Reorder imports + +use lorem; +use ipsum; +use dolor; +use sit; diff --git a/src/tools/rustfmt/tests/target/configs/reorder_imports/true.rs b/src/tools/rustfmt/tests/target/configs/reorder_imports/true.rs new file mode 100644 index 0000000000..e4ff7295fd --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/reorder_imports/true.rs @@ -0,0 +1,19 @@ +// rustfmt-reorder_imports: true +// Reorder imports + +use dolor; +use ipsum; +use lorem; +use sit; + +fn foo() { + use A; + use B; + use C; + + bar(); + + use D; + use E; + use F; +} diff --git a/src/tools/rustfmt/tests/target/configs/reorder_modules/dolor/mod.rs b/src/tools/rustfmt/tests/target/configs/reorder_modules/dolor/mod.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/reorder_modules/dolor/mod.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/target/configs/reorder_modules/false.rs b/src/tools/rustfmt/tests/target/configs/reorder_modules/false.rs new file mode 100644 index 0000000000..56b1aa03ed --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/reorder_modules/false.rs @@ -0,0 +1,7 @@ +// rustfmt-reorder_modules: false +// Reorder modules + +mod lorem; +mod ipsum; +mod dolor; +mod sit; diff --git a/src/tools/rustfmt/tests/target/configs/reorder_modules/ipsum/mod.rs b/src/tools/rustfmt/tests/target/configs/reorder_modules/ipsum/mod.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/reorder_modules/ipsum/mod.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/target/configs/reorder_modules/lorem/mod.rs b/src/tools/rustfmt/tests/target/configs/reorder_modules/lorem/mod.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/reorder_modules/lorem/mod.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/target/configs/reorder_modules/sit/mod.rs b/src/tools/rustfmt/tests/target/configs/reorder_modules/sit/mod.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/reorder_modules/sit/mod.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/target/configs/reorder_modules/true.rs b/src/tools/rustfmt/tests/target/configs/reorder_modules/true.rs new file mode 100644 index 0000000000..18361e88b5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/reorder_modules/true.rs @@ -0,0 +1,7 @@ +// rustfmt-reorder_modules: true +// Reorder modules + +mod dolor; +mod ipsum; +mod lorem; +mod sit; diff --git a/src/tools/rustfmt/tests/target/configs/skip_children/foo/mod.rs b/src/tools/rustfmt/tests/target/configs/skip_children/foo/mod.rs new file mode 100644 index 0000000000..d7ff6cdb82 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/skip_children/foo/mod.rs @@ -0,0 +1,3 @@ +fn skip_formatting_this() { + println ! ( "Skip this" ) ; +} diff --git a/src/tools/rustfmt/tests/target/configs/skip_children/true.rs b/src/tools/rustfmt/tests/target/configs/skip_children/true.rs new file mode 100644 index 0000000000..33fd782b4f --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/skip_children/true.rs @@ -0,0 +1,4 @@ +// rustfmt-skip_children: true + +mod foo; +mod void; diff --git a/src/tools/rustfmt/tests/target/configs/space_before_colon/true.rs b/src/tools/rustfmt/tests/target/configs/space_before_colon/true.rs new file mode 100644 index 0000000000..e2895b5d77 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/space_before_colon/true.rs @@ -0,0 +1,11 @@ +// rustfmt-space_before_colon: true +// Space before colon + +fn lorem(t : T) { + let ipsum : Dolor = sit; +} + +const LOREM : Lorem = Lorem { + ipsum : dolor, + sit : amet, +}; diff --git a/src/tools/rustfmt/tests/target/configs/spaces_around_ranges/false.rs b/src/tools/rustfmt/tests/target/configs/spaces_around_ranges/false.rs new file mode 100644 index 0000000000..72b1be4804 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/spaces_around_ranges/false.rs @@ -0,0 +1,34 @@ +// rustfmt-spaces_around_ranges: false +// Spaces around ranges + +fn main() { + let lorem = 0..10; + let ipsum = 0..=10; + + match lorem { + 1..5 => foo(), + _ => bar, + } + + match lorem { + 1..=5 => foo(), + _ => bar, + } + + match lorem { + 1...5 => foo(), + _ => bar, + } +} + +fn half_open() { + match [5..4, 99..105, 43..44] { + [_, 99.., _] => {} + [_, ..105, _] => {} + _ => {} + }; + + if let ..=5 = 0 {} + if let ..5 = 0 {} + if let 5.. = 0 {} +} diff --git a/src/tools/rustfmt/tests/target/configs/spaces_around_ranges/true.rs b/src/tools/rustfmt/tests/target/configs/spaces_around_ranges/true.rs new file mode 100644 index 0000000000..c56fdbb02b --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/spaces_around_ranges/true.rs @@ -0,0 +1,34 @@ +// rustfmt-spaces_around_ranges: true +// Spaces around ranges + +fn main() { + let lorem = 0 .. 10; + let ipsum = 0 ..= 10; + + match lorem { + 1 .. 5 => foo(), + _ => bar, + } + + match lorem { + 1 ..= 5 => foo(), + _ => bar, + } + + match lorem { + 1 ... 5 => foo(), + _ => bar, + } +} + +fn half_open() { + match [5 .. 4, 99 .. 105, 43 .. 44] { + [_, 99 .., _] => {} + [_, .. 105, _] => {} + _ => {} + }; + + if let ..= 5 = 0 {} + if let .. 5 = 0 {} + if let 5 .. = 0 {} +} diff --git a/src/tools/rustfmt/tests/target/configs/struct_field_align_threshold/20.rs b/src/tools/rustfmt/tests/target/configs/struct_field_align_threshold/20.rs new file mode 100644 index 0000000000..12a523e9d8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/struct_field_align_threshold/20.rs @@ -0,0 +1,471 @@ +// rustfmt-struct_field_align_threshold: 20 +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-error_on_line_overflow: false + +struct Foo { + x: u32, + yy: u32, // comment + zzz: u32, +} + +pub struct Bar { + x: u32, + yy: u32, + zzz: u32, + + xxxxxxx: u32, +} + +fn main() { + let foo = Foo { + x: 0, + yy: 1, + zzz: 2, + }; + + let bar = Bar { + x: 0, + yy: 1, + zzz: 2, + + xxxxxxx: 3, + }; +} + +/// A Doc comment +#[AnAttribute] +pub struct Foo { + #[rustfmt::skip] + f : SomeType, // Comment beside a field + f: SomeType, // Comment beside a field + // Comment on a field + #[AnAttribute] + g: SomeOtherType, + /// A doc comment on a field + h: AThirdType, + pub i: TypeForPublicField, +} + +// #1029 +pub struct Foo { + #[doc(hidden)] + // This will NOT get deleted! + bar: String, // hi +} + +// #1029 +struct X { + // `x` is an important number. + #[allow(unused)] // TODO: use + x: u32, +} + +// #410 +#[allow(missing_docs)] +pub struct Writebatch { + #[allow(dead_code)] // only used for holding the internal pointer + writebatch: RawWritebatch, + marker: PhantomData, +} + +struct Bar; + +struct NewType(Type, OtherType); + +struct NewInt( + pub i32, + SomeType, // inline comment + T, // sup +); + +struct Qux< + 'a, + N: Clone + 'a, + E: Clone + 'a, + G: Labeller<'a, N, E> + GraphWalk<'a, N, E>, + W: Write + Copy, +>( + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, // Comment + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, + #[AnAttr] + // Comment + /// Testdoc + G, + pub W, +); + +struct Tuple( + // Comment 1 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + // Comment 2 + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, +); + +// With a where-clause and generics. +pub struct Foo<'a, Y: Baz> +where + X: Whatever, +{ + f: SomeType, // Comment beside a field +} + +struct Baz { + a: A, // Comment A + b: B, // Comment B + c: C, // Comment C +} + +struct Baz { + a: A, // Comment A + + b: B, // Comment B + + c: C, // Comment C +} + +struct Baz { + a: A, + + b: B, + c: C, + + d: D, +} + +struct Baz { + // Comment A + a: A, + + // Comment B + b: B, + // Comment C + c: C, +} + +// Will this be a one-liner? +struct Tuple( + A, // Comment + B, +); + +pub struct State time::Timespec> { + now: F, +} + +pub struct State ()> { + now: F, +} + +pub struct State { + now: F, +} + +struct Palette { + /// A map of indices in the palette to a count of pixels in approximately + /// that color + foo: i32, +} + +// Splitting a single line comment into a block previously had a misalignment +// when the field had attributes +struct FieldsWithAttributes { + // Pre Comment + #[rustfmt::skip] pub host:String, /* Post comment BBBBBBBBBBBBBB BBBBBBBBBBBBBBBB + * BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBB BBBBBBBBBBB */ + // Another pre comment + #[attr1] + #[attr2] + pub id: usize, /* CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCC + * CCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCC CCCCCCCCCCCC */ +} + +struct Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: + node::Handle>, Type, NodeType>, +} + +struct Foo(T); +struct Foo(T) +where + T: Copy, + T: Eq; +struct Foo( + TTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUUUUUU, + TTTTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUU, +); +struct Foo( + TTTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUUUUUU, + TTTTTTTTTTTTTTTTTTT, +) +where + T: PartialEq; +struct Foo( + TTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUUUUUU, + TTTTTTTTTTTTTTTTTTTTT, +) +where + T: PartialEq; +struct Foo( + TTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUUUUUU, + TTTTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUU, +) +where + T: PartialEq; +struct Foo( + TTTTTTTTTTTTTTTTT, // Foo + UUUUUUUUUUUUUUUUUUUUUUUU, // Bar + // Baz + TTTTTTTTTTTTTTTTTTT, + // Qux (FIXME #572 - doc comment) + UUUUUUUUUUUUUUUUUUU, +); + +mod m { + struct X + where + T: Sized, + { + a: T, + } +} + +struct Foo( + TTTTTTTTTTTTTTTTTTT, + /// Qux + UUUUUUUUUUUUUUUUUUU, +); + +struct Issue677 { + pub ptr: *const libc::c_void, + pub trace: fn(obj: *const libc::c_void, tracer: *mut JSTracer), +} + +struct Foo {} +struct Foo {} +struct Foo { + // comment +} +struct Foo { + // trailing space -> +} +struct Foo { + // comment +} +struct Foo( + // comment +); + +struct LongStruct { + a: A, + the_quick_brown_fox_jumps_over_the_lazy_dog: + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +} + +struct Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: + node::Handle>, Type, NodeType>, +} + +struct Foo(String); + +// #1364 +fn foo() { + convex_shape.set_point(0, &Vector2f { x: 400.0, y: 100.0 }); + convex_shape.set_point(1, &Vector2f { x: 500.0, y: 70.0 }); + convex_shape.set_point(2, &Vector2f { x: 450.0, y: 100.0 }); + convex_shape.set_point(3, &Vector2f { x: 580.0, y: 150.0 }); +} + +fn main() { + let x = Bar; + + // Comment + let y = Foo { a: x }; + + Foo { + a: foo(), // comment + // comment + b: bar(), + ..something + }; + + Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: f(), b: b() }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + a: f(), + b: b(), + }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + // Comment + a: foo(), // Comment + // Comment + b: bar(), // Comment + }; + + Foo { a: Bar, b: f() }; + + Quux { + x: if cond { + bar(); + }, + y: baz(), + }; + + A { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit + // amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante + // hendrerit. Donec et mollis dolor. + first: item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + second: Item, + }; + + Some(Data::MethodCallData(MethodCallData { + span: sub_span.unwrap(), + scope: self.enclosing_scope(id), + ref_id: def_id, + decl_id: Some(decl_id), + })); + + Diagram { + // o This graph demonstrates how + // / \ significant whitespace is + // o o preserved. + // /|\ \ + // o o o o + graph: G, + } +} + +fn matcher() { + TagTerminatedByteMatcher { + matcher: ByteMatcher { + pattern: b" { + memb: T, + } + let foo = Foo:: { memb: 10 }; +} + +fn issue201() { + let s = S { a: 0, ..b }; +} + +fn issue201_2() { + let s = S { a: S2 { ..c }, ..b }; +} + +fn issue278() { + let s = S { + a: 0, + // + b: 0, + }; + let s1 = S { + a: 0, + // foo + // + // bar + b: 0, + }; +} + +fn struct_exprs() { + Foo { a: 1, b: f(2) }; + Foo { + a: 1, + b: f(2), + ..g(3) + }; + LoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongStruct { + ..base + }; + IntrinsicISizesContribution { + content_intrinsic_sizes: IntrinsicISizes { + minimum_inline_size: 0, + }, + }; +} + +fn issue123() { + Foo { a: b, c: d, e: f }; + + Foo { + a: bb, + c: dd, + e: ff, + }; + + Foo { + a: ddddddddddddddddddddd, + b: cccccccccccccccccccccccccccccccccccccc, + }; +} + +fn issue491() { + Foo { + guard: None, + arm: 0, // Comment + }; + + Foo { + arm: 0, // Comment + }; + + Foo { + a: aaaaaaaaaa, + b: bbbbbbbb, + c: cccccccccc, + d: dddddddddd, // a comment + e: eeeeeeeee, + }; +} + +fn issue698() { + Record { + ffffffffffffffffffffffffffields: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + }; + Record { + ffffffffffffffffffffffffffields: + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + } +} + +fn issue835() { + MyStruct {}; + MyStruct { /* a comment */ }; + MyStruct { + // Another comment + }; + MyStruct {} +} + +fn field_init_shorthand() { + MyStruct { x, y, z }; + MyStruct { x, y, z, ..base }; + Foo { + aaaaaaaaaa, + bbbbbbbb, + cccccccccc, + dddddddddd, // a comment + eeeeeeeee, + }; + Record { + ffffffffffffffffffffffffffieldsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + }; +} diff --git a/src/tools/rustfmt/tests/target/configs/struct_lit_single_line/false.rs b/src/tools/rustfmt/tests/target/configs/struct_lit_single_line/false.rs new file mode 100644 index 0000000000..e2732b5a7c --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/struct_lit_single_line/false.rs @@ -0,0 +1,9 @@ +// rustfmt-struct_lit_single_line: false +// Struct literal multiline-style + +fn main() { + let lorem = Lorem { + ipsum: dolor, + sit: amet, + }; +} diff --git a/src/tools/rustfmt/tests/target/configs/tab_spaces/2.rs b/src/tools/rustfmt/tests/target/configs/tab_spaces/2.rs new file mode 100644 index 0000000000..85961706ea --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/tab_spaces/2.rs @@ -0,0 +1,14 @@ +// rustfmt-tab_spaces: 2 +// rustfmt-max_width: 30 +// rustfmt-indent_style: Block +// Tab spaces + +fn lorem() { + let ipsum = dolor(); + let sit = vec![ + "amet", + "consectetur", + "adipiscing", + "elit.", + ]; +} diff --git a/src/tools/rustfmt/tests/target/configs/tab_spaces/4.rs b/src/tools/rustfmt/tests/target/configs/tab_spaces/4.rs new file mode 100644 index 0000000000..524a55121e --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/tab_spaces/4.rs @@ -0,0 +1,14 @@ +// rustfmt-tab_spaces: 4 +// rustfmt-max_width: 30 +// rustfmt-indent_style: Block +// Tab spaces + +fn lorem() { + let ipsum = dolor(); + let sit = vec![ + "amet", + "consectetur", + "adipiscing", + "elit.", + ]; +} diff --git a/src/tools/rustfmt/tests/target/configs/trailing_comma/always.rs b/src/tools/rustfmt/tests/target/configs/trailing_comma/always.rs new file mode 100644 index 0000000000..951dc68091 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/trailing_comma/always.rs @@ -0,0 +1,14 @@ +// rustfmt-trailing_comma: Always +// Trailing comma + +fn main() { + let Lorem { ipsum, dolor, sit, } = amet; + let Lorem { + ipsum, + dolor, + sit, + amet, + consectetur, + adipiscing, + } = elit; +} diff --git a/src/tools/rustfmt/tests/target/configs/trailing_comma/never.rs b/src/tools/rustfmt/tests/target/configs/trailing_comma/never.rs new file mode 100644 index 0000000000..ae0e50f96d --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/trailing_comma/never.rs @@ -0,0 +1,35 @@ +// rustfmt-trailing_comma: Never +// Trailing comma + +fn main() { + let Lorem { ipsum, dolor, sit } = amet; + let Lorem { + ipsum, + dolor, + sit, + amet, + consectetur, + adipiscing + } = elit; + + // #1544 + if let VrMsg::ClientReply { + request_num: reply_req_num, + value, + .. + } = msg + { + let _ = safe_assert_eq!(reply_req_num, request_num, op); + return Ok((request_num, op, value)); + } + + // #1710 + pub struct FileInput { + input: StringInput, + file_name: OsString + } + match len { + Some(len) => Ok(new(self.input, self.pos + len)), + None => Err(self) + } +} diff --git a/src/tools/rustfmt/tests/target/configs/trailing_comma/vertical.rs b/src/tools/rustfmt/tests/target/configs/trailing_comma/vertical.rs new file mode 100644 index 0000000000..7283cde8d0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/trailing_comma/vertical.rs @@ -0,0 +1,14 @@ +// rustfmt-trailing_comma: Vertical +// Trailing comma + +fn main() { + let Lorem { ipsum, dolor, sit } = amet; + let Lorem { + ipsum, + dolor, + sit, + amet, + consectetur, + adipiscing, + } = elit; +} diff --git a/src/tools/rustfmt/tests/target/configs/trailing_semicolon/false.rs b/src/tools/rustfmt/tests/target/configs/trailing_semicolon/false.rs new file mode 100644 index 0000000000..9fa746e9c0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/trailing_semicolon/false.rs @@ -0,0 +1,27 @@ +// rustfmt-trailing_semicolon: false + +#![feature(loop_break_value)] + +fn main() { + 'a: loop { + break 'a + } + + let mut done = false; + 'b: while !done { + done = true; + continue 'b + } + + let x = loop { + break 5 + }; + + let x = 'c: loop { + break 'c 5 + }; +} + +fn foo() -> usize { + return 0 +} diff --git a/src/tools/rustfmt/tests/target/configs/trailing_semicolon/true.rs b/src/tools/rustfmt/tests/target/configs/trailing_semicolon/true.rs new file mode 100644 index 0000000000..61b6843d67 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/trailing_semicolon/true.rs @@ -0,0 +1,27 @@ +// rustfmt-trailing_semicolon: true + +#![feature(loop_break_value)] + +fn main() { + 'a: loop { + break 'a; + } + + let mut done = false; + 'b: while !done { + done = true; + continue 'b; + } + + let x = loop { + break 5; + }; + + let x = 'c: loop { + break 'c 5; + }; +} + +fn foo() -> usize { + return 0; +} diff --git a/src/tools/rustfmt/tests/target/configs/type_punctuation_density/compressed.rs b/src/tools/rustfmt/tests/target/configs/type_punctuation_density/compressed.rs new file mode 100644 index 0000000000..6571e448e4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/type_punctuation_density/compressed.rs @@ -0,0 +1,41 @@ +// rustfmt-type_punctuation_density: Compressed +// Type punctuation density + +fn lorem() { + // body +} + +struct Foo +where + U: Eq+Clone, { + // body +} + +trait Foo<'a, T=usize> +where + T: 'a+Eq+Clone, +{ + type Bar: Eq+Clone; +} + +trait Foo: Eq+Clone { + // body +} + +impl Foo<'a> for Bar +where + for<'a> T: 'a+Eq+Clone, +{ + // body +} + +fn foo<'a, 'b, 'c>() +where + 'a: 'b+'c, +{ + // body +} + +fn Foo+Foo>() { + let i = 6; +} diff --git a/src/tools/rustfmt/tests/target/configs/type_punctuation_density/wide.rs b/src/tools/rustfmt/tests/target/configs/type_punctuation_density/wide.rs new file mode 100644 index 0000000000..01546c7b0a --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/type_punctuation_density/wide.rs @@ -0,0 +1,41 @@ +// rustfmt-type_punctuation_density: Wide +// Type punctuation density + +fn lorem() { + // body +} + +struct Foo +where + U: Eq + Clone, { + // body +} + +trait Foo<'a, T = usize> +where + T: 'a + Eq + Clone, +{ + type Bar: Eq + Clone; +} + +trait Foo: Eq + Clone { + // body +} + +impl Foo<'a> for Bar +where + for<'a> T: 'a + Eq + Clone, +{ + // body +} + +fn foo<'a, 'b, 'c>() +where + 'a: 'b + 'c, +{ + // body +} + +fn Foo + Foo>() { + let i = 6; +} diff --git a/src/tools/rustfmt/tests/target/configs/use_field_init_shorthand/false.rs b/src/tools/rustfmt/tests/target/configs/use_field_init_shorthand/false.rs new file mode 100644 index 0000000000..7433044688 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/use_field_init_shorthand/false.rs @@ -0,0 +1,15 @@ +// rustfmt-use_field_init_shorthand: false +// Use field initialization shorthand if possible. + +fn main() { + let a = Foo { x: x, y: y, z: z }; + + let b = Bar { + x: x, + y: y, + #[attr] + z: z, + #[rustfmt::skip] + skipped: skipped, + }; +} diff --git a/src/tools/rustfmt/tests/target/configs/use_field_init_shorthand/true.rs b/src/tools/rustfmt/tests/target/configs/use_field_init_shorthand/true.rs new file mode 100644 index 0000000000..8b80e81534 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/use_field_init_shorthand/true.rs @@ -0,0 +1,15 @@ +// rustfmt-use_field_init_shorthand: true +// Use field initialization shorthand if possible. + +fn main() { + let a = Foo { x, y, z }; + + let b = Bar { + x, + y, + #[attr] + z, + #[rustfmt::skip] + skipped: skipped, + }; +} diff --git a/src/tools/rustfmt/tests/target/configs/use_small_heuristics/max.rs b/src/tools/rustfmt/tests/target/configs/use_small_heuristics/max.rs new file mode 100644 index 0000000000..785dfbea01 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/use_small_heuristics/max.rs @@ -0,0 +1,15 @@ +// rustfmt-use_small_heuristics: Max + +enum Lorem { + Ipsum, + Dolor(bool), + Sit { amet: Consectetur, adipiscing: Elit }, +} + +fn main() { + lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); + + let lorem = Lorem { ipsum: dolor, sit: amet }; + + let lorem = if ipsum { dolor } else { sit }; +} diff --git a/src/tools/rustfmt/tests/target/configs/use_try_shorthand/false.rs b/src/tools/rustfmt/tests/target/configs/use_try_shorthand/false.rs new file mode 100644 index 0000000000..de7f8b4a5e --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/use_try_shorthand/false.rs @@ -0,0 +1,6 @@ +// rustfmt-use_try_shorthand: false +// Use try! shorthand + +fn main() { + let lorem = try!(ipsum.map(|dolor| dolor.sit())); +} diff --git a/src/tools/rustfmt/tests/target/configs/use_try_shorthand/true.rs b/src/tools/rustfmt/tests/target/configs/use_try_shorthand/true.rs new file mode 100644 index 0000000000..d3aa035792 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/use_try_shorthand/true.rs @@ -0,0 +1,6 @@ +// rustfmt-use_try_shorthand: true +// Use try! shorthand + +fn main() { + let lorem = ipsum.map(|dolor| dolor.sit())?; +} diff --git a/src/tools/rustfmt/tests/target/configs/where_single_line/true-with-brace-style.rs b/src/tools/rustfmt/tests/target/configs/where_single_line/true-with-brace-style.rs new file mode 100644 index 0000000000..ec7f79b689 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/where_single_line/true-with-brace-style.rs @@ -0,0 +1,22 @@ +// rustfmt-brace_style: SameLineWhere +// rustfmt-where_single_line: true + +fn lorem_multi_line_clauseless( + a: Aaaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbbbb, + c: Ccccccccccccccccc, + d: Ddddddddddddddddddddddddd, + e: Eeeeeeeeeeeeeeeeeee, +) -> T { + // body +} + +fn lorem_multi_line_clauseless( + a: Aaaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbbbb, + c: Ccccccccccccccccc, + d: Ddddddddddddddddddddddddd, + e: Eeeeeeeeeeeeeeeeeee, +) { + // body +} diff --git a/src/tools/rustfmt/tests/target/configs/where_single_line/true.rs b/src/tools/rustfmt/tests/target/configs/where_single_line/true.rs new file mode 100644 index 0000000000..7f816459e3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/where_single_line/true.rs @@ -0,0 +1,30 @@ +// rustfmt-where_single_line: true +// Where style + +fn lorem_two_items() -> T +where + Ipsum: Eq, + Lorem: Eq, +{ + // body +} + +fn lorem_multi_line( + a: Aaaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbbbb, + c: Ccccccccccccccccc, + d: Ddddddddddddddddddddddddd, + e: Eeeeeeeeeeeeeeeeeee, +) -> T +where + Ipsum: Eq, +{ + // body +} + +fn lorem() -> T +where Ipsum: Eq { + // body +} + +unsafe impl Sync for Foo where (): Send {} diff --git a/src/tools/rustfmt/tests/target/configs/wrap_comments/false.rs b/src/tools/rustfmt/tests/target/configs/wrap_comments/false.rs new file mode 100644 index 0000000000..48ecd88acc --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/wrap_comments/false.rs @@ -0,0 +1,8 @@ +// rustfmt-wrap_comments: false +// rustfmt-max_width: 50 +// rustfmt-error_on_line_overflow: false +// Wrap comments + +fn main() { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +} diff --git a/src/tools/rustfmt/tests/target/configs/wrap_comments/true.rs b/src/tools/rustfmt/tests/target/configs/wrap_comments/true.rs new file mode 100644 index 0000000000..4096fd4d89 --- /dev/null +++ b/src/tools/rustfmt/tests/target/configs/wrap_comments/true.rs @@ -0,0 +1,20 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 50 +// Wrap comments + +fn main() { + // Lorem ipsum dolor sit amet, consectetur + // adipiscing elit, sed do eiusmod tempor + // incididunt ut labore et dolore magna + // aliqua. Ut enim ad minim veniam, quis + // nostrud exercitation ullamco laboris nisi + // ut aliquip ex ea commodo consequat. +} + +fn code_block() { + // ```rust + // let x = 3; + // + // println!("x = {}", x); + // ``` +} diff --git a/src/tools/rustfmt/tests/target/const_generics.rs b/src/tools/rustfmt/tests/target/const_generics.rs new file mode 100644 index 0000000000..b30b7b58c1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/const_generics.rs @@ -0,0 +1,37 @@ +struct Message { + field2: Vec<"MessageEntity">, + field3: Vec<1>, + field4: Vec<2, 3>, +} + +struct RectangularArray { + array: [[T; WIDTH]; HEIGHT], +} + +fn main() { + const X: usize = 7; + let x: RectangularArray; + let y: RectangularArray; +} + +fn foo() { + const Y: usize = X * 2; + static Z: (usize, usize) = (X, X); + + struct Foo([i32; X]); +} + +type Foo = [i32; N + 1]; + +pub trait Foo: Bar<{ Baz::COUNT }> { + const ASD: usize; +} + +// #4263 +fn const_generics_on_params< + // AAAA + const BBBB: usize, + /* CCCC */ + const DDDD: usize, +>() { +} diff --git a/src/tools/rustfmt/tests/target/control-brace-style-always-next-line.rs b/src/tools/rustfmt/tests/target/control-brace-style-always-next-line.rs new file mode 100644 index 0000000000..054a3075ca --- /dev/null +++ b/src/tools/rustfmt/tests/target/control-brace-style-always-next-line.rs @@ -0,0 +1,50 @@ +// rustfmt-control_brace_style: AlwaysNextLine + +fn main() { + loop + { + (); + (); + } + + 'label: loop + // loop comment + { + (); + } + + cond = true; + while cond + { + (); + } + + 'while_label: while cond + { + // while comment + (); + } + + for obj in iter + { + for sub_obj in obj + { + 'nested_while_label: while cond + { + (); + } + } + } + + match some_var + { + // match comment + pattern0 => val0, + pattern1 => val1, + pattern2 | pattern3 => + { + do_stuff(); + val2 + } + }; +} diff --git a/src/tools/rustfmt/tests/target/control-brace-style-always-same-line.rs b/src/tools/rustfmt/tests/target/control-brace-style-always-same-line.rs new file mode 100644 index 0000000000..cf3f82dfcf --- /dev/null +++ b/src/tools/rustfmt/tests/target/control-brace-style-always-same-line.rs @@ -0,0 +1,40 @@ +fn main() { + loop { + (); + (); + } + + 'label: loop + // loop comment + { + (); + } + + cond = true; + while cond { + (); + } + + 'while_label: while cond { + // while comment + (); + } + + for obj in iter { + for sub_obj in obj { + 'nested_while_label: while cond { + (); + } + } + } + + match some_var { + // match comment + pattern0 => val0, + pattern1 => val1, + pattern2 | pattern3 => { + do_stuff(); + val2 + } + }; +} diff --git a/src/tools/rustfmt/tests/target/doc-attrib.rs b/src/tools/rustfmt/tests/target/doc-attrib.rs new file mode 100644 index 0000000000..36527b7cd4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/doc-attrib.rs @@ -0,0 +1,131 @@ +// rustfmt-wrap_comments: true +// rustfmt-normalize_doc_attributes: true + +// Only doc = "" attributes should be normalized +//! Example doc attribute comment +//! Example doc attribute comment with 10 leading spaces +#![doc( + html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://doc.rust-lang.org/favicon.ico", + html_root_url = "https://doc.rust-lang.org/nightly/", + html_playground_url = "https://play.rust-lang.org/", + test(attr(deny(warnings))) +)] + +// Long `#[doc = "..."]` +struct A { + /// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + b: i32, +} + +/// The `nodes` and `edges` method each return instantiations of `Cow<[T]>` to +/// leave implementers the freedom to create entirely new vectors or to pass +/// back slices into internally owned vectors. +struct B { + b: i32, +} + +/// Level 1 comment +mod tests { + /// Level 2 comment + impl A { + /// Level 3 comment + fn f() { + /// Level 4 comment + fn g() {} + } + } +} + +struct C { + /// item doc attrib comment + // regular item comment + b: i32, + + // regular item comment + /// item doc attrib comment + c: i32, +} + +// non-regression test for regular attributes, from #2647 +#[cfg( + feature = "this_line_is_101_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +)] +pub fn foo() {} + +// path attrs +#[clippy::bar] +#[clippy::bar(a, b, c)] +pub fn foo() {} + +mod issue_2620 { + #[derive(Debug, StructOpt)] + #[structopt(about = "Display information about the character on FF Logs")] + pub struct Params { + #[structopt(help = "The server the character is on")] + server: String, + #[structopt(help = "The character's first name")] + first_name: String, + #[structopt(help = "The character's last name")] + last_name: String, + #[structopt( + short = "j", + long = "job", + help = "The job to look at", + parse(try_from_str) + )] + job: Option, + } +} + +// non-regression test for regular attributes, from #2969 +#[cfg(not(all( + feature = "std", + any( + target_os = "linux", + target_os = "android", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "haiku", + target_os = "emscripten", + target_os = "solaris", + target_os = "cloudabi", + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", + target_os = "redox", + target_os = "fuchsia", + windows, + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), + ) +)))] +type Os = NoSource; + +// use cases from bindgen needing precise control over leading spaces +///
+#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct ContradictAccessors { + ///no leading spaces here + pub mBothAccessors: ::std::os::raw::c_int, + ///
+ pub mNoAccessors: ::std::os::raw::c_int, + ///
+ pub mUnsafeAccessors: ::std::os::raw::c_int, + ///
+ pub mImmutableAccessor: ::std::os::raw::c_int, +} + +/// \brief MPI structure +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct mbedtls_mpi { + ///< integer sign + pub s: ::std::os::raw::c_int, + ///< total # of limbs + pub n: ::std::os::raw::c_ulong, + ///< pointer to limbs + pub p: *mut mbedtls_mpi_uint, +} diff --git a/src/tools/rustfmt/tests/target/doc-comment-with-example.rs b/src/tools/rustfmt/tests/target/doc-comment-with-example.rs new file mode 100644 index 0000000000..c5a4e779ea --- /dev/null +++ b/src/tools/rustfmt/tests/target/doc-comment-with-example.rs @@ -0,0 +1,11 @@ +// rustfmt-format_code_in_doc_comments: true + +/// Foo +/// +/// # Example +/// ``` +/// # #![cfg_attr(not(dox), feature(cfg_target_feature, target_feature, stdsimd))] +/// # #![cfg_attr(not(dox), no_std)] +/// fn foo() {} +/// ``` +fn foo() {} diff --git a/src/tools/rustfmt/tests/target/doc.rs b/src/tools/rustfmt/tests/target/doc.rs new file mode 100644 index 0000000000..0f9e2d21c1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/doc.rs @@ -0,0 +1,5 @@ +// rustfmt-normalize_comments: true +// Part of multiple.rs + +// sadfsdfa +// sdffsdfasdf diff --git a/src/tools/rustfmt/tests/target/dyn_trait.rs b/src/tools/rustfmt/tests/target/dyn_trait.rs new file mode 100644 index 0000000000..b6e2810a57 --- /dev/null +++ b/src/tools/rustfmt/tests/target/dyn_trait.rs @@ -0,0 +1,27 @@ +#![feature(dyn_trait)] + +fn main() { + // #2506 + // checks rustfmt doesn't remove dyn + trait MyTrait { + fn method(&self) -> u64; + } + fn f1(a: Box) {} + + // checks if line wrap works correctly + trait Very_______________________Long__________________Name_______________________________Trait + { + fn method(&self) -> u64; + } + + fn f2( + a: Box< + dyn Very_______________________Long__________________Name____________________Trait + + 'static, + >, + ) { + } + + // #2582 + let _: &dyn (::std::any::Any) = &msg; +} diff --git a/src/tools/rustfmt/tests/target/else-if-brace-style-always-next-line.rs b/src/tools/rustfmt/tests/target/else-if-brace-style-always-next-line.rs new file mode 100644 index 0000000000..31e12cfa0d --- /dev/null +++ b/src/tools/rustfmt/tests/target/else-if-brace-style-always-next-line.rs @@ -0,0 +1,53 @@ +// rustfmt-control_brace_style: AlwaysNextLine + +fn main() { + if false + { + (); + (); + } + + if false + // lone if comment + { + (); + (); + } + + let a = if 0 > 1 { unreachable!() } else { 0x0 }; + + if true + { + (); + } + else if false + { + (); + (); + } + else + { + (); + (); + (); + } + + if true + // else-if-chain if comment + { + (); + } + else if false + // else-if-chain else-if comment + { + (); + (); + } + else + // else-if-chain else comment + { + (); + (); + (); + } +} diff --git a/src/tools/rustfmt/tests/target/else-if-brace-style-always-same-line.rs b/src/tools/rustfmt/tests/target/else-if-brace-style-always-same-line.rs new file mode 100644 index 0000000000..07b71fd790 --- /dev/null +++ b/src/tools/rustfmt/tests/target/else-if-brace-style-always-same-line.rs @@ -0,0 +1,43 @@ +fn main() { + if false { + (); + (); + } + + if false + // lone if comment + { + (); + (); + } + + let a = if 0 > 1 { unreachable!() } else { 0x0 }; + + if true { + (); + } else if false { + (); + (); + } else { + (); + (); + (); + } + + if true + // else-if-chain if comment + { + (); + } else if false + // else-if-chain else-if comment + { + (); + (); + } else + // else-if-chain else comment + { + (); + (); + (); + } +} diff --git a/src/tools/rustfmt/tests/target/else-if-brace-style-closing-next-line.rs b/src/tools/rustfmt/tests/target/else-if-brace-style-closing-next-line.rs new file mode 100644 index 0000000000..c99807dc06 --- /dev/null +++ b/src/tools/rustfmt/tests/target/else-if-brace-style-closing-next-line.rs @@ -0,0 +1,49 @@ +// rustfmt-control_brace_style: ClosingNextLine + +fn main() { + if false { + (); + (); + } + + if false + // lone if comment + { + (); + (); + } + + let a = if 0 > 1 { unreachable!() } else { 0x0 }; + + if true { + (); + } + else if false { + (); + (); + } + else { + (); + (); + (); + } + + if true + // else-if-chain if comment + { + (); + } + else if false + // else-if-chain else-if comment + { + (); + (); + } + else + // else-if-chain else comment + { + (); + (); + (); + } +} diff --git a/src/tools/rustfmt/tests/target/empty-tuple-no-conversion-to-unit-struct.rs b/src/tools/rustfmt/tests/target/empty-tuple-no-conversion-to-unit-struct.rs new file mode 100644 index 0000000000..0b9a15e8a9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/empty-tuple-no-conversion-to-unit-struct.rs @@ -0,0 +1,12 @@ +enum TestEnum { + Arm1(), + Arm2, +} + +fn foo() { + let test = TestEnum::Arm1; + match test { + TestEnum::Arm1() => {} + TestEnum::Arm2 => {} + } +} diff --git a/src/tools/rustfmt/tests/target/empty_file.rs b/src/tools/rustfmt/tests/target/empty_file.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/target/empty_file.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/target/enum.rs b/src/tools/rustfmt/tests/target/enum.rs new file mode 100644 index 0000000000..9a25126b44 --- /dev/null +++ b/src/tools/rustfmt/tests/target/enum.rs @@ -0,0 +1,289 @@ +// rustfmt-wrap_comments: true +// Enums test + +#[atrr] +pub enum Test { + A, + B(u32, A /* comment */, SomeType), + /// Doc comment + C, +} + +pub enum Foo<'a, Y: Baz> +where + X: Whatever, +{ + A, +} + +enum EmtpyWithComment { + // Some comment +} + +// C-style enum +enum Bar { + A = 1, + #[someAttr(test)] + B = 2, // comment + C, +} + +enum LongVariants { + First( + LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONG, // comment + VARIANT, + ), + // This is the second variant + Second, +} + +enum StructLikeVariants { + Normal(u32, String), + StructLike { + x: i32, // Test comment + // Pre-comment + #[Attr50] + y: SomeType, // Aanother Comment + }, + SL { + a: A, + }, +} + +enum X { + CreateWebGLPaintTask( + Size2D, + GLContextAttributes, + IpcSender, usize), String>>, + ), // This is a post comment +} + +pub enum EnumWithAttributes { + //This is a pre comment + // AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + TupleVar(usize, usize, usize), /* AAAA AAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA + * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA */ + // Pre Comment + #[rustfmt::skip] + SkippedItem(String,String,), // Post-comment + #[another_attr] + #[attr2] + ItemStruct { + x: usize, + y: usize, + }, /* Comment AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA */ + // And another + ForcedPreflight, /* AAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA */ +} + +pub enum SingleTuple { + // Pre Comment AAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + // AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + Match(usize, usize, String), /* Post-comment AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA */ +} + +pub enum SingleStruct { + Match { name: String, loc: usize }, // Post-comment +} + +pub enum GenericEnum +where + I: Iterator, +{ + // Pre Comment + Left { list: I, root: T }, // Post-comment + Right { list: I, root: T }, // Post Comment +} + +enum EmtpyWithComment { + // Some comment +} + +enum TestFormatFails { + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +} + +fn nested_enum_test() { + if true { + enum TestEnum { + One( + usize, + usize, + usize, + usize, + usize, + usize, + usize, + usize, + usize, + usize, + usize, + usize, + usize, + usize, + usize, + usize, + ), /* AAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAA + * AAAAAAAAAAAAAAAAAAAAAA */ + Two, /* AAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + * AAAAAAAAAAAAAAAAAA */ + } + enum TestNestedFormatFail { + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + } + } +} + +pub struct EmtpyWithComment { + // FIXME: Implement this struct +} + +// #1115 +pub enum Bencoding<'i> { + Str(&'i [u8]), + Int(i64), + List(Vec>), + /// A bencoded dict value. The first element the slice of bytes in the + /// source that the dict is composed of. The second is the dict, decoded + /// into an ordered map. + // TODO make Dict "structlike" AKA name the two values. + Dict(&'i [u8], BTreeMap<&'i [u8], Bencoding<'i>>), +} + +// #1261 +pub enum CoreResourceMsg { + SetCookieForUrl( + ServoUrl, + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + Cookie, + CookieSource, + ), +} + +enum Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong +{} +enum Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong +{} +enum Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong +{} +enum Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong +{ + Foo, +} + +// #1046 +pub enum Entry<'a, K: 'a, V: 'a> { + Vacant(#[stable(feature = "rust1", since = "1.0.0")] VacantEntry<'a, K, V>), + Occupied(#[stable(feature = "rust1", since = "1.0.0")] OccupiedEntry<'a, K, V>), +} + +// #2081 +pub enum ForegroundColor { + CYAN = + (winapi::FOREGROUND_INTENSITY | winapi::FOREGROUND_GREEN | winapi::FOREGROUND_BLUE) as u16, +} + +// #2098 +pub enum E<'a> { + V( as Iterator>::Item), +} + +// #1809 +enum State { + TryRecv { + pos: usize, + lap: u8, + closed_count: usize, + }, + Subscribe { + pos: usize, + }, + IsReady { + pos: usize, + ready: bool, + }, + Unsubscribe { + pos: usize, + lap: u8, + id_woken: usize, + }, + FinalTryRecv { + pos: usize, + id_woken: usize, + }, + TimedOut, + Disconnected, +} + +// #2190 +#[derive(Debug, Fail)] +enum AnError { + #[fail( + display = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + )] + UnexpectedSingleToken { token: syn::Token }, +} + +// #2193 +enum WidthOf101 { + #[fail(display = ".....................................................")] + Io(::std::io::Error), + #[fail(display = ".....................................................")] + Ioo(::std::io::Error), + Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(::std::io::Error), + Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( + ::std::io::Error, + ), +} + +// #2389 +pub enum QlError { + #[fail(display = "Parsing error: {}", 0)] + LexError(parser::lexer::LexError), + #[fail(display = "Parsing error: {:?}", 0)] + ParseError(parser::ParseError), + #[fail(display = "Validation error: {:?}", 0)] + ValidationError(Vec), + #[fail(display = "Execution error: {}", 0)] + ExecutionError(String), + // (from, to) + #[fail(display = "Translation error: from {} to {}", 0, 1)] + TranslationError(String, String), + // (kind, input, expected) + #[fail( + display = "aaaaaaaaaaaaCould not find {}: Found: {}, expected: {:?}", + 0, 1, 2 + )] + ResolveError(&'static str, String, Option), +} + +// #2594 +enum Foo {} +enum Bar {} + +// #3562 +enum PublishedFileVisibility { + Public = + sys::ERemoteStoragePublishedFileVisibility_k_ERemoteStoragePublishedFileVisibilityPublic, + FriendsOnly = sys::ERemoteStoragePublishedFileVisibility_k_ERemoteStoragePublishedFileVisibilityFriendsOnly, + Private = + sys::ERemoteStoragePublishedFileVisibility_k_ERemoteStoragePublishedFileVisibilityPrivate, +} + +// #3771 +//#![feature(arbitrary_enum_discriminant)] +#[repr(u32)] +pub enum E { + A { + a: u32, + } = 0x100, + B { + field1: u32, + field2: u8, + field3: m::M, + } = 0x300, // comment +} diff --git a/src/tools/rustfmt/tests/target/existential_type.rs b/src/tools/rustfmt/tests/target/existential_type.rs new file mode 100644 index 0000000000..ffc206875b --- /dev/null +++ b/src/tools/rustfmt/tests/target/existential_type.rs @@ -0,0 +1,23 @@ +// Opaque type. + +#![feature(type_alias_impl_trait)] + +pub type Adder +where + T: Clone, + F: Copy, += impl Fn(T) -> T; + +pub type Adderrr = impl Fn(T) -> T; + +impl Foo for Bar { + type E = impl Trait; +} + +pub type Adder_without_impl +where + T: Clone, + F: Copy, += Fn(T) -> T; + +pub type Adderrr_without_impl = Fn(T) -> T; diff --git a/src/tools/rustfmt/tests/target/expr-block.rs b/src/tools/rustfmt/tests/target/expr-block.rs new file mode 100644 index 0000000000..c577006508 --- /dev/null +++ b/src/tools/rustfmt/tests/target/expr-block.rs @@ -0,0 +1,305 @@ +// Test expressions with block formatting. + +fn arrays() { + []; + let empty = []; + + let foo = [a_long_name, a_very_lng_name, a_long_name]; + + let foo = [ + a_long_name, + a_very_lng_name, + a_long_name, + a_very_lng_name, + a_long_name, + a_very_lng_name, + a_long_name, + a_very_lng_name, + ]; + + vec![ + a_long_name, + a_very_lng_name, + a_long_name, + a_very_lng_name, + a_long_name, + a_very_lng_name, + a_very_lng_name, + ]; + + [ + a_long_name, + a_very_lng_name, + a_long_name, + a_very_lng_name, + a_long_name, + a_very_lng_name, + a_very_lng_name, + ] +} + +fn arrays() { + let x = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 7, 8, 9, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 0, + ]; + + let y = [/* comment */ 1, 2 /* post comment */, 3]; + + let xy = [ + strukt { + test123: value_one_two_three_four, + turbo: coolio(), + }, + /* comment */ 1, + ]; + + let a = WeightedChoice::new(&mut [ + Weighted { weight: x, item: 0 }, + Weighted { weight: 1, item: 1 }, + Weighted { weight: x, item: 2 }, + Weighted { weight: 1, item: 3 }, + ]); + + let z = [ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + yyyyyyyyyyyyyyyyyyyyyyyyyyy, + zzzzzzzzzzzzzzzzz, + q, + ]; + + [1 + 3, 4, 5, 6, 7, 7, fncall::>(3 - 1)] +} + +fn function_calls() { + let items = itemize_list( + context.source_map, + args.iter(), + ")", + |item| item.span.lo(), + |item| item.span.hi(), + |item| { + item.rewrite( + context, + Shape { + width: remaining_width, + ..nested_shape + }, + ) + }, + span.lo(), + span.hi(), + ); + + itemize_list( + context.source_map, + args.iter(), + ")", + |item| item.span.lo(), + |item| item.span.hi(), + |item| { + item.rewrite( + context, + Shape { + width: remaining_width, + ..nested_shape + }, + ) + }, + span.lo(), + span.hi(), + ) +} + +fn macros() { + baz!( + do_not, add, trailing, commas, inside, of, function, like, macros, even, if_they, are, long + ); + + baz!(one_item_macro_which_is_also_loooooooooooooooooooooooooooooooooooooooooooooooong); + + let _ = match option { + None => baz!( + function, + like, + macro_as, + expression, + which, + is, + loooooooooooooooong + ), + Some(p) => baz!(one_item_macro_as_expression_which_is_also_loooooooooooooooong), + }; +} + +fn issue_1450() { + if selfstate + .compare_exchandsfasdsdfgsdgsdfgsdfgsdfgsdfgsdfgfsfdsage_weak( + STATE_PARKED, + STATE_UNPARKED, + Release, + Relaxed, + Release, + Relaxed, + ) + .is_ok() + { + return; + } +} + +fn foo() { + if real_total <= limit + && !pre_line_comments + && !items.into_iter().any(|item| item.as_ref().is_multiline()) + { + DefinitiveListTactic::Horizontal + } +} + +fn combine_block() { + foo(Bar { + x: value, + y: value2, + }); + + foo((Bar { + x: value, + y: value2, + },)); + + foo(( + 1, + 2, + 3, + Bar { + x: value, + y: value2, + }, + )); + + foo((1, 2, 3, |x| { + let y = x + 1; + let z = y + 1; + z + })); + + let opt = Some(Struct( + long_argument_one, + long_argument_two, + long_argggggggg, + )); + + do_thing(|param| { + action(); + foo(param) + }); + + do_thing(x, |param| { + action(); + foo(param) + }); + + do_thing( + x, + (1, 2, 3, |param| { + action(); + foo(param) + }), + ); + + Ok(some_function( + lllllllllong_argument_one, + lllllllllong_argument_two, + lllllllllllllllllllllllllllllong_argument_three, + )); + + foo( + thing, + bar( + param2, + pparam1param1param1param1param1param1param1param1param1param1aram1, + param3, + ), + ); + + foo.map_or(|| { + Ok(SomeStruct { + f1: 0, + f2: 0, + f3: 0, + }) + }); + + match opt { + Some(x) => somefunc(anotherfunc( + long_argument_one, + long_argument_two, + long_argument_three, + )), + Some(x) => |x| { + let y = x + 1; + let z = y + 1; + z + }, + Some(x) => (1, 2, |x| { + let y = x + 1; + let z = y + 1; + z + }), + Some(x) => SomeStruct { + f1: long_argument_one, + f2: long_argument_two, + f3: long_argument_three, + }, + None => Ok(SomeStruct { + f1: long_argument_one, + f2: long_argument_two, + f3: long_argument_three, + }), + }; + + match x { + y => func(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx), + _ => func( + x, + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, + zzz, + ), + } +} + +fn issue_1862() { + foo( + /* bar = */ None, + something_something, + /* baz = */ None, + /* This comment waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay too long to be kept on the same line */ + None, + /* com */ + this_last_arg_is_tooooooooooooooooooooooooooooooooo_long_to_be_kept_with_the_pre_comment, + ) +} + +fn issue_3025() { + foo( + // This describes the argument below. + /* bar = */ None, + // This describes the argument below. + something_something, + // This describes the argument below. */ + None, + // This describes the argument below. + /* This comment waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay too long to be kept on the same line */ + None, + // This describes the argument below. + /* com */ + this_last_arg_is_tooooooooooooooooooooooooooooooooo_long_to_be_kept_with_the_pre_comment, + ) +} + +fn issue_1878() { + let channel: &str = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(2, &self))?; +} diff --git a/src/tools/rustfmt/tests/target/expr-overflow-delimited.rs b/src/tools/rustfmt/tests/target/expr-overflow-delimited.rs new file mode 100644 index 0000000000..b00e81fcd5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/expr-overflow-delimited.rs @@ -0,0 +1,120 @@ +// rustfmt-overflow_delimited_expr: true + +fn combine_blocklike() { + do_thing(|param| { + action(); + foo(param) + }); + + do_thing(x, |param| { + action(); + foo(param) + }); + + do_thing( + x, + // I'll be discussing the `action` with your para(m)legal counsel + |param| { + action(); + foo(param) + }, + ); + + do_thing(Bar { + x: value, + y: value2, + }); + + do_thing(x, Bar { + x: value, + y: value2, + }); + + do_thing( + x, + // Let me tell you about that one time at the `Bar` + Bar { + x: value, + y: value2, + }, + ); + + do_thing(&[ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ]); + + do_thing(x, &[ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ]); + + do_thing( + x, + // Just admit it; my list is longer than can be folded on to one line + &[ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ], + ); + + do_thing(vec![ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ]); + + do_thing(x, vec![ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ]); + + do_thing( + x, + // Just admit it; my list is longer than can be folded on to one line + vec![ + value_with_longer_name, + value2_with_longer_name, + value3_with_longer_name, + value4_with_longer_name, + ], + ); + + do_thing( + x, + (1, 2, 3, |param| { + action(); + foo(param) + }), + ); +} + +fn combine_struct_sample() { + let identity = verify(&ctx, VerifyLogin { + type_: LoginType::Username, + username: args.username.clone(), + password: Some(args.password.clone()), + domain: None, + })?; +} + +fn combine_macro_sample() { + rocket::ignite() + .mount("/", routes![ + http::auth::login, + http::auth::logout, + http::cors::options, + http::action::dance, + http::action::sleep, + ]) + .launch(); +} diff --git a/src/tools/rustfmt/tests/target/expr.rs b/src/tools/rustfmt/tests/target/expr.rs new file mode 100644 index 0000000000..84df802bc7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/expr.rs @@ -0,0 +1,671 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// Test expressions + +fn foo() -> bool { + let boxed: Box = box 5; + let referenced = &5; + + let very_long_variable_name = (a + first + simple + test); + let very_long_variable_name = + (a + first + simple + test + AAAAAAAAAAAAA + BBBBBBBBBBBBBBBBB + b + c); + + let is_internalxxxx = + self.source_map.span_to_filename(s) == self.source_map.span_to_filename(m.inner); + + let some_val = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * bbbb + / (bbbbbb - function_call(x, *very_long_pointer, y)) + + 1000; + + some_ridiculously_loooooooooooooooooooooong_function( + 10000 * 30000000000 + 40000 / 1002200000000 - 50000 * sqrt(-1), + trivial_value, + ); + (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + a + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aaaaa); + + { + for _ in 0..10 {} + } + + { + { + { + {} + } + } + } + + if 1 + 2 > 0 { + let result = 5; + result + } else { + 4 + }; + + if let Some(x) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { + // Nothing + } + + if let Some(x) = + (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) + {} + + if let ( + some_very_large, + tuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuple, + ) = 1 + 2 + 3 + {} + + if let ( + some_very_large, + tuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuple, + ) = 1111 + 2222 + {} + + if let ( + some_very_large, + tuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuple, + ) = 1 + 2 + 3 + {} + + if let ast::ItemKind::Trait(_, unsafety, ref generics, ref type_param_bounds, ref trait_items) = + item.node + { + // nothing + } + + let test = if true { 5 } else { 3 }; + + if cond() { + something(); + } else if different_cond() { + something_else(); + } else { + // Check subformatting + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + } + + // #2884 + let _ = [0; { + struct Foo; + impl Foo { + const fn get(&self) -> usize { + 5 + } + }; + Foo.get() + }]; +} + +fn bar() { + let range = + (111111111 + 333333333333333333 + 1111 + 400000000000000000)..(2222 + 2333333333333333); + + let another_range = 5..some_func(a, b /* comment */); + + for _ in 1.. { + call_forever(); + } + + syntactically_correct( + loop { + sup('?'); + }, + if cond { 0 } else { 1 }, + ); + + let third = ..10; + let infi_range = ..; + let foo = 1..; + let bar = 5; + let nonsense = (10..0)..(0..10); + + loop { + if true { + break; + } + } + + let x = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa && aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + a, + ); +} + +fn baz() { + unsafe /* {}{}{}{{{{}} */ { + let foo = 1u32; + } + + unsafe /* very looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong + * comment */ { + } + + unsafe /* So this is a very long comment. + * Multi-line, too. + * Will it still format correctly? */ { + } + + unsafe { + // Regular unsafe block + } + + unsafe { foo() } + + unsafe { + foo(); + } + + // #2289 + let identifier_0 = unsafe { this_is_58_chars_long_and_line_is_93_chars_long_xxxxxxxxxx }; + let identifier_1 = unsafe { this_is_59_chars_long_and_line_is_94_chars_long_xxxxxxxxxxx }; + let identifier_2 = unsafe { this_is_65_chars_long_and_line_is_100_chars_long_xxxxxxxxxxxxxxxx }; + let identifier_3 = + unsafe { this_is_66_chars_long_and_line_is_101_chars_long_xxxxxxxxxxxxxxxxx }; +} + +// Test some empty blocks. +fn qux() { + {} + // FIXME this one could be done better. + { /* a block with a comment */ } + {} + { + // A block with a comment. + } +} + +fn issue227() { + { + let handler = + box DocumentProgressHandler::new(addr, DocumentProgressTask::DOMContentLoaded); + } +} + +fn issue184(source: &str) { + for c in source.chars() { + if index < 'a' { + continue; + } + } +} + +fn arrays() { + let x = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 7, 8, 9, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 0, + ]; + + let y = [/* comment */ 1, 2 /* post comment */, 3]; + + let xy = [ + strukt { + test123: value_one_two_three_four, + turbo: coolio(), + }, + // comment + 1, + ]; + + let a = WeightedChoice::new(&mut [ + Weighted { + weightweight: x, + item: 0, + }, + Weighted { + weightweight: 1, + item: 1, + }, + Weighted { + weightweight: x, + item: 2, + }, + Weighted { + weightweight: 1, + item: 3, + }, + ]); + + let z = [ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + yyyyyyyyyyyyyyyyyyyyyyyyyyy, + zzzzzzzzzzzzzzzzzz, + q, + ]; + + [1 + 3, 4, 5, 6, 7, 7, fncall::>(3 - 1)] +} + +fn returns() { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + && return; + + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; +} + +fn addrof() { + &mut (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb); + &(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb); + + // raw reference operator + &raw const a; + &raw mut b; +} + +fn casts() { + fn unpack(packed: u32) -> [u16; 2] { + [(packed >> 16) as u16, (packed >> 0) as u16] + } + + let some_trait_xxx = xxxxxxxxxxx + xxxxxxxxxxxxx as SomeTraitXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX; + let slightly_longer_trait = + yyyyyyyyy + yyyyyyyyyyy as SomeTraitYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY; +} + +fn indices() { + let x = (aaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + cccccccccccccccc) + [x + y + z]; + let y = (aaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + cccccccccccccccc) + [xxxxx + yyyyy + zzzzz]; + let z = xxxxxxxxxx + .x() + .y() + .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz()[aaaaa]; + let z = xxxxxxxxxx + .x() + .y() + .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz() + [aaaaa]; +} + +fn repeats() { + let x = [aaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + cccccccccccccccc; + x + y + z]; + let y = [aaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + cccccccccccccccc; + xxxxx + yyyyy + zzzzz]; +} + +fn blocks() { + if 1 + 1 == 2 { + println!("yay arithmetix!"); + }; +} + +fn issue767() { + if false { + if false { + } else { + // A let binding here seems necessary to trigger it. + let _ = (); + } + } else if let false = false { + } +} + +fn ranges() { + let x = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa..bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; + let y = + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa..=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; + let z = ..=x; + + // #1766 + let x = [0. ..10.0]; + let x = [0. ..=10.0]; + + a..=b + + // the expr below won't compile because inclusive ranges need a defined end + // let a = 0 ..= ; +} + +fn if_else() { + let exact = diff / (if size == 0 { 1 } else { size }); + + let cx = tp1.x + any * radius * if anticlockwise { 1.0 } else { -1.0 }; +} + +fn complex_if_else() { + if let Some(x) = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx { + } else if let Some(x) = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx { + ha(); + } else if xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxx { + yo(); + } else if let Some(x) = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + { + ha(); + } else if xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxx + { + yo(); + } +} + +fn issue1106() { + { + if let hir::ItemEnum(ref enum_def, ref generics) = + self.ast_map.expect_item(enum_node_id).node + {} + } + + for entry in WalkDir::new(path) + .into_iter() + .filter_entry(|entry| exclusions.filter_entry(entry)) + {} +} + +fn issue1570() { + a_very_long_function_name({ some_func(1, { 1 }) }) +} + +fn issue1714() { + v = &mut { v }[mid..]; + let (left, right) = { v }.split_at_mut(mid); +} + +// Multi-lined index should be put on the next line if it fits in one line. +fn issue1749() { + { + { + { + if self.shape[(r as f32 + self.x_offset) as usize] + [(c as f32 + self.y_offset) as usize] + != 0 + { + // hello + } + } + } + } +} + +// #1172 +fn newlines_between_list_like_expr() { + foo( + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, + zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz, + ); + + vec![ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, + zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz, + ]; + + match x { + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + | yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + | zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz => foo(a, b, c), + _ => bar(), + }; +} + +fn issue2178() { + Ok(result + .iter() + .map(|item| ls_util::rls_to_location(item)) + .collect()) +} + +// #2493 +impl Foo { + fn bar(&self) { + { + let x = match () { + () => { + let i; + i == self + .install_config + .storage + .experimental_compressed_block_size as usize + } + }; + } + } +} + +fn dots() { + .. .. ..; // (.. (.. (..))) + ..= ..= ..; + (..).. ..; // ((..) .. (..)) +} + +// #2676 +// A function call with a large single argument. +fn foo() { + let my_var = Mutex::new( + RpcClientType::connect(server_iddd).chain_err(|| "Unable to create RPC client")?, + ); +} + +// #2704 +// Method call with prefix and suffix. +fn issue2704() { + // We should not combine the callee with a multi-lined method call. + let requires = requires.set( + &requires0 + .concat(&requires1) + .concat(&requires2) + .distinct_total(), + ); + let requires = requires.set( + box requires0 + .concat(&requires1) + .concat(&requires2) + .distinct_total(), + ); + let requires = requires.set( + requires0 + .concat(&requires1) + .concat(&requires2) + .distinct_total() as u32, + ); + let requires = requires.set( + requires0 + .concat(&requires1) + .concat(&requires2) + .distinct_total()?, + ); + let requires = requires.set( + !requires0 + .concat(&requires1) + .concat(&requires2) + .distinct_total(), + ); + // We should combine a small callee with an argument. + bar(vec![22] + .into_iter() + .map(|x| x * 2) + .filter(|_| true) + .collect()); + // But we should not combine a long callee with an argument. + barrrr( + vec![22] + .into_iter() + .map(|x| x * 2) + .filter(|_| true) + .collect(), + ); +} + +// #2782 +fn issue2782() { + { + let f = { + let f = { + { + match f { + F(f, _) => loop { + let f = { + match f { + F(f, _) => match f { + F(f, _) => loop { + let f = { + let f = { + match f { + '-' => F(f, ()), + } + }; + }; + }, + }, + } + }; + }, + } + } + }; + }; + } +} + +fn issue_2802() { + function_to_fill_this_line(some_arg, some_arg, some_arg) + * a_very_specific_length(specific_length_arg) + * very_specific_length(Foo { + a: some_much_much_longer_value, + }) + * some_value +} + +fn issue_3003() { + let mut path: PathBuf = [ + env!("CARGO_MANIFEST_DIR"), + "tests", + "support", + "dejavu-fonts-ttf-2.37", + "ttf", + ] + .iter() + .collect(); +} + +fn issue3226() { + { + { + { + return Err( + ErrorKind::ManagementInterfaceError("Server exited unexpectedly").into(), + ); + } + } + } + { + { + { + break Err( + ErrorKind::ManagementInterfaceError("Server exited unexpectedlyy").into(), + ); + } + } + } +} + +// #3457 +fn issue3457() { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + { + println!("Test"); + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// #3498 +static REPRO: &[usize] = &[ + #[cfg(feature = "zero")] + 0, +]; + +fn overflow_with_attr() { + foo( + #[cfg(feature = "zero")] + 0, + ); + foobar( + #[cfg(feature = "zero")] + 0, + ); + foobar( + x, + y, + #[cfg(feature = "zero")] + {}, + ); +} + +// https://github.com/rust-lang/rustfmt/issues/3765 +fn foo() { + async { + // Do + // some + // work + } + .await; + + async { + // Do + // some + // work + } + .await; +} + +fn underscore() { + _ = 1; + _; + [_, a, _] = [1, 2, 3]; + (a, _) = (8, 9); + TupleStruct(_, a) = TupleStruct(2, 2); + + let _: usize = foo(_, _); +} diff --git a/src/tools/rustfmt/tests/target/extern.rs b/src/tools/rustfmt/tests/target/extern.rs new file mode 100644 index 0000000000..44ed6d4b47 --- /dev/null +++ b/src/tools/rustfmt/tests/target/extern.rs @@ -0,0 +1,84 @@ +// rustfmt-normalize_comments: true + +extern crate foo; +extern crate foo as bar; + +extern crate chrono; +extern crate dotenv; +extern crate futures; + +extern crate bar; +extern crate foo; + +// #2315 +extern crate proc_macro; +extern crate proc_macro2; + +// #3128 +extern crate serde; // 1.0.78 +extern crate serde_derive; // 1.0.78 +extern crate serde_json; // 1.0.27 + +extern "C" { + fn c_func(x: *mut *mut libc::c_void); + + fn c_func( + x: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX, + y: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY, + ); + + #[test123] + fn foo() -> uint64_t; + + pub fn bar(); +} + +extern "C" { + fn DMR_GetDevice( + pHDev: *mut HDEV, + searchMode: DeviceSearchMode, + pSearchString: *const c_char, + devNr: c_uint, + wildcard: c_char, + ) -> TDMR_ERROR; + + fn quux() -> (); // Post comment + + pub type Foo; + + type Bar; +} + +extern "Rust" { + static ext: u32; + // Some comment. + pub static mut var: SomeType; +} + +extern "C" { + fn syscall( + number: libc::c_long, // comment 1 + // comm 2 + ... // sup? + ) -> libc::c_long; + + fn foo(x: *const c_char, ...) -> libc::c_long; +} + +extern "C" { + pub fn freopen( + filename: *const c_char, + mode: *const c_char, + mode2: *const c_char, + mode3: *const c_char, + file: *mut FILE, + ) -> *mut FILE; + + const fn foo() -> *mut Bar; + unsafe fn foo() -> *mut Bar; + + pub(super) const fn foo() -> *mut Bar; + pub(crate) unsafe fn foo() -> *mut Bar; +} + +extern "C" {} diff --git a/src/tools/rustfmt/tests/target/extern_not_explicit.rs b/src/tools/rustfmt/tests/target/extern_not_explicit.rs new file mode 100644 index 0000000000..b55b64d05b --- /dev/null +++ b/src/tools/rustfmt/tests/target/extern_not_explicit.rs @@ -0,0 +1,18 @@ +// rustfmt-force_explicit_abi: false + +extern { + fn some_fn() -> (); +} + +extern fn sup() {} + +type funky_func = extern fn( + unsafe extern "rust-call" fn( + *const JSJitInfo, + *mut JSContext, + HandleObject, + *mut libc::c_void, + u32, + *mut JSVal, + ) -> u8, +); diff --git a/src/tools/rustfmt/tests/target/file-lines-1.rs b/src/tools/rustfmt/tests/target/file-lines-1.rs new file mode 100644 index 0000000000..13820ec293 --- /dev/null +++ b/src/tools/rustfmt/tests/target/file-lines-1.rs @@ -0,0 +1,30 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-1.rs","range":[4,8]}] + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call() + .method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // comment + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/file-lines-2.rs b/src/tools/rustfmt/tests/target/file-lines-2.rs new file mode 100644 index 0000000000..bc25698c2e --- /dev/null +++ b/src/tools/rustfmt/tests/target/file-lines-2.rs @@ -0,0 +1,24 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-2.rs","range":[10,15]}] + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call().method_call(); + + let y = if cond { val1 } else { val2 }.method_call(); + + { + match x { + PushParam => { + // comment + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/file-lines-3.rs b/src/tools/rustfmt/tests/target/file-lines-3.rs new file mode 100644 index 0000000000..77d6fb2635 --- /dev/null +++ b/src/tools/rustfmt/tests/target/file-lines-3.rs @@ -0,0 +1,25 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-3.rs","range":[4,8]},{"file":"tests/source/file-lines-3.rs","range":[10,15]}] + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call() + .method_call(); + + let y = if cond { val1 } else { val2 }.method_call(); + + { + match x { + PushParam => { + // comment + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/file-lines-4.rs b/src/tools/rustfmt/tests/target/file-lines-4.rs new file mode 100644 index 0000000000..83928bf6fe --- /dev/null +++ b/src/tools/rustfmt/tests/target/file-lines-4.rs @@ -0,0 +1,30 @@ +// rustfmt-file_lines: [] +// (Test that nothing is formatted if an empty array is specified.) + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + // aaaaaaaaaaaaa + { + match x { + PushParam => { + // comment + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/file-lines-5.rs b/src/tools/rustfmt/tests/target/file-lines-5.rs new file mode 100644 index 0000000000..3966dc0630 --- /dev/null +++ b/src/tools/rustfmt/tests/target/file-lines-5.rs @@ -0,0 +1,17 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-5.rs","range":[3,5]}] + +struct A { + t: i64, +} + +mod foo { + fn bar() { + // test + let i = 12; + // test + } + // aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + fn baz() { + let j = 15; + } +} diff --git a/src/tools/rustfmt/tests/target/file-lines-6.rs b/src/tools/rustfmt/tests/target/file-lines-6.rs new file mode 100644 index 0000000000..8a092df869 --- /dev/null +++ b/src/tools/rustfmt/tests/target/file-lines-6.rs @@ -0,0 +1,18 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-6.rs","range":[9,10]}] + +struct A { + t: i64, +} + +mod foo { + fn bar() { + // test + let i = 12; + // test + } + + fn baz() { +/// + let j = 15; + } +} diff --git a/src/tools/rustfmt/tests/target/file-lines-7.rs b/src/tools/rustfmt/tests/target/file-lines-7.rs new file mode 100644 index 0000000000..62d913d882 --- /dev/null +++ b/src/tools/rustfmt/tests/target/file-lines-7.rs @@ -0,0 +1,21 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-7.rs","range":[8,15]}] + +struct A { + t: i64, +} + +mod foo { + fn bar() { + // test + let i = 12; + // test + } + + fn baz() { + + + + /// + let j = 15; + } +} diff --git a/src/tools/rustfmt/tests/target/file-lines-item.rs b/src/tools/rustfmt/tests/target/file-lines-item.rs new file mode 100644 index 0000000000..8d39eb6095 --- /dev/null +++ b/src/tools/rustfmt/tests/target/file-lines-item.rs @@ -0,0 +1,21 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-item.rs","range":[6,8]}] + +use foo::{c, b, a}; +use bar; + +fn foo() { + bar(); +} + +impl Drop for Context { + fn drop(&mut self) { + } +} + +impl Bar for Baz { + fn foo() { + bar( + baz, // Who knows? + ) + } +} diff --git a/src/tools/rustfmt/tests/target/fn-args-with-last-line-comment.rs b/src/tools/rustfmt/tests/target/fn-args-with-last-line-comment.rs new file mode 100644 index 0000000000..27e0e09653 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-args-with-last-line-comment.rs @@ -0,0 +1,24 @@ +// #1587 +pub trait X { + fn a(&self) -> &'static str; + fn bcd( + &self, + c: &str, // comment on this arg + d: u16, // comment on this arg + e: &Vec, // comment on this arg + ) -> Box; +} + +// #1595 +fn foo( + arg1: LongTypeName, + arg2: LongTypeName, + arg3: LongTypeName, + arg4: LongTypeName, + arg5: LongTypeName, + arg6: LongTypeName, + arg7: LongTypeName, + //arg8: LongTypeName, +) { + // do stuff +} diff --git a/src/tools/rustfmt/tests/target/fn-custom-2.rs b/src/tools/rustfmt/tests/target/fn-custom-2.rs new file mode 100644 index 0000000000..0e723396c6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-custom-2.rs @@ -0,0 +1,77 @@ +// Test different indents. + +fn foo( + a: Aaaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbbbb, + c: Ccccccccccccccccc, + d: Ddddddddddddddddddddddddd, + e: Eeeeeeeeeeeeeeeeeee, +) { + foo(); +} + +fn bar< + 'a: 'bbbbbbbbbbbbbbbbbbbbbbbbbbb, + TTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUU: WWWWWWWWWWWWWWWWWWWWWWWW, +>( + a: Aaaaaaaaaaaaaaa, +) { + bar(); +} + +fn baz() +where + X: TTTTTTTT, +{ + baz(); +} + +fn qux() +where + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, +{ + baz(); +} + +impl Foo { + fn foo( + self, + a: Aaaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbbbb, + c: Ccccccccccccccccc, + d: Ddddddddddddddddddddddddd, + e: Eeeeeeeeeeeeeeeeeee, + ) { + foo(); + } + + fn bar< + 'a: 'bbbbbbbbbbbbbbbbbbbbbbbbbbb, + TTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUU: WWWWWWWWWWWWWWWWWWWWWWWW, + >( + a: Aaaaaaaaaaaaaaa, + ) { + bar(); + } + + fn baz() + where + X: TTTTTTTT, + { + baz(); + } +} + +struct Foo< + TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUUUU, + VVVVVVVVVVVVVVVVVVVVVVVVVVV, + WWWWWWWWWWWWWWWWWWWWWWWW, +> { + foo: Foo, +} diff --git a/src/tools/rustfmt/tests/target/fn-custom-3.rs b/src/tools/rustfmt/tests/target/fn-custom-3.rs new file mode 100644 index 0000000000..bfafe45360 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-custom-3.rs @@ -0,0 +1,71 @@ +// Test different indents. + +fn foo( + a: Aaaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbbbb, + c: Ccccccccccccccccc, + d: Ddddddddddddddddddddddddd, + e: Eeeeeeeeeeeeeeeeeee, +) { + foo(); +} + +fn bar< + 'a: 'bbbbbbbbbbbbbbbbbbbbbbbbbbb, + TTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUU: WWWWWWWWWWWWWWWWWWWWWWWW, +>( + a: Aaaaaaaaaaaaaaa, +) { + bar(); +} + +fn qux() +where + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, +{ + baz(); +} + +fn qux() +where + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, +{ + baz(); +} + +impl Foo { + fn foo( + self, + a: Aaaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbbbb, + c: Ccccccccccccccccc, + d: Ddddddddddddddddddddddddd, + e: Eeeeeeeeeeeeeeeeeee, + ) { + foo(); + } + + fn bar< + 'a: 'bbbbbbbbbbbbbbbbbbbbbbbbbbb, + TTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUU: WWWWWWWWWWWWWWWWWWWWWWWW, + >( + a: Aaaaaaaaaaaaaaa, + ) { + bar(); + } +} + +struct Foo< + TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUUUU, + VVVVVVVVVVVVVVVVVVVVVVVVVVV, + WWWWWWWWWWWWWWWWWWWWWWWW, +> { + foo: Foo, +} diff --git a/src/tools/rustfmt/tests/target/fn-custom-4.rs b/src/tools/rustfmt/tests/target/fn-custom-4.rs new file mode 100644 index 0000000000..5de16e2515 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-custom-4.rs @@ -0,0 +1,26 @@ +// Test different indents. + +fn qux() +where + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, +{ + baz(); +} + +fn qux() +where + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, +{ + baz(); +} + +fn qux(a: Aaaaaaaaaaaaaaaaa) +where + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, + X: TTTTTTTTTTTTTTTTTTTTTTTTTTTT, +{ + baz(); +} diff --git a/src/tools/rustfmt/tests/target/fn-custom-6.rs b/src/tools/rustfmt/tests/target/fn-custom-6.rs new file mode 100644 index 0000000000..e891f4d588 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-custom-6.rs @@ -0,0 +1,71 @@ +// rustfmt-brace_style: PreferSameLine +// Test different indents. + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) { + foo(); +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) -> String { + foo(); +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) -> String { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) +where + T: UUUUUUUUUUU, { + foo(); +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) where + T: UUUUUUUUUUU, { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) -> String +where + T: UUUUUUUUUUU, { + foo(); +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) -> String +where + T: UUUUUUUUUUU, { + bar(); +} + +trait Test { + fn foo(a: u8) {} + + fn bar(a: u8) -> String {} +} diff --git a/src/tools/rustfmt/tests/target/fn-custom-7.rs b/src/tools/rustfmt/tests/target/fn-custom-7.rs new file mode 100644 index 0000000000..2c20ac5a75 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-custom-7.rs @@ -0,0 +1,36 @@ +// rustfmt-normalize_comments: true +// rustfmt-fn_args_layout: Vertical +// rustfmt-brace_style: AlwaysNextLine + +// Case with only one variable. +fn foo(a: u8) -> u8 +{ + bar() +} + +// Case with 2 variables and some pre-comments. +fn foo( + a: u8, // Comment 1 + b: u8, // Comment 2 +) -> u8 +{ + bar() +} + +// Case with 2 variables and some post-comments. +fn foo( + // Comment 1 + a: u8, + // Comment 2 + b: u8, +) -> u8 +{ + bar() +} + +trait Test +{ + fn foo(a: u8) {} + + fn bar(a: u8) -> String {} +} diff --git a/src/tools/rustfmt/tests/target/fn-custom-8.rs b/src/tools/rustfmt/tests/target/fn-custom-8.rs new file mode 100644 index 0000000000..29af3fca79 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-custom-8.rs @@ -0,0 +1,77 @@ +// rustfmt-brace_style: PreferSameLine +// Test different indents. + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) { + foo(); +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) -> String { + foo(); +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) -> String { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) +where + T: UUUUUUUUUUU, { + foo(); +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) where + T: UUUUUUUUUUU, { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) -> String +where + T: UUUUUUUUUUU, { + foo(); +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) -> String +where + T: UUUUUUUUUUU, { + bar(); +} + +trait Test { + fn foo(a: u8) {} + + fn bar(a: u8) -> String {} + + fn bar(a: u8) -> String + where + Foo: foooo, + Bar: barrr, { + } +} diff --git a/src/tools/rustfmt/tests/target/fn-custom.rs b/src/tools/rustfmt/tests/target/fn-custom.rs new file mode 100644 index 0000000000..2eb2a973d2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-custom.rs @@ -0,0 +1,19 @@ +// rustfmt-fn_args_layout: Compressed +// Test some of the ways function signatures can be customised. + +// Test compressed layout of args. +fn foo( + a: Aaaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbbbb, c: Ccccccccccccccccc, d: Ddddddddddddddddddddddddd, + e: Eeeeeeeeeeeeeeeeeee, +) { + foo(); +} + +impl Foo { + fn foo( + self, a: Aaaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbbbb, c: Ccccccccccccccccc, + d: Ddddddddddddddddddddddddd, e: Eeeeeeeeeeeeeeeeeee, + ) { + foo(); + } +} diff --git a/src/tools/rustfmt/tests/target/fn-param-attributes.rs b/src/tools/rustfmt/tests/target/fn-param-attributes.rs new file mode 100644 index 0000000000..829575518d --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-param-attributes.rs @@ -0,0 +1,64 @@ +// https://github.com/rust-lang/rustfmt/issues/3623 + +fn foo(#[cfg(something)] x: i32, y: i32) -> i32 { + x + y +} + +fn foo_b(#[cfg(something)] x: i32, y: i32) -> i32 { + x + y +} + +fn add( + #[cfg(something)] + #[deny(C)] + x: i32, + y: i32, +) -> i32 { + x + y +} + +struct NamedSelfRefStruct {} +impl NamedSelfRefStruct { + fn foo(#[cfg(something)] self: &Self) {} +} + +struct MutStruct {} +impl MutStruct { + fn foo(#[cfg(foo)] &mut self, #[deny(C)] b: i32) {} +} + +fn main() { + let c = |#[allow(C)] a: u32, + #[cfg(something)] b: i32, + #[cfg_attr(something, cfg(nothing))] + #[deny(C)] + c: i32| {}; + let _ = c(1, 2); +} + +pub fn bar( + /// bar + #[test] + a: u32, + /// Bar + #[must_use] + /// Baz + #[no_mangle] + b: i32, +) { +} + +fn abc( + #[foo] + #[bar] + param: u32, +) { + // ... +} + +fn really_really_really_loooooooooooooooooooong( + #[cfg(some_even_longer_config_feature_that_keeps_going_and_going_and_going_forever_and_ever_and_ever_on_and_on)] + b: i32, +) { + // ... +} diff --git a/src/tools/rustfmt/tests/target/fn-simple.rs b/src/tools/rustfmt/tests/target/fn-simple.rs new file mode 100644 index 0000000000..692739fa6a --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-simple.rs @@ -0,0 +1,120 @@ +// rustfmt-normalize_comments: true + +fn simple( + // pre-comment on a function!? + i: i32, // yes, it's possible! + response: NoWay, // hose +) { + fn op( + x: Typ, + key: &[u8], + upd: Box< + Fn( + Option<&memcache::Item>, + ) -> (memcache::Status, Result>), + >, + ) -> MapResult { + } + + "cool" +} + +fn weird_comment( + // /*/ double level */ comment + x: Hello, // /*/* triple, even */*/ + // Does this work? + y: World, +) { + simple(/* does this preserve comments now? */ 42, NoWay) +} + +fn generic(arg: T) -> &SomeType +where + T: Fn( + // First arg + A, + // Second argument + B, + C, + D, + // pre comment + E, // last comment + ) -> &SomeType, +{ + arg(a, b, c, d, e) +} + +fn foo() -> ! {} + +pub fn http_fetch_async( + listener: Box, + script_chan: Box, +) { +} + +fn some_func>(val: T) {} + +fn zzzzzzzzzzzzzzzzzzzz( + selff: Type, + mut handle: node::Handle>, Type, NodeType>, +) -> SearchStack<'a, K, V, Type, NodeType> { +} + +unsafe fn generic_call( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, + is_lenient: bool, + call: unsafe extern "C" fn( + *const JSJitInfo, + *mut JSContext, + HandleObject, + *mut libc::c_void, + u32, + *mut JSVal, + ) -> u8, +) { + let f: fn(_, _) -> _ = panic!(); +} + +pub fn start_export_thread( + database: &Database, + crypto_scheme: &C, + block_size: usize, + source_path: &Path, +) -> BonzoResult> { +} + +pub fn waltz(cwd: &Path) -> CliAssert { + { + { + formatted_comment = + rewrite_comment(comment, block_style, width, offset, formatting_fig); + } + } +} + +// #2003 +mod foo { + fn __bindgen_test_layout_i_open0_c_open1_char_a_open2_char_close2_close1_close0_instantiation() + { + foo(); + } +} + +// #2082 +pub(crate) fn init() {} + +crate fn init() {} + +// #2630 +fn make_map String)>(records: &Vec, key_fn: F) -> HashMap {} + +// #2956 +fn bar( + beans: Asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf, + spam: bool, + eggs: bool, +) -> bool { + unimplemented!(); +} diff --git a/src/tools/rustfmt/tests/target/fn-single-line/version_one.rs b/src/tools/rustfmt/tests/target/fn-single-line/version_one.rs new file mode 100644 index 0000000000..013b2cd721 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-single-line/version_one.rs @@ -0,0 +1,71 @@ +// rustfmt-fn_single_line: true +// rustfmt-version: One +// Test single-line functions. + +fn foo_expr() { 1 } + +fn foo_stmt() { foo(); } + +fn foo_decl_local() { let z = 5; } + +fn foo_decl_item(x: &mut i32) { x = 3; } + +fn empty() {} + +fn foo_return() -> String { "yay" } + +fn foo_where() -> T +where + T: Sync, +{ + let x = 2; +} + +fn fooblock() { + { + "inner-block" + } +} + +fn fooblock2(x: i32) { + let z = match x { + _ => 2, + }; +} + +fn comment() { + // this is a test comment + 1 +} + +fn comment2() { + // multi-line comment + let z = 2; + 1 +} + +fn only_comment() { + // Keep this here +} + +fn aaaaaaaaaaaaaaaaa_looooooooooooooooooooooong_name() { + let z = "aaaaaaawwwwwwwwwwwwwwwwwwwwwwwwwwww"; +} + +fn lots_of_space() { 1 } + +fn mac() -> Vec { vec![] } + +trait CoolTypes { + fn dummy(&self) {} +} + +trait CoolerTypes { + fn dummy(&self) {} +} + +fn Foo() +where + T: Bar, +{ +} diff --git a/src/tools/rustfmt/tests/target/fn-single-line/version_two.rs b/src/tools/rustfmt/tests/target/fn-single-line/version_two.rs new file mode 100644 index 0000000000..b8053d4c2f --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-single-line/version_two.rs @@ -0,0 +1,67 @@ +// rustfmt-fn_single_line: true +// rustfmt-version: Two +// Test single-line functions. + +fn foo_expr() { 1 } + +fn foo_stmt() { foo(); } + +fn foo_decl_local() { let z = 5; } + +fn foo_decl_item(x: &mut i32) { x = 3; } + +fn empty() {} + +fn foo_return() -> String { "yay" } + +fn foo_where() -> T +where + T: Sync, +{ + let x = 2; +} + +fn fooblock() { { "inner-block" } } + +fn fooblock2(x: i32) { + let z = match x { + _ => 2, + }; +} + +fn comment() { + // this is a test comment + 1 +} + +fn comment2() { + // multi-line comment + let z = 2; + 1 +} + +fn only_comment() { + // Keep this here +} + +fn aaaaaaaaaaaaaaaaa_looooooooooooooooooooooong_name() { + let z = "aaaaaaawwwwwwwwwwwwwwwwwwwwwwwwwwww"; +} + +fn lots_of_space() { 1 } + +fn mac() -> Vec { vec![] } + +trait CoolTypes { + fn dummy(&self) {} +} + +trait CoolerTypes { + fn dummy(&self) {} +} + +fn Foo() +where + T: Bar, +{ +} diff --git a/src/tools/rustfmt/tests/target/fn-ty.rs b/src/tools/rustfmt/tests/target/fn-ty.rs new file mode 100644 index 0000000000..7d48f3b32d --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn-ty.rs @@ -0,0 +1,14 @@ +fn f( + xxxxxxxxxxxxxxxxxx: fn(a, b, b) -> a, + xxxxxxxxxxxxxxxxxx: fn() -> a, + xxxxxxxxxxxxxxxxxx: fn(a, b, b), + xxxxxxxxxxxxxxxxxx: fn(), + xxxxxxxxxxxxxxxxxx: fn(a, b, b) -> !, + xxxxxxxxxxxxxxxxxx: fn() -> !, +) where + F1: Fn(a, b, b) -> a, + F2: Fn(a, b, b), + F3: Fn(), + F4: Fn() -> u32, +{ +} diff --git a/src/tools/rustfmt/tests/target/fn.rs b/src/tools/rustfmt/tests/target/fn.rs new file mode 100644 index 0000000000..0ad775ee1d --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn.rs @@ -0,0 +1,120 @@ +// Tests different fns + +fn foo(a: AAAA, b: BBB, c: CCC) -> RetType {} + +fn foo(a: AAAA, b: BBB /* some, weird, inline comment */, c: CCC) -> RetType +where + T: Blah, +{ +} + +fn foo(a: AAA /* (comment) */) +where + T: Blah, +{ +} + +fn foo( + a: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + b: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, +) -> RetType +where + T: Blah, +{ +} + +fn foo( + a: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + b: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, +) -> RetType +where + T: Blah, + U: dsfasdfasdfasd, +{ +} + +fn foo B /* paren inside generics */>() {} + +impl Foo { + fn with_no_errors(&mut self, f: F) -> T + where + F: FnOnce(&mut Resolver) -> T, + { + } + + fn foo(mut self, mut bar: u32) {} + + fn bar(self, mut bazz: u32) {} +} + +pub fn render< + 'a, + N: Clone + 'a, + E: Clone + 'a, + G: Labeller<'a, N, E> + GraphWalk<'a, N, E>, + W: Write, +>( + g: &'a G, + w: &mut W, +) -> io::Result<()> { + render_opts(g, w, &[]) +} + +const fn foo() { + x; +} + +pub const fn foo() { + x; +} + +impl Foo { + const fn foo() { + x; + } +} + +fn homura>(_: T) {} + +fn issue377() -> (Box, Box) {} + +fn main() { + let _ = function(move || 5); + let _ = move || 42; + let _ = || unsafe { abort() }; +} + +// With inner attributes. +fn inner() { + #![inline] + x +} + +#[cfg_attr(rustfmt, rustfmt::skip)] +fn foo(a: i32) -> i32 { + // comment + if a > 0 { 1 } else { 2 } +} + +fn ______________________baz( + a: i32, +) -> *mut ::std::option::Option< + extern "C" fn(arg1: i32, _____________________a: i32, arg3: i32) -> (), +> { +} + +pub fn check_path<'a, 'tcx>( + tcx: TyCtxt<'a, 'tcx, 'tcx>, + path: &hir::Path, + id: ast::NodeId, + cb: &mut FnMut(DefId, Span, &Option<&Stability>, &Option), +) { +} + +pub fn check_path<'a, 'tcx>( + tcx: TyCtxt<'a, 'tcx, 'tcx>, + path: &hir::Path, + id: ast::NodeId, + cb: &mut FnMut(DefId, Span, &Option<&Stability>, &Option), +) { +} diff --git a/src/tools/rustfmt/tests/target/fn_args_indent-block.rs b/src/tools/rustfmt/tests/target/fn_args_indent-block.rs new file mode 100644 index 0000000000..f5232a4881 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn_args_indent-block.rs @@ -0,0 +1,143 @@ +// rustfmt-normalize_comments: true + +fn foo() { + foo(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) { + foo(); +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) { + bar(); +} + +fn foo(a: Aaaaaaaaaaaaaa, b: Bbbbbbbbbbbbbb) -> String { + foo(); +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) -> String { + bar(); +} + +fn foo(a: u8 /* Comment 1 */, b: u8 /* Comment 2 */) -> u8 { + bar() +} + +fn foo( + a: u8, // Comment 1 + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, // Comment 2 +) -> u8 { + bar() +} + +fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, +) -> String +where + X: Fooooo, + Y: Baaar, +{ + bar(); +} + +fn foo() -> T { + foo(); +} + +fn foo() -> T +where + X: Foooo, + Y: Baaar, +{ + foo(); +} + +fn foo() +where + X: Foooo, +{ +} + +fn foo() +where + X: Foooo, + Y: Baaar, +{ +} + +fn foo() -> ( + Loooooooooooooooooooooong, + Reeeeeeeeeeeeeeeeeeeeeeeeturn, + iiiiiiiiis, + Looooooooooooooooong, +) { + foo(); +} + +fn foo() { + foo(); +} + +fn foo< + L: Loooooooooooooooooooooong, + G: Geeeeeeeeeeeneric, + I: iiiiiiiiis, + L: Looooooooooooooooong, +>() { + foo(); +} + +fn foo() { + foo(); +} + +trait Test { + fn foo(a: u8) {} + + fn bar( + a: Aaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, + e: Eeeeeeeeeeeeeee, + ) -> String { + } +} + +fn foo( + a: Aaaaaaaaaaaaaaaaaaaa, + b: Bbbbbbbbbbbbbbbbb, + c: Cccccccccccccccccc, + d: Dddddddddddddddd, +) { + foo(); +} + +fn foo() -> ( + Looooooooooooooooooooooooooong, + Reeeeeeeeeeeeeeeeeeeeeeeeeeeeeturn, + iiiiiiiiiiiiiis, + Loooooooooooooooooooooong, +) { + foo(); +} diff --git a/src/tools/rustfmt/tests/target/fn_args_layout-vertical.rs b/src/tools/rustfmt/tests/target/fn_args_layout-vertical.rs new file mode 100644 index 0000000000..da0ac981d8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn_args_layout-vertical.rs @@ -0,0 +1,39 @@ +// rustfmt-fn_args_layout: Vertical + +// Empty list should stay on one line. +fn do_bar() -> u8 { + bar() +} + +// A single argument should stay on the same line. +fn do_bar(a: u8) -> u8 { + bar() +} + +// Multiple arguments should each get their own line. +fn do_bar( + a: u8, + mut b: u8, + c: &u8, + d: &mut u8, + closure: &Fn(i32) -> i32, +) -> i32 { + // This feature should not affect closures. + let bar = |x: i32, y: i32| -> i32 { x + y }; + bar(a, b) +} + +// If the first argument doesn't fit on the same line with the function name, +// the whole list should probably be pushed to the next line with hanging +// indent. That's not what happens though, so check current behaviour instead. +// In any case, it should maintain single argument per line. +fn do_this_that_and_the_other_thing( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: u8, + b: u8, + c: u8, + d: u8, +) { + this(); + that(); + the_other_thing(); +} diff --git a/src/tools/rustfmt/tests/target/fn_once.rs b/src/tools/rustfmt/tests/target/fn_once.rs new file mode 100644 index 0000000000..42b8f98e78 --- /dev/null +++ b/src/tools/rustfmt/tests/target/fn_once.rs @@ -0,0 +1,8 @@ +struct Add(usize); + +impl FnOnce<(usize,)> for Add { + type Output = Add; + extern "rust-call" fn call_once(self, to: (usize,)) -> Add { + Add(self.0 + to.0) + } +} diff --git a/src/tools/rustfmt/tests/target/format_strings/issue-202.rs b/src/tools/rustfmt/tests/target/format_strings/issue-202.rs new file mode 100644 index 0000000000..2a2c24140d --- /dev/null +++ b/src/tools/rustfmt/tests/target/format_strings/issue-202.rs @@ -0,0 +1,25 @@ +// rustfmt-format_strings: true + +#[test] +fn compile_empty_program() { + let result = get_result(); + let expected = "; ModuleID = \'foo\' + +; Function Attrs: nounwind +declare void @llvm.memset.p0i8.i32(i8* nocapture, i8, i32, i32, i1) #0 + +declare i32 @write(i32, i8*, i32) + +declare i32 @putchar(i32) + +declare i32 @getchar() + +define i32 @main() { +entry: + ret i32 0 +} + +attributes #0 = { nounwind } +"; + assert_eq!(result, CString::new(expected).unwrap()); +} diff --git a/src/tools/rustfmt/tests/target/format_strings/issue-2833.rs b/src/tools/rustfmt/tests/target/format_strings/issue-2833.rs new file mode 100644 index 0000000000..7048353252 --- /dev/null +++ b/src/tools/rustfmt/tests/target/format_strings/issue-2833.rs @@ -0,0 +1,15 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 80 + +fn test1() { + let expected = "\ +but Doctor Watson has to have it taken out for him and dusted, +"; +} + +fn test2() { + let expected = "\ +[Omitted long matching line] +but Doctor Watson has to have it taken out for him and dusted, +"; +} diff --git a/src/tools/rustfmt/tests/target/format_strings/issue-3263.rs b/src/tools/rustfmt/tests/target/format_strings/issue-3263.rs new file mode 100644 index 0000000000..72f7e9cc62 --- /dev/null +++ b/src/tools/rustfmt/tests/target/format_strings/issue-3263.rs @@ -0,0 +1,26 @@ +// rustfmt-format_strings: true +// rustfmt-newline_style: Windows + +#[test] +fn compile_empty_program() { + let result = get_result(); + let expected = "; ModuleID = \'foo\' + +; Function Attrs: nounwind +declare void @llvm.memset.p0i8.i32(i8* nocapture, i8, i32, i32, i1) #0 + +declare i32 @write(i32, i8*, i32) + +declare i32 @putchar(i32) + +declare i32 @getchar() + +define i32 @main() { +entry: + ret i32 0 +} + +attributes #0 = { nounwind } +"; + assert_eq!(result, CString::new(expected).unwrap()); +} diff --git a/src/tools/rustfmt/tests/target/format_strings/issue-687.rs b/src/tools/rustfmt/tests/target/format_strings/issue-687.rs new file mode 100644 index 0000000000..21d292f9eb --- /dev/null +++ b/src/tools/rustfmt/tests/target/format_strings/issue-687.rs @@ -0,0 +1,10 @@ +// rustfmt-format_strings: true + +fn foo() -> &'static str { + let sql = "ATTACH DATABASE ':memory:' AS my_attached; + BEGIN; + CREATE TABLE my_attached.foo(x INTEGER); + INSERT INTO my_attached.foo VALUES(42); + END;"; + sql +} diff --git a/src/tools/rustfmt/tests/target/format_strings/issue564.rs b/src/tools/rustfmt/tests/target/format_strings/issue564.rs new file mode 100644 index 0000000000..d9ef077c25 --- /dev/null +++ b/src/tools/rustfmt/tests/target/format_strings/issue564.rs @@ -0,0 +1,7 @@ +// rustfmt-format_strings: true + +const USAGE: &'static str = " +Usage: codegen project + codegen regenerate + codegen verify +"; diff --git a/src/tools/rustfmt/tests/target/hard-tabs.rs b/src/tools/rustfmt/tests/target/hard-tabs.rs new file mode 100644 index 0000000000..aca7e09c0e --- /dev/null +++ b/src/tools/rustfmt/tests/target/hard-tabs.rs @@ -0,0 +1,98 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-hard_tabs: true + +fn main() { + let x = Bar; + + let y = Foo { a: x }; + + Foo { + a: foo(), // comment + // comment + b: bar(), + ..something + }; + + fn foo(a: i32, a: i32, a: i32, a: i32, a: i32, a: i32, a: i32, a: i32, a: i32, a: i32, a: i32) { + } + + let str = "AAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAa"; + + if let ( + some_very_large, + tuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuple, + ) = 1 + 2 + 3 + {} + + if cond() { + something(); + } else if different_cond() { + something_else(); + } else { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + } + + unsafe /* very looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong + * comment */ { + } + + unsafe /* So this is a very long comment. + * Multi-line, too. + * Will it still format correctly? */ { + } + + let chain = funktion_kall() + .go_to_next_line_with_tab() + .go_to_next_line_with_tab() + .go_to_next_line_with_tab(); + + let z = [ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + yyyyyyyyyyyyyyyyyyyyyyyyyyy, + zzzzzzzzzzzzzzzzzz, + q, + ]; + + fn generic(arg: T) -> &SomeType + where + T: Fn( + // First arg + A, + // Second argument + B, + C, + D, + // pre comment + E, // last comment + ) -> &SomeType, + { + arg(a, b, c, d, e) + } + + loong_func().quux(move || if true { 1 } else { 2 }); + + fffffffffffffffffffffffffffffffffff(a, { + SCRIPT_TASK_ROOT.with(|root| { + *root.borrow_mut() = Some(&script_task); + }); + }); + a.b.c.d(); + + x().y(|| match cond() { + true => (), + false => (), + }); +} + +// #2296 +impl Foo { + // a comment + // on multiple lines + fn foo() { + // another comment + // on multiple lines + let x = true; + } +} diff --git a/src/tools/rustfmt/tests/target/hello.rs b/src/tools/rustfmt/tests/target/hello.rs new file mode 100644 index 0000000000..d9f90b0b5e --- /dev/null +++ b/src/tools/rustfmt/tests/target/hello.rs @@ -0,0 +1,8 @@ +// rustfmt-config: small_tabs.toml +// rustfmt-target: hello.rs + +// Smoke test - hello world. + +fn main() { + println!("Hello world!"); +} diff --git a/src/tools/rustfmt/tests/target/if_while_or_patterns.rs b/src/tools/rustfmt/tests/target/if_while_or_patterns.rs new file mode 100644 index 0000000000..61a357afcb --- /dev/null +++ b/src/tools/rustfmt/tests/target/if_while_or_patterns.rs @@ -0,0 +1,38 @@ +#![feature(if_while_or_patterns)] + +fn main() { + if let 0 | 1 = 0 { + println!("hello, world"); + }; + + if let aaaaaaaaaaaaaaaaaaaaaaaaaa | bbbbbbbbbbbbbbbbbbbbbbbbbbb | cccccccccccccccc | d_100 = 0 { + println!("hello, world"); + } + + if let aaaaaaaaaaaaaaaaaaaaaaaaaa | bbbbbbbbbbbbbbbbbbbbbbb | ccccccccccccccccccccc | d_101 = 0 + { + println!("hello, world"); + } + + if let aaaaaaaaaaaaaaaaaaaaaaaaaaaa | bbbbbbbbbbbbbbbbbbbbbbb | ccccccccccccccccccccc | d_103 = + 0 + { + println!("hello, world"); + } + + if let aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + | bbbbbbbbbbbbbbbbbbbbbbb + | ccccccccccccccccccccc + | d_105 = 0 + { + println!("hello, world"); + } + + while let xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx + | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx | xxx = foo_bar( + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + cccccccccccccccccccccccccccccccccccccccc, + ) { + println!("hello, world"); + } +} diff --git a/src/tools/rustfmt/tests/target/immovable_generators.rs b/src/tools/rustfmt/tests/target/immovable_generators.rs new file mode 100644 index 0000000000..0bf7a2d91b --- /dev/null +++ b/src/tools/rustfmt/tests/target/immovable_generators.rs @@ -0,0 +1,7 @@ +#![feature(generators)] + +unsafe fn foo() { + let mut ga = static || { + yield 1; + }; +} diff --git a/src/tools/rustfmt/tests/target/impl.rs b/src/tools/rustfmt/tests/target/impl.rs new file mode 100644 index 0000000000..f37fbcf1fc --- /dev/null +++ b/src/tools/rustfmt/tests/target/impl.rs @@ -0,0 +1,43 @@ +// Test impls + +impl JSTraceable for SmallVec<[T; 1]> {} + +impl>> Handle { + // Keep this. +} + +impl Test +where + V: Clone, // This comment is NOT removed by formatting! +{ + pub fn new(value: V) -> Self { + Test { + cloned_value: value.clone(), + value, + } + } +} + +impl X /* comment */ {} +impl Y // comment +{ +} + +impl Foo for T +// comment1 +where + // comment2 + // blah + T: Clone, +{ +} + +// #1823 +default impl Trait for X {} +default unsafe impl Trait for Y {} +pub default unsafe impl Trait for Z {} + +// #2212 +impl ConstWithDefault { + default const CAN_RECONSTRUCT_QUERY_KEY: bool = false; +} diff --git a/src/tools/rustfmt/tests/target/impls.rs b/src/tools/rustfmt/tests/target/impls.rs new file mode 100644 index 0000000000..bf63f924a3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/impls.rs @@ -0,0 +1,244 @@ +// rustfmt-normalize_comments: true +impl Foo for Bar { + fn foo() { + "hi" + } +} + +pub impl Foo for Bar { + // Associated Constants + const Baz: i32 = 16; + // Associated Types + type FooBar = usize; + // Comment 1 + fn foo() { + "hi" + } + // Comment 2 + fn foo() { + "hi" + } + // Comment 3 +} + +pub unsafe impl<'a, 'b, X, Y: Foo> !Foo<'a, X> for Bar<'b, Y> +where + X: Foo<'a, Z>, +{ + fn foo() { + "hi" + } +} + +impl<'a, 'b, X, Y: Foo> Foo<'a, X> for Bar<'b, Y> +where + X: Fooooooooooooooooooooooooooooo<'a, Z>, +{ + fn foo() { + "hi" + } +} + +impl<'a, 'b, X, Y: Foo> Foo<'a, X> for Bar<'b, Y> +where + X: Foooooooooooooooooooooooooooo<'a, Z>, +{ + fn foo() { + "hi" + } +} + +impl Foo for Bar where T: Baz {} + +impl Foo for Bar +where + T: Baz, +{ + // Comment +} + +impl Foo { + fn foo() {} +} + +impl Boo { + // BOO + fn boo() {} + // FOO +} + +mod a { + impl Foo { + // Hello! + fn foo() {} + } +} + +mod b { + mod a { + impl Foo { + fn foo() {} + } + } +} + +impl Foo { + add_fun!(); +} + +impl Blah { + fn boop() {} + add_fun!(); +} + +impl X { + fn do_parse(mut self: X) {} +} + +impl Y5000 { + fn bar(self: X<'a, 'b>, y: Y) {} + + fn bad(&self, (x, y): CoorT) {} + + fn turbo_bad(self: X<'a, 'b>, (x, y): CoorT) {} +} + +pub impl Foo for Bar +where + T: Foo, +{ + fn foo() { + "hi" + } +} + +pub impl Foo for Bar +where + T: Foo, + Z: Baz, +{ +} + +mod m { + impl PartialEq for S + where + T: PartialEq, + { + fn eq(&self, other: &Self) { + true + } + } + + impl PartialEq for S where T: PartialEq {} +} + +impl + Handle, HandleType> +{ +} + +impl PartialEq + for Handle, HandleType> +{ +} + +mod x { + impl Foo + where + A: 'static, + B: 'static, + C: 'static, + D: 'static, + { + } +} + +impl + Issue1249 +{ + // Creates a new flow constructor. + fn foo() {} +} + +// #1600 +impl<#[may_dangle] K, #[may_dangle] V> Drop for RawTable { + fn drop() {} +} + +// #1168 +pub trait Number: + Copy + + Eq + + Not + + Shl + + Shr + + BitAnd + + BitOr + + BitAndAssign + + BitOrAssign +{ + // test + fn zero() -> Self; +} + +// #1642 +pub trait SomeTrait: + Clone + + Eq + + PartialEq + + Ord + + PartialOrd + + Default + + Hash + + Debug + + Display + + Write + + Read + + FromStr +{ + // comment +} + +// #1995 +impl Foo { + fn f( + S { + aaaaaaaaaa: aaaaaaaaaa, + bbbbbbbbbb: bbbbbbbbbb, + cccccccccc: cccccccccc, + }: S, + ) -> u32 { + 1 + } +} + +// #2491 +impl<'a, 'b, 'c> SomeThing + for ( + &'a mut SomethingLong, + &'b mut SomethingLong, + &'c mut SomethingLong, + ) +{ + fn foo() {} +} + +// #2746 +impl<'seq1, 'seq2, 'body, 'scope, Channel> + Adc12< + Dual, + MasterRunningDma<'seq1, 'body, 'scope, Channel>, + SlaveRunningDma<'seq2, 'body, 'scope>, + > +where + Channel: DmaChannel, +{ +} + +// #4084 +impl const std::default::Default for Struct { + #[inline] + fn default() -> Self { + Self { f: 12.5 } + } +} diff --git a/src/tools/rustfmt/tests/target/import-fencepost-length.rs b/src/tools/rustfmt/tests/target/import-fencepost-length.rs new file mode 100644 index 0000000000..fd09d50d72 --- /dev/null +++ b/src/tools/rustfmt/tests/target/import-fencepost-length.rs @@ -0,0 +1,7 @@ +use aaaaaaaaaaaaaaa::bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; +use aaaaaaaaaaaaaaa::{ + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccccccccccccc, dddddddd, +}; +use aaaaaaaaaaaaaaa::{ + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccccccccccccc, ddddddddd, +}; diff --git a/src/tools/rustfmt/tests/target/imports-impl-only-use.rs b/src/tools/rustfmt/tests/target/imports-impl-only-use.rs new file mode 100644 index 0000000000..d290d8d918 --- /dev/null +++ b/src/tools/rustfmt/tests/target/imports-impl-only-use.rs @@ -0,0 +1,4 @@ +#![feature(underscore_imports)] + +use attr; +use std::iter::Iterator as _; diff --git a/src/tools/rustfmt/tests/target/imports-reorder-lines-and-items.rs b/src/tools/rustfmt/tests/target/imports-reorder-lines-and-items.rs new file mode 100644 index 0000000000..98a5afe434 --- /dev/null +++ b/src/tools/rustfmt/tests/target/imports-reorder-lines-and-items.rs @@ -0,0 +1,7 @@ +use std::cmp::{a, b, c, d}; +use std::ddd::aaa; +use std::ddd::{a, b, c as g, d as p}; +/// This comment should stay with `use std::str;` +use std::str; +// This comment should stay with `use std::ddd:bbb;` +use std::ddd::bbb; diff --git a/src/tools/rustfmt/tests/target/imports-reorder-lines.rs b/src/tools/rustfmt/tests/target/imports-reorder-lines.rs new file mode 100644 index 0000000000..5b85503b55 --- /dev/null +++ b/src/tools/rustfmt/tests/target/imports-reorder-lines.rs @@ -0,0 +1,31 @@ +use std::cmp::{a, b, c, d}; +use std::cmp::{b, e, f, g}; +use std::ddd::aaa; +use std::str; +// This comment should stay with `use std::ddd;` +use std::ddd; +use std::ddd::bbb; + +mod test {} + +use aaa; +use aaa::bbb; +use aaa::*; + +mod test {} +// If item names are equal, order by rename + +use test::{a as bb, b}; +use test::{a as aa, c}; + +mod test {} +// If item names are equal, order by rename - no rename comes before a rename + +use test::{a as bb, b}; +use test::{a, c}; + +mod test {} +// `self` always comes first + +use test::{self as bb, b}; +use test::{a as aa, c}; diff --git a/src/tools/rustfmt/tests/target/imports-reorder.rs b/src/tools/rustfmt/tests/target/imports-reorder.rs new file mode 100644 index 0000000000..84e97c0224 --- /dev/null +++ b/src/tools/rustfmt/tests/target/imports-reorder.rs @@ -0,0 +1,5 @@ +// rustfmt-normalize_comments: true + +use path::{self /* self */, /* A */ A, B /* B */, C}; + +use {aa, ab, ac, b, Z}; diff --git a/src/tools/rustfmt/tests/target/imports.rs b/src/tools/rustfmt/tests/target/imports.rs new file mode 100644 index 0000000000..87584d89f6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/imports.rs @@ -0,0 +1,129 @@ +// rustfmt-normalize_comments: true + +// Imports. + +// Long import. +use exceedingly::loooooooooooooooooooooooooooooooooooooooooooooooooooooooong::import::path::{ + ItemA, ItemB, +}; +use exceedingly::looooooooooooooooooooooooooooooooooooooooooooooooooooooooooong::import::path::{ + ItemA, ItemB, +}; +use rustc_ast::ast::{ItemDefaultImpl, ItemForeignMod, ItemImpl, ItemMac, ItemMod, ItemStatic}; + +use list::{ + // Another item + AnotherItem, // Another Comment + // Last Item + LastItem, + // Some item + SomeItem, // Comment +}; + +use test::{/* A */ self /* B */, Other /* C */}; + +pub use rustc_ast::ast::{Expr, ExprAssign, ExprCall, ExprMethodCall, ExprPath, Expr_}; +use rustc_ast::{self}; +use Foo::{Bar, Baz}; +use {Bar /* comment */, /* Pre-comment! */ Foo}; + +use std::io; +use std::io::{self}; + +mod Foo { + pub use rustc_ast::ast::{ + ItemDefaultImpl, ItemForeignMod, ItemImpl, ItemMac, ItemMod, ItemStatic, + }; + + mod Foo2 { + pub use rustc_ast::ast::{ + self, ItemDefaultImpl, ItemForeignMod, ItemImpl, ItemMac, ItemMod, ItemStatic, + }; + } +} + +fn test() { + use Baz::*; + use Qux; +} + +// Simple imports +use bar::quux as kaas; +use foo; +use foo::bar::baz; + +// With aliases. +use foo::qux as bar; +use foo::{self as bar}; +use foo::{self as bar, baz}; +use foo::{baz, qux as bar}; + +// With absolute paths +use foo; +use foo::Bar; +use foo::{Bar, Baz}; +use Foo; +use {Bar, Baz}; + +// Root globs +use *; +use *; + +// spaces used to cause glob imports to disappear (#1356) +use super::*; +use foo::issue_1356::*; + +// We shouldn't remove imports which have attributes attached (#1858) +#[cfg(unix)] +use self::unix::{}; + +// nested imports +use foo::{ + a, b, + bar::{ + baz, + foo::{a, b, cxxxxxxxxxxxxx, yyyyyyyyyyyyyy, zzzzzzzzzzzzzzzz}, + qux, xxxxxxxxxxx, yyyyyyyyyyyyy, zzzzzzzzzzzzzzzz, + }, + boo, c, +}; + +use fooo::{ + baar::foobar::{ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, + zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz, + }, + bar, + bar::*, + x, y, z, +}; + +use exonum::{ + api::{Api, ApiError}, + blockchain::{self, BlockProof, Blockchain, Transaction, TransactionSet}, + crypto::{Hash, PublicKey}, + helpers::Height, + node::TransactionSend, + storage::{ListProof, MapProof}, +}; + +// nested imports with a single sub-tree. +use a::b::c::d; +use a::b::c::*; +use a::b::c::{xxx, yyy, zzz}; + +// #2645 +/// This line is not affected. +// This line is deleted. +use c; + +// #2670 +#[macro_use] +use imports_with_attr; + +// #2888 +use std::f64::consts::{E, PI, SQRT_2}; + +// #3273 +#[rustfmt::skip] +use std::fmt::{self, {Display, Formatter}}; diff --git a/src/tools/rustfmt/tests/target/imports_2021_edition.rs b/src/tools/rustfmt/tests/target/imports_2021_edition.rs new file mode 100644 index 0000000000..34dcc866a0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/imports_2021_edition.rs @@ -0,0 +1,3 @@ +// rustfmt-edition: 2021 + +use ::happy::new::year; diff --git a/src/tools/rustfmt/tests/target/imports_block_indent.rs b/src/tools/rustfmt/tests/target/imports_block_indent.rs new file mode 100644 index 0000000000..8c90f7ce29 --- /dev/null +++ b/src/tools/rustfmt/tests/target/imports_block_indent.rs @@ -0,0 +1,4 @@ +// #2569 +use apns2::request::notification::{ + Notificatio, NotificationBuilder, Priority, SilentNotificationBuilder, +}; diff --git a/src/tools/rustfmt/tests/target/imports_granularity_crate.rs b/src/tools/rustfmt/tests/target/imports_granularity_crate.rs new file mode 100644 index 0000000000..d75906d30f --- /dev/null +++ b/src/tools/rustfmt/tests/target/imports_granularity_crate.rs @@ -0,0 +1,28 @@ +// rustfmt-imports_granularity: Crate + +use a::{a, b, c, d, e, f, g}; + +#[doc(hidden)] +use a::b; +use a::{c, d}; + +#[doc(hidden)] +use a::b; +use a::{c, d, e}; + +use foo::{a, b, c}; +pub use foo::{bar, foobar}; + +use a::b::c::{d, xxx, yyy, zzz, *}; + +// https://github.com/rust-lang/rustfmt/issues/3808 +use d::{self}; +use e::{self as foo}; +use f::{self, b}; +use g::{self, a, b}; +use h::a; +use i::a::{self}; +use j::a::{self}; + +use k::{a, b, c, d}; +use l::{a, b, c, d}; diff --git a/src/tools/rustfmt/tests/target/imports_granularity_item.rs b/src/tools/rustfmt/tests/target/imports_granularity_item.rs new file mode 100644 index 0000000000..eace785e67 --- /dev/null +++ b/src/tools/rustfmt/tests/target/imports_granularity_item.rs @@ -0,0 +1,13 @@ +// rustfmt-imports_granularity: Item + +use a::b; +use a::c; +use a::d; +use a::f::g; +use a::h::i; +use a::h::j; +use a::l::m; +use a::l::n::o; +use a::l::p::*; +use a::l::{self}; +use a::q::{self}; diff --git a/src/tools/rustfmt/tests/target/imports_granularity_module.rs b/src/tools/rustfmt/tests/target/imports_granularity_module.rs new file mode 100644 index 0000000000..9c1387c466 --- /dev/null +++ b/src/tools/rustfmt/tests/target/imports_granularity_module.rs @@ -0,0 +1,20 @@ +// rustfmt-imports_granularity: Module + +use a::b::c; +use a::d::e; +use a::f; +use a::g::{h, i}; +use a::j::k::{self, l}; +use a::j::{self, m}; +use a::n::o::p; +use a::n::q; +pub use a::r::s; +pub use a::t; + +use foo::e; +#[cfg(test)] +use foo::{a::b, c::d}; + +use bar::a::b; +use bar::c::d; +use bar::e::f; diff --git a/src/tools/rustfmt/tests/target/indented-impl.rs b/src/tools/rustfmt/tests/target/indented-impl.rs new file mode 100644 index 0000000000..eff579ddda --- /dev/null +++ b/src/tools/rustfmt/tests/target/indented-impl.rs @@ -0,0 +1,13 @@ +// rustfmt-brace_style: AlwaysNextLine +mod x +{ + struct X(i8); + + impl Y for X + { + fn y(self) -> () + { + println!("ok"); + } + } +} diff --git a/src/tools/rustfmt/tests/target/inner-module-path/b.rs b/src/tools/rustfmt/tests/target/inner-module-path/b.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/target/inner-module-path/b.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/target/inner-module-path/c/d.rs b/src/tools/rustfmt/tests/target/inner-module-path/c/d.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tools/rustfmt/tests/target/inner-module-path/c/d.rs @@ -0,0 +1 @@ + diff --git a/src/tools/rustfmt/tests/target/inner-module-path/lib.rs b/src/tools/rustfmt/tests/target/inner-module-path/lib.rs new file mode 100644 index 0000000000..60d246dd5f --- /dev/null +++ b/src/tools/rustfmt/tests/target/inner-module-path/lib.rs @@ -0,0 +1,8 @@ +#[path = "."] +mod a { + mod b; +} + +mod c { + mod d; +} diff --git a/src/tools/rustfmt/tests/target/invalid-rust-code-in-doc-comment.rs b/src/tools/rustfmt/tests/target/invalid-rust-code-in-doc-comment.rs new file mode 100644 index 0000000000..f8479d4e34 --- /dev/null +++ b/src/tools/rustfmt/tests/target/invalid-rust-code-in-doc-comment.rs @@ -0,0 +1,18 @@ +// rustfmt-format_code_in_doc_comments: true + +/// ```rust +/// if (true) { … } +/// ``` +fn a() {} + +/// ```rust +/// if foo() { +/// … +/// } +/// ``` +fn a() {} + +/// ```rust +/// k1 == k2 ⇒ hash(k1) == hash(k2) +/// ``` +pub struct a; diff --git a/src/tools/rustfmt/tests/target/issue-1021.rs b/src/tools/rustfmt/tests/target/issue-1021.rs new file mode 100644 index 0000000000..ba1029d4e6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1021.rs @@ -0,0 +1,22 @@ +// rustfmt-normalize_comments: true +fn main() { + match x { + S(true, .., true) => (), + S(true, ..) => (), + S(.., true) => (), + S(..) => (), + S(_) => (), + S(/* .. */ ..) => (), + S(/* .. */ .., true) => (), + } + + match y { + (true, .., true) => (), + (true, ..) => (), + (.., true) => (), + (..) => (), + (_,) => (), + (/* .. */ ..) => (), + (/* .. */ .., true) => (), + } +} diff --git a/src/tools/rustfmt/tests/target/issue-1049.rs b/src/tools/rustfmt/tests/target/issue-1049.rs new file mode 100644 index 0000000000..c788519ca9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1049.rs @@ -0,0 +1,29 @@ +// Test overlong function signature +pub unsafe fn reborrow_mut( + &mut X: Abcde, +) -> Handle, HandleType> { +} + +pub fn merge( + mut X: Abcdef, +) -> Handle, K, V, marker::Internal>, marker::Edge> { +} + +impl Handle { + pub fn merge( + a: Abcd, + ) -> Handle, K, V, marker::Internal>, marker::Edge> { + } +} + +// Long function without return type that should not be reformatted. +fn veeeeeeeeeeeeeeeeeeeeery_long_name(a: FirstTypeeeeeeeeee, b: SecondTypeeeeeeeeeeeeeeeeeeeeeee) {} + +fn veeeeeeeeeeeeeeeeeeeeeery_long_name(a: FirstTypeeeeeeeeee, b: SecondTypeeeeeeeeeeeeeeeeeeeeeee) { +} + +fn veeeeeeeeeeeeeeeeeeeeeeery_long_name( + a: FirstTypeeeeeeeeee, + b: SecondTypeeeeeeeeeeeeeeeeeeeeeee, +) { +} diff --git a/src/tools/rustfmt/tests/target/issue-1055.rs b/src/tools/rustfmt/tests/target/issue-1055.rs new file mode 100644 index 0000000000..ee143e792b --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1055.rs @@ -0,0 +1,3 @@ +fn issue_1055() { + let foo = (|| {})(); +} diff --git a/src/tools/rustfmt/tests/target/issue-1096.rs b/src/tools/rustfmt/tests/target/issue-1096.rs new file mode 100644 index 0000000000..de78e73648 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1096.rs @@ -0,0 +1,71 @@ +struct StructA /* comment 1 */ { + t: T, +} + +struct StructB /* comment 2 */; + +struct StructC /* comment 3 */; + +struct StructD /* comment 4 */ { + t: usize, +} + +struct StructE +/* comment 5 */ +where + T: Clone, +{ + t: usize, +} + +struct StructF +/* comment 6 */ +where + T: Clone, +{ + t: usize, +} + +struct StructG +/* comment 7 */ +// why a line comment?? +{ + t: T, +} + +struct StructH +/* comment 8 */ +// why a line comment?? +where + T: Clone, +{ + t: T, +} + +enum EnumA /* comment 8 */ { + Field(T), +} + +enum EnumB /* comment 9 */ { + Field, +} + +// Issue 2781 +struct StructX1 +// where +// T: Clone +{ + inner: String, +} + +struct StructX2< + T, + U: Iterator, + V: Iterator, + W: Iterator, +> +// where +// T: Clone +{ + inner: String, +} diff --git a/src/tools/rustfmt/tests/target/issue-1111.rs b/src/tools/rustfmt/tests/target/issue-1111.rs new file mode 100644 index 0000000000..2e1a89ad78 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1111.rs @@ -0,0 +1 @@ +use bar; diff --git a/src/tools/rustfmt/tests/target/issue-1113.rs b/src/tools/rustfmt/tests/target/issue-1113.rs new file mode 100644 index 0000000000..1245bcd057 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1113.rs @@ -0,0 +1,33 @@ +pub fn foo() -> fmt::Result +//pub fn writeStringToken +{ + panic!() +} + +pub fn foo() -> fmt::Result // pub fn writeStringToken +{ + panic!() +} + +pub fn foo() -> fmt::Result /* pub fn writeStringToken */ { + panic!() +} + +pub fn foo() -> fmt::Result +/* pub fn writeStringToken */ { + panic!() +} + +pub fn foo() -> fmt::Result +/* pub fn writeStringToken */ +{ + panic!() +} + +pub fn foo() -> fmt::Result /* + * + * + */ +{ + panic!() +} diff --git a/src/tools/rustfmt/tests/target/issue-1120.rs b/src/tools/rustfmt/tests/target/issue-1120.rs new file mode 100644 index 0000000000..f44597e7d1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1120.rs @@ -0,0 +1,11 @@ +// rustfmt-reorder_imports: true + +// Ensure that a use at the start of an inline module is correctly formatted. +mod foo { + use bar; +} + +// Ensure that an indented `use` gets the correct indentation. +mod foo { + use bar; +} diff --git a/src/tools/rustfmt/tests/target/issue-1124.rs b/src/tools/rustfmt/tests/target/issue-1124.rs new file mode 100644 index 0000000000..f0fc485a3c --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1124.rs @@ -0,0 +1,21 @@ +// rustfmt-reorder_imports: true + +use a; +use b; +use c; +use d; +// The previous line has a space after the `use a;` + +mod a { + use a; + use b; + use c; + use d; +} + +use z; + +use y; + +use a; +use x; diff --git a/src/tools/rustfmt/tests/target/issue-1127.rs b/src/tools/rustfmt/tests/target/issue-1127.rs new file mode 100644 index 0000000000..fb09036d1f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1127.rs @@ -0,0 +1,25 @@ +// rustfmt-max_width: 120 +// rustfmt-match_arm_blocks: false +// rustfmt-match_block_trailing_comma: true + +fn a_very_very_very_very_very_very_very_very_very_very_very_long_function_name() -> i32 { + 42 +} + +enum TestEnum { + AVeryVeryLongEnumName, + AnotherVeryLongEnumName, + TheLastVeryLongEnumName, +} + +fn main() { + let var = TestEnum::AVeryVeryLongEnumName; + let num = match var { + TestEnum::AVeryVeryLongEnumName => + a_very_very_very_very_very_very_very_very_very_very_very_long_function_name(), + TestEnum::AnotherVeryLongEnumName => + a_very_very_very_very_very_very_very_very_very_very_very_long_function_name(), + TestEnum::TheLastVeryLongEnumName => + a_very_very_very_very_very_very_very_very_very_very_very_long_function_name(), + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-1158.rs b/src/tools/rustfmt/tests/target/issue-1158.rs new file mode 100644 index 0000000000..2abfa5a29f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1158.rs @@ -0,0 +1,3 @@ +trait T { + itemmacro!(this, is.now().formatted(yay)); +} diff --git a/src/tools/rustfmt/tests/target/issue-1177.rs b/src/tools/rustfmt/tests/target/issue-1177.rs new file mode 100644 index 0000000000..dcda397281 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1177.rs @@ -0,0 +1,7 @@ +// rustfmt-normalize_comments: true +fn main() { + // Line Comment + // Block Comment + + let d = 5; +} diff --git a/src/tools/rustfmt/tests/target/issue-1192.rs b/src/tools/rustfmt/tests/target/issue-1192.rs new file mode 100644 index 0000000000..432fe8cce7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1192.rs @@ -0,0 +1,3 @@ +fn main() { + assert!(true); +} diff --git a/src/tools/rustfmt/tests/target/issue-1210/a.rs b/src/tools/rustfmt/tests/target/issue-1210/a.rs new file mode 100644 index 0000000000..94c1b44e5e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1210/a.rs @@ -0,0 +1,16 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 50 + +impl Foo { + fn cxx(&self, target: &str) -> &Path { + match self.cxx.get(target) { + Some(p) => p.path(), + None => panic!( + "\n\ntarget `{}` is not \ + configured as a host, + only as a target\n\n", + target + ), + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-1210/b.rs b/src/tools/rustfmt/tests/target/issue-1210/b.rs new file mode 100644 index 0000000000..a7b1e3bcdc --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1210/b.rs @@ -0,0 +1,16 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 50 + +impl Foo { + fn cxx(&self, target: &str) -> &Path { + match self.cxx.get(target) { + Some(p) => p.path(), + None => panic!( + "\ntarget `{}`: is not, \ + configured as a host, + only as a target\n\n", + target + ), + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-1210/c.rs b/src/tools/rustfmt/tests/target/issue-1210/c.rs new file mode 100644 index 0000000000..183d79f925 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1210/c.rs @@ -0,0 +1,7 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 50 + +const foo: String = + "trailing_spaces!! + keep them! Amet neque. Praesent \ + rhoncus eros non velit."; diff --git a/src/tools/rustfmt/tests/target/issue-1210/d.rs b/src/tools/rustfmt/tests/target/issue-1210/d.rs new file mode 100644 index 0000000000..9279e6fc99 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1210/d.rs @@ -0,0 +1,4 @@ +// rustfmt-wrap_comments: true + +// trailing_spaces_in_comment!! +// remove those from above diff --git a/src/tools/rustfmt/tests/target/issue-1210/e.rs b/src/tools/rustfmt/tests/target/issue-1210/e.rs new file mode 100644 index 0000000000..55f80c6c3c --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1210/e.rs @@ -0,0 +1,11 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 50 + +// explicit line breaks should be kept in order to preserve the layout + +const foo: String = + "Suspendisse vel augue at felis tincidunt \ + sollicitudin. Fusce arcu. + Duis et odio et leo + sollicitudin consequat. Aliquam \ + lobortis. Phasellus condimentum."; diff --git a/src/tools/rustfmt/tests/target/issue-1211.rs b/src/tools/rustfmt/tests/target/issue-1211.rs new file mode 100644 index 0000000000..de4c5c87ee --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1211.rs @@ -0,0 +1,13 @@ +fn main() { + for iface in &ifaces { + match iface.addr { + get_if_addrs::IfAddr::V4(ref addr) => match addr.broadcast { + Some(ip) => { + sock.send_to(&buf, (ip, 8765)).expect("foobar"); + } + _ => (), + }, + _ => (), + }; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-1214.rs b/src/tools/rustfmt/tests/target/issue-1214.rs new file mode 100644 index 0000000000..c622abb3a8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1214.rs @@ -0,0 +1,8 @@ +/*! +# Example + +``` + // Here goes some example +``` + */ +struct Item; diff --git a/src/tools/rustfmt/tests/target/issue-1216.rs b/src/tools/rustfmt/tests/target/issue-1216.rs new file mode 100644 index 0000000000..d727c158ab --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1216.rs @@ -0,0 +1,5 @@ +// rustfmt-normalize_comments: true +enum E { + A, //* I am not a block comment (caused panic) + B, +} diff --git a/src/tools/rustfmt/tests/target/issue-1239.rs b/src/tools/rustfmt/tests/target/issue-1239.rs new file mode 100644 index 0000000000..e950200b15 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1239.rs @@ -0,0 +1,11 @@ +fn foo() { + let with_alignment = if condition__uses_alignment_for_first_if__0 + || condition__uses_alignment_for_first_if__1 + || condition__uses_alignment_for_first_if__2 + { + } else if condition__no_alignment_for_later_else__0 + || condition__no_alignment_for_later_else__1 + || condition__no_alignment_for_later_else__2 + { + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-1247.rs b/src/tools/rustfmt/tests/target/issue-1247.rs new file mode 100644 index 0000000000..16c63e0f53 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1247.rs @@ -0,0 +1,8 @@ +// rustfmt-max_width: 80 + +fn foo() { + polyfill::slice::fill( + &mut self.pending[padding_pos..(self.algorithm.block_len - 8)], + 0, + ); +} diff --git a/src/tools/rustfmt/tests/target/issue-1255.rs b/src/tools/rustfmt/tests/target/issue-1255.rs new file mode 100644 index 0000000000..2d4633844a --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1255.rs @@ -0,0 +1,10 @@ +// Test for issue #1255 +// Default annotation incorrectly removed on associated types +#![feature(specialization)] + +trait Trait { + type Type; +} +impl Trait for T { + default type Type = u64; // 'default' should not be removed +} diff --git a/src/tools/rustfmt/tests/target/issue-1278.rs b/src/tools/rustfmt/tests/target/issue-1278.rs new file mode 100644 index 0000000000..e25376561a --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1278.rs @@ -0,0 +1,9 @@ +// rustfmt-indent_style = "block" + +#![feature(pub_restricted)] + +mod inner_mode { + pub(super) fn func_name(abc: i32) -> i32 { + abc + } +} diff --git a/src/tools/rustfmt/tests/target/issue-1350.rs b/src/tools/rustfmt/tests/target/issue-1350.rs new file mode 100644 index 0000000000..2cf65509c9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1350.rs @@ -0,0 +1,14 @@ +// rustfmt-max_width: 120 +// rustfmt-comment_width: 110 + +impl Struct { + fn fun() { + let result = match ::deserialize(&json) { + Ok(v) => v, + Err(e) => match ::deserialize(&json) { + Ok(v) => return Err(Error::with_json(v)), + Err(e2) => return Err(Error::with_json(e)), + }, + }; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-1366.rs b/src/tools/rustfmt/tests/target/issue-1366.rs new file mode 100644 index 0000000000..eee147baab --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1366.rs @@ -0,0 +1,13 @@ +fn main() { + fn f() -> Option { + Some("fffffffsssssssssddddssssfffffddddff") + .map(|s| s) + .map(|s| s.to_string()) + .map(|res| match Some(res) { + Some(ref s) if s == "" => 41, + Some(_) => 42, + _ => 43, + }) + } + println!("{:?}", f()) +} diff --git a/src/tools/rustfmt/tests/target/issue-1397.rs b/src/tools/rustfmt/tests/target/issue-1397.rs new file mode 100644 index 0000000000..86b7a78411 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1397.rs @@ -0,0 +1,25 @@ +pub enum TransactionState { + Committed(i64), +} + +pub enum Packet { + Transaction { state: TransactionState }, +} + +fn baz(p: Packet) { + loop { + loop { + loop { + loop { + if let Packet::Transaction { + state: TransactionState::Committed(ts, ..), + .. + } = p + { + unreachable!() + } + } + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-1468.rs b/src/tools/rustfmt/tests/target/issue-1468.rs new file mode 100644 index 0000000000..4c14a0f746 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1468.rs @@ -0,0 +1,29 @@ +fn issue1468() { + euc_jp_decoder_functions!({ + let trail_minus_offset = byte.wrapping_sub(0xA1); + // Fast-track Hiragana (60% according to Lunde) + // and Katakana (10% according to Lunde). + if jis0208_lead_minus_offset == 0x03 && trail_minus_offset < 0x53 { + // Hiragana + handle.write_upper_bmp(0x3041 + trail_minus_offset as u16) + } else if jis0208_lead_minus_offset == 0x04 && trail_minus_offset < 0x56 { + // Katakana + handle.write_upper_bmp(0x30A1 + trail_minus_offset as u16) + } else if trail_minus_offset > (0xFE - 0xA1) { + if byte < 0x80 { + return ( + DecoderResult::Malformed(1, 0), + unread_handle_trail.unread(), + handle.written(), + ); + } + return ( + DecoderResult::Malformed(2, 0), + unread_handle_trail.consumed(), + handle.written(), + ); + } else { + unreachable!(); + } + }); +} diff --git a/src/tools/rustfmt/tests/target/issue-1598.rs b/src/tools/rustfmt/tests/target/issue-1598.rs new file mode 100644 index 0000000000..c7e02c961a --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1598.rs @@ -0,0 +1,6 @@ +fn main() { + //foo + /* + */ + format!("hello"); +} diff --git a/src/tools/rustfmt/tests/target/issue-1624.rs b/src/tools/rustfmt/tests/target/issue-1624.rs new file mode 100644 index 0000000000..477fc27353 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1624.rs @@ -0,0 +1,6 @@ +// #1624 +pub unsafe fn some_long_function_name( + arg1: Type1, + arg2: Type2, +) -> (SomeLongTypeName, AnotherLongTypeName, AnotherLongTypeName) { +} diff --git a/src/tools/rustfmt/tests/target/issue-1681.rs b/src/tools/rustfmt/tests/target/issue-1681.rs new file mode 100644 index 0000000000..902765302e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1681.rs @@ -0,0 +1,21 @@ +// rustfmt-max_width: 80 + +// We would like to surround closure body with block when overflowing the last +// argument of function call if the last argument has condition and without +// block it may go multi lines. +fn foo() { + refmut_map_result(self.cache.borrow_mut(), |cache| { + match cache.entry(cache_key) { + Occupied(entry) => Ok(entry.into_mut()), + Vacant(entry) => { + let statement = { + let sql = try!(entry.key().sql(source)); + prepare_fn(&sql) + }; + + Ok(entry.insert(try!(statement))) + } + } + }) + .map(MaybeCached::Cached) +} diff --git a/src/tools/rustfmt/tests/target/issue-1693.rs b/src/tools/rustfmt/tests/target/issue-1693.rs new file mode 100644 index 0000000000..85421a123b --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1693.rs @@ -0,0 +1,10 @@ +fn issue1693() { + let pixel_data = vec![ + ( + f16::from_f32(0.82), + f16::from_f32(1.78), + f16::from_f32(0.21) + ); + 256 * 256 + ]; +} diff --git a/src/tools/rustfmt/tests/target/issue-1703.rs b/src/tools/rustfmt/tests/target/issue-1703.rs new file mode 100644 index 0000000000..4079ef4cfc --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1703.rs @@ -0,0 +1,9 @@ +// rustfmt should not remove doc comments or comments inside attributes. + +/** +This function has a block doc comment. + */ +fn test_function() {} + +#[foo /* do not remove this! */] +fn foo() {} diff --git a/src/tools/rustfmt/tests/target/issue-1800.rs b/src/tools/rustfmt/tests/target/issue-1800.rs new file mode 100644 index 0000000000..06c5cfd056 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1800.rs @@ -0,0 +1,3 @@ +#![doc(html_root_url = "http://example.com")] +#[cfg(feature = "foo")] +fn a() {} diff --git a/src/tools/rustfmt/tests/target/issue-1802.rs b/src/tools/rustfmt/tests/target/issue-1802.rs new file mode 100644 index 0000000000..ef7ee89105 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1802.rs @@ -0,0 +1,9 @@ +// rustfmt-tab_spaces: 2 +// rustfmt-max_width: 30 + +enum F { + X { + a: dddddddddddddd, + b: eeeeeeeeeeeeeee, + }, +} diff --git a/src/tools/rustfmt/tests/target/issue-1824.rs b/src/tools/rustfmt/tests/target/issue-1824.rs new file mode 100644 index 0000000000..1c4c2db46d --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1824.rs @@ -0,0 +1,5 @@ +pub trait Ingredient +where + Self: Send, +{ +} diff --git a/src/tools/rustfmt/tests/target/issue-1914.rs b/src/tools/rustfmt/tests/target/issue-1914.rs new file mode 100644 index 0000000000..d2d532af1d --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-1914.rs @@ -0,0 +1,7 @@ +// rustfmt-max_width: 80 + +extern "C" { + #[link_name = "_ZN7MyClass26example_check_no_collisionE"] + pub static mut MyClass_example_check_no_collision: + *const ::std::os::raw::c_int; +} diff --git a/src/tools/rustfmt/tests/target/issue-2025.rs b/src/tools/rustfmt/tests/target/issue-2025.rs new file mode 100644 index 0000000000..38bf369bea --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2025.rs @@ -0,0 +1,4 @@ +// See if rustfmt removes empty lines on top of the file. +pub fn foo() { + println!("hello, world"); +} diff --git a/src/tools/rustfmt/tests/target/issue-2103.rs b/src/tools/rustfmt/tests/target/issue-2103.rs new file mode 100644 index 0000000000..5a043d54b7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2103.rs @@ -0,0 +1,14 @@ +struct X +where + i32: Sized, +{ + x: i32, +} + +struct X +// with comment +where + i32: Sized, +{ + x: i32, +} diff --git a/src/tools/rustfmt/tests/target/issue-2111.rs b/src/tools/rustfmt/tests/target/issue-2111.rs new file mode 100644 index 0000000000..42c1862e88 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2111.rs @@ -0,0 +1,26 @@ +// An import with single line comments. +use super::{ + DelayChoice, + Destinations, + Holding, + LodaModel, + MethodDescription, + ModelBehaviour, + ModelEdges, + ModelProperties, + ModelRequestGraph, + ModelSelector, + RequestDescription, + StringMap, + Switch, + // ModelMetaData, + // Generated, + // SecondsString, + // DateString, + // ModelConfiguration, + // ModelRequests, + // RestResponse, + // RestResponseCode, + // UniformHolding + SCHEMA_VERSIONS, +}; diff --git a/src/tools/rustfmt/tests/target/issue-2123.rs b/src/tools/rustfmt/tests/target/issue-2123.rs new file mode 100644 index 0000000000..5e9917b402 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2123.rs @@ -0,0 +1,6 @@ +// rustfmt-wrap_comments: true + +//hello +//world + +fn main() {} diff --git a/src/tools/rustfmt/tests/target/issue-2164.rs b/src/tools/rustfmt/tests/target/issue-2164.rs new file mode 100644 index 0000000000..dbf92107ce --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2164.rs @@ -0,0 +1,135 @@ +// A stress test against code generated by bindgen. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct emacs_env_25 { + pub size: isize, + pub private_members: *mut emacs_env_private, + pub make_global_ref: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, any_reference: emacs_value) -> emacs_value, + >, + pub free_global_ref: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, global_reference: emacs_value), + >, + pub non_local_exit_check: + ::std::option::Option emacs_funcall_exit>, + pub non_local_exit_clear: ::std::option::Option, + pub non_local_exit_get: ::std::option::Option< + unsafe extern "C" fn( + env: *mut emacs_env, + non_local_exit_symbol_out: *mut emacs_value, + non_local_exit_data_out: *mut emacs_value, + ) -> emacs_funcall_exit, + >, + pub non_local_exit_signal: ::std::option::Option< + unsafe extern "C" fn( + env: *mut emacs_env, + non_local_exit_symbol: emacs_value, + non_local_exit_data: emacs_value, + ), + >, + pub non_local_exit_throw: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, tag: emacs_value, value: emacs_value), + >, + pub make_function: ::std::option::Option< + unsafe extern "C" fn( + env: *mut emacs_env, + min_arity: isize, + max_arity: isize, + function: ::std::option::Option< + unsafe extern "C" fn( + env: *mut emacs_env, + nargs: isize, + args: *mut emacs_value, + arg1: *mut ::libc::c_void, + ) -> emacs_value, + >, + documentation: *const ::libc::c_char, + data: *mut ::libc::c_void, + ) -> emacs_value, + >, + pub funcall: ::std::option::Option< + unsafe extern "C" fn( + env: *mut emacs_env, + function: emacs_value, + nargs: isize, + args: *mut emacs_value, + ) -> emacs_value, + >, + pub intern: ::std::option::Option< + unsafe extern "C" fn( + env: *mut emacs_env, + symbol_name: *const ::libc::c_char, + ) -> emacs_value, + >, + pub type_of: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, value: emacs_value) -> emacs_value, + >, + pub is_not_nil: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, value: emacs_value) -> bool, + >, + pub eq: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, a: emacs_value, b: emacs_value) -> bool, + >, + pub extract_integer: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, value: emacs_value) -> intmax_t, + >, + pub make_integer: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, value: intmax_t) -> emacs_value, + >, + pub extract_float: + ::std::option::Option f64>, + pub make_float: + ::std::option::Option emacs_value>, + pub copy_string_contents: ::std::option::Option< + unsafe extern "C" fn( + env: *mut emacs_env, + value: emacs_value, + buffer: *mut ::libc::c_char, + size_inout: *mut isize, + ) -> bool, + >, + pub make_string: ::std::option::Option< + unsafe extern "C" fn( + env: *mut emacs_env, + contents: *const ::libc::c_char, + length: isize, + ) -> emacs_value, + >, + pub make_user_ptr: ::std::option::Option< + unsafe extern "C" fn( + env: *mut emacs_env, + fin: ::std::option::Option, + ptr: *mut ::libc::c_void, + ) -> emacs_value, + >, + pub get_user_ptr: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, uptr: emacs_value) -> *mut ::libc::c_void, + >, + pub set_user_ptr: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, uptr: emacs_value, ptr: *mut ::libc::c_void), + >, + pub get_user_finalizer: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut ::libc::c_void, + env: *mut emacs_env, + uptr: emacs_value, + ) -> ::std::option::Option< + unsafe extern "C" fn(arg1: *mut ::libc::c_void, env: *mut emacs_env, uptr: emacs_value), + >, + >, + pub set_user_finalizer: ::std::option::Option< + unsafe extern "C" fn( + env: *mut emacs_env, + uptr: emacs_value, + fin: ::std::option::Option, + ), + >, + pub vec_get: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, vec: emacs_value, i: isize) -> emacs_value, + >, + pub vec_set: ::std::option::Option< + unsafe extern "C" fn(env: *mut emacs_env, vec: emacs_value, i: isize, val: emacs_value), + >, + pub vec_size: + ::std::option::Option isize>, +} diff --git a/src/tools/rustfmt/tests/target/issue-2179/one.rs b/src/tools/rustfmt/tests/target/issue-2179/one.rs new file mode 100644 index 0000000000..3f98acc8dc --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2179/one.rs @@ -0,0 +1,37 @@ +// rustfmt-version: One +// rustfmt-error_on_line_overflow: false + +fn issue_2179() { + let (opts, rustflags, clear_env_rust_log) = { + // We mustn't lock configuration for the whole build process + let rls_config = rls_config.lock().unwrap(); + + let opts = CargoOptions::new(&rls_config); + trace!("Cargo compilation options:\n{:?}", opts); + let rustflags = prepare_cargo_rustflags(&rls_config); + + // Warn about invalid specified bin target or package depending on current mode + // TODO: Return client notifications along with diagnostics to inform the user + if !rls_config.workspace_mode { + let cur_pkg_targets = ws.current().unwrap().targets(); + + if let &Some(ref build_bin) = rls_config.build_bin.as_ref() { + let mut bins = cur_pkg_targets.iter().filter(|x| x.is_bin()); + if let None = bins.find(|x| x.name() == build_bin) { + warn!( + "cargo - couldn't find binary `{}` specified in `build_bin` configuration", + build_bin + ); + } + } + } else { + for package in &opts.package { + if let None = ws.members().find(|x| x.name() == package) { + warn!("cargo - couldn't find member package `{}` specified in `analyze_package` configuration", package); + } + } + } + + (opts, rustflags, rls_config.clear_env_rust_log) + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-2179/two.rs b/src/tools/rustfmt/tests/target/issue-2179/two.rs new file mode 100644 index 0000000000..96531509ea --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2179/two.rs @@ -0,0 +1,40 @@ +// rustfmt-version: Two +// rustfmt-error_on_line_overflow: false + +fn issue_2179() { + let (opts, rustflags, clear_env_rust_log) = { + // We mustn't lock configuration for the whole build process + let rls_config = rls_config.lock().unwrap(); + + let opts = CargoOptions::new(&rls_config); + trace!("Cargo compilation options:\n{:?}", opts); + let rustflags = prepare_cargo_rustflags(&rls_config); + + // Warn about invalid specified bin target or package depending on current mode + // TODO: Return client notifications along with diagnostics to inform the user + if !rls_config.workspace_mode { + let cur_pkg_targets = ws.current().unwrap().targets(); + + if let &Some(ref build_bin) = rls_config.build_bin.as_ref() { + let mut bins = cur_pkg_targets.iter().filter(|x| x.is_bin()); + if let None = bins.find(|x| x.name() == build_bin) { + warn!( + "cargo - couldn't find binary `{}` specified in `build_bin` configuration", + build_bin + ); + } + } + } else { + for package in &opts.package { + if let None = ws.members().find(|x| x.name() == package) { + warn!( + "cargo - couldn't find member package `{}` specified in `analyze_package` configuration", + package + ); + } + } + } + + (opts, rustflags, rls_config.clear_env_rust_log) + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-2197.rs b/src/tools/rustfmt/tests/target/issue-2197.rs new file mode 100644 index 0000000000..d42c08e19d --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2197.rs @@ -0,0 +1,17 @@ +// rustfmt-max_width: 79 +// rustfmt-wrap_comments: true + +/// ```rust +/// unsafe fn sum_sse2(x: i32x4) -> i32 { +/// let x = vendor::_mm_add_epi32( +/// x, +/// vendor::_mm_srli_si128(x.into(), 8).into(), +/// ); +/// let x = vendor::_mm_add_epi32( +/// x, +/// vendor::_mm_srli_si128(x.into(), 4).into(), +/// ); +/// vendor::_mm_cvtsi128_si32(x) +/// } +/// ``` +fn foo() {} diff --git a/src/tools/rustfmt/tests/target/issue-2256.rs b/src/tools/rustfmt/tests/target/issue-2256.rs new file mode 100644 index 0000000000..0a59c30839 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2256.rs @@ -0,0 +1,7 @@ +// こんにちは +use std::borrow::Cow; + +/* comment 1 */ +/* comment 2 */ + +/* comment 3 */ diff --git a/src/tools/rustfmt/tests/target/issue-2324.rs b/src/tools/rustfmt/tests/target/issue-2324.rs new file mode 100644 index 0000000000..9211b24d81 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2324.rs @@ -0,0 +1,7 @@ +// nested function calls with cast. +fn main() { + self.ptr + .set(intrinsics::arith_offset(self.ptr.get() as *mut u8, 1) as *mut T); + self.ptr + .set(intrinsics::arith_offset(self.ptr.get(), mem::size_of::() as isize) as *mut u8); +} diff --git a/src/tools/rustfmt/tests/target/issue-2329.rs b/src/tools/rustfmt/tests/target/issue-2329.rs new file mode 100644 index 0000000000..e36e9546b2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2329.rs @@ -0,0 +1,30 @@ +// Comments with characters which must be represented by multibyte. + +// フー +use foo; +// バー +use bar; + +impl MyStruct { + // コメント + fn f1() {} // こんにちは + fn f2() {} // ありがとう + // コメント +} + +trait MyTrait { + // コメント + fn f1() {} // こんにちは + fn f2() {} // ありがとう + // コメント +} + +fn main() { + // コメント + let x = 1; // X + println!( + "x = {}", // xの値 + x, // X + ); + // コメント +} diff --git a/src/tools/rustfmt/tests/target/issue-2342.rs b/src/tools/rustfmt/tests/target/issue-2342.rs new file mode 100644 index 0000000000..f9c26857e1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2342.rs @@ -0,0 +1,6 @@ +// rustfmt-max_width: 80 + +struct Foo { + #[cfg(feature = "serde")] + bytes: [[u8; 17]; 5], // Same size as signature::ED25519_PKCS8_V2_LEN +} diff --git a/src/tools/rustfmt/tests/target/issue-2346.rs b/src/tools/rustfmt/tests/target/issue-2346.rs new file mode 100644 index 0000000000..07817221a5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2346.rs @@ -0,0 +1,4 @@ +// rustfmt-normalize_comments: true +// the following empty comment should not have any trailing space added. +// +fn main() {} diff --git a/src/tools/rustfmt/tests/target/issue-2401.rs b/src/tools/rustfmt/tests/target/issue-2401.rs new file mode 100644 index 0000000000..ec8f27b732 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2401.rs @@ -0,0 +1,7 @@ +// rustfmt-hard_tabs = true +// rustfmt-normalize_comments = true + +/// ``` +/// println!("Hello, World!"); +/// ``` +fn main() {} diff --git a/src/tools/rustfmt/tests/target/issue-2445.rs b/src/tools/rustfmt/tests/target/issue-2445.rs new file mode 100644 index 0000000000..1bc7752fd1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2445.rs @@ -0,0 +1,21 @@ +test!(RunPassPretty { + // comment + path: "src/test/run-pass/pretty", + mode: "pretty", + suite: "run-pass", + default: false, + host: true // should, force, , no trailing comma here +}); + +test!(RunPassPretty { + // comment + path: "src/test/run-pass/pretty", + mode: "pretty", + suite: "run-pass", + default: false, + host: true, // should, , preserve, the trailing comma +}); + +test!(Test { + field: i32, // comment +}); diff --git a/src/tools/rustfmt/tests/target/issue-2446.rs b/src/tools/rustfmt/tests/target/issue-2446.rs new file mode 100644 index 0000000000..be62e9c9c3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2446.rs @@ -0,0 +1,9 @@ +enum Issue2446 { + V { + f: u8, // x + }, +} + +enum Issue2446TrailingCommentsOnly { + V { f: u8 /* */ }, +} diff --git a/src/tools/rustfmt/tests/target/issue-2479.rs b/src/tools/rustfmt/tests/target/issue-2479.rs new file mode 100644 index 0000000000..3683ab2208 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2479.rs @@ -0,0 +1,12 @@ +// Long attributes. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum POLARITYR { + #[doc = "Task mode: No effect on pin from OUT[n] task. Event mode: no IN[n] event generated on pin activity."] + NONE, + #[doc = "Task mode: Set pin from OUT[n] task. Event mode: Generate IN[n] event when rising edge on pin."] + LOTOHI, + #[doc = "Task mode: Clear pin from OUT[n] task. Event mode: Generate IN[n] event when falling edge on pin."] + HITOLO, + #[doc = "Task mode: Toggle pin from OUT[n]. Event mode: Generate IN[n] when any change on pin."] + TOGGLE, +} diff --git a/src/tools/rustfmt/tests/target/issue-2482/a.rs b/src/tools/rustfmt/tests/target/issue-2482/a.rs new file mode 100644 index 0000000000..fbbcb52a87 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2482/a.rs @@ -0,0 +1,9 @@ +// rustfmt-reorder_modules: true + +// Do not reorder inline modules. + +mod c; +mod a { + fn a() {} +} +mod b; diff --git a/src/tools/rustfmt/tests/target/issue-2482/b.rs b/src/tools/rustfmt/tests/target/issue-2482/b.rs new file mode 100644 index 0000000000..40a8d94212 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2482/b.rs @@ -0,0 +1 @@ +pub fn b() {} diff --git a/src/tools/rustfmt/tests/target/issue-2482/c.rs b/src/tools/rustfmt/tests/target/issue-2482/c.rs new file mode 100644 index 0000000000..d937545511 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2482/c.rs @@ -0,0 +1 @@ +pub fn c() {} diff --git a/src/tools/rustfmt/tests/target/issue-2496.rs b/src/tools/rustfmt/tests/target/issue-2496.rs new file mode 100644 index 0000000000..60c4f55ddf --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2496.rs @@ -0,0 +1,14 @@ +// rustfmt-indent_style: Visual +fn main() { + match option { + None => some_function(first_reasonably_long_argument, + second_reasonably_long_argument), + } +} + +fn main() { + match option { + None => some_function(first_reasonably_long_argument, + second_reasonably_long_argument), + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2520.rs b/src/tools/rustfmt/tests/target/issue-2520.rs new file mode 100644 index 0000000000..7c134d3972 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2520.rs @@ -0,0 +1,13 @@ +// rustfmt-normalize_comments: true +// rustfmt-format_code_in_doc_comments: true + +//! ```rust +//! println!("hello, world"); +//! ``` + +#![deny(missing_docs)] + +//! ```rust +//! println!("hello, world"); + +#![deny(missing_docs)] diff --git a/src/tools/rustfmt/tests/target/issue-2523.rs b/src/tools/rustfmt/tests/target/issue-2523.rs new file mode 100644 index 0000000000..612f93249a --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2523.rs @@ -0,0 +1,20 @@ +// rustfmt-normalize_comments: true +// rustfmt-format_code_in_doc_comments: true + +// Do not unindent macro calls in comment with unformattable syntax. +//! ```rust +//! let x = 3 ; +//! some_macro!(pub fn fn foo() ( +//! println!("Don't unindent me!"); +//! )); +//! ``` + +// Format items that appear as arguments of macro call. +//! ```rust +//! let x = 3; +//! some_macro!( +//! pub fn foo() { +//! println!("Don't unindent me!"); +//! } +//! ); +//! ``` diff --git a/src/tools/rustfmt/tests/target/issue-2526.rs b/src/tools/rustfmt/tests/target/issue-2526.rs new file mode 100644 index 0000000000..7dd58aba31 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2526.rs @@ -0,0 +1,8 @@ +// Test that rustfmt will not warn about comments exceeding max width around lifetime. +// See #2526. + +// comment comment comment comment comment comment comment comment comment comment comment comment comment +fn foo() -> F<'a> { + bar() +} +// comment comment comment comment comment comment comment comment comment comment comment comment comment diff --git a/src/tools/rustfmt/tests/target/issue-2551.rs b/src/tools/rustfmt/tests/target/issue-2551.rs new file mode 100644 index 0000000000..d7b0d625b9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2551.rs @@ -0,0 +1,3 @@ +mcro!(func(A { + a: 12345667800111111111111, +})); diff --git a/src/tools/rustfmt/tests/target/issue-2554.rs b/src/tools/rustfmt/tests/target/issue-2554.rs new file mode 100644 index 0000000000..d5f0563a6f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2554.rs @@ -0,0 +1,13 @@ +// #2554 +// Do not add the beginning vert to the first match arm's pattern. + +fn main() { + match foo(|_| { + bar(|_| { + // + }) + }) { + Ok(()) => (), + Err(_) => (), + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2582.rs b/src/tools/rustfmt/tests/target/issue-2582.rs new file mode 100644 index 0000000000..f328e4d9d0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2582.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/tools/rustfmt/tests/target/issue-2641.rs b/src/tools/rustfmt/tests/target/issue-2641.rs new file mode 100644 index 0000000000..fbf5326c33 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2641.rs @@ -0,0 +1,3 @@ +macro_rules! a { + () => {{}}; +} diff --git a/src/tools/rustfmt/tests/target/issue-2644.rs b/src/tools/rustfmt/tests/target/issue-2644.rs new file mode 100644 index 0000000000..a87e4c0b4f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2644.rs @@ -0,0 +1,8 @@ +// rustfmt-max_width: 80 +fn foo(e: Enum) { + match e { + Enum::Var { element1, element2 } => { + return; + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2673-nonmodrs-mods/foo.rs b/src/tools/rustfmt/tests/target/issue-2673-nonmodrs-mods/foo.rs new file mode 100644 index 0000000000..5340816d61 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2673-nonmodrs-mods/foo.rs @@ -0,0 +1,4 @@ +// rustfmt-config: skip_children.toml +mod bar; + +mod baz {} diff --git a/src/tools/rustfmt/tests/target/issue-2673-nonmodrs-mods/foo/bar.rs b/src/tools/rustfmt/tests/target/issue-2673-nonmodrs-mods/foo/bar.rs new file mode 100644 index 0000000000..9ceacd59d8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2673-nonmodrs-mods/foo/bar.rs @@ -0,0 +1 @@ +fn dummy() {} diff --git a/src/tools/rustfmt/tests/target/issue-2673-nonmodrs-mods/lib.rs b/src/tools/rustfmt/tests/target/issue-2673-nonmodrs-mods/lib.rs new file mode 100644 index 0000000000..82425de565 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2673-nonmodrs-mods/lib.rs @@ -0,0 +1,6 @@ +#![feature(non_modrs_mods)] + +// Test that submodules in non-mod.rs files work. This is just an idempotence +// test since we just want to verify that rustfmt doesn't fail. + +mod foo; diff --git a/src/tools/rustfmt/tests/target/issue-2728.rs b/src/tools/rustfmt/tests/target/issue-2728.rs new file mode 100644 index 0000000000..6cb41b75b6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2728.rs @@ -0,0 +1,8 @@ +// rustfmt-wrap_comments: true +// rustfmt-newline_style: Windows + +//! ```rust +//! extern crate uom; +//! ``` + +fn main() {} diff --git a/src/tools/rustfmt/tests/target/issue-2759.rs b/src/tools/rustfmt/tests/target/issue-2759.rs new file mode 100644 index 0000000000..b7176ec66f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2759.rs @@ -0,0 +1,65 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 89 + +// Code block in doc comments that will exceed max width. +/// ```rust +/// extern crate actix_web; +/// use actix_web::{actix, server, App, HttpResponse}; +/// +/// fn main() { +/// // Run actix system, this method actually starts all async processes +/// actix::System::run(|| { +/// server::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) +/// .bind("127.0.0.1:0") +/// .expect("Can not bind to 127.0.0.1:0") +/// .start(); +/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +/// }); +/// } +/// ``` +fn foo() {} + +// Code block in doc comments without the closing '```'. +/// ```rust +/// # extern crate actix_web; +/// use actix_web::{App, HttpResponse, http}; +/// +/// fn main() { +/// let app = App::new() +/// .resource( +/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok())) +/// .finish(); +/// } +fn bar() {} + +// `#` with indent. +/// ```rust +/// # use std::thread; +/// # extern crate actix_web; +/// use actix_web::{server, App, HttpResponse}; +/// +/// struct State1; +/// +/// struct State2; +/// +/// fn main() { +/// # thread::spawn(|| { +/// server::new(|| { +/// vec![ +/// App::with_state(State1) +/// .prefix("/app1") +/// .resource("/", |r| r.f(|r| HttpResponse::Ok())) +/// .boxed(), +/// App::with_state(State2) +/// .prefix("/app2") +/// .resource("/", |r| r.f(|r| HttpResponse::Ok())) +/// .boxed(), +/// ] +/// }) +/// .bind("127.0.0.1:8080") +/// .unwrap() +/// .run() +/// # }); +/// } +/// ``` +fn foobar() {} diff --git a/src/tools/rustfmt/tests/target/issue-2761.rs b/src/tools/rustfmt/tests/target/issue-2761.rs new file mode 100644 index 0000000000..ae4086617f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2761.rs @@ -0,0 +1,15 @@ +const DATA: &'static [u8] = &[ + 0x42, 0x50, 0x54, 0x44, //type + 0x23, 0x00, 0x00, 0x00, //size + 0x00, 0x00, 0x04, 0x00, //flags + 0xEC, 0x0C, 0x00, 0x00, //id + 0x00, 0x00, 0x00, 0x00, //revision + 0x2B, 0x00, //version + 0x00, 0x00, //unknown + 0x42, 0x50, 0x54, 0x4E, //field type + 0x1D, 0x00, //field size + 0x19, 0x00, 0x00, 0x00, //decompressed field size + 0x75, 0xc5, 0x21, 0x0d, 0x00, 0x00, 0x08, 0x05, 0xd1, 0x6c, //field data (compressed) + 0x6c, 0xdc, 0x57, 0x48, 0x3c, 0xfd, 0x5b, 0x5c, 0x02, 0xd4, //field data (compressed) + 0x6b, 0x32, 0xb5, 0xdc, 0xa3, //field data (compressed) +]; diff --git a/src/tools/rustfmt/tests/target/issue-2781.rs b/src/tools/rustfmt/tests/target/issue-2781.rs new file mode 100644 index 0000000000..f144d716be --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2781.rs @@ -0,0 +1,11 @@ +pub // Oh, no. A line comment. +struct Foo {} + +pub /* Oh, no. A block comment. */ struct Foo {} + +mod inner { + pub // Oh, no. A line comment. + struct Foo {} + + pub /* Oh, no. A block comment. */ struct Foo {} +} diff --git a/src/tools/rustfmt/tests/target/issue-2794.rs b/src/tools/rustfmt/tests/target/issue-2794.rs new file mode 100644 index 0000000000..951c0af206 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2794.rs @@ -0,0 +1,12 @@ +// rustfmt-indent_style: Block +// rustfmt-imports_indent: Block +// rustfmt-imports_layout: Vertical + +use std::{ + env, + fs, + io::{ + Read, + Write, + }, +}; diff --git a/src/tools/rustfmt/tests/target/issue-2810.rs b/src/tools/rustfmt/tests/target/issue-2810.rs new file mode 100644 index 0000000000..34140c7a1f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2810.rs @@ -0,0 +1,14 @@ +// rustfmt-newline_style: Windows + +#[macro_export] +macro_rules! hmmm___ffi_error { + ($result:ident) => { + pub struct $result { + success: bool, + } + + impl $result { + pub fn foo(self) {} + } + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-2835.rs b/src/tools/rustfmt/tests/target/issue-2835.rs new file mode 100644 index 0000000000..21e8ce4114 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2835.rs @@ -0,0 +1,4 @@ +// rustfmt-brace_style: AlwaysNextLine +// rustfmt-fn_single_line: true + +fn lorem() -> i32 { 42 } diff --git a/src/tools/rustfmt/tests/target/issue-2863.rs b/src/tools/rustfmt/tests/target/issue-2863.rs new file mode 100644 index 0000000000..35a80f7a62 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2863.rs @@ -0,0 +1,54 @@ +// rustfmt-reorder_impl_items: true + +impl IntoIterator for SafeVec { + type Bar = u32; + type BarFoo = u32; + type FooBar = u32; + // comment on FoooooBar + type FoooooBar = u32; + type IntoIter = self::IntoIter; + type Item = T; + + type E = impl Trait; + type F = impl Trait; + + const AnotherConst: i32 = 100; + const SomeConst: i32 = 100; + + // comment on foo() + fn foo() { + println!("hello, world"); + } + + fn foo1() { + println!("hello, world"); + } + + fn foo2() { + println!("hello, world"); + } + + fn foo3() { + println!("hello, world"); + } + + fn foo4() { + println!("hello, world"); + } + + fn foo5() { + println!("hello, world"); + } + + fn foo6() { + println!("hello, world"); + } + + fn foo7() { + println!("hello, world"); + } + + fn foo8() { + println!("hello, world"); + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2869.rs b/src/tools/rustfmt/tests/target/issue-2869.rs new file mode 100644 index 0000000000..6a68c2d95f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2869.rs @@ -0,0 +1,41 @@ +// rustfmt-struct_field_align_threshold: 50 + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +struct AuditLog1 { + creation_time: String, + id: String, + operation: String, + organization_id: String, + record_type: u32, + result_status: Option, + #[serde(rename = "ClientIP")] + client_ip: Option, + object_id: String, + actor: Option>, + actor_context_id: Option, + actor_ip_address: Option, + azure_active_directory_event_type: Option, + + #[serde(rename = "very")] + aaaaa: String, + #[serde(rename = "cool")] + bb: i32, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +struct AuditLog2 { + creation_time: String, + id: String, + operation: String, + organization_id: String, + record_type: u32, + result_status: Option, + client_ip: Option, + object_id: String, + actor: Option>, + actor_context_id: Option, + actor_ip_address: Option, + azure_active_directory_event_type: Option, +} diff --git a/src/tools/rustfmt/tests/target/issue-2896.rs b/src/tools/rustfmt/tests/target/issue-2896.rs new file mode 100644 index 0000000000..6fb6b12ed3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2896.rs @@ -0,0 +1,165 @@ +extern crate differential_dataflow; +extern crate rand; +extern crate timely; + +use rand::{Rng, SeedableRng, StdRng}; + +use timely::dataflow::operators::*; + +use differential_dataflow::input::InputSession; +use differential_dataflow::operators::*; +use differential_dataflow::AsCollection; + +// mod loglikelihoodratio; + +fn main() { + // define a new timely dataflow computation. + timely::execute_from_args(std::env::args().skip(6), move |worker| { + // capture parameters of the experiment. + let users: usize = std::env::args().nth(1).unwrap().parse().unwrap(); + let items: usize = std::env::args().nth(2).unwrap().parse().unwrap(); + let scale: usize = std::env::args().nth(3).unwrap().parse().unwrap(); + let batch: usize = std::env::args().nth(4).unwrap().parse().unwrap(); + let noisy: bool = std::env::args().nth(5).unwrap() == "noisy"; + + let index = worker.index(); + let peers = worker.peers(); + + let (input, probe) = worker.dataflow(|scope| { + // input of (user, item) collection. + let (input, occurrences) = scope.new_input(); + let occurrences = occurrences.as_collection(); + + //TODO adjust code to only work with upper triangular half of cooccurrence matrix + + /* Compute the cooccurrence matrix C = A'A from the binary interaction matrix A. */ + let cooccurrences = occurrences + .join_map(&occurrences, |_user, &item_a, &item_b| (item_a, item_b)) + .filter(|&(item_a, item_b)| item_a != item_b) + .count(); + + /* compute the rowsums of C indicating how often we encounter individual items. */ + let row_sums = occurrences.map(|(_user, item)| item).count(); + + // row_sums.inspect(|record| println!("[row_sums] {:?}", record)); + + /* Join the cooccurrence pairs with the corresponding row sums. */ + let mut cooccurrences_with_row_sums = cooccurrences + .map(|((item_a, item_b), num_cooccurrences)| (item_a, (item_b, num_cooccurrences))) + .join_map( + &row_sums, + |&item_a, &(item_b, num_cooccurrences), &row_sum_a| { + assert!(row_sum_a > 0); + (item_b, (item_a, num_cooccurrences, row_sum_a)) + }, + ) + .join_map( + &row_sums, + |&item_b, &(item_a, num_cooccurrences, row_sum_a), &row_sum_b| { + assert!(row_sum_a > 0); + assert!(row_sum_b > 0); + (item_a, (item_b, num_cooccurrences, row_sum_a, row_sum_b)) + }, + ); + + // cooccurrences_with_row_sums + // .inspect(|record| println!("[cooccurrences_with_row_sums] {:?}", record)); + + // //TODO compute top-k "similar items" per item + // /* Compute LLR scores for each item pair. */ + // let llr_scores = cooccurrences_with_row_sums.map( + // |(item_a, (item_b, num_cooccurrences, row_sum_a, row_sum_b))| { + + // println!( + // "[llr_scores] item_a={} item_b={}, num_cooccurrences={} row_sum_a={} row_sum_b={}", + // item_a, item_b, num_cooccurrences, row_sum_a, row_sum_b); + + // let k11: isize = num_cooccurrences; + // let k12: isize = row_sum_a as isize - k11; + // let k21: isize = row_sum_b as isize - k11; + // let k22: isize = 10000 - k12 - k21 + k11; + + // let llr_score = loglikelihoodratio::log_likelihood_ratio(k11, k12, k21, k22); + + // ((item_a, item_b), llr_score) + // }); + + if noisy { + cooccurrences_with_row_sums = + cooccurrences_with_row_sums.inspect(|x| println!("change: {:?}", x)); + } + + let probe = cooccurrences_with_row_sums.probe(); + /* + // produce the (item, item) collection + let cooccurrences = occurrences + .join_map(&occurrences, |_user, &item_a, &item_b| (item_a, item_b)); + // count the occurrences of each item. + let counts = cooccurrences + .map(|(item_a,_)| item_a) + .count(); + // produce ((item1, item2), count1, count2, count12) tuples + let cooccurrences_with_counts = cooccurrences + .join_map(&counts, |&item_a, &item_b, &count_item_a| (item_b, (item_a, count_item_a))) + .join_map(&counts, |&item_b, &(item_a, count_item_a), &count_item_b| { + ((item_a, item_b), count_item_a, count_item_b) + }); + let probe = cooccurrences_with_counts + .inspect(|x| println!("change: {:?}", x)) + .probe(); + */ + (input, probe) + }); + + let seed: &[_] = &[1, 2, 3, index]; + let mut rng1: StdRng = SeedableRng::from_seed(seed); // rng for edge additions + let mut rng2: StdRng = SeedableRng::from_seed(seed); // rng for edge deletions + + let mut input = InputSession::from(input); + + for count in 0..scale { + if count % peers == index { + let user = rng1.gen_range(0, users); + let item = rng1.gen_range(0, items); + // println!("[INITIAL INPUT] ({}, {})", user, item); + input.insert((user, item)); + } + } + + // load the initial data up! + while probe.less_than(input.time()) { + worker.step(); + } + + for round in 1.. { + for element in (round * batch)..((round + 1) * batch) { + if element % peers == index { + // advance the input timestamp. + input.advance_to(round * batch); + // insert a new item. + let user = rng1.gen_range(0, users); + let item = rng1.gen_range(0, items); + if noisy { + println!("[INPUT: insert] ({}, {})", user, item); + } + input.insert((user, item)); + // remove an old item. + let user = rng2.gen_range(0, users); + let item = rng2.gen_range(0, items); + if noisy { + println!("[INPUT: remove] ({}, {})", user, item); + } + input.remove((user, item)); + } + } + + input.advance_to(round * batch); + input.flush(); + + while probe.less_than(input.time()) { + worker.step(); + } + } + }) + .unwrap(); +} diff --git a/src/tools/rustfmt/tests/target/issue-2916.rs b/src/tools/rustfmt/tests/target/issue-2916.rs new file mode 100644 index 0000000000..fb07cc8065 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2916.rs @@ -0,0 +1,2 @@ +a_macro!(name, +); diff --git a/src/tools/rustfmt/tests/target/issue-2917/minimal.rs b/src/tools/rustfmt/tests/target/issue-2917/minimal.rs new file mode 100644 index 0000000000..e81e1e6a5a --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2917/minimal.rs @@ -0,0 +1,8 @@ +macro_rules! foo { + () => { + // comment + /* + + */ + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-2917/packed_simd.rs b/src/tools/rustfmt/tests/target/issue-2917/packed_simd.rs new file mode 100644 index 0000000000..274614f83e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2917/packed_simd.rs @@ -0,0 +1,63 @@ +// rustfmt-wrap_comments: true +//! Implements `From` and `Into` for vector types. + +macro_rules! impl_from_vector { + ([$elem_ty:ident; $elem_count:expr]: $id:ident | $test_tt:tt | $source:ident) => { + impl From<$source> for $id { + #[inline] + fn from(source: $source) -> Self { + fn static_assert_same_number_of_lanes() + where + T: crate::sealed::Simd, + U: crate::sealed::Simd, + { + } + use llvm::simd_cast; + static_assert_same_number_of_lanes::<$id, $source>(); + Simd(unsafe { simd_cast(source.0) }) + } + } + + // FIXME: `Into::into` is not inline, but due to + // the blanket impl in `std`, which is not + // marked `default`, we cannot override it here with + // specialization. + /* + impl Into<$id> for $source { + #[inline] + fn into(self) -> $id { + unsafe { simd_cast(self) } + } + } + */ + + test_if! { + $test_tt: + interpolate_idents! { + mod [$id _from_ $source] { + use super::*; + #[test] + fn from() { + assert_eq!($id::lanes(), $source::lanes()); + let source: $source = Default::default(); + let vec: $id = Default::default(); + + let e = $id::from(source); + assert_eq!(e, vec); + + let e: $id = source.into(); + assert_eq!(e, vec); + } + } + } + } + }; +} + +macro_rules! impl_from_vectors { + ([$elem_ty:ident; $elem_count:expr]: $id:ident | $test_tt:tt | $($source:ident),*) => { + $( + impl_from_vector!([$elem_ty; $elem_count]: $id | $test_tt | $source); + )* + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2922.rs b/src/tools/rustfmt/tests/target/issue-2922.rs new file mode 100644 index 0000000000..501f78c78b --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2922.rs @@ -0,0 +1,10 @@ +// rustfmt-indent_style: Visual +struct Functions { + RunListenServer: unsafe extern "C" fn(*mut c_void, + *mut c_char, + *mut c_char, + *mut c_char, + *mut c_void, + *mut c_void) + -> c_int, +} diff --git a/src/tools/rustfmt/tests/target/issue-2927-2.rs b/src/tools/rustfmt/tests/target/issue-2927-2.rs new file mode 100644 index 0000000000..e895783ba8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2927-2.rs @@ -0,0 +1,7 @@ +// rustfmt-edition: 2015 +#![feature(rust_2018_preview, uniform_paths)] +use futures::prelude::*; +use http_03::cli::Cli; +use hyper::{service::service_fn_ok, Body, Response, Server}; +use log::{error, info, log}; +use structopt::StructOpt; diff --git a/src/tools/rustfmt/tests/target/issue-2927.rs b/src/tools/rustfmt/tests/target/issue-2927.rs new file mode 100644 index 0000000000..3267be28d7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2927.rs @@ -0,0 +1,7 @@ +// rustfmt-edition: 2018 +#![feature(rust_2018_preview, uniform_paths)] +use ::log::{error, info, log}; +use futures::prelude::*; +use http_03::cli::Cli; +use hyper::{service::service_fn_ok, Body, Response, Server}; +use structopt::StructOpt; diff --git a/src/tools/rustfmt/tests/target/issue-2930.rs b/src/tools/rustfmt/tests/target/issue-2930.rs new file mode 100644 index 0000000000..41e763a7c0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2930.rs @@ -0,0 +1,5 @@ +// rustfmt-indent_style: Visual +fn main() { + let (first_variable, second_variable) = (this_is_something_with_an_extraordinarily_long_name, + this_variable_name_is_also_pretty_long); +} diff --git a/src/tools/rustfmt/tests/target/issue-2936.rs b/src/tools/rustfmt/tests/target/issue-2936.rs new file mode 100644 index 0000000000..1d6eb6d605 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2936.rs @@ -0,0 +1,21 @@ +struct AStruct { + A: u32, + B: u32, + C: u32, +} + +impl Something for AStruct { + fn a_func() { + match a_val { + ContextualParseError::InvalidMediaRule(ref err) => { + let err: &CStr = match err.kind { + ParseErrorKind::Custom(StyleParseErrorKind::MediaQueryExpectedFeatureName( + .., + )) => { + cstr!("PEMQExpectedFeatureName") + } + }; + } + }; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2941.rs b/src/tools/rustfmt/tests/target/issue-2941.rs new file mode 100644 index 0000000000..3c6c702c2a --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2941.rs @@ -0,0 +1,5 @@ +// rustfmt-wrap_comments: true + +//! ``` +//! \ +//! ``` diff --git a/src/tools/rustfmt/tests/target/issue-2955.rs b/src/tools/rustfmt/tests/target/issue-2955.rs new file mode 100644 index 0000000000..799cd36e29 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2955.rs @@ -0,0 +1,6 @@ +// rustfmt-condense_wildcard_suffixes: true +fn main() { + match (1, 2, 3) { + (..) => (), + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2973.rs b/src/tools/rustfmt/tests/target/issue-2973.rs new file mode 100644 index 0000000000..86574bd866 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2973.rs @@ -0,0 +1,158 @@ +#[cfg(test)] +mod test { + summary_test! { + tokenize_recipe_interpolation_eol, + "foo: # some comment + {{hello}} +", + "foo: \ + {{hello}} \ + {{ahah}}", + "N:#$>^{N}$<.", + } + + summary_test! { + tokenize_strings, + r#"a = "'a'" + '"b"' + "'c'" + '"d"'#echo hello"#, + r#"N="+'+"+'#."#, + } + + summary_test! { + tokenize_recipe_interpolation_eol, + "foo: # some comment + {{hello}} +", + "N:#$>^{N}$<.", + } + + summary_test! { + tokenize_recipe_interpolation_eof, + "foo: # more comments + {{hello}} +# another comment +", + "N:#$>^{N}$<#$.", + } + + summary_test! { + tokenize_recipe_complex_interpolation_expression, + "foo: #lol\n {{a + b + \"z\" + blarg}}", + "N:#$>^{N+N+\"+N}<.", + } + + summary_test! { + tokenize_recipe_multiple_interpolations, + "foo:,#ok\n {{a}}0{{b}}1{{c}}", + "N:,#$>^{N}_{N}_{N}<.", + } + + summary_test! { + tokenize_junk, + "bob + +hello blah blah blah : a b c #whatever + ", + "N$$NNNN:NNN#$.", + } + + summary_test! { + tokenize_empty_lines, + " +# this does something +hello: + asdf + bsdf + + csdf + + dsdf # whatever + +# yolo + ", + "$#$N:$>^_$^_$$^_$$^_$$<#$.", + } + + summary_test! { + tokenize_comment_before_variable, + " +# +A='1' +echo: + echo {{A}} + ", + "$#$N='$N:$>^_{N}$<.", + } + + summary_test! { + tokenize_interpolation_backticks, + "hello:\n echo {{`echo hello` + `echo goodbye`}}", + "N:$>^_{`+`}<.", + } + + summary_test! { + tokenize_assignment_backticks, + "a = `echo hello` + `echo goodbye`", + "N=`+`.", + } + + summary_test! { + tokenize_multiple, + " +hello: + a + b + + c + + d + +# hello +bob: + frank + ", + + "$N:$>^_$^_$$^_$$^_$$<#$N:$>^_$<.", + } + + summary_test! { + tokenize_comment, + "a:=#", + "N:=#." + } + + summary_test! { + tokenize_comment_with_bang, + "a:=#foo!", + "N:=#." + } + + summary_test! { + tokenize_order, + r" +b: a + @mv a b + +a: + @touch F + @touch a + +d: c + @rm c + +c: b + @mv b c", + "$N:N$>^_$$^_$^_$$^_$$^_<.", + } + + summary_test! { + tokenize_parens, + r"((())) )abc(+", + "((())))N(+.", + } + + summary_test! { + crlf_newline, + "#\r\n#asdf\r\n", + "#$#$.", + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2976.rs b/src/tools/rustfmt/tests/target/issue-2976.rs new file mode 100644 index 0000000000..51c94a84ba --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2976.rs @@ -0,0 +1,3 @@ +fn a(_ /*comment*/: u8 /* toto */) {} +fn b(/*comment*/ _: u8 /* tata */) {} +fn c(_: /*comment*/ u8) {} diff --git a/src/tools/rustfmt/tests/target/issue-2977/block.rs b/src/tools/rustfmt/tests/target/issue-2977/block.rs new file mode 100644 index 0000000000..d376e370c7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2977/block.rs @@ -0,0 +1,11 @@ +macro_rules! atomic_bits { + ($ldrex:expr) => { + execute(|| { + asm!($ldrex + : "=r"(raw) + : "r"(address) + : + : "volatile"); + }) + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-2977/impl.rs b/src/tools/rustfmt/tests/target/issue-2977/impl.rs new file mode 100644 index 0000000000..8d7bb9414e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2977/impl.rs @@ -0,0 +1,44 @@ +macro_rules! atomic_bits { + // the println macro cannot be rewritten because of the asm macro + ($type:ty, $ldrex:expr, $strex:expr) => { + impl AtomicBits for $type { + unsafe fn load_excl(address: usize) -> Self { + let raw: $type; + asm!($ldrex + : "=r"(raw) + : "r"(address) + : + : "volatile"); + raw + } + + unsafe fn store_excl(self, address: usize) -> bool { + let status: $type; + println!("{}", + status); + status == 0 + } + } + }; + + // the println macro should be rewritten here + ($type:ty) => { + fn some_func(self) { + let status: $type; + println!("{}", status); + } + }; + + // unrewritale macro in func + ($type:ty, $ldrex:expr) => { + unsafe fn load_excl(address: usize) -> Self { + let raw: $type; + asm!($ldrex + : "=r"(raw) + : "r"(address) + : + : "volatile"); + raw + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2977/item.rs b/src/tools/rustfmt/tests/target/issue-2977/item.rs new file mode 100644 index 0000000000..857065ca93 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2977/item.rs @@ -0,0 +1,11 @@ +macro_rules! atomic_bits { + ($ldrex:expr) => { + some_macro!(pub fn foo() { + asm!($ldrex + : "=r"(raw) + : "r"(address) + : + : "volatile"); + }) + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-2977/trait.rs b/src/tools/rustfmt/tests/target/issue-2977/trait.rs new file mode 100644 index 0000000000..ae20668cd7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2977/trait.rs @@ -0,0 +1,44 @@ +macro_rules! atomic_bits { + // the println macro cannot be rewritten because of the asm macro + ($type:ty, $ldrex:expr, $strex:expr) => { + trait $type { + unsafe fn load_excl(address: usize) -> Self { + let raw: $type; + asm!($ldrex + : "=r"(raw) + : "r"(address) + : + : "volatile"); + raw + } + + unsafe fn store_excl(self, address: usize) -> bool { + let status: $type; + println!("{}", + status); + status == 0 + } + } + }; + + // the println macro should be rewritten here + ($type:ty) => { + fn some_func(self) { + let status: $type; + println!("{}", status); + } + }; + + // unrewritale macro in func + ($type:ty, $ldrex:expr) => { + unsafe fn load_excl(address: usize) -> Self { + let raw: $type; + asm!($ldrex + : "=r"(raw) + : "r"(address) + : + : "volatile"); + raw + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2985.rs b/src/tools/rustfmt/tests/target/issue-2985.rs new file mode 100644 index 0000000000..faad859236 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2985.rs @@ -0,0 +1,36 @@ +// rustfmt-indent_style: Visual +fn foo() { + { + { + let extra_encoder_settings = extra_encoder_settings.iter() + .filter_map(|&(name, value)| { + value.split() + .next() + .something() + .something2() + .something3() + .something4() + }); + let extra_encoder_settings = extra_encoder_settings.iter() + .filter_map(|&(name, value)| { + value.split() + .next() + .something() + .something2() + .something3() + .something4() + }) + .something(); + if let Some(subpod) = pod.subpods.iter().find(|s| { + !s.plaintext + .as_ref() + .map(String::as_ref) + .unwrap_or("") + .is_empty() + }) + { + do_something(); + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-2995.rs b/src/tools/rustfmt/tests/target/issue-2995.rs new file mode 100644 index 0000000000..890da8def9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2995.rs @@ -0,0 +1,7 @@ +fn issue_2995() { + // '\u{2028}' is inserted in the code below. + + [0, 1]; + [0, /* */ 1]; + [0, 1]; +} diff --git a/src/tools/rustfmt/tests/target/issue-3029.rs b/src/tools/rustfmt/tests/target/issue-3029.rs new file mode 100644 index 0000000000..a7ac5c32b0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3029.rs @@ -0,0 +1,94 @@ +fn keep_if() { + { + { + { + EvaluateJSReply::NumberValue( + if FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + unimplemented!(); + }, + ) + } + } + } +} + +fn keep_if_let() { + { + { + { + EvaluateJSReply::NumberValue( + if let Some(e) = FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + unimplemented!(); + }, + ) + } + } + } +} + +fn keep_for() { + { + { + { + EvaluateJSReply::NumberValue( + for conv in FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + unimplemented!(); + }, + ) + } + } + } +} + +fn keep_loop() { + { + { + { + EvaluateJSReply::NumberValue(loop { + FromJSValConvertible::from_jsval(cx, rval.handle(), ()); + }) + } + } + } +} + +fn keep_while() { + { + { + { + EvaluateJSReply::NumberValue( + while FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + unimplemented!(); + }, + ) + } + } + } +} + +fn keep_while_let() { + { + { + { + EvaluateJSReply::NumberValue( + while let Some(e) = FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + unimplemented!(); + }, + ) + } + } + } +} + +fn keep_match() { + { + { + EvaluateJSReply::NumberValue( + match FromJSValConvertible::from_jsval(cx, rval.handle(), ()) { + Ok(ConversionResult::Success(v)) => v, + _ => unreachable!(), + }, + ) + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3032.rs b/src/tools/rustfmt/tests/target/issue-3032.rs new file mode 100644 index 0000000000..3533a81fbc --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3032.rs @@ -0,0 +1,36 @@ +pub fn get_array_index_from_id(_cx: *mut JSContext, id: HandleId) -> Option { + let raw_id = id.into(); + unsafe { + if RUST_JSID_IS_INT(raw_id) { + return Some(RUST_JSID_TO_INT(raw_id) as u32); + } + None + } + // If `id` is length atom, `-1`, otherwise: + /*return if JSID_IS_ATOM(id) { + let atom = JSID_TO_ATOM(id); + //let s = *GetAtomChars(id); + if s > 'a' && s < 'z' { + return -1; + } + + let i = 0; + let str = AtomToLinearString(JSID_TO_ATOM(id)); + return if StringIsArray(str, &mut i) != 0 { i } else { -1 } + } else { + IdToInt32(cx, id); + }*/ +} + +impl Foo { + fn bar() -> usize { + 42 + /* a block comment */ + } + + fn baz() -> usize { + 42 + // this is a line + /* a block comment */ + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3038.rs b/src/tools/rustfmt/tests/target/issue-3038.rs new file mode 100644 index 0000000000..3c398b825d --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3038.rs @@ -0,0 +1,29 @@ +impl HTMLTableElement { + fn func() { + if number_of_row_elements == 0 { + if let Some(last_tbody) = node + .rev_children() + .filter_map(DomRoot::downcast::) + .find(|n| { + n.is::() && n.local_name() == &local_name!("tbody") + }) + { + last_tbody + .upcast::() + .AppendChild(new_row.upcast::()) + .expect("InsertRow failed to append first row."); + } + } + + if number_of_row_elements == 0 { + if let Some(last_tbody) = node.find(|n| { + n.is::() && n.local_name() == &local_name!("tbody") + }) { + last_tbody + .upcast::() + .AppendChild(new_row.upcast::()) + .expect("InsertRow failed to append first row."); + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3043.rs b/src/tools/rustfmt/tests/target/issue-3043.rs new file mode 100644 index 0000000000..b54e244a40 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3043.rs @@ -0,0 +1,5 @@ +// rustfmt-edition: 2018 + +use ::std::vec::Vec; + +fn main() {} diff --git a/src/tools/rustfmt/tests/target/issue-3049.rs b/src/tools/rustfmt/tests/target/issue-3049.rs new file mode 100644 index 0000000000..fad1543543 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3049.rs @@ -0,0 +1,45 @@ +// rustfmt-indent_style: Visual +fn main() { + something.aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .bench_function(|| { + let x = hello(); + }); + + something.aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .bench_function(arg, || { + let x = hello(); + }); + + something.aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .aaaaaaaaaaaa() + .bench_function(arg, + || { + let x = hello(); + }, + arg); + + AAAAAAAAAAA.function(|| { + let _ = (); + }); + + AAAAAAAAAAA.chain().function(|| { + let _ = (); + }) +} diff --git a/src/tools/rustfmt/tests/target/issue-3055/backtick.rs b/src/tools/rustfmt/tests/target/issue-3055/backtick.rs new file mode 100644 index 0000000000..f5bae8d3db --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3055/backtick.rs @@ -0,0 +1,10 @@ +// rustfmt-wrap_comments: true + +/// Simple block +/// +/// ```text +/// ` +/// ``` +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/rustfmt/tests/target/issue-3055/empty-code-block.rs b/src/tools/rustfmt/tests/target/issue-3055/empty-code-block.rs new file mode 100644 index 0000000000..566f7ef9b6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3055/empty-code-block.rs @@ -0,0 +1,18 @@ +// rustfmt-wrap_comments: true + +/// Simple block +/// +/// ``` +/// ``` +/// +/// ```no_run +/// ``` +/// +/// ```should_panic +/// ``` +/// +/// ```compile_fail +/// ``` +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/rustfmt/tests/target/issue-3055/original.rs b/src/tools/rustfmt/tests/target/issue-3055/original.rs new file mode 100644 index 0000000000..2df6adbb5e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3055/original.rs @@ -0,0 +1,42 @@ +// rustfmt-wrap_comments: true +// rustfmt-format_code_in_doc_comments: true + +/// Vestibulum elit nibh, rhoncus non, euismod sit amet, pretium eu, enim. Nunc +/// commodo ultricies dui. +/// +/// Should not format with text attribute +/// ```text +/// .--------------. +/// | v +/// Park <- Idle -> Poll -> Probe -> Download -> Install -> Reboot +/// ^ ^ ' ' ' +/// ' ' ' ' ' +/// ' `--------' ' ' +/// `---------------' ' ' +/// `--------------------------' ' +/// `-------------------------------------' +/// ``` +/// +/// Should not format with ignore attribute +/// ```text +/// .--------------. +/// | v +/// Park <- Idle -> Poll -> Probe -> Download -> Install -> Reboot +/// ^ ^ ' ' ' +/// ' ' ' ' ' +/// ' `--------' ' ' +/// `---------------' ' ' +/// `--------------------------' ' +/// `-------------------------------------' +/// ``` +/// +/// Should format with rust attribute +/// ```rust +/// let x = 42; +/// ``` +/// +/// Should format with no attribute as it defaults to rust +/// ``` +/// let x = 42; +/// ``` +fn func() {} diff --git a/src/tools/rustfmt/tests/target/issue-3059.rs b/src/tools/rustfmt/tests/target/issue-3059.rs new file mode 100644 index 0000000000..f750c12875 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3059.rs @@ -0,0 +1,8 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 80 + +/// Vestibulum elit nibh, rhoncus non, euismod sit amet, pretium eu, enim. Nunc +/// commodo ultricies dui. Cras gravida rutrum massa. Donec accumsan mattis +/// turpis. Quisque sem. Quisque elementum sapien iaculis augue. In dui sem, +/// congue sit amet, feugiat quis, lobortis at, eros. +fn func4() {} diff --git a/src/tools/rustfmt/tests/target/issue-3066.rs b/src/tools/rustfmt/tests/target/issue-3066.rs new file mode 100644 index 0000000000..d4dccc97e4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3066.rs @@ -0,0 +1,7 @@ +// rustfmt-indent_style: Visual +fn main() { + Struct { field: aaaaaaaaaaa }; + Struct { field: aaaaaaaaaaaa }; + Struct { field: value, + field2: value2 }; +} diff --git a/src/tools/rustfmt/tests/target/issue-3105.rs b/src/tools/rustfmt/tests/target/issue-3105.rs new file mode 100644 index 0000000000..4f1123805b --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3105.rs @@ -0,0 +1,48 @@ +// rustfmt-wrap_comments: true + +/// Although the indentation of the skipped method is off, it shouldn't be +/// changed. +/// +/// ``` +/// pub unsafe fn _mm256_shufflehi_epi16(a: __m256i, imm8: i32) -> __m256i { +/// let imm8 = (imm8 & 0xFF) as u8; +/// let a = a.as_i16x16(); +/// macro_rules! shuffle_done { +/// ($x01:expr, $x23:expr, $x45:expr, $x67:expr) => { +/// #[cfg_attr(rustfmt, rustfmt_skip)] +/// simd_shuffle16(a, a, [ +/// 0, 1, 2, 3, 4+$x01, 4+$x23, 4+$x45, 4+$x67, +/// 8, 9, 10, 11, 12+$x01, 12+$x23, 12+$x45, 12+$x67 +/// ]); +/// }; +/// } +/// } +/// ``` +pub unsafe fn _mm256_shufflehi_epi16(a: __m256i, imm8: i32) -> __m256i { + let imm8 = (imm8 & 0xFF) as u8; + let a = a.as_i16x16(); + macro_rules! shuffle_done { + ($x01:expr, $x23:expr, $x45:expr, $x67:expr) => { + #[cfg_attr(rustfmt, rustfmt_skip)] + simd_shuffle16(a, a, [ + 0, 1, 2, 3, 4+$x01, 4+$x23, 4+$x45, 4+$x67, + 8, 9, 10, 11, 12+$x01, 12+$x23, 12+$x45, 12+$x67 + ]); + }; + } +} + +/// The skipped method shouldn't right-shift +pub unsafe fn _mm256_shufflehi_epi32(a: __m256i, imm8: i32) -> __m256i { + let imm8 = (imm8 & 0xFF) as u8; + let a = a.as_i16x16(); + macro_rules! shuffle_done { + ($x01:expr, $x23:expr, $x45:expr, $x67:expr) => { + #[cfg_attr(rustfmt, rustfmt_skip)] + simd_shuffle32(a, a, [ + 0, 1, 2, 3, 4+$x01, 4+$x23, 4+$x45, 4+$x67, + 8, 9, 10, 11, 12+$x01, 12+$x23, 12+$x45, 12+$x67 + ]); + }; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3118.rs b/src/tools/rustfmt/tests/target/issue-3118.rs new file mode 100644 index 0000000000..ce73a5c789 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3118.rs @@ -0,0 +1,11 @@ +use { + crate::foo::bar, + bytes::{Buf, BufMut}, + std::io, +}; + +mod foo { + pub mod bar {} +} + +fn main() {} diff --git a/src/tools/rustfmt/tests/target/issue-3124.rs b/src/tools/rustfmt/tests/target/issue-3124.rs new file mode 100644 index 0000000000..1083050d8f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3124.rs @@ -0,0 +1,14 @@ +pub fn fail1() { + // Some comment. + /**/// +} + +pub fn fail2() { + // Some comment. + /**/ +} + +pub fn fail3() { + // Some comment. + // +} diff --git a/src/tools/rustfmt/tests/target/issue-3131.rs b/src/tools/rustfmt/tests/target/issue-3131.rs new file mode 100644 index 0000000000..c7304dd558 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3131.rs @@ -0,0 +1,8 @@ +fn main() { + match 3 { + t if match t { + _ => true, + } => {} + _ => {} + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3132.rs b/src/tools/rustfmt/tests/target/issue-3132.rs new file mode 100644 index 0000000000..4dffe0ab83 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3132.rs @@ -0,0 +1,15 @@ +// rustfmt-version: Two + +fn test() { + /* + a + */ + let x = 42; + /* + aaa + "line 1 + line 2 + line 3" + */ + let x = 42; +} diff --git a/src/tools/rustfmt/tests/target/issue-3153.rs b/src/tools/rustfmt/tests/target/issue-3153.rs new file mode 100644 index 0000000000..39e569c0d5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3153.rs @@ -0,0 +1,9 @@ +// rustfmt-wrap_comments: true + +/// This may panic if: +/// - there are fewer than `max_header_bytes` bytes preceding the body +/// - there are fewer than `max_footer_bytes` bytes following the body +/// - the sum of the body bytes and post-body bytes is less than the sum of +/// `min_body_and_padding_bytes` and `max_footer_bytes` (in other words, the +/// minimum body and padding byte requirement is not met) +fn foo() {} diff --git a/src/tools/rustfmt/tests/target/issue-3182.rs b/src/tools/rustfmt/tests/target/issue-3182.rs new file mode 100644 index 0000000000..d8de844381 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3182.rs @@ -0,0 +1,10 @@ +// rustfmt-max_width: 79 +// rustfmt-wrap_comments: true + +/// ```rust +/// # #![cfg_attr(not(dox), feature(cfg_target_feature, target_feature, stdsimd)not(dox), feature(cfg_target_feature, target_feature, stdsimd))] +/// +/// // Est lectus hendrerit lorem, eget dignissim orci nisl sit amet massa. Etiam volutpat lobortis eros. +/// let x = 42; +/// ``` +fn func() {} diff --git a/src/tools/rustfmt/tests/target/issue-3184.rs b/src/tools/rustfmt/tests/target/issue-3184.rs new file mode 100644 index 0000000000..f8d9b169f1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3184.rs @@ -0,0 +1,5 @@ +/*/ +struct Error{ + message: String, +} +*/ diff --git a/src/tools/rustfmt/tests/target/issue-3194.rs b/src/tools/rustfmt/tests/target/issue-3194.rs new file mode 100644 index 0000000000..a9614913ed --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3194.rs @@ -0,0 +1,52 @@ +mod m { + struct S + where + A: B; +} + +mod n { + struct Foo + where + A: B, + { + foo: usize, + } +} + +mod o { + enum Bar + where + A: B, + { + Bar, + } +} + +mod with_comments { + mod m { + struct S + /* before where */ + where + A: B; /* after where */ + } + + mod n { + struct Foo + /* before where */ + where + A: B, /* after where */ + { + foo: usize, + } + } + + mod o { + enum Bar + /* before where */ + where + A: B, /* after where */ + { + Bar, + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3198.rs b/src/tools/rustfmt/tests/target/issue-3198.rs new file mode 100644 index 0000000000..9291f181d0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3198.rs @@ -0,0 +1,67 @@ +impl TestTrait { + fn foo_one_pre(/* Important comment1 */ self) {} + + fn foo_one_post(self /* Important comment1 */) {} + + fn foo_pre(/* Important comment1 */ self, /* Important comment2 */ a: i32) {} + + fn foo_post(self /* Important comment1 */, a: i32 /* Important comment2 */) {} + + fn bar_pre(/* Important comment1 */ &mut self, /* Important comment2 */ a: i32) {} + + fn bar_post(&mut self /* Important comment1 */, a: i32 /* Important comment2 */) {} + + fn baz_pre( + /* Important comment1 */ + self: X<'a, 'b>, + /* Important comment2 */ + a: i32, + ) { + } + + fn baz_post( + self: X<'a, 'b>, /* Important comment1 */ + a: i32, /* Important comment2 */ + ) { + } + + fn baz_tree_pre( + /* Important comment1 */ + self: X<'a, 'b>, + /* Important comment2 */ + a: i32, + /* Important comment3 */ + b: i32, + ) { + } + + fn baz_tree_post( + self: X<'a, 'b>, /* Important comment1 */ + a: i32, /* Important comment2 */ + b: i32, /* Important comment3 */ + ) { + } + + fn multi_line( + self: X<'a, 'b>, /* Important comment1-1 */ + /* Important comment1-2 */ + a: i32, /* Important comment2 */ + b: i32, /* Important comment3 */ + ) { + } + + fn two_line_comment( + self: X<'a, 'b>, /* Important comment1-1 + Important comment1-2 */ + a: i32, /* Important comment2 */ + b: i32, /* Important comment3 */ + ) { + } + + fn no_first_line_comment( + self: X<'a, 'b>, + /* Important comment2 */ a: i32, + /* Important comment3 */ b: i32, + ) { + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3213/version_one.rs b/src/tools/rustfmt/tests/target/issue-3213/version_one.rs new file mode 100644 index 0000000000..307903b128 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3213/version_one.rs @@ -0,0 +1,13 @@ +// rustfmt-version: One + +fn foo() { + match 0 { + 0 => { + return AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + } + 1 => { + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + } + _ => "", + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-3213/version_two.rs b/src/tools/rustfmt/tests/target/issue-3213/version_two.rs new file mode 100644 index 0000000000..de93d04ba9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3213/version_two.rs @@ -0,0 +1,13 @@ +// rustfmt-version: Two + +fn foo() { + match 0 { + 0 => { + return AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; + } + 1 => { + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + } + _ => "", + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-3217.rs b/src/tools/rustfmt/tests/target/issue-3217.rs new file mode 100644 index 0000000000..5121320a09 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3217.rs @@ -0,0 +1,24 @@ +#![feature(label_break_value)] + +fn main() { + let mut res = 0; + 's_39: { + if res == 0i32 { + println!("Hello, world!"); + } + } + 's_40: loop { + println!("res = {}", res); + res += 1; + if res == 3i32 { + break 's_40; + } + } + let toto = || { + if true { + 42 + } else { + 24 + } + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-3224.rs b/src/tools/rustfmt/tests/target/issue-3224.rs new file mode 100644 index 0000000000..6476d2117c --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3224.rs @@ -0,0 +1,11 @@ +// rustfmt-wrap_comments: true + +//! Test: +//! * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +//! * [`examples/simple`] – Demonstrates use of the [`init`] API with plain +//! structs. +//! * [`examples/simple_flatbuffer`] – Demonstrates use of the [`init`] API with +//! FlatBuffers. +//! * [`examples/gravity`] – Demonstrates use of the [`RLBot::set_game_state`] +//! API +fn foo() {} diff --git a/src/tools/rustfmt/tests/target/issue-3227/one.rs b/src/tools/rustfmt/tests/target/issue-3227/one.rs new file mode 100644 index 0000000000..fcc8331000 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3227/one.rs @@ -0,0 +1,13 @@ +// rustfmt-version: One + +fn main() { + thread::spawn(|| { + while true { + println!("iteration"); + } + }); + + thread::spawn(|| loop { + println!("iteration"); + }); +} diff --git a/src/tools/rustfmt/tests/target/issue-3227/two.rs b/src/tools/rustfmt/tests/target/issue-3227/two.rs new file mode 100644 index 0000000000..374ab54305 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3227/two.rs @@ -0,0 +1,15 @@ +// rustfmt-version: Two + +fn main() { + thread::spawn(|| { + while true { + println!("iteration"); + } + }); + + thread::spawn(|| { + loop { + println!("iteration"); + } + }); +} diff --git a/src/tools/rustfmt/tests/target/issue-3234.rs b/src/tools/rustfmt/tests/target/issue-3234.rs new file mode 100644 index 0000000000..c7d9d42bdb --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3234.rs @@ -0,0 +1,14 @@ +macro_rules! fuzz_target { + (|$data:ident: &[u8]| $body:block) => {}; +} + +fuzz_target!(|data: &[u8]| { + if let Ok(app_img) = AppImage::parse(data) { + if let Ok(app_img) = + app_img.sign_for_secureboot(include_str!("../../test-data/signing-key")) + { + assert!(app_img.is_signed()); + Gbl::from_app_image(app_img).to_bytes(); + } + } +}); diff --git a/src/tools/rustfmt/tests/target/issue-3241.rs b/src/tools/rustfmt/tests/target/issue-3241.rs new file mode 100644 index 0000000000..60b452abdd --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3241.rs @@ -0,0 +1,11 @@ +// rustfmt-edition: 2018 + +use ::baz::{bar, foo}; +use ::ignore; +use ::ignore::some::more; +use ::*; +use ::{bar, foo}; + +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/rustfmt/tests/target/issue-3253/bar.rs b/src/tools/rustfmt/tests/target/issue-3253/bar.rs new file mode 100644 index 0000000000..6c6ab945b8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3253/bar.rs @@ -0,0 +1,2 @@ +// Empty +fn empty() {} diff --git a/src/tools/rustfmt/tests/target/issue-3253/foo.rs b/src/tools/rustfmt/tests/target/issue-3253/foo.rs new file mode 100644 index 0000000000..1a42a10159 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3253/foo.rs @@ -0,0 +1,3 @@ +pub fn hello() { + println!("Hello World!"); +} diff --git a/src/tools/rustfmt/tests/target/issue-3253/lib.rs b/src/tools/rustfmt/tests/target/issue-3253/lib.rs new file mode 100644 index 0000000000..3eef586bd6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3253/lib.rs @@ -0,0 +1,14 @@ +#[macro_use] +extern crate cfg_if; + +cfg_if! { + if #[cfg(target_family = "unix")] { + mod foo; + #[path = "paths/bar_foo.rs"] + mod bar_foo; + } else { + mod bar; + #[path = "paths/foo_bar.rs"] + mod foo_bar; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3253/paths/bar_foo.rs b/src/tools/rustfmt/tests/target/issue-3253/paths/bar_foo.rs new file mode 100644 index 0000000000..f7e1de29a3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3253/paths/bar_foo.rs @@ -0,0 +1,3 @@ +fn foo_decl_item(x: &mut i32) { + x = 3; +} diff --git a/src/tools/rustfmt/tests/target/issue-3253/paths/excluded.rs b/src/tools/rustfmt/tests/target/issue-3253/paths/excluded.rs new file mode 100644 index 0000000000..9ab88414d4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3253/paths/excluded.rs @@ -0,0 +1,17 @@ +// This module is not imported in the cfg_if macro in lib.rs so it is ignored +// while the foo and bar mods are formatted. +// Check the corresponding file in tests/source/issue-3253/paths/excluded.rs + + + + + fn Foo() where T: Bar { +} + + + +trait CoolerTypes { fn dummy(&self) { +} +} + + diff --git a/src/tools/rustfmt/tests/target/issue-3253/paths/foo_bar.rs b/src/tools/rustfmt/tests/target/issue-3253/paths/foo_bar.rs new file mode 100644 index 0000000000..f52ac11b73 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3253/paths/foo_bar.rs @@ -0,0 +1,5 @@ +fn Foo() +where + T: Bar, +{ +} diff --git a/src/tools/rustfmt/tests/target/issue-3265.rs b/src/tools/rustfmt/tests/target/issue-3265.rs new file mode 100644 index 0000000000..7db1dbd8b7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3265.rs @@ -0,0 +1,14 @@ +// rustfmt-newline_style: Windows +#[cfg(test)] +mod test { + summary_test! { + tokenize_recipe_interpolation_eol, + "foo: # some comment + {{hello}} +", + "foo: \ + {{hello}} \ + {{ahah}}", + "N:#$>^{N}$<.", + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3270/one.rs b/src/tools/rustfmt/tests/target/issue-3270/one.rs new file mode 100644 index 0000000000..78de947324 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3270/one.rs @@ -0,0 +1,12 @@ +// rustfmt-version: One + +pub fn main() { + /* let s = String::from( + " + hello + world + ", + ); */ + + assert_eq!(s, "\nhello\nworld\n"); +} diff --git a/src/tools/rustfmt/tests/target/issue-3270/two.rs b/src/tools/rustfmt/tests/target/issue-3270/two.rs new file mode 100644 index 0000000000..e48b592132 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3270/two.rs @@ -0,0 +1,12 @@ +// rustfmt-version: Two + +pub fn main() { + /* let s = String::from( + " +hello +world +", + ); */ + + assert_eq!(s, "\nhello\nworld\n"); +} diff --git a/src/tools/rustfmt/tests/target/issue-3270/wrap.rs b/src/tools/rustfmt/tests/target/issue-3270/wrap.rs new file mode 100644 index 0000000000..7435c5f086 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3270/wrap.rs @@ -0,0 +1,13 @@ +// rustfmt-wrap_comments: true +// rustfmt-version: Two + +// check that a line below max_width does not get over the limit when wrapping +// it in a block comment +fn func() { + let x = 42; + /* + let something = "one line line line line line line line line line line line line line + two lines + three lines"; + */ +} diff --git a/src/tools/rustfmt/tests/target/issue-3272/v1.rs b/src/tools/rustfmt/tests/target/issue-3272/v1.rs new file mode 100644 index 0000000000..aab201027d --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3272/v1.rs @@ -0,0 +1,15 @@ +// rustfmt-version: One + +fn main() { + assert!(HAYSTACK + .par_iter() + .find_any(|&&x| x[0] % 1000 == 999) + .is_some()); + + assert( + HAYSTACK + .par_iter() + .find_any(|&&x| x[0] % 1000 == 999) + .is_some(), + ); +} diff --git a/src/tools/rustfmt/tests/target/issue-3272/v2.rs b/src/tools/rustfmt/tests/target/issue-3272/v2.rs new file mode 100644 index 0000000000..a42a2fccd5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3272/v2.rs @@ -0,0 +1,17 @@ +// rustfmt-version: Two + +fn main() { + assert!( + HAYSTACK + .par_iter() + .find_any(|&&x| x[0] % 1000 == 999) + .is_some() + ); + + assert( + HAYSTACK + .par_iter() + .find_any(|&&x| x[0] % 1000 == 999) + .is_some(), + ); +} diff --git a/src/tools/rustfmt/tests/target/issue-3278/version_one.rs b/src/tools/rustfmt/tests/target/issue-3278/version_one.rs new file mode 100644 index 0000000000..580679fbae --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3278/version_one.rs @@ -0,0 +1,8 @@ +// rustfmt-version: One + +pub fn parse_conditional<'a, I: 'a>( +) -> impl Parser + 'a +where + I: Stream, +{ +} diff --git a/src/tools/rustfmt/tests/target/issue-3278/version_two.rs b/src/tools/rustfmt/tests/target/issue-3278/version_two.rs new file mode 100644 index 0000000000..c17b1742d3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3278/version_two.rs @@ -0,0 +1,8 @@ +// rustfmt-version: Two + +pub fn parse_conditional<'a, I: 'a>() +-> impl Parser + 'a +where + I: Stream, +{ +} diff --git a/src/tools/rustfmt/tests/target/issue-3295/two.rs b/src/tools/rustfmt/tests/target/issue-3295/two.rs new file mode 100644 index 0000000000..3e669a0bb7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3295/two.rs @@ -0,0 +1,14 @@ +// rustfmt-version: Two +pub enum TestEnum { + a, + b, +} + +fn the_test(input: TestEnum) { + match input { + TestEnum::a => String::from("aaa"), + TestEnum::b => String::from( + "this is a very very very very very very very very very very very very very very very ong string", + ), + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-3302.rs b/src/tools/rustfmt/tests/target/issue-3302.rs new file mode 100644 index 0000000000..146cb98381 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3302.rs @@ -0,0 +1,43 @@ +// rustfmt-version: Two + +macro_rules! moo1 { + () => { + bar! { + " +" + } + }; +} + +macro_rules! moo2 { + () => { + bar! { + " +" + } + }; +} + +macro_rules! moo3 { + () => { + 42 + /* + bar! { + " + toto +tata" + } + */ + }; +} + +macro_rules! moo4 { + () => { + bar! { + " + foo + bar +baz" + } + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-3304.rs b/src/tools/rustfmt/tests/target/issue-3304.rs new file mode 100644 index 0000000000..cc1910ce25 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3304.rs @@ -0,0 +1,42 @@ +// rustfmt-error_on_line_overflow: true + +#[rustfmt::skip] use one::two::three::four::five::six::seven::eight::night::ten::eleven::twelve::thirteen::fourteen::fiveteen; +#[rustfmt::skip] + +use one::two::three::four::five::six::seven::eight::night::ten::eleven::twelve::thirteen::fourteen::fiveteen; + +macro_rules! test_macro { + ($($id:ident),*) => {}; +} + +macro_rules! test_macro2 { + ($($id:ident),*) => { + 1 + }; +} + +fn main() { + #[rustfmt::skip] test_macro! { one, two, three, four, five, six, seven, eight, night, ten, eleven, twelve, thirteen, fourteen, fiveteen }; + #[rustfmt::skip] + + test_macro! { one, two, three, four, five, six, seven, eight, night, ten, eleven, twelve, thirteen, fourteen, fiveteen }; +} + +fn test_local() { + #[rustfmt::skip] let x = test_macro! { one, two, three, four, five, six, seven, eight, night, ten, eleven, twelve, thirteen, fourteen, fiveteen }; + #[rustfmt::skip] + + let x = test_macro! { one, two, three, four, five, six, seven, eight, night, ten, eleven, twelve, thirteen, fourteen, fiveteen }; +} + +fn test_expr(_: [u32]) -> u32 { + #[rustfmt::skip] test_expr([9999999999999, 9999999999999, 9999999999999, 9999999999999, 9999999999999, 9999999999999, 9999999999999, 9999999999999]); + #[rustfmt::skip] + + test_expr([9999999999999, 9999999999999, 9999999999999, 9999999999999, 9999999999999, 9999999999999, 9999999999999, 9999999999999]) +} + +#[rustfmt::skip] mod test { use one::two::three::four::five::six::seven::eight::night::ten::eleven::twelve::thirteen::fourteen::fiveteen; } +#[rustfmt::skip] + +mod test { use one::two::three::four::five::six::seven::eight::night::ten::eleven::twelve::thirteen::fourteen::fiveteen; } diff --git a/src/tools/rustfmt/tests/target/issue-3314.rs b/src/tools/rustfmt/tests/target/issue-3314.rs new file mode 100644 index 0000000000..1cd32afb02 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3314.rs @@ -0,0 +1,5 @@ +/*code +/*code*/ +if true { + println!("1"); +}*/ diff --git a/src/tools/rustfmt/tests/target/issue-3343.rs b/src/tools/rustfmt/tests/target/issue-3343.rs new file mode 100644 index 0000000000..d0497758e6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3343.rs @@ -0,0 +1,44 @@ +// rustfmt-inline_attribute_width: 50 + +#[cfg(feature = "alloc")] use core::slice; + +#[cfg(feature = "alloc")] use total_len_is::_50__; + +#[cfg(feature = "alloc")] +use total_len_is::_51___; + +#[cfg(feature = "alloc")] extern crate len_is_50_; + +#[cfg(feature = "alloc")] +extern crate len_is_51__; + +/// this is a comment to test is_sugared_doc property +use core::convert; + +#[fooooo] +#[barrrrr] +use total_len_is_::_51______; + +#[cfg(not(all( + feature = "std", + any( + target_os = "linux", + target_os = "android", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "haiku", + target_os = "emscripten", + target_os = "solaris", + target_os = "cloudabi", + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", + target_os = "redox", + target_os = "fuchsia", + windows, + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), + ) +)))] +use core::slice; diff --git a/src/tools/rustfmt/tests/target/issue-3423.rs b/src/tools/rustfmt/tests/target/issue-3423.rs new file mode 100644 index 0000000000..cd60251771 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3423.rs @@ -0,0 +1,5 @@ +/* a nice comment with a trailing whitespace */ +fn foo() {} + +/* a nice comment with a trailing tab */ +fn bar() {} diff --git a/src/tools/rustfmt/tests/target/issue-3434/lib.rs b/src/tools/rustfmt/tests/target/issue-3434/lib.rs new file mode 100644 index 0000000000..2fd7aea21c --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3434/lib.rs @@ -0,0 +1,57 @@ +#![rustfmt::skip::macros(skip_macro_mod)] + +mod no_entry; + +#[rustfmt::skip::macros(html, skip_macro)] +fn main() { + let macro_result1 = html! {
+this should be skipped
+ } + .to_string(); + + let macro_result2 = not_skip_macro! {
+ this should be mangled
+ } + .to_string(); + + skip_macro! { +this should be skipped +}; + + foo(); +} + +fn foo() { + let macro_result1 = html! {
+ this should be mangled
+ } + .to_string(); +} + +fn bar() { + let macro_result1 = skip_macro_mod! {
+this should be skipped
+ } + .to_string(); +} + +fn visitor_made_from_same_context() { + let pair = ( + || { + foo!(
+ this should be mangled
+ ); + skip_macro_mod!(
+this should be skipped
+ ); + }, + || { + foo!(
+ this should be mangled
+ ); + skip_macro_mod!(
+this should be skipped
+ ); + }, + ); +} diff --git a/src/tools/rustfmt/tests/target/issue-3434/no_entry.rs b/src/tools/rustfmt/tests/target/issue-3434/no_entry.rs new file mode 100644 index 0000000000..a2ecf2c2f9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3434/no_entry.rs @@ -0,0 +1,19 @@ +#[rustfmt::skip::macros(another_macro)] +fn foo() { + another_macro!( +This should be skipped. + ); +} + +fn bar() { + skip_macro_mod!( +This should be skipped. + ); +} + +fn baz() { + let macro_result1 = no_skip_macro! {
+ this should be mangled
+ } + .to_string(); +} diff --git a/src/tools/rustfmt/tests/target/issue-3434/not_skip_macro.rs b/src/tools/rustfmt/tests/target/issue-3434/not_skip_macro.rs new file mode 100644 index 0000000000..c90d09744b --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3434/not_skip_macro.rs @@ -0,0 +1,8 @@ +#[this::is::not::skip::macros(ouch)] + +fn main() { + let macro_result1 = ouch! {
+ this should be mangled
+ } + .to_string(); +} diff --git a/src/tools/rustfmt/tests/target/issue-3442.rs b/src/tools/rustfmt/tests/target/issue-3442.rs new file mode 100644 index 0000000000..3664c50ee7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3442.rs @@ -0,0 +1,10 @@ +// rustfmt-file_lines: [{"file":"tests/target/issue-3442.rs","range":[5,5]},{"file":"tests/target/issue-3442.rs","range":[8,8]}] + +extern crate alpha; // comment 1 +extern crate beta; // comment 2 +#[allow(aaa)] // comment 3 +#[macro_use] +extern crate gamma; +#[allow(bbb)] // comment 4 +#[macro_use] +extern crate lazy_static; diff --git a/src/tools/rustfmt/tests/target/issue-3465.rs b/src/tools/rustfmt/tests/target/issue-3465.rs new file mode 100644 index 0000000000..9e2680f0fe --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3465.rs @@ -0,0 +1,42 @@ +fn main() { + ((((((((((((((((((((((((((((((((((((((((((0) + 1) + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1) + + 1); +} diff --git a/src/tools/rustfmt/tests/target/issue-3494/crlf.rs b/src/tools/rustfmt/tests/target/issue-3494/crlf.rs new file mode 100644 index 0000000000..cae615a066 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3494/crlf.rs @@ -0,0 +1,8 @@ +// rustfmt-file_lines: [{"file":"tests/source/issue-3494/crlf.rs","range":[4,5]}] + +pub fn main() { + let world1 = "world"; + println!("Hello, {}!", world1); +let world2 = "world"; println!("Hello, {}!", world2); +let world3 = "world"; println!("Hello, {}!", world3); +} diff --git a/src/tools/rustfmt/tests/target/issue-3494/lf.rs b/src/tools/rustfmt/tests/target/issue-3494/lf.rs new file mode 100644 index 0000000000..60aafe19a0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3494/lf.rs @@ -0,0 +1,8 @@ +// rustfmt-file_lines: [{"file":"tests/source/issue-3494/lf.rs","range":[4,5]}] + +pub fn main() { + let world1 = "world"; + println!("Hello, {}!", world1); +let world2 = "world"; println!("Hello, {}!", world2); +let world3 = "world"; println!("Hello, {}!", world3); +} diff --git a/src/tools/rustfmt/tests/target/issue-3499.rs b/src/tools/rustfmt/tests/target/issue-3499.rs new file mode 100644 index 0000000000..88fd7f7e16 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3499.rs @@ -0,0 +1 @@ +test![]; diff --git a/src/tools/rustfmt/tests/target/issue-3508.rs b/src/tools/rustfmt/tests/target/issue-3508.rs new file mode 100644 index 0000000000..5f4e156582 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3508.rs @@ -0,0 +1,22 @@ +fn foo(foo2: F) +where + F: Fn( + // this comment is deleted + ), +{ +} + +fn foo_block(foo2: F) +where + F: Fn(/* this comment is deleted */), +{ +} + +fn bar( + bar2: impl Fn( + // this comment is deleted + ), +) { +} + +fn bar_block(bar2: impl Fn(/* this comment is deleted */)) {} diff --git a/src/tools/rustfmt/tests/target/issue-3515.rs b/src/tools/rustfmt/tests/target/issue-3515.rs new file mode 100644 index 0000000000..b59d03c6c8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3515.rs @@ -0,0 +1,6 @@ +// rustfmt-reorder_imports: false + +use std::fmt::{self, Display}; +use std::collections::HashMap; + +fn main() {} diff --git a/src/tools/rustfmt/tests/target/issue-3532.rs b/src/tools/rustfmt/tests/target/issue-3532.rs new file mode 100644 index 0000000000..f41902620c --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3532.rs @@ -0,0 +1,6 @@ +fn foo(a: T) { + match a { + 1 => {} + 0 => {} // _ => panic!("doesn't format!"), + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3539.rs b/src/tools/rustfmt/tests/target/issue-3539.rs new file mode 100644 index 0000000000..aa2fa72ece --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3539.rs @@ -0,0 +1,8 @@ +use std::io::Error; + +fn main() { + let _read_num: fn() -> Result<(i32), Error> = || -> Result<(i32), Error> { + let a = 1; + Ok(a) + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-3554.rs b/src/tools/rustfmt/tests/target/issue-3554.rs new file mode 100644 index 0000000000..4ece90403e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3554.rs @@ -0,0 +1,4 @@ +#![feature(const_generics)] + +pub struct S; +impl S<{ 0 }> {} diff --git a/src/tools/rustfmt/tests/target/issue-3567.rs b/src/tools/rustfmt/tests/target/issue-3567.rs new file mode 100644 index 0000000000..3cf08628db --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3567.rs @@ -0,0 +1,3 @@ +fn check() { + vec![vec!(0; 10); 10]; +} diff --git a/src/tools/rustfmt/tests/target/issue-3568.rs b/src/tools/rustfmt/tests/target/issue-3568.rs new file mode 100644 index 0000000000..a146f3df24 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3568.rs @@ -0,0 +1 @@ +use a::b::{self}; diff --git a/src/tools/rustfmt/tests/target/issue-3585/extern_crate.rs b/src/tools/rustfmt/tests/target/issue-3585/extern_crate.rs new file mode 100644 index 0000000000..dc7c9e0246 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3585/extern_crate.rs @@ -0,0 +1,10 @@ +// rustfmt-inline_attribute_width: 100 + +#[macro_use] extern crate static_assertions; + +#[cfg(unix)] extern crate static_assertions; + +// a comment before the attribute +#[macro_use] +// some comment after +extern crate static_assertions; diff --git a/src/tools/rustfmt/tests/target/issue-3585/reorder_imports_disabled.rs b/src/tools/rustfmt/tests/target/issue-3585/reorder_imports_disabled.rs new file mode 100644 index 0000000000..f9637729b7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3585/reorder_imports_disabled.rs @@ -0,0 +1,8 @@ +// rustfmt-inline_attribute_width: 100 +// rustfmt-reorder_imports: false + +#[cfg(unix)] extern crate crateb; +#[cfg(unix)] extern crate cratea; + +#[cfg(unix)] use crateb; +#[cfg(unix)] use cratea; diff --git a/src/tools/rustfmt/tests/target/issue-3585/reorder_imports_enabled.rs b/src/tools/rustfmt/tests/target/issue-3585/reorder_imports_enabled.rs new file mode 100644 index 0000000000..d040d0ed34 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3585/reorder_imports_enabled.rs @@ -0,0 +1,8 @@ +// rustfmt-inline_attribute_width: 100 +// rustfmt-reorder_imports: true + +#[cfg(unix)] extern crate cratea; +#[cfg(unix)] extern crate crateb; + +#[cfg(unix)] use cratea; +#[cfg(unix)] use crateb; diff --git a/src/tools/rustfmt/tests/target/issue-3585/use.rs b/src/tools/rustfmt/tests/target/issue-3585/use.rs new file mode 100644 index 0000000000..c76a9eaacc --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3585/use.rs @@ -0,0 +1,5 @@ +// rustfmt-inline_attribute_width: 100 + +#[macro_use] use static_assertions; + +#[cfg(unix)] use static_assertions; diff --git a/src/tools/rustfmt/tests/target/issue-3592.rs b/src/tools/rustfmt/tests/target/issue-3592.rs new file mode 100644 index 0000000000..3142268e08 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3592.rs @@ -0,0 +1,13 @@ +fn r() -> (Biz, ()) { + ( + Biz { + #![cfg(unix)] + field: 9 + }, + Biz { + #![cfg(not(unix))] + field: 200 + }, + (), + ) +} diff --git a/src/tools/rustfmt/tests/target/issue-3595.rs b/src/tools/rustfmt/tests/target/issue-3595.rs new file mode 100644 index 0000000000..3e06538a4d --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3595.rs @@ -0,0 +1,4 @@ +struct ReqMsg(); +struct RespMsg(); + +pub type TestType = fn() -> (ReqMsg, fn(RespMsg) -> ()); diff --git a/src/tools/rustfmt/tests/target/issue-3601.rs b/src/tools/rustfmt/tests/target/issue-3601.rs new file mode 100644 index 0000000000..c86ca24e70 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3601.rs @@ -0,0 +1,11 @@ +#![feature(const_generics)] + +trait A { + fn foo(&self); +} + +pub struct B([usize; N]); + +impl A for B<{ N }> { + fn foo(&self) {} +} diff --git a/src/tools/rustfmt/tests/target/issue-3614/version_one.rs b/src/tools/rustfmt/tests/target/issue-3614/version_one.rs new file mode 100644 index 0000000000..8ab2830473 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3614/version_one.rs @@ -0,0 +1,15 @@ +// rustfmt-version: One + +fn main() { + let toto = || { + if true { + 42 + } else { + 24 + } + }; + + { + T + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3614/version_two.rs b/src/tools/rustfmt/tests/target/issue-3614/version_two.rs new file mode 100644 index 0000000000..5d6f8e7a31 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3614/version_two.rs @@ -0,0 +1,8 @@ +// rustfmt-version: Two + +fn main() { + let toto = || { + if true { 42 } else { 24 } + }; + { T } +} diff --git a/src/tools/rustfmt/tests/target/issue-3636.rs b/src/tools/rustfmt/tests/target/issue-3636.rs new file mode 100644 index 0000000000..d467ed7389 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3636.rs @@ -0,0 +1,8 @@ +// rustfmt-file_lines: [{"file":"tests/source/issue-3636.rs","range":[4,7]},{"file":"tests/target/issue-3636.rs","range":[3,6]}] + +fn foo() { + let x = 42; + let y = 42; + let z = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + let z = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; +} diff --git a/src/tools/rustfmt/tests/target/issue-3639.rs b/src/tools/rustfmt/tests/target/issue-3639.rs new file mode 100644 index 0000000000..e8fddce2de --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3639.rs @@ -0,0 +1,5 @@ +trait Foo {} +struct Bar {} +struct Bax; +struct Baz(String); +impl Foo for Bar {} diff --git a/src/tools/rustfmt/tests/target/issue-3645.rs b/src/tools/rustfmt/tests/target/issue-3645.rs new file mode 100644 index 0000000000..14bf96e638 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3645.rs @@ -0,0 +1,3 @@ +mod x { + use super::self as x; +} diff --git a/src/tools/rustfmt/tests/target/issue-3651.rs b/src/tools/rustfmt/tests/target/issue-3651.rs new file mode 100644 index 0000000000..4a95a1712e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3651.rs @@ -0,0 +1,7 @@ +fn f() -> Box< + dyn FnMut() -> Thing< + WithType = LongItemName, + Error = LONGLONGLONGLONGLONGONGEvenLongerErrorNameLongerLonger, + >, +> { +} diff --git a/src/tools/rustfmt/tests/target/issue-3665/lib.rs b/src/tools/rustfmt/tests/target/issue-3665/lib.rs new file mode 100644 index 0000000000..c313f32036 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3665/lib.rs @@ -0,0 +1,31 @@ +#![rustfmt::skip::attributes(skip_mod_attr)] + +mod sub_mod; + +#[rustfmt::skip::attributes(other, skip_attr)] +fn main() { + #[other(should, +skip, + this, format)] + struct S {} + + #[skip_attr(should, skip, +this, format,too)] + fn doesnt_mater() {} + + #[skip_mod_attr(should, skip, +this, format, + enerywhere)] + fn more() {} + + #[not_skip(not, skip, me)] + struct B {} +} + +#[other(should, not, skip, this, format, here)] +fn foo() {} + +#[skip_mod_attr(should, skip, +this, format,in, master, + and, sub, module)] +fn bar() {} diff --git a/src/tools/rustfmt/tests/target/issue-3665/not_skip_attribute.rs b/src/tools/rustfmt/tests/target/issue-3665/not_skip_attribute.rs new file mode 100644 index 0000000000..a4e8b94873 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3665/not_skip_attribute.rs @@ -0,0 +1,4 @@ +#![this::is::not::skip::attribute(ouch)] + +#[ouch(not, skip, me)] +fn main() {} diff --git a/src/tools/rustfmt/tests/target/issue-3665/sub_mod.rs b/src/tools/rustfmt/tests/target/issue-3665/sub_mod.rs new file mode 100644 index 0000000000..30a2b0fd9d --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3665/sub_mod.rs @@ -0,0 +1,13 @@ +#[rustfmt::skip::attributes(more_skip)] +#[more_skip(should, + skip, +this, format)] +fn foo() {} + +#[skip_mod_attr(should, skip, +this, format,in, master, + and, sub, module)] +fn bar() {} + +#[skip_attr(should, not, skip, this, attribute, here)] +fn baz() {} diff --git a/src/tools/rustfmt/tests/target/issue-3672.rs b/src/tools/rustfmt/tests/target/issue-3672.rs new file mode 100644 index 0000000000..8cc3d3fd2c --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3672.rs @@ -0,0 +1,3 @@ +fn main() { + let x = 5; +} diff --git a/src/tools/rustfmt/tests/target/issue-3675.rs b/src/tools/rustfmt/tests/target/issue-3675.rs new file mode 100644 index 0000000000..62d986e773 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3675.rs @@ -0,0 +1,6 @@ +fn main() { + println!( + "{}", // comment + 111 + ); +} diff --git a/src/tools/rustfmt/tests/target/issue-3701/one.rs b/src/tools/rustfmt/tests/target/issue-3701/one.rs new file mode 100644 index 0000000000..9d1ef9eed9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3701/one.rs @@ -0,0 +1,12 @@ +// rustfmt-version: One + +fn build_sorted_static_get_entry_names( + mut entries: Vec<(u8, &'static str)>, +) -> (impl Fn( + AlphabeticalTraversal, + Box>, +) -> BoxFuture<'static, Result, Status>> + + Send + + Sync + + 'static) { +} diff --git a/src/tools/rustfmt/tests/target/issue-3701/two.rs b/src/tools/rustfmt/tests/target/issue-3701/two.rs new file mode 100644 index 0000000000..62ffc9d823 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3701/two.rs @@ -0,0 +1,14 @@ +// rustfmt-version: Two + +fn build_sorted_static_get_entry_names( + mut entries: Vec<(u8, &'static str)>, +) -> ( + impl Fn( + AlphabeticalTraversal, + Box>, + ) -> BoxFuture<'static, Result, Status>> + + Send + + Sync + + 'static +) { +} diff --git a/src/tools/rustfmt/tests/target/issue-3709.rs b/src/tools/rustfmt/tests/target/issue-3709.rs new file mode 100644 index 0000000000..0f3eae048d --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3709.rs @@ -0,0 +1,10 @@ +// rustfmt-edition: 2018 + +macro_rules! token { + ($t:tt) => {}; +} + +fn main() { + token!(dyn); + token!(dyn); +} diff --git a/src/tools/rustfmt/tests/target/issue-3711.rs b/src/tools/rustfmt/tests/target/issue-3711.rs new file mode 100644 index 0000000000..62d986e773 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3711.rs @@ -0,0 +1,6 @@ +fn main() { + println!( + "{}", // comment + 111 + ); +} diff --git a/src/tools/rustfmt/tests/target/issue-3717.rs b/src/tools/rustfmt/tests/target/issue-3717.rs new file mode 100644 index 0000000000..b769cd3ecc --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3717.rs @@ -0,0 +1,7 @@ +fn main() { + { + #[rustfmt::skip] + let _ = + [1]; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3718.rs b/src/tools/rustfmt/tests/target/issue-3718.rs new file mode 100644 index 0000000000..8ad21ffc70 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3718.rs @@ -0,0 +1,7 @@ +fn main() { + let x: &[i32] = &[2, 2]; + match x { + [_a, _] => println!("Wrong username or password"), + _ => println!("Logged in"), + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3740.rs b/src/tools/rustfmt/tests/target/issue-3740.rs new file mode 100644 index 0000000000..995a6bee35 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3740.rs @@ -0,0 +1,8 @@ +impl IntoNormalized for Vector +where + Vector: Div>, + for<'a> &'a Vector: IntoLength, +{ + type Output = Vector; + fn into_normalized(self) -> Self::Output {} +} diff --git a/src/tools/rustfmt/tests/target/issue-3741.rs b/src/tools/rustfmt/tests/target/issue-3741.rs new file mode 100644 index 0000000000..34d22dc91e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3741.rs @@ -0,0 +1,5 @@ +pub enum PublishedFileVisibility { + Public = sys::ERemoteStoragePublishedFileVisibility_k_ERemoteStoragePublishedFileVisibilityPublic as i32, + FriendsOnly = sys::ERemoteStoragePublishedFileVisibility_k_ERemoteStoragePublishedFileVisibilityFriendsOnly as i32, + Private = sys::ERemoteStoragePublishedFileVisibility_k_ERemoteStoragePublishedFileVisibilityPrivate as i32, +} diff --git a/src/tools/rustfmt/tests/target/issue-3750.rs b/src/tools/rustfmt/tests/target/issue-3750.rs new file mode 100644 index 0000000000..6875f8d389 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3750.rs @@ -0,0 +1,15 @@ +// rustfmt-imports_granularity: Crate + +pub mod foo { + pub mod bar { + pub struct Bar; + } + + pub fn bar() {} +} + +use foo::{bar, bar::Bar}; + +fn main() { + bar(); +} diff --git a/src/tools/rustfmt/tests/target/issue-3751.rs b/src/tools/rustfmt/tests/target/issue-3751.rs new file mode 100644 index 0000000000..e5a03956e9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3751.rs @@ -0,0 +1,10 @@ +// rustfmt-format_code_in_doc_comments: true + +//! Empty pound line +//! +//! ```rust +//! # +//! # fn main() { +//! foo(); +//! # } +//! ``` diff --git a/src/tools/rustfmt/tests/target/issue-3759.rs b/src/tools/rustfmt/tests/target/issue-3759.rs new file mode 100644 index 0000000000..b53f5391a3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3759.rs @@ -0,0 +1,27 @@ +fn main() { + let Test { + #[cfg(feature = "test")] + x, + } = Test { + #[cfg(feature = "test")] + x: 1, + }; + + let Test { + #[cfg(feature = "test")] + // comment + x, + } = Test { + #[cfg(feature = "test")] + x: 1, + }; + + let Test { + // comment + #[cfg(feature = "test")] + x, + } = Test { + #[cfg(feature = "test")] + x: 1, + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-3779/ice.rs b/src/tools/rustfmt/tests/target/issue-3779/ice.rs new file mode 100644 index 0000000000..cde21412d9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3779/ice.rs @@ -0,0 +1,3 @@ +pub fn bar() { + 1x; +} diff --git a/src/tools/rustfmt/tests/target/issue-3779/lib.rs b/src/tools/rustfmt/tests/target/issue-3779/lib.rs new file mode 100644 index 0000000000..a5673a4db7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3779/lib.rs @@ -0,0 +1,9 @@ +// rustfmt-unstable: true +// rustfmt-config: issue-3779.toml + +#[path = "ice.rs"] +mod ice; + +fn foo() { + println!("abc"); +} diff --git a/src/tools/rustfmt/tests/target/issue-3786.rs b/src/tools/rustfmt/tests/target/issue-3786.rs new file mode 100644 index 0000000000..d90cba15d2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3786.rs @@ -0,0 +1,9 @@ +fn main() { + let _ = r#" +this is a very long string exceeded maximum width in this case maximum 100. (current this line width is about 115) +"#; + + let _with_newline = r#" +this is a very long string exceeded maximum width in this case maximum 100. (current this line width is about 115) +"#; +} diff --git a/src/tools/rustfmt/tests/target/issue-3787.rs b/src/tools/rustfmt/tests/target/issue-3787.rs new file mode 100644 index 0000000000..32cf7e3d75 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3787.rs @@ -0,0 +1,13 @@ +// rustfmt-wrap_comments: true + +//! URLs in items +//! * [This is a link with a very loooooooooooooooooooooooooooooooooooooooooong URL.](https://example.com/This/is/a/link/with/a/very/loooooooooooooooooooooooooooooooooooooooooong/URL) +//! * This is a [link](https://example.com/This/is/a/link/with/a/very/loooooooooooooooooooooooooooooooooooooooooong/URL) +//! with a very loooooooooooooooooooooooooooooooooooooooooong URL. +//! * there is no link here: In hac habitasse platea dictumst. Maecenas in +//! ligula. Duis tincidunt odio sollicitudin quam. Nullam non mauris. +//! Phasellus lacinia, velit sit amet bibendum euismod, leo diam interdum +//! ligula, eu scelerisque sem purus in tellus. +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/rustfmt/tests/target/issue-3815.rs b/src/tools/rustfmt/tests/target/issue-3815.rs new file mode 100644 index 0000000000..eff27e2de3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3815.rs @@ -0,0 +1,4 @@ +pub type Type = impl Deref; + +pub type Type = + impl VeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryLongType; diff --git a/src/tools/rustfmt/tests/target/issue-3840/version-one_hard-tabs.rs b/src/tools/rustfmt/tests/target/issue-3840/version-one_hard-tabs.rs new file mode 100644 index 0000000000..4aa905ce9e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3840/version-one_hard-tabs.rs @@ -0,0 +1,25 @@ +// rustfmt-hard_tabs: true + +impl< + Target: FromEvent + FromEvent, + A: Widget2, + B: Widget2, + C: for<'a> CtxFamily<'a>, + > Widget2 for WidgetEventLifter +{ + type Ctx = C; + type Event = Vec; +} + +mod foo { + impl< + Target: FromEvent + FromEvent, + A: Widget2, + B: Widget2, + C: for<'a> CtxFamily<'a>, + > Widget2 for WidgetEventLifter + { + type Ctx = C; + type Event = Vec; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3840/version-one_soft-tabs.rs b/src/tools/rustfmt/tests/target/issue-3840/version-one_soft-tabs.rs new file mode 100644 index 0000000000..099e680182 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3840/version-one_soft-tabs.rs @@ -0,0 +1,23 @@ +impl< + Target: FromEvent + FromEvent, + A: Widget2, + B: Widget2, + C: for<'a> CtxFamily<'a>, + > Widget2 for WidgetEventLifter +{ + type Ctx = C; + type Event = Vec; +} + +mod foo { + impl< + Target: FromEvent + FromEvent, + A: Widget2, + B: Widget2, + C: for<'a> CtxFamily<'a>, + > Widget2 for WidgetEventLifter + { + type Ctx = C; + type Event = Vec; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3840/version-two_hard-tabs.rs b/src/tools/rustfmt/tests/target/issue-3840/version-two_hard-tabs.rs new file mode 100644 index 0000000000..084db3d146 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3840/version-two_hard-tabs.rs @@ -0,0 +1,26 @@ +// rustfmt-hard_tabs: true +// rustfmt-version: Two + +impl< + Target: FromEvent + FromEvent, + A: Widget2, + B: Widget2, + C: for<'a> CtxFamily<'a>, +> Widget2 for WidgetEventLifter +{ + type Ctx = C; + type Event = Vec; +} + +mod foo { + impl< + Target: FromEvent + FromEvent, + A: Widget2, + B: Widget2, + C: for<'a> CtxFamily<'a>, + > Widget2 for WidgetEventLifter + { + type Ctx = C; + type Event = Vec; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3840/version-two_soft-tabs.rs b/src/tools/rustfmt/tests/target/issue-3840/version-two_soft-tabs.rs new file mode 100644 index 0000000000..bc59b0baa5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3840/version-two_soft-tabs.rs @@ -0,0 +1,25 @@ +// rustfmt-version: Two + +impl< + Target: FromEvent + FromEvent, + A: Widget2, + B: Widget2, + C: for<'a> CtxFamily<'a>, +> Widget2 for WidgetEventLifter +{ + type Ctx = C; + type Event = Vec; +} + +mod foo { + impl< + Target: FromEvent + FromEvent, + A: Widget2, + B: Widget2, + C: for<'a> CtxFamily<'a>, + > Widget2 for WidgetEventLifter + { + type Ctx = C; + type Event = Vec; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-3845.rs b/src/tools/rustfmt/tests/target/issue-3845.rs new file mode 100644 index 0000000000..877c05b86a --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3845.rs @@ -0,0 +1,8 @@ +fn main() { + || { + #[allow(deprecated)] + { + u8::max_value() + } + }; +} diff --git a/src/tools/rustfmt/tests/target/issue-3882.rs b/src/tools/rustfmt/tests/target/issue-3882.rs new file mode 100644 index 0000000000..5eb442af97 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3882.rs @@ -0,0 +1,4 @@ +// rustfmt-version: Two +fn bar(_t: T, // bar +) { +} diff --git a/src/tools/rustfmt/tests/target/issue-3974.rs b/src/tools/rustfmt/tests/target/issue-3974.rs new file mode 100644 index 0000000000..a9f992ebd5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3974.rs @@ -0,0 +1,10 @@ +fn emulate_foreign_item() { + match link_name { + // A comment here will duplicate the attribute + #[rustfmt::skip] + | "pthread_mutexattr_init" + | "pthread_mutexattr_settype" + | "pthread_mutex_init" + => {} + } +} diff --git a/src/tools/rustfmt/tests/target/issue-4018.rs b/src/tools/rustfmt/tests/target/issue-4018.rs new file mode 100644 index 0000000000..cef3be0614 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4018.rs @@ -0,0 +1,11 @@ +fn main() { + /* extra comment */ +} + +fn main() { + println!(""); + // comment 1 + // comment 2 + // comment 3 + // comment 4 +} diff --git a/src/tools/rustfmt/tests/target/issue-4020.rs b/src/tools/rustfmt/tests/target/issue-4020.rs new file mode 100644 index 0000000000..f29ecec028 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4020.rs @@ -0,0 +1,9 @@ +// rustfmt-wrap_comments: true + +/** foobar */ +const foo1: u32 = 0; + +/** + * foobar + */ +const foo2: u32 = 0; diff --git a/src/tools/rustfmt/tests/target/issue-4029.rs b/src/tools/rustfmt/tests/target/issue-4029.rs new file mode 100644 index 0000000000..314d018058 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4029.rs @@ -0,0 +1,7 @@ +// issue #4029 +#[derive(Debug, Clone, Default Hash)] +struct S; + +// issue #3898 +#[derive(Debug, Clone, Default,, Hash)] +struct T; diff --git a/src/tools/rustfmt/tests/target/issue-4068.rs b/src/tools/rustfmt/tests/target/issue-4068.rs new file mode 100644 index 0000000000..cd8a1f2765 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4068.rs @@ -0,0 +1,3 @@ +fn main() { + extern "C" fn packet_records_options_impl_layout_length_encoding_option_len_multiplier(); +} diff --git a/src/tools/rustfmt/tests/target/issue-4079.rs b/src/tools/rustfmt/tests/target/issue-4079.rs new file mode 100644 index 0000000000..1871c5b8a1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4079.rs @@ -0,0 +1,11 @@ +// rustfmt-wrap_comments: true + +/*! + * Lorem ipsum dolor sit amet, consectetur adipiscing elit. In lacinia + * ullamcorper lorem, non hendrerit enim convallis ut. Curabitur id sem + * volutpat + */ + +/*! Lorem ipsum dolor sit amet, consectetur adipiscing elit. In lacinia + * ullamcorper lorem, non hendrerit enim convallis ut. Curabitur id sem + * volutpat */ diff --git a/src/tools/rustfmt/tests/target/issue-4115.rs b/src/tools/rustfmt/tests/target/issue-4115.rs new file mode 100644 index 0000000000..0dd7bdbd04 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4115.rs @@ -0,0 +1,8 @@ +#[derive( + A, + B, + C, + D, + // E, +)] +fn foo() {} diff --git a/src/tools/rustfmt/tests/target/issue-4120.rs b/src/tools/rustfmt/tests/target/issue-4120.rs new file mode 100644 index 0000000000..a7d461dcfd --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4120.rs @@ -0,0 +1,85 @@ +fn main() { + let x = if true { + 1 + // In if + } else { + 0 + // In else + }; + + let x = if true { + 1 + /* In if */ + } else { + 0 + /* In else */ + }; + + let z = if true { + if true { + 1 + + // In if level 2 + } else { + 2 + } + } else { + 3 + }; + + let a = if true { + 1 + // In if + } else { + 0 + // In else + }; + + let a = if true { + 1 + + // In if + } else { + 0 + // In else + }; + + let b = if true { + 1 + + // In if + } else { + 0 + // In else + }; + + let c = if true { + 1 + + // In if + } else { + 0 + // In else + }; + for i in 0..2 { + println!("Something"); + // In for + } + + for i in 0..2 { + println!("Something"); + /* In for */ + } + + extern "C" { + fn first(); + + // In foreign mod + } + + extern "C" { + fn first(); + + /* In foreign mod */ + } +} diff --git a/src/tools/rustfmt/tests/target/issue-4152.rs b/src/tools/rustfmt/tests/target/issue-4152.rs new file mode 100644 index 0000000000..80f9ff5e30 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4152.rs @@ -0,0 +1,18 @@ +// rustfmt-hard_tabs: true + +macro_rules! bit { + ($bool:expr) => { + if $bool { + 1; + 1 + } else { + 0; + 0 + } + }; +} +macro_rules! add_one { + ($vec:expr) => {{ + $vec.push(1); + }}; +} diff --git a/src/tools/rustfmt/tests/target/issue-4159.rs b/src/tools/rustfmt/tests/target/issue-4159.rs new file mode 100644 index 0000000000..2f8cf20da2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4159.rs @@ -0,0 +1,18 @@ +extern "C" { + type A: Ord; + + type A<'a> + where + 'a: 'static; + + type A + where + T: 'static; + + type A = u8; + + type A<'a: 'static, T: Ord + 'static>: Eq + PartialEq + where + T: 'static + Copy, + = Vec; +} diff --git a/src/tools/rustfmt/tests/target/issue-4243.rs b/src/tools/rustfmt/tests/target/issue-4243.rs new file mode 100644 index 0000000000..67fa1d2a31 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4243.rs @@ -0,0 +1,28 @@ +fn main() { + type A: AA /*AA*/ + /*AB*/ AB + AC = AA + /*AA*/ + + + /*AB*/ + AB + + AC; + + type B: BA /*BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA*/ + + /*BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*/ BB + + BC = BA /*BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA*/ + + /*BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*/ BB + + BC; + + type C: CA // CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + // CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + + // CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + // CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + CB + + CC = CA // CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + // CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + + // CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + // CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + CB + + CC; +} diff --git a/src/tools/rustfmt/tests/target/issue-4244.rs b/src/tools/rustfmt/tests/target/issue-4244.rs new file mode 100644 index 0000000000..8958ba99e8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4244.rs @@ -0,0 +1,20 @@ +pub struct SS {} + +pub type A /* A Comment */ = SS; + +pub type B // Comment + // B + = SS; + +pub type C + /* Comment C */ + = SS; + +pub trait D { + type E /* Comment E */ = SS; +} + +type F<'a: 'static, T: Ord + 'static>: Eq + PartialEq +where + T: 'static + Copy, /* x */ += Vec; diff --git a/src/tools/rustfmt/tests/target/issue-4245.rs b/src/tools/rustfmt/tests/target/issue-4245.rs new file mode 100644 index 0000000000..e3d40eb426 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4245.rs @@ -0,0 +1,34 @@ +fn a( + a: & // Comment + // Another comment + 'a File, +) { +} + +fn b(b: & /* Another Comment */ 'a File) {} + +fn c(c: &'a /*Comment */ mut /*Comment */ File) {} + +fn d( + c: & // Comment + 'b // Multi Line + // Comment + mut // Multi Line + // Comment + File, +) { +} + +fn e( + c: & // Comment + File, +) { +} + +fn d( + c: & // Comment + mut // Multi Line + // Comment + File, +) { +} diff --git a/src/tools/rustfmt/tests/target/issue-4310.rs b/src/tools/rustfmt/tests/target/issue-4310.rs new file mode 100644 index 0000000000..6cf494fc5b --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4310.rs @@ -0,0 +1,9 @@ +#![feature(const_generics)] + +fn foo< + const N: [u8; { + struct Inner<'a>(&'a ()); + 3 + }], +>() { +} diff --git a/src/tools/rustfmt/tests/target/issue-4313.rs b/src/tools/rustfmt/tests/target/issue-4313.rs new file mode 100644 index 0000000000..c390ee6ba3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4313.rs @@ -0,0 +1,5 @@ +extern "C" { + fn f() { + fn g() {} + } +} diff --git a/src/tools/rustfmt/tests/target/issue-4382.rs b/src/tools/rustfmt/tests/target/issue-4382.rs new file mode 100644 index 0000000000..740fa9bfe0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4382.rs @@ -0,0 +1,10 @@ +pub const NAME_MAX: usize = { + #[cfg(target_os = "linux")] + { + 1024 + } + #[cfg(target_os = "freebsd")] + { + 255 + } +}; diff --git a/src/tools/rustfmt/tests/target/issue-4398.rs b/src/tools/rustfmt/tests/target/issue-4398.rs new file mode 100644 index 0000000000..2ca894528e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4398.rs @@ -0,0 +1,19 @@ +impl Struct { + /// Documentation for `foo` + #[rustfmt::skip] // comment on why use a skip here + pub fn foo(&self) {} +} + +impl Struct { + /// Documentation for `foo` + #[rustfmt::skip] // comment on why use a skip here + pub fn foo(&self) {} +} + +/// Documentation for `Struct` +#[rustfmt::skip] // comment +impl Struct { + /// Documentation for `foo` + #[rustfmt::skip] // comment on why use a skip here + pub fn foo(&self) {} +} diff --git a/src/tools/rustfmt/tests/target/issue-447.rs b/src/tools/rustfmt/tests/target/issue-447.rs new file mode 100644 index 0000000000..d41cdb65cd --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-447.rs @@ -0,0 +1,40 @@ +// rustfmt-normalize_comments: true + +fn main() { + if + // shouldn't be dropped + // shouldn't be dropped + cond + // shouldn't be dropped + // shouldn't be dropped + { + } + // shouldn't be dropped + // shouldn't be dropped + else + // shouldn't be dropped + // shouldn't be dropped + if + // shouldn't be dropped + // shouldn't be dropped + cond + // shouldn't be dropped + // shouldn't be dropped + { + } + // shouldn't be dropped + // shouldn't be dropped + else + // shouldn't be dropped + // shouldn't be dropped + { + } + + if + // shouldn't be dropped + // shouldn't be dropped + let Some(x) = y + // shouldn't be dropped + // shouldn't be dropped + {} +} diff --git a/src/tools/rustfmt/tests/target/issue-4577.rs b/src/tools/rustfmt/tests/target/issue-4577.rs new file mode 100644 index 0000000000..1bd9eb6b80 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4577.rs @@ -0,0 +1,15 @@ +fn main() { + let s: String = "ABAABBAA" + .chars() + .filter(|c| if *c == 'A' { true } else { false }) + .map(|c| -> char { + if c == 'A' { + '0' + } else { + '1' + } + }) + .collect(); + + println!("{}", s); +} diff --git a/src/tools/rustfmt/tests/target/issue-4646.rs b/src/tools/rustfmt/tests/target/issue-4646.rs new file mode 100644 index 0000000000..4e149399f0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4646.rs @@ -0,0 +1,20 @@ +trait Foo { + fn bar(&self) + // where + // Self: Bar + ; +} + +trait Foo { + fn bar(&self) + // where + // Self: Bar + ; +} + +trait Foo { + fn bar(&self) + // where + // Self: Bar + ; +} diff --git a/src/tools/rustfmt/tests/target/issue-4656/format_me_please.rs b/src/tools/rustfmt/tests/target/issue-4656/format_me_please.rs new file mode 100644 index 0000000000..421e195a2f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4656/format_me_please.rs @@ -0,0 +1 @@ +pub fn hello() {} diff --git a/src/tools/rustfmt/tests/target/issue-4656/lib.rs b/src/tools/rustfmt/tests/target/issue-4656/lib.rs new file mode 100644 index 0000000000..5dac91b8aa --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4656/lib.rs @@ -0,0 +1,7 @@ +extern crate cfg_if; + +cfg_if::cfg_if! { + if #[cfg(target_family = "unix")] { + mod format_me_please; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-4656/lib2.rs b/src/tools/rustfmt/tests/target/issue-4656/lib2.rs new file mode 100644 index 0000000000..b17fffc58e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4656/lib2.rs @@ -0,0 +1,3 @@ +its_a_macro! { + // Contents +} diff --git a/src/tools/rustfmt/tests/target/issue-510.rs b/src/tools/rustfmt/tests/target/issue-510.rs new file mode 100644 index 0000000000..a166b68498 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-510.rs @@ -0,0 +1,41 @@ +impl ISizeAndMarginsComputer for AbsoluteNonReplaced { + fn solve_inline_size_constraints( + &self, + block: &mut BlockFlow, + input: &ISizeConstraintInput, + ) -> ISizeConstraintSolution { + let (inline_start, inline_size, margin_inline_start, margin_inline_end) = match ( + inline_startssssssxxxxxxsssssxxxxxxxxxssssssxxx, + inline_startssssssxxxxxxsssssxxxxxxxxxssssssxxx, + ) { + (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + // Now it is the same situation as inline-start Specified and inline-end + // and inline-size Auto. + // + // Set inline-end to zero to calculate inline-size. + let inline_size = block.get_shrink_to_fit_inline_size( + available_inline_size - (margin_start + margin_end), + ); + (Au(0), inline_size, margin_start, margin_end) + } + }; + + let (inline_start, inline_size, margin_inline_start, margin_inline_end) = + match (inline_start, inline_end, computed_inline_size) { + (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + // Now it is the same situation as inline-start Specified and inline-end + // and inline-size Auto. + // + // Set inline-end to zero to calculate inline-size. + let inline_size = block.get_shrink_to_fit_inline_size( + available_inline_size - (margin_start + margin_end), + ); + (Au(0), inline_size, margin_start, margin_end) + } + }; + } +} diff --git a/src/tools/rustfmt/tests/target/issue-539.rs b/src/tools/rustfmt/tests/target/issue-539.rs new file mode 100644 index 0000000000..adeb33555f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-539.rs @@ -0,0 +1,3 @@ +// rustfmt-normalize_comments: true +// FIXME (#3300): Should allow items to be anonymous. Right now +// we just use dummy names for anon items. diff --git a/src/tools/rustfmt/tests/target/issue-64.rs b/src/tools/rustfmt/tests/target/issue-64.rs new file mode 100644 index 0000000000..c066063020 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-64.rs @@ -0,0 +1,7 @@ +// Regression test for issue 64 + +pub fn header_name() -> &'static str { + let name = ::header_name(); + let func = ::header_name; + name +} diff --git a/src/tools/rustfmt/tests/target/issue-683.rs b/src/tools/rustfmt/tests/target/issue-683.rs new file mode 100644 index 0000000000..adeb33555f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-683.rs @@ -0,0 +1,3 @@ +// rustfmt-normalize_comments: true +// FIXME (#3300): Should allow items to be anonymous. Right now +// we just use dummy names for anon items. diff --git a/src/tools/rustfmt/tests/target/issue-691.rs b/src/tools/rustfmt/tests/target/issue-691.rs new file mode 100644 index 0000000000..7473d070ef --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-691.rs @@ -0,0 +1,9 @@ +// rustfmt-normalize_comments: true + +//! `std` or `core` and simply link to this library. In case the target +//! platform has no hardware +//! support for some operation, software implementations provided by this +//! library will be used automagically. +// TODO: provide instructions to override default libm link and how to link to +// this library. +fn foo() {} diff --git a/src/tools/rustfmt/tests/target/issue-770.rs b/src/tools/rustfmt/tests/target/issue-770.rs new file mode 100644 index 0000000000..5fbedd7b73 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-770.rs @@ -0,0 +1,10 @@ +fn main() { + if false { + if false { + } else { + // A let binding here seems necessary to trigger it. + let _ = (); + } + } else if let false = false { + } +} diff --git a/src/tools/rustfmt/tests/target/issue-811.rs b/src/tools/rustfmt/tests/target/issue-811.rs new file mode 100644 index 0000000000..b7a89b5d0f --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-811.rs @@ -0,0 +1,19 @@ +trait FooTrait: Sized { + type Bar: BarTrait; +} + +trait BarTrait: Sized { + type Baz; + fn foo(); +} + +type Foo = <>::Bar as BarTrait>::Baz; +type Bar = >::Baz; + +fn some_func, U>() { + <>::Bar as BarTrait>::foo(); +} + +fn some_func>() { + >::foo(); +} diff --git a/src/tools/rustfmt/tests/target/issue-831.rs b/src/tools/rustfmt/tests/target/issue-831.rs new file mode 100644 index 0000000000..1d6327c215 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-831.rs @@ -0,0 +1,9 @@ +fn main() { + let y = a.iter().any(|x| { + println!("a"); + }) || b.iter().any(|x| { + println!("b"); + }) || c.iter().any(|x| { + println!("c"); + }); +} diff --git a/src/tools/rustfmt/tests/target/issue-850.rs b/src/tools/rustfmt/tests/target/issue-850.rs new file mode 100644 index 0000000000..c939716a6a --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-850.rs @@ -0,0 +1 @@ +const unsafe fn x() {} diff --git a/src/tools/rustfmt/tests/target/issue-855.rs b/src/tools/rustfmt/tests/target/issue-855.rs new file mode 100644 index 0000000000..0430cc6295 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-855.rs @@ -0,0 +1,27 @@ +fn main() { + 'running: loop { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => break 'running, + } + } + } +} + +fn main2() { + 'running: loop { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } + | Event::KeyDownXXXXXXXXXXXXX { + keycode: Some(Keycode::Escape), + .. + } => break 'running, + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-913.rs b/src/tools/rustfmt/tests/target/issue-913.rs new file mode 100644 index 0000000000..a2b5800a74 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-913.rs @@ -0,0 +1,22 @@ +mod client { + impl Client { + fn test(self) -> Result<()> { + let next_state = match self.state { + State::V5(v5::State::Command(v5::coand::State::WriteVersion(ref mut response))) => { + let x = reformat.meeee(); + } + }; + + let next_state = match self.state { + State::V5(v5::State::Command(v5::comand::State::WriteVersion( + ref mut response, + ))) => { + // The pattern cannot be formatted in a way that the match stays + // within the column limit. The rewrite should therefore be + // skipped. + let x = dont.reformat.meeee(); + } + }; + } + } +} diff --git a/src/tools/rustfmt/tests/target/issue-945.rs b/src/tools/rustfmt/tests/target/issue-945.rs new file mode 100644 index 0000000000..d46c69a4f0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-945.rs @@ -0,0 +1,17 @@ +impl Bar { + default const unsafe fn foo() { + "hi" + } +} + +impl Baz { + default unsafe extern "C" fn foo() { + "hi" + } +} + +impl Foo for Bar { + default fn foo() { + "hi" + } +} diff --git a/src/tools/rustfmt/tests/target/issue-977.rs b/src/tools/rustfmt/tests/target/issue-977.rs new file mode 100644 index 0000000000..3784a38745 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-977.rs @@ -0,0 +1,16 @@ +// rustfmt-normalize_comments: true + +trait NameC { + // comment +} +struct FooC { + // comment +} +enum MooC { + // comment +} +mod BarC { // comment +} +extern "C" { + // comment +} diff --git a/src/tools/rustfmt/tests/target/issue_3839.rs b/src/tools/rustfmt/tests/target/issue_3839.rs new file mode 100644 index 0000000000..b7bdf4c757 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_3839.rs @@ -0,0 +1,8 @@ +struct Foo { + a: i32, + /* + asd + */ + // foo + b: i32, +} diff --git a/src/tools/rustfmt/tests/target/issue_3844.rs b/src/tools/rustfmt/tests/target/issue_3844.rs new file mode 100644 index 0000000000..81d2083463 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_3844.rs @@ -0,0 +1,3 @@ +fn main() { + || {}; +} diff --git a/src/tools/rustfmt/tests/target/issue_3853.rs b/src/tools/rustfmt/tests/target/issue_3853.rs new file mode 100644 index 0000000000..eae59eff94 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_3853.rs @@ -0,0 +1,47 @@ +fn by_ref_with_block_before_ident() { + if let Some(ref /*def*/ state) = foo { + println!("asdfasdfasdf"); + } +} + +fn mut_block_before_ident() { + if let Some(mut /*def*/ state) = foo { + println!("123"); + } +} + +fn ref_and_mut_blocks_before_ident() { + if let Some(ref /*abc*/ mut /*def*/ state) = foo { + println!("deefefefefefwea"); + } +} + +fn sub_pattern() { + let foo @ /*foo*/ bar(f) = 42; +} + +fn no_prefix_block_before_ident() { + if let Some(/*def*/ state) = foo { + println!("129387123123"); + } +} + +fn issue_3853() { + if let Some(ref /*mut*/ state) = foo {} +} + +fn double_slash_comment_between_lhs_and_rhs() { + if let Some(e) = + // self.foo.bar(e, tx) + packet.transaction.state.committed + { + // body + println!("a2304712836123"); + } +} + +fn block_comment_between_lhs_and_rhs() { + if let Some(ref /*def*/ mut /*abc*/ state) = /*abc*/ foo { + println!("asdfasdfasdf"); + } +} diff --git a/src/tools/rustfmt/tests/target/issue_3854.rs b/src/tools/rustfmt/tests/target/issue_3854.rs new file mode 100644 index 0000000000..3051335c27 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_3854.rs @@ -0,0 +1,3 @@ +fn main() { + println!("{:?}", -1. ..1.); +} diff --git a/src/tools/rustfmt/tests/target/issue_3868.rs b/src/tools/rustfmt/tests/target/issue_3868.rs new file mode 100644 index 0000000000..0672413592 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_3868.rs @@ -0,0 +1,9 @@ +fn foo() {} + +fn bar() { + for _ in 0..1 {} +} + +fn baz() { + (); +} diff --git a/src/tools/rustfmt/tests/target/issue_3934.rs b/src/tools/rustfmt/tests/target/issue_3934.rs new file mode 100644 index 0000000000..68f9fd0aec --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_3934.rs @@ -0,0 +1,8 @@ +mod repro { + pub fn push() -> Result<(), ()> { + self.api.map_api_result(|api| { + #[allow(deprecated)] + match api.apply_extrinsic_before_version_4_with_context()? {} + }) + } +} diff --git a/src/tools/rustfmt/tests/target/issue_4057.rs b/src/tools/rustfmt/tests/target/issue_4057.rs new file mode 100644 index 0000000000..467e67bca7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4057.rs @@ -0,0 +1,15 @@ +// rustfmt-format_code_in_doc_comments: true + +/// ``` +/// # #[rustversion::since(1.36)] +/// # fn dox() { +/// # use std::pin::Pin; +/// # type Projection<'a> = &'a (); +/// # type ProjectionRef<'a> = &'a (); +/// # trait Dox { +/// fn project_ex(self: Pin<&mut Self>) -> Projection<'_>; +/// fn project_ref(self: Pin<&Self>) -> ProjectionRef<'_>; +/// # } +/// # } +/// ``` +struct Foo; diff --git a/src/tools/rustfmt/tests/target/issue_4086.rs b/src/tools/rustfmt/tests/target/issue_4086.rs new file mode 100644 index 0000000000..959d3b3d46 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4086.rs @@ -0,0 +1,2 @@ +#[cfg(any())] +extern "C++" {} diff --git a/src/tools/rustfmt/tests/target/issue_4374.rs b/src/tools/rustfmt/tests/target/issue_4374.rs new file mode 100644 index 0000000000..f5bf657bbc --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4374.rs @@ -0,0 +1,13 @@ +fn a(_f: F) -> () +where + F: FnOnce() -> (), +{ +} +fn main() { + a(|| { + #[allow(irrefutable_let_patterns)] + while let _ = 0 { + break; + } + }); +} diff --git a/src/tools/rustfmt/tests/target/issue_4467.rs b/src/tools/rustfmt/tests/target/issue_4467.rs new file mode 100644 index 0000000000..f5ee96c4c1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4467.rs @@ -0,0 +1,6 @@ +pub fn main() { + #[cfg(feature = "std")] + { + // Comment + } +} diff --git a/src/tools/rustfmt/tests/target/issue_4475.rs b/src/tools/rustfmt/tests/target/issue_4475.rs new file mode 100644 index 0000000000..ea6726c5a0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4475.rs @@ -0,0 +1,29 @@ +fn main() { + #[cfg(debug_assertions)] + { + println!("DEBUG"); + } +} + +fn main() { + #[cfg(feature = "foo")] + { + /* + let foo = 0 + */ + } +} + +fn main() { + #[cfg(feature = "foo")] + { /* let foo = 0; */ } +} + +fn main() { + #[foo] + #[bar] + #[baz] + { + // let foo = 0; + } +} diff --git a/src/tools/rustfmt/tests/target/issue_4522.rs b/src/tools/rustfmt/tests/target/issue_4522.rs new file mode 100644 index 0000000000..5ca70e1c09 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4522.rs @@ -0,0 +1,6 @@ +fn main() { + #[cfg(feature = "foo")] + { + // let foo = 0; + } +} diff --git a/src/tools/rustfmt/tests/target/issue_4528.rs b/src/tools/rustfmt/tests/target/issue_4528.rs new file mode 100644 index 0000000000..7828804b09 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4528.rs @@ -0,0 +1,8 @@ +#![allow(clippy::no_effect)] + +extern "C" { + // N.B., mutability can be easily incorrect in FFI calls -- as + // in C, the default is mutable pointers. + fn ffi(c: *mut u8); + fn int_ffi(c: *mut i32); +} diff --git a/src/tools/rustfmt/tests/target/issue_4545.rs b/src/tools/rustfmt/tests/target/issue_4545.rs new file mode 100644 index 0000000000..f87c810365 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4545.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Foo)] +enum Bar {} + +#[derive(Debug, , Default)] +struct Struct(i32); diff --git a/src/tools/rustfmt/tests/target/issue_4584.rs b/src/tools/rustfmt/tests/target/issue_4584.rs new file mode 100644 index 0000000000..20255beadb --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4584.rs @@ -0,0 +1,32 @@ +// rustfmt-indent_style: Visual + +#[derive(Debug)] +pub enum Case { + Upper, + Lower, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Case { + Upper, + Lower, +} + +// NB - This formatting looks potentially off the desired state, but is +// consistent with current behavior. Included here to provide a line wrapped +// derive case with the changes applied to resolve issue #4584 +#[derive(Add, + Sub, + Mul, + Div, + Clone, + Copy, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Hash, + Serialize, + Mul)] +struct Foo {} diff --git a/src/tools/rustfmt/tests/target/issue_4636.rs b/src/tools/rustfmt/tests/target/issue_4636.rs new file mode 100644 index 0000000000..a6465e29a0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4636.rs @@ -0,0 +1,13 @@ +pub trait PrettyPrinter<'tcx>: + Printer< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > + fmt::Write +{ + // +} diff --git a/src/tools/rustfmt/tests/target/issue_4675.rs b/src/tools/rustfmt/tests/target/issue_4675.rs new file mode 100644 index 0000000000..a65f86832e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4675.rs @@ -0,0 +1,8 @@ +macro_rules! foo { + ($s:ident ( $p:pat )) => { + Foo { + name: Name::$s($p), + .. + } + }; +} diff --git a/src/tools/rustfmt/tests/target/item-brace-style-always-next-line.rs b/src/tools/rustfmt/tests/target/item-brace-style-always-next-line.rs new file mode 100644 index 0000000000..531ac59868 --- /dev/null +++ b/src/tools/rustfmt/tests/target/item-brace-style-always-next-line.rs @@ -0,0 +1,42 @@ +// rustfmt-brace_style: AlwaysNextLine + +mod M +{ + enum A + { + A, + } + + struct B + { + b: i32, + } + + // For empty enums and structs, the brace remains on the same line. + enum C {} + + struct D {} + + enum A + where + T: Copy, + { + A, + } + + struct B + where + T: Copy, + { + b: i32, + } + + // For empty enums and structs, the brace remains on the same line. + enum C + where + T: Copy, {} + + struct D + where + T: Copy, {} +} diff --git a/src/tools/rustfmt/tests/target/item-brace-style-prefer-same-line.rs b/src/tools/rustfmt/tests/target/item-brace-style-prefer-same-line.rs new file mode 100644 index 0000000000..ef8dc028c2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/item-brace-style-prefer-same-line.rs @@ -0,0 +1,35 @@ +// rustfmt-brace_style: PreferSameLine + +mod M { + enum A { + A, + } + + struct B { + b: i32, + } + + enum C {} + + struct D {} + + enum A + where + T: Copy, { + A, + } + + struct B + where + T: Copy, { + b: i32, + } + + enum C + where + T: Copy, {} + + struct D + where + T: Copy, {} +} diff --git a/src/tools/rustfmt/tests/target/item-brace-style-same-line-where.rs b/src/tools/rustfmt/tests/target/item-brace-style-same-line-where.rs new file mode 100644 index 0000000000..fabe5822c8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/item-brace-style-same-line-where.rs @@ -0,0 +1,37 @@ +mod M { + enum A { + A, + } + + struct B { + b: i32, + } + + // For empty enums and structs, the brace remains on the same line. + enum C {} + + struct D {} + + enum A + where + T: Copy, + { + A, + } + + struct B + where + T: Copy, + { + b: i32, + } + + // For empty enums and structs, the brace remains on the same line. + enum C + where + T: Copy, {} + + struct D + where + T: Copy, {} +} diff --git a/src/tools/rustfmt/tests/target/itemized-blocks/no_wrap.rs b/src/tools/rustfmt/tests/target/itemized-blocks/no_wrap.rs new file mode 100644 index 0000000000..de88563827 --- /dev/null +++ b/src/tools/rustfmt/tests/target/itemized-blocks/no_wrap.rs @@ -0,0 +1,47 @@ +// rustfmt-normalize_comments: true +// rustfmt-format_code_in_doc_comments: true + +//! This is a list: +//! * Outer +//! * Outer +//! * Inner +//! * Inner with lots of text so that it could be reformatted something something something lots of text so that it could be reformatted something something something +//! +//! This example shows how to configure fern to output really nicely colored logs +//! - when the log level is error, the whole line is red +//! - when the log level is warn, the whole line is yellow +//! - when the log level is info, the level name is green and the rest of the line is white +//! - when the log level is debug, the whole line is white +//! - when the log level is trace, the whole line is gray ("bright black") + +/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote +/// theater, i.e., as passed to [`Theater::send`] on the remote actor: +/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means +/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater +/// * `tag` is a tag that identifies the message type +/// * `msg` is the (serialized) message +/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote +/// theater, i.e., as passed to [`Theater::send`] on the remote actor +fn func1() {} + +/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote +/// theater, i.e., as passed to [`Theater::send`] on the remote actor: +/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means +/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater +/// * `tag` is a tag that identifies the message type +/// * `msg` is the (serialized) message +/// ``` +/// let x = 42; +/// ``` +fn func2() {} + +/// Look: +/// +/// ``` +/// let x = 42; +/// ``` +/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means +/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater +/// * `tag` is a tag that identifies the message type +/// * `msg` is the (serialized) message +fn func3() {} diff --git a/src/tools/rustfmt/tests/target/itemized-blocks/rewrite_fail.rs b/src/tools/rustfmt/tests/target/itemized-blocks/rewrite_fail.rs new file mode 100644 index 0000000000..a118ef6faa --- /dev/null +++ b/src/tools/rustfmt/tests/target/itemized-blocks/rewrite_fail.rs @@ -0,0 +1,14 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 50 + +// This example shows how to configure fern to +// output really nicely colored logs +// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +// - when the log level is info, the level +// name is green and the rest of the line is +// white +// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +fn func1() {} diff --git a/src/tools/rustfmt/tests/target/itemized-blocks/urls.rs b/src/tools/rustfmt/tests/target/itemized-blocks/urls.rs new file mode 100644 index 0000000000..bc46ea47e1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/itemized-blocks/urls.rs @@ -0,0 +1,25 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 79 + +//! CMSIS: Cortex Microcontroller Software Interface Standard +//! +//! The version 5 of the standard can be found at: +//! +//! http://arm-software.github.io/CMSIS_5/Core/html/index.html +//! +//! The API reference of the standard can be found at: +//! +//! - example -- http://example.org -- something something something something +//! something something +//! - something something something something something something more -- http://example.org +//! - http://example.org/something/something/something/something/something/something +//! and the rest +//! - Core function access -- http://arm-software.github.io/CMSIS_5/Core/html/group__Core__Register__gr.html +//! - Intrinsic functions for CPU instructions -- http://arm-software.github.io/CMSIS_5/Core/html/group__intrinsic__CPU__gr.html +//! - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum sem +//! lacus, commodo vitae. +//! +//! The reference C implementation used as the base of this Rust port can be +//! found at +//! +//! https://github.com/ARM-software/CMSIS_5/blob/5.3.0/CMSIS/Core/Include/cmsis_gcc.h diff --git a/src/tools/rustfmt/tests/target/itemized-blocks/wrap.rs b/src/tools/rustfmt/tests/target/itemized-blocks/wrap.rs new file mode 100644 index 0000000000..a4907303c9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/itemized-blocks/wrap.rs @@ -0,0 +1,89 @@ +// rustfmt-wrap_comments: true +// rustfmt-format_code_in_doc_comments: true +// rustfmt-max_width: 50 + +//! This is a list: +//! * Outer +//! * Outer +//! * Inner +//! * Inner with lots of text so that it could +//! be reformatted something something +//! something lots of text so that it could be +//! reformatted something something something +//! +//! This example shows how to configure fern to +//! output really nicely colored logs +//! - when the log level is error, the whole line +//! is red +//! - when the log level is warn, the whole line +//! is yellow +//! - when the log level is info, the level name +//! is green and the rest of the line is white +//! - when the log level is debug, the whole line +//! is white +//! - when the log level is trace, the whole line +//! is gray ("bright black") + +// This example shows how to configure fern to +// output really nicely colored logs +// - when the log level is error, the whole line +// is red +// - when the log level is warn, the whole line +// is yellow +// - when the log level is info, the level +// name is green and the rest of the line is +// white +// - when the log level is debug, the whole line +// is white +// - when the log level is trace, the whole line +// is gray ("bright black") + +/// All the parameters ***except for +/// `from_theater`*** should be inserted as sent +/// by the remote theater, i.e., as passed to +/// [`Theater::send`] on the remote actor: +/// * `from` is the sending (remote) [`ActorId`], +/// as reported by the remote theater by +/// theater-specific means +/// * `to` is the receiving (local) [`ActorId`], +/// as requested by the remote theater +/// * `tag` is a tag that identifies the message +/// type +/// * `msg` is the (serialized) message +/// All the parameters ***except for +/// `from_theater`*** should be inserted as sent +/// by the remote theater, i.e., as passed to +/// [`Theater::send`] on the remote actor +fn func1() {} + +/// All the parameters ***except for +/// `from_theater`*** should be inserted as sent +/// by the remote theater, i.e., as passed to +/// [`Theater::send`] on the remote actor: +/// * `from` is the sending (remote) [`ActorId`], +/// as reported by the remote theater by +/// theater-specific means +/// * `to` is the receiving (local) [`ActorId`], +/// as requested by the remote theater +/// * `tag` is a tag that identifies the message +/// type +/// * `msg` is the (serialized) message +/// ``` +/// let x = 42; +/// ``` +fn func2() {} + +/// Look: +/// +/// ``` +/// let x = 42; +/// ``` +/// * `from` is the sending (remote) [`ActorId`], +/// as reported by the remote theater by +/// theater-specific means +/// * `to` is the receiving (local) [`ActorId`], +/// as requested by the remote theater +/// * `tag` is a tag that identifies the message +/// type +/// * `msg` is the (serialized) message +fn func3() {} diff --git a/src/tools/rustfmt/tests/target/label_break.rs b/src/tools/rustfmt/tests/target/label_break.rs new file mode 100644 index 0000000000..728d78137c --- /dev/null +++ b/src/tools/rustfmt/tests/target/label_break.rs @@ -0,0 +1,28 @@ +// format with label break value. +fn main() { + 'empty_block: {} + + 'block: { + do_thing(); + if condition_not_met() { + break 'block; + } + do_next_thing(); + if condition_not_met() { + break 'block; + } + do_last_thing(); + } + + let result = 'block: { + if foo() { + // comment + break 'block 1; + } + if bar() { + /* comment */ + break 'block 2; + } + 3 + }; +} diff --git a/src/tools/rustfmt/tests/target/large-block.rs b/src/tools/rustfmt/tests/target/large-block.rs new file mode 100644 index 0000000000..09e9169f34 --- /dev/null +++ b/src/tools/rustfmt/tests/target/large-block.rs @@ -0,0 +1,5 @@ +fn issue1351() { + std_fmt_Arguments_new_v1_std_rt_begin_panic_fmt_sdfasfasdfasdf({ + static __STATIC_FMTSTR: &'static [&'static str] = &[]; + }); +} diff --git a/src/tools/rustfmt/tests/target/large_vec.rs b/src/tools/rustfmt/tests/target/large_vec.rs new file mode 100644 index 0000000000..95d1fc43c0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/large_vec.rs @@ -0,0 +1,42 @@ +// See #1470. + +impl Environment { + pub fn new_root() -> Rc> { + let mut env = Environment::new(); + let builtin_functions = &[ + ( + "println", + Function::NativeVoid( + CallSign { + num_params: 0, + variadic: true, + param_types: vec![], + }, + native_println, + ), + ), + ( + "run_http_server", + Function::NativeVoid( + CallSign { + num_params: 1, + variadic: false, + param_types: vec![Some(ConstraintType::Function)], + }, + native_run_http_server, + ), + ), + ( + "len", + Function::NativeReturning( + CallSign { + num_params: 1, + variadic: false, + param_types: vec![None], + }, + native_len, + ), + ), + ]; + } +} diff --git a/src/tools/rustfmt/tests/target/lazy_static.rs b/src/tools/rustfmt/tests/target/lazy_static.rs new file mode 100644 index 0000000000..3625e0a5f5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/lazy_static.rs @@ -0,0 +1,49 @@ +// Format `lazy_static!`. + +lazy_static! { + static ref CONFIG_NAME_REGEX: regex::Regex = + regex::Regex::new(r"^## `([^`]+)`").expect("Failed creating configuration pattern"); + static ref CONFIG_VALUE_REGEX: regex::Regex = regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#) + .expect("Failed creating configuration value pattern"); +} + +// We need to be able to format `lazy_static!` without known syntax. +lazy_static!(xxx, yyyy, zzzzz); + +lazy_static! {} + +// #2354 +lazy_static! { + pub static ref Sbase64_encode_string: ::lisp::LispSubrRef = { + let subr = ::remacs_sys::Lisp_Subr { + header: ::remacs_sys::Lisp_Vectorlike_Header { + size: ((::remacs_sys::PseudovecType::PVEC_SUBR as ::libc::ptrdiff_t) + << ::remacs_sys::PSEUDOVECTOR_AREA_BITS), + }, + function: self::Fbase64_encode_string as *const ::libc::c_void, + min_args: 1i16, + max_args: 2i16, + symbol_name: (b"base64-encode-string\x00").as_ptr() as *const ::libc::c_char, + intspec: ::std::ptr::null(), + doc: ::std::ptr::null(), + lang: ::remacs_sys::Lisp_Subr_Lang_Rust, + }; + unsafe { + let ptr = ::remacs_sys::xmalloc(::std::mem::size_of::<::remacs_sys::Lisp_Subr>()) + as *mut ::remacs_sys::Lisp_Subr; + ::std::ptr::copy_nonoverlapping(&subr, ptr, 1); + ::std::mem::forget(subr); + ::lisp::ExternalPtr::new(ptr) + } + }; +} + +lazy_static! { + static ref FOO: HashMap< + String, + ( + &'static str, + fn(Foo) -> Result, Either> + ), + > = HashMap::new(); +} diff --git a/src/tools/rustfmt/tests/target/license-templates/empty_license_path.rs b/src/tools/rustfmt/tests/target/license-templates/empty_license_path.rs new file mode 100644 index 0000000000..950f103ed3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/license-templates/empty_license_path.rs @@ -0,0 +1,5 @@ +// rustfmt-config: issue-3802.toml + +fn main() { + println!("Hello world!"); +} diff --git a/src/tools/rustfmt/tests/target/license-templates/license.rs b/src/tools/rustfmt/tests/target/license-templates/license.rs new file mode 100644 index 0000000000..7169c7b257 --- /dev/null +++ b/src/tools/rustfmt/tests/target/license-templates/license.rs @@ -0,0 +1,6 @@ +// rustfmt-license_template_path: tests/license-template/lt.txt +// Copyright 2019 The rustfmt developers. + +fn main() { + println!("Hello world!"); +} diff --git a/src/tools/rustfmt/tests/target/long-fn-1/version_one.rs b/src/tools/rustfmt/tests/target/long-fn-1/version_one.rs new file mode 100644 index 0000000000..05f69953c2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/long-fn-1/version_one.rs @@ -0,0 +1,29 @@ +// rustfmt-version: One +// Tests that a function which is almost short enough, but not quite, gets +// formatted correctly. + +impl Foo { + fn some_input( + &mut self, + input: Input, + input_path: Option, + ) -> (Input, Option) { + } + + fn some_inpu(&mut self, input: Input, input_path: Option) -> (Input, Option) { + } +} + +// #1843 +#[allow(non_snake_case)] +pub extern "C" fn Java_com_exonum_binding_storage_indices_ValueSetIndexProxy_nativeContainsByHash( +) -> bool { + false +} + +// #3009 +impl Something { + fn my_function_name_is_way_to_long_but_used_as_a_case_study_or_an_example_its_fine( + ) -> Result<(), String> { + } +} diff --git a/src/tools/rustfmt/tests/target/long-fn-1/version_two.rs b/src/tools/rustfmt/tests/target/long-fn-1/version_two.rs new file mode 100644 index 0000000000..32794bccde --- /dev/null +++ b/src/tools/rustfmt/tests/target/long-fn-1/version_two.rs @@ -0,0 +1,29 @@ +// rustfmt-version: Two +// Tests that a function which is almost short enough, but not quite, gets +// formatted correctly. + +impl Foo { + fn some_input( + &mut self, + input: Input, + input_path: Option, + ) -> (Input, Option) { + } + + fn some_inpu(&mut self, input: Input, input_path: Option) -> (Input, Option) { + } +} + +// #1843 +#[allow(non_snake_case)] +pub extern "C" fn Java_com_exonum_binding_storage_indices_ValueSetIndexProxy_nativeContainsByHash() +-> bool { + false +} + +// #3009 +impl Something { + fn my_function_name_is_way_to_long_but_used_as_a_case_study_or_an_example_its_fine() + -> Result<(), String> { + } +} diff --git a/src/tools/rustfmt/tests/target/long-match-arms-brace-newline.rs b/src/tools/rustfmt/tests/target/long-match-arms-brace-newline.rs new file mode 100644 index 0000000000..aeb384e720 --- /dev/null +++ b/src/tools/rustfmt/tests/target/long-match-arms-brace-newline.rs @@ -0,0 +1,15 @@ +// rustfmt-format_strings: true +// rustfmt-max_width: 80 +// rustfmt-control_brace_style: AlwaysNextLine + +fn main() { + match x + { + aaaaaaaa::Bbbbb::Ccccccccccccc(_, Some(ref x)) + if x == "aaaaaaaaaaa aaaaaaa aaaaaa" => + { + Ok(()) + } + _ => Err(x), + } +} diff --git a/src/tools/rustfmt/tests/target/long-use-statement-issue-3154.rs b/src/tools/rustfmt/tests/target/long-use-statement-issue-3154.rs new file mode 100644 index 0000000000..877241e3b6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/long-use-statement-issue-3154.rs @@ -0,0 +1,3 @@ +// rustfmt-reorder_imports: false + +pub use self::super::super::super::root::mozilla::detail::StringClassFlags as nsTStringRepr_ClassFlags; diff --git a/src/tools/rustfmt/tests/target/long_field_access.rs b/src/tools/rustfmt/tests/target/long_field_access.rs new file mode 100644 index 0000000000..349d2c2f63 --- /dev/null +++ b/src/tools/rustfmt/tests/target/long_field_access.rs @@ -0,0 +1,4 @@ +fn f() { + block_flow.base.stacking_relative_position_of_display_port = + self.base.stacking_relative_position_of_display_port; +} diff --git a/src/tools/rustfmt/tests/target/loop.rs b/src/tools/rustfmt/tests/target/loop.rs new file mode 100644 index 0000000000..f669e7e2c5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/loop.rs @@ -0,0 +1,34 @@ +fn main() { + loop { + return some_val; + } + + let x = loop { + do_forever(); + }; + + 'label: loop { + // Just comments + } + + 'a: while loooooooooooooooooooooooooooooooooong_variable_name + another_value > some_other_value + { + } + + while aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa > bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb { + } + + while aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { + } + + 'b: for xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx in some_iter(arg1, arg2) + { + // do smth + } + + while let Some(i) = x.find('s') { + x.update(); + continue; + continue 'foo; + } +} diff --git a/src/tools/rustfmt/tests/target/macro_not_expr.rs b/src/tools/rustfmt/tests/target/macro_not_expr.rs new file mode 100644 index 0000000000..45f85ff2c9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/macro_not_expr.rs @@ -0,0 +1,7 @@ +macro_rules! test { + ($($t:tt)*) => {}; +} + +fn main() { + test!( a : B => c d ); +} diff --git a/src/tools/rustfmt/tests/target/macro_rules.rs b/src/tools/rustfmt/tests/target/macro_rules.rs new file mode 100644 index 0000000000..97444aef40 --- /dev/null +++ b/src/tools/rustfmt/tests/target/macro_rules.rs @@ -0,0 +1,360 @@ +// rustfmt-format_macro_matchers: true + +macro_rules! m { + () => {}; + ($x:ident) => {}; + ($m1:ident, $m2:ident, $x:ident) => {}; + ($($beginning:ident),*; $middle:ident; $($end:ident),*) => {}; + ( + $($beginning:ident),*; + $middle:ident; + $($end:ident),*; + $($beginning:ident),*; + $middle:ident; + $($end:ident),* + ) => {}; + ($name:ident($($dol:tt $var:ident)*) $($body:tt)*) => {}; + ( + $($i:ident : $ty:ty, $def:expr, $stb:expr, $($dstring:tt),+);+ $(;)* + $($i:ident : $ty:ty, $def:expr, $stb:expr, $($dstring:tt),+);+ $(;)* + ) => {}; + ($foo:tt foo[$attr:meta] $name:ident) => {}; + ($foo:tt[$attr:meta] $name:ident) => {}; + ($foo:tt &'a[$attr:meta] $name:ident) => {}; + ($foo:tt foo #[$attr:meta] $name:ident) => {}; + ($foo:tt #[$attr:meta] $name:ident) => {}; + ($foo:tt &'a #[$attr:meta] $name:ident) => {}; + ($x:tt foo bar foo bar foo bar $y:tt => x * y * z $z:tt, $($a:tt),*) => {}; +} + +macro_rules! impl_a_method { + ($n:ident($a:ident : $ta:ty) -> $ret:ty { $body:expr }) => { + fn $n($a: $ta) -> $ret { + $body + } + macro_rules! $n { + ($va: expr) => { + $n($va) + }; + } + }; + ($n:ident($a:ident : $ta:ty, $b:ident : $tb:ty) -> $ret:ty { $body:expr }) => { + fn $n($a: $ta, $b: $tb) -> $ret { + $body + } + macro_rules! $n { + ($va: expr,$vb: expr) => { + $n($va, $vb) + }; + } + }; + ( + $n:ident($a:ident : $ta:ty, $b:ident : $tb:ty, $c:ident : $tc:ty) -> $ret:ty { $body:expr } + ) => { + fn $n($a: $ta, $b: $tb, $c: $tc) -> $ret { + $body + } + macro_rules! $n { + ($va: expr,$vb: expr,$vc: expr) => { + $n($va, $vb, $vc) + }; + } + }; + ( + $n:ident($a:ident : $ta:ty, $b:ident : $tb:ty, $c:ident : $tc:ty, $d:ident : $td:ty) -> + $ret:ty { $body:expr } + ) => { + fn $n($a: $ta, $b: $tb, $c: $tc, $d: $td) -> $ret { + $body + } + macro_rules! $n { + ($va: expr,$vb: expr,$vc: expr,$vd: expr) => { + $n($va, $vb, $vc, $vd) + }; + } + }; +} + +macro_rules! m { + // a + ($expr:expr, $($func:ident)*) => {{ + let x = $expr; + $func(x) + }}; + + /* b */ + () => { + /* c */ + }; + + (@tag) => {}; + + // d + ($item:ident) => { + mod macro_item { + struct $item; + } + }; +} + +macro m2 { + // a + ($expr:expr, $($func:ident)*) => {{ + let x = $expr; + $func(x) + }} + + /* b */ + () => { + /* c */ + } + + (@tag) => {} + + // d + ($item:ident) => { + mod macro_item { + struct $item; + } + } +} + +// #2438, #2476 +macro_rules! m { + () => { + fn foo() { + this_line_is_98_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(); + } + }; +} +macro_rules! m { + () => { + fn foo() { + this_line_is_99_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( + ); + } + }; +} +macro_rules! m { + () => { + fn foo() { + this_line_is_100_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( + ); + } + }; +} +macro_rules! m { + () => { + fn foo() { + this_line_is_101_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( + ); + } + }; +} + +// #2439 +macro_rules! m { + ( + $line0_xxxxxxxxxxxxxxxxx:expr, + $line1_xxxxxxxxxxxxxxxxx:expr, + $line2_xxxxxxxxxxxxxxxxx:expr, + $line3_xxxxxxxxxxxxxxxxx:expr, + ) => {}; +} + +// #2466 +// Skip formatting `macro_rules!` that are not using `{}`. +macro_rules! m ( + () => () +); +macro_rules! m [ + () => () +]; + +// #2470 +macro foo($type_name:ident, $docs:expr) { + #[allow(non_camel_case_types)] + #[doc=$docs] + #[derive(Debug, Clone, Copy)] + pub struct $type_name; +} + +// #2534 +macro_rules! foo { + ($a:ident : $b:ty) => {}; + ($a:ident $b:ident $c:ident) => {}; +} + +// #2538 +macro_rules! add_message_to_notes { + ($msg:expr) => {{ + let mut lines = message.lines(); + notes.push_str(&format!("\n{}: {}", level, lines.next().unwrap())); + for line in lines { + notes.push_str(&format!( + "\n{:indent$}{line}", + "", + indent = level.len() + 2, + line = line, + )); + } + }}; +} + +// #2560 +macro_rules! binary { + ($_self:ident, $expr:expr, $lhs:expr, $func:ident) => { + while $_self.matched($expr) { + let op = $_self.get_binary_op()?; + + let rhs = Box::new($_self.$func()?); + + $lhs = Spanned { + span: $lhs.get_span().to(rhs.get_span()), + value: Expression::Binary { + lhs: Box::new($lhs), + op, + rhs, + }, + } + } + }; +} + +// #2558 +macro_rules! m { + ($x:) => {}; + ($($foo:expr)()?) => {}; +} + +// #2749 +macro_rules! foo { + ($(x)* {}) => {}; + ($(x)*()) => {}; + ($(x)*[]) => {}; +} +macro_rules! __wundergraph_expand_sqlite_mutation { + ( + $mutation_name:ident $((context = $($context:tt)*))* { + $( + $entity_name:ident( + $(insert = $insert:ident,)* + $(update = $update:ident,)* + $(delete = $($delete:tt)+)* + ), + )* + } + ) => {}; +} + +// #2607 +macro_rules! bench { + ($ty:ident) => { + criterion_group!( + name = benches; + config = ::common_bench::reduced_samples(); + targets = call, map; + ); + }; +} + +// #2770 +macro_rules! save_regs { + () => { + asm!("push rax + push rcx + push rdx + push rsi + push rdi + push r8 + push r9 + push r10 + push r11" + :::: "intel", "volatile"); + }; +} + +// #2721 +macro_rules! impl_as_byte_slice_arrays { + ($n:expr,) => {}; + ($n:expr, $N:ident, $($NN:ident,)*) => { + impl_as_byte_slice_arrays!($n - 1, $($NN,)*); + + impl AsByteSliceMut for [T; $n] where [T]: AsByteSliceMut { + fn as_byte_slice_mut(&mut self) -> &mut [u8] { + self[..].as_byte_slice_mut() + } + + fn to_le(&mut self) { + self[..].to_le() + } + } + }; + (!div $n:expr,) => {}; + (!div $n:expr, $N:ident, $($NN:ident,)*) => { + impl_as_byte_slice_arrays!(!div $n / 2, $($NN,)*); + + impl AsByteSliceMut for [T; $n] where [T]: AsByteSliceMut { + fn as_byte_slice_mut(&mut self) -> &mut [u8] { + self[..].as_byte_slice_mut() + } + + fn to_le(&mut self) { + self[..].to_le() + } + } + }; +} + +// #2919 +fn foo() { + { + macro_rules! touch_value { + ($func:ident, $value:expr) => {{ + let result = API::get_cached().$func( + self, + key.as_ptr(), + $value, + ffi::VSPropAppendMode::paTouch, + ); + let result = API::get_cached().$func(self, key.as_ptr(), $value, ffi::VSPropAppend); + let result = + API::get_cached().$func(self, key.as_ptr(), $value, ffi::VSPropAppendM); + let result = + APIIIIIIIII::get_cached().$func(self, key.as_ptr(), $value, ffi::VSPropAppendM); + let result = API::get_cached().$func( + self, + key.as_ptr(), + $value, + ffi::VSPropAppendMMMMMMMMMM, + ); + debug_assert!(result == 0); + }}; + } + } +} + +// #2642 +macro_rules! template { + ($name:expr) => { + format_args!( + r##" +"http://example.com" + +# test +"##, + $name + ) + }; +} + +macro_rules! template { + () => { + format_args!( + r" +// + +" + ) + }; +} diff --git a/src/tools/rustfmt/tests/target/macro_rules_semi.rs b/src/tools/rustfmt/tests/target/macro_rules_semi.rs new file mode 100644 index 0000000000..84e12d16e6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/macro_rules_semi.rs @@ -0,0 +1,22 @@ +macro_rules! expr { + (no_semi) => { + return true + }; + (semi) => { + return true; + }; +} + +fn foo() -> bool { + match true { + true => expr!(no_semi), + false if false => { + expr!(semi) + } + false => { + expr!(semi); + } + } +} + +fn main() {} diff --git a/src/tools/rustfmt/tests/target/macros.rs b/src/tools/rustfmt/tests/target/macros.rs new file mode 100644 index 0000000000..e930b5037d --- /dev/null +++ b/src/tools/rustfmt/tests/target/macros.rs @@ -0,0 +1,1062 @@ +// rustfmt-normalize_comments: true +// rustfmt-format_macro_matchers: true +itemmacro!(this, is.now().formatted(yay)); + +itemmacro!( + really, + long.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbb() + .is + .formatted() +); + +itemmacro! {this, is.brace().formatted()} + +fn main() { + foo!(); + + foo!(,); + + bar!(a, b, c); + + bar!(a, b, c,); + + baz!(1 + 2 + 3, quux.kaas()); + + quux!( + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + ); + + kaas!( + // comments + a, // post macro + b // another + ); + + trailingcomma!(a, b, c,); + // Preserve trailing comma only when necessary. + ok!(file.seek(SeekFrom::Start( + table.map(|table| fixture.offset(table)).unwrap_or(0), + ))); + + noexpr!( i am not an expression, OK? ); + + vec![a, b, c]; + + vec![ + AAAAAA, + AAAAAA, + AAAAAA, + AAAAAA, + AAAAAA, + AAAAAA, + AAAAAA, + AAAAAA, + AAAAAA, + BBBBB, + 5, + 100 - 30, + 1.33, + b, + b, + b, + ]; + + vec![a /* comment */]; + + // Trailing spaces after a comma + vec![a]; + + vec![a; b]; + vec![a; b]; + vec![a; b]; + + vec![a, b; c]; + vec![a; b, c]; + + vec![ + a; + (|x| { + let y = x + 1; + let z = y + 1; + z + })(2) + ]; + vec![ + a; + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + ]; + vec![a; unsafe { x + 1 }]; + + unknown_bracket_macro__comma_should_not_be_stripped![a,]; + + foo(makro!(1, 3)); + + hamkaas! { () }; + + macrowithbraces! {dont, format, me} + + x!(fn); + + some_macro!(); + + some_macro![]; + + some_macro! { + // comment + }; + + some_macro! { + // comment + }; + + some_macro!( + // comment + not function like + ); + + // #1712 + let image = gray_image!( + 00, 01, 02; + 10, 11, 12; + 20, 21, 22); + + // #1092 + chain!(input, a: take!(max_size), || []); + + // #2727 + foo!("bar"); +} + +impl X { + empty_invoc! {} + empty_invoc! {} +} + +fn issue_1279() { + println!("dsfs"); // a comment +} + +fn issue_1555() { + let hello = &format!( + "HTTP/1.1 200 OK\r\nServer: {}\r\n\r\n{}", + "65454654654654654654654655464", "4" + ); +} + +fn issue1178() { + macro_rules! foo { + (#[$attr:meta] $name:ident) => {}; + } + + foo!( + #[doc = "bar"] + baz + ); +} + +fn issue1739() { + sql_function!( + add_rss_item, + add_rss_item_t, + ( + a: types::Integer, + b: types::Timestamptz, + c: types::Text, + d: types::Text, + e: types::Text + ) + ); + + w.slice_mut(s![ + .., + init_size[1] - extreeeeeeeeeeeeeeeeeeeeeeeem..init_size[1], + .. + ]) + .par_map_inplace(|el| *el = 0.); +} + +fn issue_1885() { + let threads = people + .into_iter() + .map(|name| { + chan_select! { + rx.recv() => {} + } + }) + .collect::>(); +} + +fn issue_1917() { + mod x { + quickcheck! { + fn test(a: String, s: String, b: String) -> TestResult { + if a.find(&s).is_none() { + + TestResult::from_bool(true) + } else { + TestResult::discard() + } + } + } + } +} + +fn issue_1921() { + // Macro with tabs. + lazy_static! { + static ref ONE: u32 = 1; + static ref TWO: u32 = 2; + static ref THREE: u32 = 3; + static ref FOUR: u32 = { + let mut acc = 1; + acc += 1; + acc += 2; + acc + }; + } +} + +// #1577 +fn issue1577() { + let json = json!({ + "foo": "bar", + }); +} + +// #3174 +fn issue_3174() { + let data = if let Some(debug) = error.debug_info() { + json!({ + "errorKind": format!("{:?}", error.err_kind()), + "debugMessage": debug.message, + }) + } else { + json!({ "errorKind": format!("{:?}", error.err_kind()) }) + }; +} + +gfx_pipeline!(pipe { + vbuf: gfx::VertexBuffer = (), + out: gfx::RenderTarget = "Target0", +}); + +// #1919 +#[test] +fn __bindgen_test_layout_HandleWithDtor_open0_int_close0_instantiation() { + assert_eq!( + ::std::mem::size_of::>(), + 8usize, + concat!( + "Size of template specialization: ", + stringify!(HandleWithDtor<::std::os::raw::c_int>) + ) + ); + assert_eq!( + ::std::mem::align_of::>(), + 8usize, + concat!( + "Alignment of template specialization: ", + stringify!(HandleWithDtor<::std::os::raw::c_int>) + ) + ); +} + +// #878 +macro_rules! try_opt { + ($expr:expr) => { + match $expr { + Some(val) => val, + + None => { + return None; + } + } + }; +} + +// #2214 +// macro call whose argument is an array with trailing comma. +fn issue2214() { + make_test!( + str_searcher_ascii_haystack, + "bb", + "abbcbbd", + [ + Reject(0, 1), + Match(1, 3), + Reject(3, 4), + Match(4, 6), + Reject(6, 7), + ] + ); +} + +fn special_case_macros() { + let p = eprint!(); + let q = eprint!("{}", 1); + let r = eprint!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + let s = eprint!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + let q = eprintln!("{}", 1); + let r = eprintln!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + let s = eprintln!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + let q = format!("{}", 1); + let r = format!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + let s = format!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + let q = format_args!("{}", 1); + let r = format_args!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + let s = format_args!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + let q = print!("{}", 1); + let r = print!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + let s = print!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + let q = println!("{}", 1); + let r = println!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + let s = println!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + let q = unreachable!("{}", 1); + let r = unreachable!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + let s = unreachable!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + debug!("{}", 1); + debug!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + debug!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + error!("{}", 1); + error!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + error!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + info!("{}", 1); + info!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + info!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + panic!("{}", 1); + panic!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + panic!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + warn!("{}", 1); + warn!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ); + warn!( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + assert!(); + assert!(result == 42); + assert!(result == 42, "Ahoy there, {}!", target); + assert!( + result == 42, + "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", + result, + input, + expected + ); + assert!( + result == 42, + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + assert_eq!(); + assert_eq!(left); + assert_eq!(left, right); + assert_eq!(left, right, "Ahoy there, {}!", target); + assert_eq!( + left, right, + "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", + result, input, expected + ); + assert_eq!( + first_realllllllllllly_long_variable_that_doesnt_fit_one_one_line, + second_reallllllllllly_long_variable_that_doesnt_fit_one_one_line, + "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", + result, + input, + expected + ); + assert_eq!( + left + 42, + right, + "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", + result, + input, + expected + ); + assert_eq!( + left, + right, + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + write!(&mut s, "Ahoy there, {}!", target); + write!( + &mut s, + "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", + result, input, expected + ); + write!( + &mut s, + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); + + writeln!(&mut s, "Ahoy there, {}!", target); + writeln!( + &mut s, + "Arr! While plunderin' the hold, we got '{}' when given '{}' (we expected '{}')", + result, input, expected + ); + writeln!( + &mut s, + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); +} + +// #1209 +impl Foo { + /// foo + pub fn foo(&self) -> Bar {} +} + +// #819 +fn macro_in_pattern_position() { + let x = match y { + foo!() => (), + bar!(a, b, c) => (), + bar!(a, b, c,) => (), + baz!(1 + 2 + 3, quux.kaas()) => (), + quux!( + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + ) => (), + }; +} + +macro foo() {} + +pub macro bar($x:ident + $y:expr;) { + fn foo($x: Foo) { + long_function( + a_long_argument_to_a_long_function_is_what_this_is(AAAAAAAAAAAAAAAAAAAAAAAAAAAA), + $x.bar($y), + ); + } +} + +macro foo() { + // a comment + fn foo() { + // another comment + bar(); + } +} + +// #2574 +macro_rules! test { + () => {{}}; +} + +macro lex_err($kind: ident $(, $body: expr)*) { + Err(QlError::LexError(LexError::$kind($($body,)*))) +} + +// Preserve trailing comma on item-level macro with `()` or `[]`. +methods![get, post, delete,]; +methods!(get, post, delete,); + +// #2588 +macro_rules! m { + () => { + r#" + test + "# + }; +} +fn foo() { + f! {r#" + test + "#}; +} + +// #2591 +fn foo() { + match 0u32 { + 0 => (), + _ => unreachable!(/* obviously */), + } +} + +fn foo() { + let _ = column!(/* here */); +} + +// #2616 +// Preserve trailing comma when using mixed layout for macro call. +fn foo() { + foo!( + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ); + foo!( + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ); +} + +// #2830 +// Preserve trailing comma-less/ness inside nested macro. +named!( + do_parse_gsv, + map_res!( + do_parse!( + number_of_sentences: map_res!(digit, parse_num::) + >> char!(',') + >> sentence_index: map_res!(digit, parse_num::) + >> char!(',') + >> total_number_of_sats: map_res!(digit, parse_num::) + >> char!(',') + >> sat0: opt!(complete!(parse_gsv_sat_info)) + >> sat1: opt!(complete!(parse_gsv_sat_info)) + >> sat2: opt!(complete!(parse_gsv_sat_info)) + >> sat3: opt!(complete!(parse_gsv_sat_info)) + >> ( + number_of_sentences, + sentence_index, + total_number_of_sats, + sat0, + sat1, + sat2, + sat3 + ) + ), + construct_gsv_data + ) +); + +// #2857 +convert_args!(vec!(1, 2, 3)); + +// #3031 +thread_local!( + /// TLV Holds a set of JSTraceables that need to be rooted + static ROOTED_TRACEABLES: RefCell = RefCell::new(RootedTraceableSet::new()); +); + +thread_local![ + /// TLV Holds a set of JSTraceables that need to be rooted + static ROOTED_TRACEABLES: RefCell = RefCell::new(RootedTraceableSet::new()); + + /// TLV Holds a set of JSTraceables that need to be rooted + static ROOTED_TRACEABLES: RefCell = + RefCell::new(RootedTraceableSet::new(0)); + + /// TLV Holds a set of JSTraceables that need to be rooted + static ROOTED_TRACEABLES: RefCell = + RefCell::new(RootedTraceableSet::new(), xxx, yyy); + + /// TLV Holds a set of JSTraceables that need to be rooted + static ROOTED_TRACEABLES: RefCell = + RefCell::new(RootedTraceableSet::new(1234)); +]; + +fn issue3004() { + foo!(|_| { () }); + stringify!((foo+)); +} + +// #3331 +pub fn fold_abi(_visitor: &mut V, _i: Abi) -> Abi { + Abi { + extern_token: Token![extern](tokens_helper(_visitor, &_i.extern_token.span)), + name: (_i.name).map(|it| _visitor.fold_lit_str(it)), + } +} + +// #3463 +x! {()} + +// #3746 +f!(match a { + 4 => &[ + (3, false), // Missing + (4, true) // I-frame + ][..], +}); + +// #3583 +foo!(|x = y|); diff --git a/src/tools/rustfmt/tests/target/markdown-comment-with-options.rs b/src/tools/rustfmt/tests/target/markdown-comment-with-options.rs new file mode 100644 index 0000000000..ede2bc0d03 --- /dev/null +++ b/src/tools/rustfmt/tests/target/markdown-comment-with-options.rs @@ -0,0 +1,17 @@ +// rustfmt-wrap_comments: true + +// Preserve two trailing whitespaces in doc comment, +// but trim any whitespaces in normal comment. + +//! hello world +//! hello world + +/// hello world +/// hello world +/// hello world +fn foo() { + // hello world + // hello world + let x = 3; + println!("x = {}", x); +} diff --git a/src/tools/rustfmt/tests/target/markdown-comment.rs b/src/tools/rustfmt/tests/target/markdown-comment.rs new file mode 100644 index 0000000000..71a9921d28 --- /dev/null +++ b/src/tools/rustfmt/tests/target/markdown-comment.rs @@ -0,0 +1,15 @@ +// Preserve two trailing whitespaces in doc comment, +// but trim any whitespaces in normal comment. + +//! hello world +//! hello world + +/// hello world +/// hello world +/// hello world +fn foo() { + // hello world + // hello world + let x = 3; + println!("x = {}", x); +} diff --git a/src/tools/rustfmt/tests/target/match-block-trailing-comma.rs b/src/tools/rustfmt/tests/target/match-block-trailing-comma.rs new file mode 100644 index 0000000000..44d1f289f8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/match-block-trailing-comma.rs @@ -0,0 +1,16 @@ +// rustfmt-match_block_trailing_comma: true +// Match expressions, no unwrapping of block arms or wrapping of multiline +// expressions. + +fn foo() { + match x { + a => { + "line1"; + "line2" + }, + b => ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + ), + } +} diff --git a/src/tools/rustfmt/tests/target/match-flattening.rs b/src/tools/rustfmt/tests/target/match-flattening.rs new file mode 100644 index 0000000000..f246952a08 --- /dev/null +++ b/src/tools/rustfmt/tests/target/match-flattening.rs @@ -0,0 +1,23 @@ +fn main() { + match option { + None => { + if condition { + true + } else { + false + } + } + } +} + +fn main() { + match option { + None => { + if condition { + true + } else { + false + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/match-nowrap-trailing-comma.rs b/src/tools/rustfmt/tests/target/match-nowrap-trailing-comma.rs new file mode 100644 index 0000000000..19ef214482 --- /dev/null +++ b/src/tools/rustfmt/tests/target/match-nowrap-trailing-comma.rs @@ -0,0 +1,17 @@ +// rustfmt-match_arm_blocks: false +// rustfmt-match_block_trailing_comma: true +// Match expressions, no unwrapping of block arms or wrapping of multiline +// expressions. + +fn foo() { + match x { + a => { + "line1"; + "line2" + }, + b => ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + ), + } +} diff --git a/src/tools/rustfmt/tests/target/match-nowrap.rs b/src/tools/rustfmt/tests/target/match-nowrap.rs new file mode 100644 index 0000000000..9e674b1e2f --- /dev/null +++ b/src/tools/rustfmt/tests/target/match-nowrap.rs @@ -0,0 +1,13 @@ +// rustfmt-match_arm_blocks: false +// Match expressions, no unwrapping of block arms or wrapping of multiline +// expressions. + +fn foo() { + match x { + a => foo(), + b => ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + ), + } +} diff --git a/src/tools/rustfmt/tests/target/match.rs b/src/tools/rustfmt/tests/target/match.rs new file mode 100644 index 0000000000..123c4c55f6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/match.rs @@ -0,0 +1,611 @@ +// rustfmt-normalize_comments: true +// Match expressions. + +fn foo() { + // A match expression. + match x { + // Some comment. + a => foo(), + b if 0 < 42 => foo(), + c => { + // Another comment. + // Comment. + an_expression; + foo() + } + Foo(ref bar) => { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + } + Pattern1 | Pattern2 | Pattern3 => false, + Paternnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn + | Paternnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn => blah, + Patternnnnnnnnnnnnnnnnnnn + | Patternnnnnnnnnnnnnnnnnnn + | Patternnnnnnnnnnnnnnnnnnn + | Patternnnnnnnnnnnnnnnnnnn => meh, + + Patternnnnnnnnnnnnnnnnnnn | Patternnnnnnnnnnnnnnnnnnn if looooooooooooooooooong_guard => { + meh + } + + Patternnnnnnnnnnnnnnnnnnnnnnnnn | Patternnnnnnnnnnnnnnnnnnnnnnnnn + if looooooooooooooooooooooooooooooooooooooooong_guard => + { + meh + } + + // Test that earlier patterns can take the guard space + (aaaa, bbbbb, ccccccc, aaaaa, bbbbbbbb, cccccc, aaaa, bbbbbbbb, cccccc, dddddd) + | Patternnnnnnnnnnnnnnnnnnnnnnnnn + if loooooooooooooooooooooooooooooooooooooooooong_guard => {} + + _ => {} + ast::PathParameters::AngleBracketedParameters(ref data) + if data.lifetimes.len() > 0 || data.types.len() > 0 || data.bindings.len() > 0 => {} + } + + let whatever = match something { + /// DOC COMMENT! + Some(_) => 42, + // Comment on an attribute. + #[an_attribute] + // Comment after an attribute. + None => 0, + #[rustfmt::skip] + Blurb => { } + }; +} + +// Test that a match on an overflow line is laid out properly. +fn main() { + let sub_span = + match xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx { + Some(sub_span) => Some(sub_span), + None => sub_span, + }; +} + +// Test that one-line bodies align. +fn main() { + match r { + Variableeeeeeeeeeeeeeeeee => ( + "variable", + vec!["id", "name", "qualname", "value", "type", "scopeid"], + true, + true, + ), + Enummmmmmmmmmmmmmmmmmmmm => ( + "enum", + vec!["id", "qualname", "scopeid", "value"], + true, + true, + ), + Variantttttttttttttttttttttttt => ( + "variant", + vec!["id", "name", "qualname", "type", "value", "scopeid"], + true, + true, + ), + }; + + match x { + y => { /*Block with comment. Preserve me.*/ } + z => { + stmt(); + } + } +} + +fn matches() { + match 1 { + -1 => 10, + 1 => 1, // foo + 2 => 2, + // bar + 3 => 3, + _ => 0, // baz + } +} + +fn match_skip() { + let _ = match Some(1) { + #[rustfmt::skip] + Some( n ) => n, + None => 1, + }; +} + +fn issue339() { + match a { + b => {} + c => {} + d => {} + e => {} + // collapsing here is safe + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff => {} + // collapsing here exceeds line length + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffg => { + } + h => { // comment above block + } + i => {} // comment below block + j => { + // comment inside block + } + j2 => { + // comments inside... + } // ... and after + // TODO uncomment when vertical whitespace is handled better + // k => { + // + // // comment with WS above + // } + // l => { + // // comment with ws below + // + // } + m => {} + n => {} + o => {} + p => { // Don't collapse me + } + q => {} + r => {} + s => 0, // s comment + // t comment + t => 1, + u => 2, + v => {} /* funky block + * comment */ + /* final comment */ + } +} + +fn issue355() { + match mac { + a => println!("a", b), + b => vec![1, 2], + c => vec![3; 4], + d => { + println!("a", b) + } + e => { + vec![1, 2] + } + f => { + vec![3; 4] + } + h => println!("a", b), // h comment + i => vec![1, 2], // i comment + j => vec![3; 4], // j comment + // k comment + k => println!("a", b), + // l comment + l => vec![1, 2], + // m comment + m => vec![3; 4], + // Rewrite splits macro + nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn => { + println!("a", b) + } + // Rewrite splits macro + oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo => { + vec![1, 2] + } + // Macro support fails to recognise this macro as splittable + // We push the whole expr to a new line, TODO split this macro as well + pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp => { + vec![3; 4] + } + // q, r and s: Rewrite splits match arm + qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq => { + println!("a", b) + } + rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr => { + vec![1, 2] + } + ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss => { + vec![3; 4] + } + // Funky bracketing styles + t => println! {"a", b}, + u => vec![1, 2], + v => vec![3; 4], + w => println!["a", b], + x => vec![1, 2], + y => vec![3; 4], + // Brackets with comments + tc => println! {"a", b}, // comment + uc => vec![1, 2], // comment + vc => vec![3; 4], // comment + wc => println!["a", b], // comment + xc => vec![1, 2], // comment + yc => vec![3; 4], // comment + yd => looooooooooooooooooooooooooooooooooooooooooooooooooooooooong_func( + aaaaaaaaaa, bbbbbbbbbb, cccccccccc, dddddddddd, + ), + } +} + +fn issue280() { + { + match x { + CompressionMode::DiscardNewline | CompressionMode::CompressWhitespaceNewline => { + ch == '\n' + } + ast::ItemConst(ref typ, ref expr) => { + self.process_static_or_const_item(item, &typ, &expr) + } + } + } +} + +fn issue383() { + match resolution.last_private { + LastImport { .. } => false, + _ => true, + }; +} + +fn issue507() { + match 1 { + 1 => unsafe { std::intrinsics::abort() }, + _ => (), + } +} + +fn issue508() { + match s.type_id() { + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLCanvasElement, + ))) => true, + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLObjectElement, + ))) => s.has_object_data(), + Some(NodeTypeId::Element(_)) => false, + } +} + +fn issue496() { + { + { + { + match def { + def::DefConst(def_id) | def::DefAssociatedConst(def_id) => { + match const_eval::lookup_const_by_id(cx.tcx, def_id, Some(self.pat.id)) { + Some(const_expr) => x, + } + } + } + } + } + } +} + +fn issue494() { + { + match stmt.node { + hir::StmtExpr(ref expr, id) | hir::StmtSemi(ref expr, id) => { + result.push(StmtRef::Mirror(Box::new(Stmt { + span: stmt.span, + kind: StmtKind::Expr { + scope: cx.tcx.region_maps.node_extent(id), + expr: expr.to_ref(), + }, + }))) + } + } + } +} + +fn issue386() { + match foo { + BiEq | BiLt | BiLe | BiNe | BiGt | BiGe => true, + BiAnd | BiOr | BiAdd | BiSub | BiMul | BiDiv | BiRem | BiBitXor | BiBitAnd | BiBitOr + | BiShl | BiShr => false, + } +} + +fn guards() { + match foo { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + if foooooooooooooo && barrrrrrrrrrrr => {} + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + if foooooooooooooo && barrrrrrrrrrrr => {} + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + if fooooooooooooooooooooo + && (bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + || cccccccccccccccccccccccccccccccccccccccc) => {} + } +} + +fn issue1371() { + Some(match type_ { + sfEvtClosed => Closed, + sfEvtResized => { + let e = unsafe { *event.size.as_ref() }; + + Resized { + width: e.width, + height: e.height, + } + } + sfEvtLostFocus => LostFocus, + sfEvtGainedFocus => GainedFocus, + sfEvtTextEntered => TextEntered { + unicode: unsafe { + ::std::char::from_u32((*event.text.as_ref()).unicode) + .expect("Invalid unicode encountered on TextEntered event") + }, + }, + sfEvtKeyPressed => { + let e = unsafe { event.key.as_ref() }; + + KeyPressed { + code: unsafe { ::std::mem::transmute(e.code) }, + alt: e.alt.to_bool(), + ctrl: e.control.to_bool(), + shift: e.shift.to_bool(), + system: e.system.to_bool(), + } + } + sfEvtKeyReleased => { + let e = unsafe { event.key.as_ref() }; + + KeyReleased { + code: unsafe { ::std::mem::transmute(e.code) }, + alt: e.alt.to_bool(), + ctrl: e.control.to_bool(), + shift: e.shift.to_bool(), + system: e.system.to_bool(), + } + } + }) +} + +fn issue1395() { + let bar = Some(true); + let foo = Some(true); + let mut x = false; + bar.and_then(|_| match foo { + None => None, + Some(b) => { + x = true; + Some(b) + } + }); +} + +fn issue1456() { + Ok(Recording { + artists: match reader.evaluate(".//mb:recording/mb:artist-credit/mb:name-credit")? { + Nodeset(nodeset) => { + let res: Result, ReadError> = nodeset + .iter() + .map(|node| { + XPathNodeReader::new(node, &context).and_then(|r| ArtistRef::from_xml(&r)) + }) + .collect(); + res? + } + _ => Vec::new(), + }, + }) +} + +fn issue1460() { + let _ = match foo { + REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT => { + "internal_spec_insert_internal_spec_insert_internal_spec_insert" + } + _ => "reorder_something", + }; +} + +fn issue525() { + foobar( + f, + "{}", + match *self { + TaskState::Started => "started", + TaskState::Success => "success", + TaskState::Failed => "failed", + }, + ); +} + +// #1838, #1839 +fn match_with_near_max_width() { + let (this_line_uses_99_characters_and_is_formatted_properly, x012345) = match some_expression { + _ => unimplemented!(), + }; + + let (should_be_formatted_like_the_line_above_using_100_characters, x0) = match some_expression { + _ => unimplemented!(), + }; + + let (should_put_the_brace_on_the_next_line_using_101_characters, x0000) = match some_expression + { + _ => unimplemented!(), + }; + match m { + Variant::Tag + | Variant::Tag2 + | Variant::Tag3 + | Variant::Tag4 + | Variant::Tag5 + | Variant::Tag6 => {} + } +} + +fn match_with_trailing_spaces() { + match x { + #![allow(simple_match)] + Some(..) => 0, + None => 1, + } +} + +fn issue_2099() { + let a = match x {}; + let b = match x {}; + + match x {} +} + +// #2021 +impl<'tcx> Const<'tcx> { + pub fn from_constval<'a>() -> Const<'tcx> { + let val = match *cv { + ConstVal::Variant(_) | ConstVal::Aggregate(..) | ConstVal::Unevaluated(..) => bug!( + "MIR must not use `{:?}` (aggregates are expanded to MIR rvalues)", + cv + ), + }; + } +} + +// #2151 +fn issue_2151() { + match either { + x => {} + y => (), + } +} + +// #2152 +fn issue_2152() { + match m { + "aaaaaaaaaaaaa" | "bbbbbbbbbbbbb" | "cccccccccccccccccccccccccccccccccccccccccccc" + if true => {} + "bind" | "writev" | "readv" | "sendmsg" | "recvmsg" if android && (aarch64 || x86_64) => { + true + } + } +} + +// #2376 +// Preserve block around expressions with condition. +fn issue_2376() { + let mut x = None; + match x { + Some(0) => { + for i in 1..11 { + x = Some(i); + } + } + Some(ref mut y) => { + while *y < 10 { + *y += 1; + } + } + None => { + while let None = x { + x = Some(10); + } + } + } +} + +// #2621 +// Strip leading `|` in match arm patterns +fn issue_2621() { + let x = Foo::A; + match x { + Foo::A => println!("No vert single condition"), + Foo::B | Foo::C => println!("Center vert two conditions"), + Foo::D => println!("Preceding vert single condition"), + Foo::E | Foo::F => println!("Preceding vert over two lines"), + Foo::G | Foo::H => println!("Trailing vert over two lines"), + // Comment on its own line + Foo::I => println!("With comment"), // Comment after line + } +} + +fn issue_2377() { + match tok { + Tok::Not + | Tok::BNot + | Tok::Plus + | Tok::Minus + | Tok::PlusPlus + | Tok::MinusMinus + | Tok::Void + | Tok::Delete + if prec <= 16 => + { + // code here... + } + Tok::TypeOf if prec <= 16 => {} + } +} + +// #3040 +fn issue_3040() { + { + match foo { + DevtoolScriptControlMsg::WantsLiveNotifications(id, to_send) => { + match documents.find_window(id) { + Some(window) => { + devtools::handle_wants_live_notifications(window.upcast(), to_send) + } + None => return warn!("Message sent to closed pipeline {}.", id), + } + } + } + } +} + +// #3030 +fn issue_3030() { + match input.trim().parse::() { + Ok(val) + if !( + // A valid number is the same as what rust considers to be valid, + // except for +1., NaN, and Infinity. + val.is_infinite() || val.is_nan() || input.ends_with(".") || input.starts_with("+") + ) => {} + } +} + +fn issue_3005() { + match *token { + Token::Dimension { + value, ref unit, .. + } if num_context.is_ok(context.parsing_mode, value) => { + return NoCalcLength::parse_dimension(context, value, unit) + .map(LengthOrPercentage::Length) + .map_err(|()| location.new_unexpected_token_error(token.clone())); + } + } +} + +// #3774 +fn issue_3774() { + { + { + { + match foo { + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => unreachab(), + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => unreacha!(), + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => { + unreachabl() + } + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => { + unreachae!() + } + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => { + unreachable() + } + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => { + unreachable!() + } + Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => { + rrunreachable!() + } + } + } + } + } +} diff --git a/src/tools/rustfmt/tests/target/match_overflow_expr.rs b/src/tools/rustfmt/tests/target/match_overflow_expr.rs new file mode 100644 index 0000000000..b817879d1f --- /dev/null +++ b/src/tools/rustfmt/tests/target/match_overflow_expr.rs @@ -0,0 +1,50 @@ +// rustfmt-overflow_delimited_expr: true + +fn main() { + println!("Foobar: {}", match "input" { + "a" => "", + "b" => "", + "c" => "", + "d" => "", + "e" => "", + "f" => "", + "g" => "", + "h" => "", + "i" => "", + "j" => "", + "k" => "", + "l" => "", + "m" => "", + "n" => "", + "o" => "", + "p" => "", + "q" => "", + "r" => "Rust", + }); +} + +fn main() { + println!( + "Very Long Input String Which Makes It Impossible To Fit On The Same Line: {}", + match "input" { + "a" => "", + "b" => "", + "c" => "", + "d" => "", + "e" => "", + "f" => "", + "g" => "", + "h" => "", + "i" => "", + "j" => "", + "k" => "", + "l" => "", + "m" => "", + "n" => "", + "o" => "", + "p" => "", + "q" => "", + "r" => "Rust", + } + ); +} diff --git a/src/tools/rustfmt/tests/target/max-line-length-in-chars.rs b/src/tools/rustfmt/tests/target/max-line-length-in-chars.rs new file mode 100644 index 0000000000..d49fbb7e30 --- /dev/null +++ b/src/tools/rustfmt/tests/target/max-line-length-in-chars.rs @@ -0,0 +1,4 @@ +// rustfmt-max_width: 25 + +// абвгдеёжзийклмнопрст +fn main() {} diff --git a/src/tools/rustfmt/tests/target/merge_imports_true_compat.rs b/src/tools/rustfmt/tests/target/merge_imports_true_compat.rs new file mode 100644 index 0000000000..46cd0a3b8a --- /dev/null +++ b/src/tools/rustfmt/tests/target/merge_imports_true_compat.rs @@ -0,0 +1,3 @@ +// rustfmt-merge_imports: true + +use a::{b, c}; diff --git a/src/tools/rustfmt/tests/target/mod-1.rs b/src/tools/rustfmt/tests/target/mod-1.rs new file mode 100644 index 0000000000..4118d123dd --- /dev/null +++ b/src/tools/rustfmt/tests/target/mod-1.rs @@ -0,0 +1,37 @@ +// Deeply indented modules. + +mod foo { + mod bar { + mod baz {} + } +} + +mod foo { + mod bar { + mod baz { + fn foo() { + bar() + } + } + } + + mod qux {} +} + +mod boxed { + pub use std::boxed::{Box, HEAP}; +} + +pub mod x { + pub fn freopen( + filename: *const c_char, + mode: *const c_char, + mode2: *const c_char, + mode3: *const c_char, + file: *mut FILE, + ) -> *mut FILE { + } +} + +mod y { // sup boooooiiii +} diff --git a/src/tools/rustfmt/tests/target/mod-2.rs b/src/tools/rustfmt/tests/target/mod-2.rs new file mode 100644 index 0000000000..1a093bd520 --- /dev/null +++ b/src/tools/rustfmt/tests/target/mod-2.rs @@ -0,0 +1,5 @@ +// Some nested mods + +#[cfg(test)] +mod nestedmod; +pub mod no_new_line_beginning; diff --git a/src/tools/rustfmt/tests/target/mod_skip_child.rs b/src/tools/rustfmt/tests/target/mod_skip_child.rs new file mode 100644 index 0000000000..d48c4a37e8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/mod_skip_child.rs @@ -0,0 +1,2 @@ +// rustfmt-skip_children: true +mod nested_skipped; diff --git a/src/tools/rustfmt/tests/target/mulit-file.rs b/src/tools/rustfmt/tests/target/mulit-file.rs new file mode 100644 index 0000000000..1f829b36f3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/mulit-file.rs @@ -0,0 +1,10 @@ +// Tests that where a single file is referred to in multiple places, we don't +// crash. + +#[cfg(all(foo))] +#[path = "closure.rs"] +pub mod imp; + +#[cfg(all(bar))] +#[path = "closure.rs"] +pub mod imp; diff --git a/src/tools/rustfmt/tests/target/multiline_string_in_macro_def.rs b/src/tools/rustfmt/tests/target/multiline_string_in_macro_def.rs new file mode 100644 index 0000000000..dafc738f8d --- /dev/null +++ b/src/tools/rustfmt/tests/target/multiline_string_in_macro_def.rs @@ -0,0 +1,14 @@ +macro_rules! assert_approx_eq { + ($a:expr, $b:expr, $eps:expr) => {{ + let (a, b) = (&$a, &$b); + assert!( + (*a - *b).abs() < $eps, + "assertion failed: `(left !== right)` \ + (left: `{:?}`, right: `{:?}`, expect diff: `{:?}`, real diff: `{:?}`)", + *a, + *b, + $eps, + (*a - *b).abs() + ); + }}; +} diff --git a/src/tools/rustfmt/tests/target/multiple.rs b/src/tools/rustfmt/tests/target/multiple.rs new file mode 100644 index 0000000000..ee6ef220c4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/multiple.rs @@ -0,0 +1,180 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-format_strings: true +// Test of lots of random stuff. +// FIXME split this into multiple, self-contained tests. + +#[attr1] +extern crate foo; +#[attr2] +#[attr3] +extern crate foo; +#[attr1] +extern crate foo; +#[attr2] +#[attr3] +extern crate foo; + +use std::cell::*; +use std::{ + self, any, ascii, borrow, borrow, borrow, borrow, borrow, borrow, borrow, borrow, borrow, + borrow, borrow, boxed, boxed, boxed, boxed, boxed, boxed, boxed, boxed, boxed, boxed, char, + char, char, char, char, char, char, char, char, char, +}; + +mod doc; +mod other; + +// sfdgfffffffffffffffffffffffffffffffffffffffffffffffffffffff +// ffffffffffffffffffffffffffffffffffffffffff + +fn foo(a: isize, b: u32 /* blah blah */, c: f64) {} + +fn foo() -> Box +where + 'a: 'b, + for<'a> D<'b>: 'a, +{ + hello!() +} + +fn baz< + 'a: 'b, // comment on 'a + T: SomsssssssssssssssssssssssssssssssssssssssssssssssssssssseType, // comment on T +>( + a: A, + b: B, // comment on b + c: C, +) -> Bob { + #[attr1] + extern crate foo; + #[attr2] + #[attr3] + extern crate foo; + #[attr1] + extern crate foo; + #[attr2] + #[attr3] + extern crate foo; +} + +#[rustfmt::skip] +fn qux(a: dadsfa, // Comment 1 + b: sdfasdfa, // Comment 2 + c: dsfdsafa) // Comment 3 +{ + +} + +/// Blah blah blah. +impl Bar { + fn foo( + &mut self, + a: sdfsdfcccccccccccccccccccccccccccccccccccccccccccccccccc, // comment on a + b: sdfasdfsdfasfs, // closing comment + ) -> isize { + } + + /// Blah blah blah. + pub fn f2(self) { + (foo, bar) + } + + #[an_attribute] + fn f3(self) -> Dog {} +} + +/// The `nodes` and `edges` method each return instantiations of +/// `Cow<[T]>` to leave implementers the freedom to create + +/// entirely new vectors or to pass back slices into internally owned +/// vectors. +pub trait GraphWalk<'a, N, E> { + /// Returns all the nodes in this graph. + fn nodes(&'a self) -> Nodes<'a, N>; + /// Returns all of the edges in this graph. + fn edges(&'a self) -> Edges<'a, E>; + /// The source node for `edge`. + fn source(&'a self, edge: &E) -> N; + /// The target node for `edge`. + fn target(&'a self, edge: &E) -> N; +} + +/// A Doc comment +#[AnAttribute] +pub struct Foo { + #[rustfmt::skip] + f : SomeType, // Comment beside a field + f: SomeType, // Comment beside a field + // Comment on a field + g: SomeOtherType, + /// A doc comment on a field + h: AThirdType, +} + +struct Bar; + +// With a where-clause and generics. +pub struct Foo<'a, Y: Baz> +where + X: Whatever, +{ + f: SomeType, // Comment beside a field +} + +fn foo(ann: &'a (PpAnn + 'a)) {} + +fn main() { + for i in 0i32..4 { + println!("{}", i); + } + + while true { + hello(); + } + + let rc = Cell::new( + 42usize, + 42usize, + Cell::new( + 42usize, + remaining_widthremaining_widthremaining_widthremaining_width, + ), + 42usize, + ); + let rc = RefCell::new(42usize, remaining_width, remaining_width); // a comment + let x = "Hello!!!!!!!!! abcd abcd abcd abcd abcd abcd\n abcd abcd abcd abcd abcd abcd abcd \ + abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd \ + abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd \ + abcd abcd"; + let s = expand(a, b); +} + +fn deconstruct() -> ( + SocketAddr, + Method, + Headers, + RequestUri, + HttpVersion, + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +) { +} + +fn deconstruct( + foo: Bar, +) -> ( + SocketAddr, + Method, + Headers, + RequestUri, + HttpVersion, + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +) { +} + +#[rustfmt::skip] +mod a{ +fn foo(x: T) { + let x: T = dfasdf; +} +} diff --git a/src/tools/rustfmt/tests/target/negative-impl.rs b/src/tools/rustfmt/tests/target/negative-impl.rs new file mode 100644 index 0000000000..16ce7e26a9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/negative-impl.rs @@ -0,0 +1,14 @@ +impl !Display for JoinHandle {} + +impl !Box {} + +impl !std::fmt::Display + for JoinHandle +{ +} + +impl + !JoinHandle + std::marker::Send + std::marker::Sync + 'static> + + 'static +{ +} diff --git a/src/tools/rustfmt/tests/target/nested-if-else.rs b/src/tools/rustfmt/tests/target/nested-if-else.rs new file mode 100644 index 0000000000..9a54789ddc --- /dev/null +++ b/src/tools/rustfmt/tests/target/nested-if-else.rs @@ -0,0 +1,11 @@ +fn issue1518() { + Some(Object { + field: if a { + a_thing + } else if b { + b_thing + } else { + c_thing + }, + }) +} diff --git a/src/tools/rustfmt/tests/target/nested-visual-block.rs b/src/tools/rustfmt/tests/target/nested-visual-block.rs new file mode 100644 index 0000000000..fe7190d0ab --- /dev/null +++ b/src/tools/rustfmt/tests/target/nested-visual-block.rs @@ -0,0 +1,60 @@ +fn main() { + // #1078 + let items = itemize_list( + context.source_map, + field_iter, + "}", + |item| match *item { + StructLitField::Regular(ref field) => field.span.lo(), + StructLitField::Base(ref expr) => { + let last_field_hi = fields.last().map_or(span.lo(), |field| field.span.hi()); + let snippet = context.snippet(mk_sp(last_field_hi, expr.span.lo())); + let pos = snippet.find_uncommented("..").unwrap(); + last_field_hi + BytePos(pos as u32) + } + }, + |item| match *item { + StructLitField::Regular(ref field) => field.span.hi(), + StructLitField::Base(ref expr) => expr.span.hi(), + }, + |item| { + match *item { + StructLitField::Regular(ref field) => rewrite_field( + inner_context, + &field, + &Constraints::new(v_budget.checked_sub(1).unwrap_or(0), indent), + ), + StructLitField::Base(ref expr) => { + // 2 = .. + expr.rewrite( + inner_context, + &Constraints::new(try_opt!(v_budget.checked_sub(2)), indent + 2), + ) + .map(|s| format!("..{}", s)) + } + } + }, + context.source_map.span_after(span, "{"), + span.hi(), + ); + + // #1580 + self.0.pool.execute(move || { + let _timer = segments.0.rotate_timer.time(); + if let Err(e) = segments.rotate_async(wal) { + error!("error compacting segment storage WAL", unsafe { error: e.display() }); + } + }); + + // #1581 + bootstrap.checks.register("PERSISTED_LOCATIONS", move || { + if locations2.0.inner_mut.lock().poisoned { + Check::new( + State::Error, + "Persisted location storage is poisoned due to a write failure", + ) + } else { + Check::new(State::Healthy, "Persisted location storage is healthy") + } + }); +} diff --git a/src/tools/rustfmt/tests/target/nested_skipped/mod.rs b/src/tools/rustfmt/tests/target/nested_skipped/mod.rs new file mode 100644 index 0000000000..0ab6f081e4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/nested_skipped/mod.rs @@ -0,0 +1,3 @@ +fn ugly() { + 92; +} diff --git a/src/tools/rustfmt/tests/target/nestedmod/mod.rs b/src/tools/rustfmt/tests/target/nestedmod/mod.rs new file mode 100644 index 0000000000..1df4629318 --- /dev/null +++ b/src/tools/rustfmt/tests/target/nestedmod/mod.rs @@ -0,0 +1,12 @@ +mod mod2a; +mod mod2b; + +mod mymod1 { + use mod2a::{Bar, Foo}; + mod mod3a; +} + +#[path = "mod2c.rs"] +mod mymod2; + +mod submod2; diff --git a/src/tools/rustfmt/tests/target/nestedmod/mod2a.rs b/src/tools/rustfmt/tests/target/nestedmod/mod2a.rs new file mode 100644 index 0000000000..5df457a831 --- /dev/null +++ b/src/tools/rustfmt/tests/target/nestedmod/mod2a.rs @@ -0,0 +1,4 @@ +// This is an empty file containing only +// comments + +// ................... diff --git a/src/tools/rustfmt/tests/target/nestedmod/mod2b.rs b/src/tools/rustfmt/tests/target/nestedmod/mod2b.rs new file mode 100644 index 0000000000..9b6ea844e6 --- /dev/null +++ b/src/tools/rustfmt/tests/target/nestedmod/mod2b.rs @@ -0,0 +1,2 @@ +#[path = "mod2a.rs"] +mod c; diff --git a/src/tools/rustfmt/tests/target/nestedmod/mod2c.rs b/src/tools/rustfmt/tests/target/nestedmod/mod2c.rs new file mode 100644 index 0000000000..7db4572e77 --- /dev/null +++ b/src/tools/rustfmt/tests/target/nestedmod/mod2c.rs @@ -0,0 +1,3 @@ +// A standard mod + +fn a() {} diff --git a/src/tools/rustfmt/tests/target/nestedmod/mymod1/mod3a.rs b/src/tools/rustfmt/tests/target/nestedmod/mymod1/mod3a.rs new file mode 100644 index 0000000000..ae09d8ddac --- /dev/null +++ b/src/tools/rustfmt/tests/target/nestedmod/mymod1/mod3a.rs @@ -0,0 +1,2 @@ +// Another mod +fn a() {} diff --git a/src/tools/rustfmt/tests/target/nestedmod/submod2/a.rs b/src/tools/rustfmt/tests/target/nestedmod/submod2/a.rs new file mode 100644 index 0000000000..120b17145e --- /dev/null +++ b/src/tools/rustfmt/tests/target/nestedmod/submod2/a.rs @@ -0,0 +1,6 @@ +// Yet Another mod +// Nested + +use c::a; + +fn foo() {} diff --git a/src/tools/rustfmt/tests/target/nestedmod/submod2/mod.rs b/src/tools/rustfmt/tests/target/nestedmod/submod2/mod.rs new file mode 100644 index 0000000000..52f8be9102 --- /dev/null +++ b/src/tools/rustfmt/tests/target/nestedmod/submod2/mod.rs @@ -0,0 +1,5 @@ +// Another mod + +mod a; + +use a::a; diff --git a/src/tools/rustfmt/tests/target/no_arg_with_commnet.rs b/src/tools/rustfmt/tests/target/no_arg_with_commnet.rs new file mode 100644 index 0000000000..69f61b60f2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/no_arg_with_commnet.rs @@ -0,0 +1 @@ +fn foo(/* cooment */) {} diff --git a/src/tools/rustfmt/tests/target/no_new_line_beginning.rs b/src/tools/rustfmt/tests/target/no_new_line_beginning.rs new file mode 100644 index 0000000000..f328e4d9d0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/no_new_line_beginning.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/tools/rustfmt/tests/target/normalize_doc_attributes_should_not_imply_format_doc_comments.rs b/src/tools/rustfmt/tests/target/normalize_doc_attributes_should_not_imply_format_doc_comments.rs new file mode 100644 index 0000000000..562d9565e9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/normalize_doc_attributes_should_not_imply_format_doc_comments.rs @@ -0,0 +1,15 @@ +// rustfmt-normalize_doc_attributes: true + +/// Foo +/// +/// # Example +/// ``` +/// # #![cfg_attr(not(dox), feature(cfg_target_feature, target_feature, stdsimd))] +/// # #![cfg_attr(not(dox), no_std)] +/// fn foo() { } +/// ``` +/// +fn foo() {} + +///Bar documents +fn bar() {} diff --git a/src/tools/rustfmt/tests/target/normalize_multiline_doc_attribute.rs b/src/tools/rustfmt/tests/target/normalize_multiline_doc_attribute.rs new file mode 100644 index 0000000000..890c9bb20c --- /dev/null +++ b/src/tools/rustfmt/tests/target/normalize_multiline_doc_attribute.rs @@ -0,0 +1,12 @@ +// rustfmt-unstable: true +// rustfmt-normalize_doc_attributes: true + +///This comment +///is split +///on multiple lines +fn foo() {} + +/// B1 +/// +/// A1 +fn bar() {} diff --git a/src/tools/rustfmt/tests/target/obsolete_in_place.rs b/src/tools/rustfmt/tests/target/obsolete_in_place.rs new file mode 100644 index 0000000000..3f364c1aec --- /dev/null +++ b/src/tools/rustfmt/tests/target/obsolete_in_place.rs @@ -0,0 +1,9 @@ +// #2953 + +macro_rules! demo { + ($a:ident <- $b:expr) => {}; +} + +fn main() { + demo!(i <- 0); +} diff --git a/src/tools/rustfmt/tests/target/one_line_if_v1.rs b/src/tools/rustfmt/tests/target/one_line_if_v1.rs new file mode 100644 index 0000000000..b3c6c4cbeb --- /dev/null +++ b/src/tools/rustfmt/tests/target/one_line_if_v1.rs @@ -0,0 +1,46 @@ +// rustfmt-version: One + +fn plain_if(x: bool) -> u8 { + if x { + 0 + } else { + 1 + } +} + +fn paren_if(x: bool) -> u8 { + (if x { 0 } else { 1 }) +} + +fn let_if(x: bool) -> u8 { + let x = if x { foo() } else { bar() }; + x +} + +fn return_if(x: bool) -> u8 { + return if x { 0 } else { 1 }; +} + +fn multi_if() { + use std::io; + if x { + foo() + } else { + bar() + } + if x { + foo() + } else { + bar() + } +} + +fn middle_if() { + use std::io; + if x { + foo() + } else { + bar() + } + let x = 1; +} diff --git a/src/tools/rustfmt/tests/target/one_line_if_v2.rs b/src/tools/rustfmt/tests/target/one_line_if_v2.rs new file mode 100644 index 0000000000..81ca4c8b8b --- /dev/null +++ b/src/tools/rustfmt/tests/target/one_line_if_v2.rs @@ -0,0 +1,38 @@ +// rustfmt-version: Two + +fn plain_if(x: bool) -> u8 { + if x { 0 } else { 1 } +} + +fn paren_if(x: bool) -> u8 { + (if x { 0 } else { 1 }) +} + +fn let_if(x: bool) -> u8 { + let x = if x { foo() } else { bar() }; + x +} + +fn return_if(x: bool) -> u8 { + return if x { 0 } else { 1 }; +} + +fn multi_if() { + use std::io; + if x { + foo() + } else { + bar() + } + if x { foo() } else { bar() } +} + +fn middle_if() { + use std::io; + if x { + foo() + } else { + bar() + } + let x = 1; +} diff --git a/src/tools/rustfmt/tests/target/other.rs b/src/tools/rustfmt/tests/target/other.rs new file mode 100644 index 0000000000..dfce84fcdc --- /dev/null +++ b/src/tools/rustfmt/tests/target/other.rs @@ -0,0 +1,5 @@ +// Part of multiple.rs + +fn bob() { + println!("hello other!"); +} diff --git a/src/tools/rustfmt/tests/target/paren.rs b/src/tools/rustfmt/tests/target/paren.rs new file mode 100644 index 0000000000..f7714d85dd --- /dev/null +++ b/src/tools/rustfmt/tests/target/paren.rs @@ -0,0 +1,6 @@ +fn main() { + let x = (1); + let y = (/* comment */(2)); + let z = ((3)/* comment */); + let a = (4/* comment */); +} diff --git a/src/tools/rustfmt/tests/target/path_clarity/foo.rs b/src/tools/rustfmt/tests/target/path_clarity/foo.rs new file mode 100644 index 0000000000..cd247fabfe --- /dev/null +++ b/src/tools/rustfmt/tests/target/path_clarity/foo.rs @@ -0,0 +1,2 @@ +// rustfmt-edition: 2018 +mod bar; diff --git a/src/tools/rustfmt/tests/target/path_clarity/foo/bar.rs b/src/tools/rustfmt/tests/target/path_clarity/foo/bar.rs new file mode 100644 index 0000000000..b18a7d3499 --- /dev/null +++ b/src/tools/rustfmt/tests/target/path_clarity/foo/bar.rs @@ -0,0 +1,3 @@ +pub fn fn_in_bar() { + println!("foo/bar.rs"); +} diff --git a/src/tools/rustfmt/tests/target/paths.rs b/src/tools/rustfmt/tests/target/paths.rs new file mode 100644 index 0000000000..0d2ba797eb --- /dev/null +++ b/src/tools/rustfmt/tests/target/paths.rs @@ -0,0 +1,28 @@ +// rustfmt-normalize_comments: true + +fn main() { + let constellation_chan = + Constellation::::start( + compositor_proxy, + resource_task, + image_cache_task, + font_cache_task, + time_profiler_chan, + mem_profiler_chan, + devtools_chan, + storage_task, + supports_clipboard, + ); + + Quux::< + ParamOne, // Comment 1 + ParamTwo, // Comment 2 + >::some_func(); + + <*mut JSObject>::relocate(entry); + + let x: Foo; + let x: Foo/*::*/; +} + +fn op(foo: Bar, key: &[u8], upd: Fn(Option<&memcache::Item>, Baz) -> Result) -> MapResult {} diff --git a/src/tools/rustfmt/tests/target/pattern-condense-wildcards.rs b/src/tools/rustfmt/tests/target/pattern-condense-wildcards.rs new file mode 100644 index 0000000000..a85a16004a --- /dev/null +++ b/src/tools/rustfmt/tests/target/pattern-condense-wildcards.rs @@ -0,0 +1,12 @@ +// rustfmt-normalize_comments: true +// rustfmt-condense_wildcard_suffixes: true + +fn main() { + match x { + Butt(..) => "hah", + Tup(_) => "nah", + Quad(_, _, x, _) => " also no rewrite", + Quad(x, ..) => "condense me pls", + Weird(x, _, _, /* don't condense before */ ..) => "pls work", + } +} diff --git a/src/tools/rustfmt/tests/target/pattern.rs b/src/tools/rustfmt/tests/target/pattern.rs new file mode 100644 index 0000000000..576018ac62 --- /dev/null +++ b/src/tools/rustfmt/tests/target/pattern.rs @@ -0,0 +1,98 @@ +// rustfmt-normalize_comments: true +#![feature(exclusive_range_pattern)] +use core::u8::MAX; + +fn main() { + let z = match x { + "pat1" => 1, + (ref x, ref mut y /* comment */) => 2, + }; + + if let ::CONST = ident { + do_smth(); + } + + let Some(ref xyz /* comment! */) = opt; + + if let None = opt2 { + panic!("oh noes"); + } + + let foo @ bar(f) = 42; + let a::foo(..) = 42; + let [] = 42; + let [a, b, c] = 42; + let [a, b, c] = 42; + let [a, b, c, d, e, f, g] = 42; + let foo {} = 42; + let foo { .. } = 42; + let foo { x, y: ref foo, .. } = 42; + let foo { + x, + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: ref foo, + .. + } = 42; + let foo { + x, + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: ref foo, + } = 42; + let foo { + x, + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: ref foo, + .. + }; + let foo { + x, + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: ref foo, + }; + + match b"12" { + [0, 1..MAX] => {} + _ => {} + } +} + +impl<'a, 'b> ResolveGeneratedContentFragmentMutator<'a, 'b> { + fn mutate_fragment(&mut self, fragment: &mut Fragment) { + match **info { + GeneratedContentInfo::ContentItem(ContentItem::Counter( + ref counter_name, + counter_style, + )) => {} + } + } +} + +fn issue_1319() { + if let (Event { .. }, ..) = ev_state {} +} + +fn issue_1874() { + if let Some(()) = x { + y + } +} + +fn combine_patterns() { + let x = match y { + Some(Some(Foo { + z: Bar(..), + a: Bar(..), + b: Bar(..), + })) => z, + _ => return, + }; +} + +fn slice_patterns() { + match b"123" { + [0, ..] => {} + [0, foo] => {} + _ => {} + } +} + +fn issue3728() { + let foo = |(c,)| c; + foo((1,)); +} diff --git a/src/tools/rustfmt/tests/target/preserves_carriage_return_for_unix.rs b/src/tools/rustfmt/tests/target/preserves_carriage_return_for_unix.rs new file mode 100644 index 0000000000..e5e0b28659 --- /dev/null +++ b/src/tools/rustfmt/tests/target/preserves_carriage_return_for_unix.rs @@ -0,0 +1,2 @@ +// rustfmt-newline_style: Unix +// Foo Bar diff --git a/src/tools/rustfmt/tests/target/preserves_carriage_return_for_windows.rs b/src/tools/rustfmt/tests/target/preserves_carriage_return_for_windows.rs new file mode 100644 index 0000000000..1085360ee5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/preserves_carriage_return_for_windows.rs @@ -0,0 +1,2 @@ +// rustfmt-newline_style: Windows +// Foo Bar diff --git a/src/tools/rustfmt/tests/target/pub-restricted.rs b/src/tools/rustfmt/tests/target/pub-restricted.rs new file mode 100644 index 0000000000..8cc2ade612 --- /dev/null +++ b/src/tools/rustfmt/tests/target/pub-restricted.rs @@ -0,0 +1,64 @@ +pub(super) enum WriteState { + WriteId { + id: U64Writer, + size: U64Writer, + payload: Option>, + }, + WriteSize { + size: U64Writer, + payload: Option>, + }, + WriteData(Writer), +} + +pub(crate) enum WriteState { + WriteId { + id: U64Writer, + size: U64Writer, + payload: Option>, + }, + WriteSize { + size: U64Writer, + payload: Option>, + }, + WriteData(Writer), +} + +crate enum WriteState { + WriteId { + id: U64Writer, + size: U64Writer, + payload: Option>, + }, + WriteSize { + size: U64Writer, + payload: Option>, + }, + WriteData(Writer), +} + +pub(in global::path::to::some_mod) enum WriteState { + WriteId { + id: U64Writer, + size: U64Writer, + payload: Option>, + }, + WriteSize { + size: U64Writer, + payload: Option>, + }, + WriteData(Writer), +} + +pub(in local::path::to::some_mod) enum WriteState { + WriteId { + id: U64Writer, + size: U64Writer, + payload: Option>, + }, + WriteSize { + size: U64Writer, + payload: Option>, + }, + WriteData(Writer), +} diff --git a/src/tools/rustfmt/tests/target/raw_identifiers.rs b/src/tools/rustfmt/tests/target/raw_identifiers.rs new file mode 100644 index 0000000000..6ab0fdf053 --- /dev/null +++ b/src/tools/rustfmt/tests/target/raw_identifiers.rs @@ -0,0 +1,66 @@ +#![feature(custom_attribute)] +#![feature(raw_identifiers)] +#![feature(extern_types)] +#![allow(invalid_type_param_default)] +#![allow(unused_attributes)] + +use r#foo as r#alias_foo; + +// https://github.com/rust-lang/rustfmt/issues/3837 +pub(crate) static r#break: &'static str = "foo"; + +fn main() { + #[r#attr] + r#foo::r#bar(); + + let r#local = r#Struct { r#field: () }; + r#local.r#field = 1; + r#foo.r#barr(); + let r#async = r#foo(r#local); + r#macro!(); + + if let r#sub_pat @ r#Foo(_) = r#Foo(3) {} + + match r#async { + r#Foo | r#Bar => r#foo(), + } +} + +fn r#bar<'a, r#T>(r#x: &'a r#T) {} + +mod r#foo { + pub fn r#bar() {} +} + +enum r#Foo { + r#Bar {}, +} + +struct r#Struct { + r#field: r#FieldType, +} + +trait r#Trait { + type r#Type; +} + +impl r#Trait for r#Impl { + type r#Type = r#u32; + fn r#xxx(r#fjio: r#u32) {} +} + +extern "C" { + type r#ccc; + static r#static_val: u32; +} + +macro_rules! r#macro { + () => {}; +} + +macro_rules! foo { + ($x:expr) => { + let r#catch = $x + 1; + println!("{}", r#catch); + }; +} diff --git a/src/tools/rustfmt/tests/target/remove_blank_lines.rs b/src/tools/rustfmt/tests/target/remove_blank_lines.rs new file mode 100644 index 0000000000..de74c81ef5 --- /dev/null +++ b/src/tools/rustfmt/tests/target/remove_blank_lines.rs @@ -0,0 +1,28 @@ +fn main() { + let x = 1; + + let y = 2; + + println!("x + y = {}", x + y); +} + +fn foo() { + #![attribute] + + let x = 1; + + // comment +} +// comment after item + +// comment before item +fn bar() { + let x = 1; + // comment after statement + + // comment before statement + let y = 2; + let z = 3; + + println!("x + y + z = {}", x + y + z); +} diff --git a/src/tools/rustfmt/tests/target/reorder-impl-items.rs b/src/tools/rustfmt/tests/target/reorder-impl-items.rs new file mode 100644 index 0000000000..16efff55b0 --- /dev/null +++ b/src/tools/rustfmt/tests/target/reorder-impl-items.rs @@ -0,0 +1,15 @@ +// rustfmt-reorder_impl_items: true + +// The ordering of the following impl items should be idempotent. +impl<'a> Command<'a> { + pub fn send_to(&self, w: &mut io::Write) -> io::Result<()> { + match self { + &Command::Data(ref c) => c.send_to(w), + &Command::Vrfy(ref c) => c.send_to(w), + } + } + + pub fn parse(arg: &[u8]) -> Result { + nom_to_result(command(arg)) + } +} diff --git a/src/tools/rustfmt/tests/target/should_not_format_string_when_format_strings_is_not_set.rs b/src/tools/rustfmt/tests/target/should_not_format_string_when_format_strings_is_not_set.rs new file mode 100644 index 0000000000..efb755d4ae --- /dev/null +++ b/src/tools/rustfmt/tests/target/should_not_format_string_when_format_strings_is_not_set.rs @@ -0,0 +1,16 @@ +// format_strings is false by default. + +println!( + "DirEntry {{ \ + binary_name: {:<64}, \ + context_id: {:>2}, \ + file_size: {:>6}, \ + offset: 0x {:>08X}, \ + actual_crc: 0x{:>08X} \ + }}", + dir_entry.binary_name, + dir_entry.context_id, + dir_entry.file_size, + dir_entry.offset, + dir_entry.actual_crc +); diff --git a/src/tools/rustfmt/tests/target/single-line-if-else.rs b/src/tools/rustfmt/tests/target/single-line-if-else.rs new file mode 100644 index 0000000000..98fd793cba --- /dev/null +++ b/src/tools/rustfmt/tests/target/single-line-if-else.rs @@ -0,0 +1,58 @@ +// Format if-else expressions on a single line, when possible. + +fn main() { + let a = if 1 > 2 { unreachable!() } else { 10 }; + + let a = if x { + 1 + } else if y { + 2 + } else { + 3 + }; + + let b = if cond() { + 5 + } else { + // Brief comment. + 10 + }; + + let c = if cond() { + statement(); + + 5 + } else { + 10 + }; + + let d = if let Some(val) = turbo { + "cool" + } else { + "beans" + }; + + if cond() { + statement(); + } else { + other_statement(); + } + + if true { + do_something() + } + + let x = if veeeeeeeeery_loooooong_condition() { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + } else { + bbbbbbbbbb + }; + + let x = if veeeeeeeeery_loooooong_condition() { + aaaaaaaaaaaaaaaaaaaaaaaaa + } else { + bbbbbbbbbb + }; + + funk(if test() { 1 } else { 2 }, arg2); +} diff --git a/src/tools/rustfmt/tests/target/single-line-macro/v1.rs b/src/tools/rustfmt/tests/target/single-line-macro/v1.rs new file mode 100644 index 0000000000..a3aa631ed4 --- /dev/null +++ b/src/tools/rustfmt/tests/target/single-line-macro/v1.rs @@ -0,0 +1,10 @@ +// rustfmt-version: One + +// #2652 +// Preserve trailing comma inside macro, even if it looks an array. +macro_rules! bar { + ($m:ident) => { + $m!([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,]); + $m!([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z]); + }; +} diff --git a/src/tools/rustfmt/tests/target/single-line-macro/v2.rs b/src/tools/rustfmt/tests/target/single-line-macro/v2.rs new file mode 100644 index 0000000000..9c6bcf33ad --- /dev/null +++ b/src/tools/rustfmt/tests/target/single-line-macro/v2.rs @@ -0,0 +1,14 @@ +// rustfmt-version: Two + +// #2652 +// Preserve trailing comma inside macro, even if it looks an array. +macro_rules! bar { + ($m:ident) => { + $m!([ + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, + ]); + $m!([ + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z + ]); + }; +} diff --git a/src/tools/rustfmt/tests/target/skip.rs b/src/tools/rustfmt/tests/target/skip.rs new file mode 100644 index 0000000000..6c9737a337 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip.rs @@ -0,0 +1,87 @@ +// Test the skip attribute works + +#[rustfmt::skip] +fn foo() { badly; formatted; stuff +; } + +#[rustfmt::skip] +trait Foo +{ +fn foo( +); +} + +impl LateLintPass for UsedUnderscoreBinding { + #[cfg_attr(rustfmt, rustfmt::skip)] + fn check_expr() { // comment + } +} + +fn issue1346() { + #[cfg_attr(rustfmt, rustfmt::skip)] + Box::new(self.inner.call(req).then(move |result| { + match result { + Ok(resp) => Box::new(future::done(Ok(resp))), + Err(e) => { + try_error!(clo_stderr, "{}", e); + Box::new(future::err(e)) + } + } + })) +} + +fn skip_on_statements() { + // Outside block + #[rustfmt::skip] + { + foo; bar; + // junk + } + + { + // Inside block + #![rustfmt::skip] + foo; bar; + // junk + } + + // Semi + #[cfg_attr(rustfmt, rustfmt::skip)] + foo( + 1, 2, 3, 4, + 1, 2, + 1, 2, 3, + ); + + // Local + #[cfg_attr(rustfmt, rustfmt::skip)] + let x = foo( a, b , c); + + // Item + #[cfg_attr(rustfmt, rustfmt::skip)] + use foobar; + + // Mac + #[cfg_attr(rustfmt, rustfmt::skip)] + vec![ + 1, 2, 3, 4, + 1, 2, 3, 4, + 1, 2, 3, 4, + 1, 2, 3, + 1, + 1, 2, + 1, + ]; + + // Expr + #[cfg_attr(rustfmt, rustfmt::skip)] + foo( a, b , c) +} + +// Check that the skip attribute applies to other attributes. +#[rustfmt::skip] +#[cfg +( a , b +)] +fn +main() {} diff --git a/src/tools/rustfmt/tests/target/skip/foo.rs b/src/tools/rustfmt/tests/target/skip/foo.rs new file mode 100644 index 0000000000..776658f8fe --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip/foo.rs @@ -0,0 +1,5 @@ +#![rustfmt::skip] + +fn +foo() +{} diff --git a/src/tools/rustfmt/tests/target/skip/main.rs b/src/tools/rustfmt/tests/target/skip/main.rs new file mode 100644 index 0000000000..2d33bef925 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip/main.rs @@ -0,0 +1,5 @@ +mod foo; + +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/rustfmt/tests/target/skip_mod.rs b/src/tools/rustfmt/tests/target/skip_mod.rs new file mode 100644 index 0000000000..d770ab349f --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_mod.rs @@ -0,0 +1,3 @@ +#![rustfmt::skip] +use a :: b +; diff --git a/src/tools/rustfmt/tests/target/soft-wrapping.rs b/src/tools/rustfmt/tests/target/soft-wrapping.rs new file mode 100644 index 0000000000..5b4c6d9e85 --- /dev/null +++ b/src/tools/rustfmt/tests/target/soft-wrapping.rs @@ -0,0 +1,15 @@ +// rustfmt-wrap_comments: true +// rustfmt-max_width: 80 +// Soft wrapping for comments. + +// #535, soft wrapping for comments +// Compare the lowest `f32` of both inputs for greater than or equal. The +// lowest 32 bits of the result will be `0xffffffff` if `a.extract(0)` is +// ggreater than or equal `b.extract(0)`, or `0` otherwise. The upper 96 bits +// off the result are the upper 96 bits of `a`. + +/// Compares the lowest `f32` of both inputs for greater than or equal. The +/// lowest 32 bits of the result will be `0xffffffff` if `a.extract(0)` is +/// greater than or equal `b.extract(0)`, or `0` otherwise. The upper 96 bits +/// off the result are the upper 96 bits of `a`. +fn foo() {} diff --git a/src/tools/rustfmt/tests/target/space-not-before-newline.rs b/src/tools/rustfmt/tests/target/space-not-before-newline.rs new file mode 100644 index 0000000000..9d75b726aa --- /dev/null +++ b/src/tools/rustfmt/tests/target/space-not-before-newline.rs @@ -0,0 +1,8 @@ +struct Foo { + a: (), + // spaces ^^^ to be removed +} +enum Foo { + Bar, + // spaces ^^^ to be removed +} diff --git a/src/tools/rustfmt/tests/target/spaces-around-ranges.rs b/src/tools/rustfmt/tests/target/spaces-around-ranges.rs new file mode 100644 index 0000000000..b53e5b58b8 --- /dev/null +++ b/src/tools/rustfmt/tests/target/spaces-around-ranges.rs @@ -0,0 +1,15 @@ +// rustfmt-spaces_around_ranges: true + +fn bar(v: &[u8]) {} + +fn foo() { + let a = vec![0; 20]; + for j in 0 ..= 20 { + for i in 0 .. 3 { + bar(a[i .. j]); + bar(a[i ..]); + bar(a[.. j]); + bar(a[..= (j + 1)]); + } + } +} diff --git a/src/tools/rustfmt/tests/target/statements.rs b/src/tools/rustfmt/tests/target/statements.rs new file mode 100644 index 0000000000..c1e7dc464c --- /dev/null +++ b/src/tools/rustfmt/tests/target/statements.rs @@ -0,0 +1,42 @@ +// FIXME(calebcartwright) - Hopefully one day we can +// elide these redundant semis like we do in other contexts. +fn redundant_item_semis() { + impl Foo { + fn get(&self) -> usize { + 5 + } + }; + + impl Bar { + fn get(&self) -> usize { + 5 + } + } /*asdfsf*/ + ; + + impl Baz { + fn get(&self) -> usize { + 5 + } + } /*asdfsf*/ + + // why would someone do this + ; + + impl Qux { + fn get(&self) -> usize { + 5 + } + } + + // why + ; + + impl Lorem { + fn get(&self) -> usize { + 5 + } + } + // oh why + ; +} diff --git a/src/tools/rustfmt/tests/target/static.rs b/src/tools/rustfmt/tests/target/static.rs new file mode 100644 index 0000000000..5daccf3e7f --- /dev/null +++ b/src/tools/rustfmt/tests/target/static.rs @@ -0,0 +1,27 @@ +const FILE_GENERIC_READ: DWORD = + STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE; + +static boolnames: &'static [&'static str] = &[ + "bw", "am", "xsb", "xhp", "xenl", "eo", "gn", "hc", "km", "hs", "in", "db", "da", "mir", + "msgr", "os", "eslok", "xt", "hz", "ul", "xon", "nxon", "mc5i", "chts", "nrrmc", "npc", + "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy", "xvpa", "sam", "cpix", "lpix", "OTbs", + "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr", +]; + +static mut name: SomeType = + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; + +pub static count: u8 = 10; + +pub const test: &Type = &val; + +impl Color { + pub const WHITE: u32 = 10; +} + +// #1391 +pub const XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: NTSTATUS = + 0 as usize; + +pub const XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: + Yyyyyyyyyyyyyyyyyyyyyyyyyyyy = 1; diff --git a/src/tools/rustfmt/tests/target/string-lit-2.rs b/src/tools/rustfmt/tests/target/string-lit-2.rs new file mode 100644 index 0000000000..6b95e25a05 --- /dev/null +++ b/src/tools/rustfmt/tests/target/string-lit-2.rs @@ -0,0 +1,25 @@ +fn main() -> &'static str { + let too_many_lines = "Hello"; + let leave_me = "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss\ + s + jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj"; +} + +fn issue_1237() { + let msg = "eedadn\n\ + drvtee\n\ + eandsr\n\ + raavrd\n\ + atevrs\n\ + tsrnev\n\ + sdttsa\n\ + rasrtv\n\ + nssdts\n\ + ntnada\n\ + svetve\n\ + tesnvt\n\ + vntsnd\n\ + vrdear\n\ + dvrsen\n\ + enarar"; +} diff --git a/src/tools/rustfmt/tests/target/string-lit-custom.rs b/src/tools/rustfmt/tests/target/string-lit-custom.rs new file mode 100644 index 0000000000..89639b8ebd --- /dev/null +++ b/src/tools/rustfmt/tests/target/string-lit-custom.rs @@ -0,0 +1,20 @@ +fn main() { + let expected = "; ModuleID = \'foo\' + +; Function Attrs: nounwind +declare void @llvm.memset.p0i8.i32(i8* nocapture, i8, i32, i32, i1) #0 + +declare i32 @write(i32, i8*, i32) + +declare i32 @putchar(i32) + +declare i32 @getchar() + +define i32 @main() { +entry: + ret i32 0 +} + +attributes #0 = { nounwind } +"; +} diff --git a/src/tools/rustfmt/tests/target/string-lit.rs b/src/tools/rustfmt/tests/target/string-lit.rs new file mode 100644 index 0000000000..2d33061074 --- /dev/null +++ b/src/tools/rustfmt/tests/target/string-lit.rs @@ -0,0 +1,63 @@ +// rustfmt-format_strings: true +// Long string literals + +fn main() -> &'static str { + let str = "AAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAaAA \ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAa"; + let str = "AAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAa"; + let str = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + + let too_many_lines = "Hello"; + + // Make sure we don't break after an escape character. + let odd_length_name = + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; + let even_length_name = + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; + + let really_long_variable_name = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + + let raw_string = r#"Do +not +remove +formatting"#; + + filename.replace(" ", "\\"); + + let xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = + funktion("yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"); + + let unicode = "a̐éö̲\r\n"; + let unicode2 = "Löwe 老虎 Léopard"; + let unicode3 = "中华Việt Nam"; + let unicode4 = "☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃"; + + "stuffin'" +} + +fn issue682() { + let a = "hello \\ o/"; + let b = a.replace("\\ ", "\\"); +} + +fn issue716() { + println!( + "forall x. mult(e(), x) = x /\\ + forall x. mult(x, x) = e()" + ); +} + +fn issue_1282() { + { + match foo { + Permission::AndroidPermissionAccessLocationExtraCommands => { + "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" + } + } + } +} + +// #1987 +#[link_args = "-s NO_FILESYSTEM=1 -s NO_EXIT_RUNTIME=1 -s EXPORTED_RUNTIME_METHODS=[\"_malloc\"] \ + -s NO_DYNAMIC_EXECUTION=1 -s ELIMINATE_DUPLICATE_FUNCTIONS=1 -s EVAL_CTORS=1"] +extern "C" {} diff --git a/src/tools/rustfmt/tests/target/string_punctuation.rs b/src/tools/rustfmt/tests/target/string_punctuation.rs new file mode 100644 index 0000000000..0b8ec1b7f3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/string_punctuation.rs @@ -0,0 +1,24 @@ +// rustfmt-format_strings: true + +fn main() { + println!( + "ThisIsAReallyLongStringWithNoSpaces.It_should_prefer_to_break_onpunctuation:\ + Likethisssssssssssss" + ); + format!("{}__{}__{}ItShouldOnlyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyNoticeSemicolonsPeriodsColonsAndCommasAndResortToMid-CharBreaksAfterPunctuation{}{}",x,y,z,a,b); + println!( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaalhijalfhiigjapdighjapdigjapdighdapighapdighpaidhg;\ + adopgihadoguaadbadgad,qeoihapethae8t0aet8haetadbjtaeg;\ + ooeouthaoeutgadlgajduabgoiuadogabudogubaodugbadgadgadga;adoughaoeugbaouea" + ); + println!( + "sentuhaesnuthaesnutheasunteahusnaethuseantuihaesntdiastnidaetnuhaideuhsenathe。\ + WeShouldSupportNonAsciiPunctuations§\ + ensuhatheasunteahsuneathusneathuasnuhaesnuhaesnuaethusnaetuheasnuth" + ); + println!( + "ThisIsASampleOfCJKString.祇園精舍の鐘の声、諸行無常の響きあり。娑羅双樹の花の色、\ + 盛者必衰の理をあらはす。奢れる人も久しからず、ただ春の夜の夢のごとし。\ + 猛き者もつひにはほろびぬ、ひとへに風の前の塵に同じ。" + ); +} diff --git a/src/tools/rustfmt/tests/target/struct-field-attributes.rs b/src/tools/rustfmt/tests/target/struct-field-attributes.rs new file mode 100644 index 0000000000..0f461b98bc --- /dev/null +++ b/src/tools/rustfmt/tests/target/struct-field-attributes.rs @@ -0,0 +1,62 @@ +// #1535 +#![feature(struct_field_attributes)] + +struct Foo { + bar: u64, + + #[cfg(test)] + qux: u64, +} + +fn do_something() -> Foo { + Foo { + bar: 0, + + #[cfg(test)] + qux: 1, + } +} + +fn main() { + do_something(); +} + +// #1462 +struct Foo { + foo: usize, + #[cfg(feature = "include-bar")] + bar: usize, +} + +fn new_foo() -> Foo { + Foo { + foo: 0, + #[cfg(feature = "include-bar")] + bar: 0, + } +} + +// #2044 +pub enum State { + Closure( + #[cfg_attr( + feature = "serde_derive", + serde(state_with = "::serialization::closure") + )] + GcPtr, + ), +} + +struct Fields( + #[cfg_attr( + feature = "serde_derive", + serde(state_with = "::base::serialization::shared") + )] + Arc>, +); + +// #2309 +pub struct A { + #[doc = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"] + pub foos: Vec, +} diff --git a/src/tools/rustfmt/tests/target/struct_lits.rs b/src/tools/rustfmt/tests/target/struct_lits.rs new file mode 100644 index 0000000000..d3bc364c35 --- /dev/null +++ b/src/tools/rustfmt/tests/target/struct_lits.rs @@ -0,0 +1,190 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// Struct literal expressions. + +fn main() { + let x = Bar; + + // Comment + let y = Foo { a: x }; + + Foo { + a: foo(), // comment + // comment + b: bar(), + ..something + }; + + Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: f(), b: b() }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + a: f(), + b: b(), + }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + // Comment + a: foo(), // Comment + // Comment + b: bar(), // Comment + }; + + Foo { a: Bar, b: f() }; + + Quux { + x: if cond { + bar(); + }, + y: baz(), + }; + + A { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit + // amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. + // Donec et mollis dolor. + first: item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + second: Item, + }; + + Some(Data::MethodCallData(MethodCallData { + span: sub_span.unwrap(), + scope: self.enclosing_scope(id), + ref_id: def_id, + decl_id: Some(decl_id), + })); + + Diagram { + // o This graph demonstrates how + // / \ significant whitespace is + // o o preserved. + // /|\ \ + // o o o o + graph: G, + } +} + +fn matcher() { + TagTerminatedByteMatcher { + matcher: ByteMatcher { + pattern: b" { + memb: T, + } + let foo = Foo:: { memb: 10 }; +} + +fn issue201() { + let s = S { a: 0, ..b }; +} + +fn issue201_2() { + let s = S { a: S2 { ..c }, ..b }; +} + +fn issue278() { + let s = S { + a: 0, + // + b: 0, + }; + let s1 = S { + a: 0, + // foo + // + // bar + b: 0, + }; +} + +fn struct_exprs() { + Foo { a: 1, b: f(2) }; + Foo { + a: 1, + b: f(2), + ..g(3) + }; + LoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongStruct { + ..base + }; + IntrinsicISizesContribution { + content_intrinsic_sizes: IntrinsicISizes { + minimum_inline_size: 0, + }, + }; +} + +fn issue123() { + Foo { a: b, c: d, e: f }; + + Foo { + a: bb, + c: dd, + e: ff, + }; + + Foo { + a: ddddddddddddddddddddd, + b: cccccccccccccccccccccccccccccccccccccc, + }; +} + +fn issue491() { + Foo { + guard: None, + arm: 0, // Comment + }; + + Foo { + arm: 0, // Comment + }; + + Foo { + a: aaaaaaaaaa, + b: bbbbbbbb, + c: cccccccccc, + d: dddddddddd, // a comment + e: eeeeeeeee, + }; +} + +fn issue698() { + Record { + ffffffffffffffffffffffffffields: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + }; + Record { + ffffffffffffffffffffffffffields: + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + } +} + +fn issue835() { + MyStruct {}; + MyStruct { /* a comment */ }; + MyStruct { + // Another comment + }; + MyStruct {} +} + +fn field_init_shorthand() { + MyStruct { x, y, z }; + MyStruct { x, y, z, ..base }; + Foo { + aaaaaaaaaa, + bbbbbbbb, + cccccccccc, + dddddddddd, // a comment + eeeeeeeee, + }; + Record { + ffffffffffffffffffffffffffieldsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + }; +} diff --git a/src/tools/rustfmt/tests/target/struct_lits_multiline.rs b/src/tools/rustfmt/tests/target/struct_lits_multiline.rs new file mode 100644 index 0000000000..b29aafd054 --- /dev/null +++ b/src/tools/rustfmt/tests/target/struct_lits_multiline.rs @@ -0,0 +1,117 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-struct_lit_single_line: false + +// Struct literal expressions. + +fn main() { + let x = Bar; + + // Comment + let y = Foo { + a: x, + }; + + Foo { + a: foo(), // comment + // comment + b: bar(), + ..something + }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + a: foo(), + b: bar(), + }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + a: foo(), + b: bar(), + }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { + // Comment + a: foo(), // Comment + // Comment + b: bar(), // Comment + }; + + Foo { + a: Bar, + b: foo(), + }; + + Quux { + x: if cond { + bar(); + }, + y: baz(), + }; + + A { + // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit + // amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. + // Donec et mollis dolor. + first: item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + second: Item, + }; + + Some(Data::MethodCallData(MethodCallData { + span: sub_span.unwrap(), + scope: self.enclosing_scope(id), + ref_id: def_id, + decl_id: Some(decl_id), + })); + + Diagram { + // o This graph demonstrates how + // / \ significant whitespace is + // o o preserved. + // /|\ \ + // o o o o + graph: G, + } +} + +fn matcher() { + TagTerminatedByteMatcher { + matcher: ByteMatcher { + pattern: b" { + memb: T, + } + let foo = Foo:: { + memb: 10, + }; +} + +fn issue201() { + let s = S { + a: 0, + ..b + }; +} + +fn issue201_2() { + let s = S { + a: S2 { + ..c + }, + ..b + }; +} + +fn issue491() { + Foo { + guard: None, + arm: 0, // Comment + }; +} diff --git a/src/tools/rustfmt/tests/target/struct_lits_visual.rs b/src/tools/rustfmt/tests/target/struct_lits_visual.rs new file mode 100644 index 0000000000..a9627fb90f --- /dev/null +++ b/src/tools/rustfmt/tests/target/struct_lits_visual.rs @@ -0,0 +1,49 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-indent_style: Visual + +// Struct literal expressions. + +fn main() { + let x = Bar; + + // Comment + let y = Foo { a: x }; + + Foo { a: foo(), // comment + // comment + b: bar(), + ..something }; + + Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: f(), b: b() }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { // Comment + a: foo(), /* Comment */ + // Comment + b: bar() /* Comment */ }; + + Foo { a: Bar, b: f() }; + + Quux { x: if cond { + bar(); + }, + y: baz() }; + + Baz { x: yxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + z: zzzzz /* test */ }; + + A { // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit + // amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante + // hendrerit. Donec et mollis dolor. + first: item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + second: Item }; + + Diagram { // o This graph demonstrates how + // / \ significant whitespace is + // o o preserved. + // /|\ \ + // o o o o + graph: G } +} diff --git a/src/tools/rustfmt/tests/target/struct_lits_visual_multiline.rs b/src/tools/rustfmt/tests/target/struct_lits_visual_multiline.rs new file mode 100644 index 0000000000..3f43ef0c98 --- /dev/null +++ b/src/tools/rustfmt/tests/target/struct_lits_visual_multiline.rs @@ -0,0 +1,49 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-indent_style: Visual +// rustfmt-struct_lit_single_line: false + +// Struct literal expressions. + +fn main() { + let x = Bar; + + // Comment + let y = Foo { a: x }; + + Foo { a: foo(), // comment + // comment + b: bar(), + ..something }; + + Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(), + b: bar() }; + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { // Comment + a: foo(), /* Comment */ + // Comment + b: bar() /* Comment */ }; + + Foo { a: Bar, + b: foo() }; + + Quux { x: if cond { + bar(); + }, + y: baz() }; + + A { // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit + // amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante + // hendrerit. Donec et mollis dolor. + first: item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + second: Item }; + + Diagram { // o This graph demonstrates how + // / \ significant whitespace is + // o o preserved. + // /|\ \ + // o o o o + graph: G } +} diff --git a/src/tools/rustfmt/tests/target/struct_tuple_visual.rs b/src/tools/rustfmt/tests/target/struct_tuple_visual.rs new file mode 100644 index 0000000000..f95f3fe4fd --- /dev/null +++ b/src/tools/rustfmt/tests/target/struct_tuple_visual.rs @@ -0,0 +1,36 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true +// rustfmt-indent_style: Visual +fn foo() { + Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(f(), b()); + + Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(// Comment + foo(), /* Comment */ + // Comment + bar() /* Comment */); + + Foo(Bar, f()); + + Quux(if cond { + bar(); + }, + baz()); + + Baz(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + zzzzz /* test */); + + A(// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit + // amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante + // hendrerit. Donec et mollis dolor. + item(), + // Praesent et diam eget libero egestas mattis sit amet vitae augue. + // Nam tincidunt congue enim, ut porta lorem lacinia consectetur. + Item); + + Diagram(// o This graph demonstrates how + // / \ significant whitespace is + // o o preserved. + // /|\ \ + // o o o o + G) +} diff --git a/src/tools/rustfmt/tests/target/structs.rs b/src/tools/rustfmt/tests/target/structs.rs new file mode 100644 index 0000000000..4948e37a5a --- /dev/null +++ b/src/tools/rustfmt/tests/target/structs.rs @@ -0,0 +1,358 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true + +/// A Doc comment +#[AnAttribute] +pub struct Foo { + #[rustfmt::skip] + f : SomeType, // Comment beside a field + f: SomeType, // Comment beside a field + // Comment on a field + #[AnAttribute] + g: SomeOtherType, + /// A doc comment on a field + h: AThirdType, + pub i: TypeForPublicField, +} + +// Destructuring +fn foo() { + S { x: 5, .. }; + Struct { .. } = Struct { a: 1, b: 4 }; + Struct { a, .. } = Struct { a: 1, b: 2, c: 3 }; + TupleStruct(a, .., b) = TupleStruct(1, 2); + TupleStruct(..) = TupleStruct(3, 4); + TupleStruct( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + .., + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + ) = TupleStruct(1, 2); +} + +// #1095 +struct S { + t: T, +} + +// #1029 +pub struct Foo { + #[doc(hidden)] + // This will NOT get deleted! + bar: String, // hi +} + +// #1029 +struct X { + // `x` is an important number. + #[allow(unused)] // TODO: use + x: u32, +} + +// #410 +#[allow(missing_docs)] +pub struct Writebatch { + #[allow(dead_code)] // only used for holding the internal pointer + writebatch: RawWritebatch, + marker: PhantomData, +} + +struct Bar; + +struct NewType(Type, OtherType); + +struct NewInt( + pub i32, + SomeType, // inline comment + T, // sup +); + +struct Qux< + 'a, + N: Clone + 'a, + E: Clone + 'a, + G: Labeller<'a, N, E> + GraphWalk<'a, N, E>, + W: Write + Copy, +>( + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, // Comment + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, + #[AnAttr] + // Comment + /// Testdoc + G, + pub W, +); + +struct Tuple( + // Comment 1 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + // Comment 2 + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, +); + +// With a where-clause and generics. +pub struct Foo<'a, Y: Baz> +where + X: Whatever, +{ + f: SomeType, // Comment beside a field +} + +struct Baz { + a: A, // Comment A + b: B, // Comment B + c: C, // Comment C +} + +struct Baz { + a: A, // Comment A + + b: B, // Comment B + + c: C, // Comment C +} + +struct Baz { + a: A, + + b: B, + c: C, + + d: D, +} + +struct Baz { + // Comment A + a: A, + + // Comment B + b: B, + // Comment C + c: C, +} + +// Will this be a one-liner? +struct Tuple( + A, // Comment + B, +); + +pub struct State time::Timespec> { + now: F, +} + +pub struct State ()> { + now: F, +} + +pub struct State { + now: F, +} + +struct Palette { + /// A map of indices in the palette to a count of pixels in approximately + /// that color + foo: i32, +} + +// Splitting a single line comment into a block previously had a misalignment +// when the field had attributes +struct FieldsWithAttributes { + // Pre Comment + #[rustfmt::skip] pub host:String, /* Post comment BBBBBBBBBBBBBB BBBBBBBBBBBBBBBB + * BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBB BBBBBBBBBBB */ + // Another pre comment + #[attr1] + #[attr2] + pub id: usize, /* CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCC + * CCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCC CCCCCCCCCCCC */ +} + +struct Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: + node::Handle>, Type, NodeType>, +} + +struct Foo(T); +struct Foo(T) +where + T: Copy, + T: Eq; +struct Foo( + TTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUUUUUU, + TTTTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUU, +); +struct Foo( + TTTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUUUUUU, + TTTTTTTTTTTTTTTTTTT, +) +where + T: PartialEq; +struct Foo( + TTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUUUUUU, + TTTTTTTTTTTTTTTTTTTTT, +) +where + T: PartialEq; +struct Foo( + TTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUUUUUUU, + TTTTTTTTTTTTTTTTTTT, + UUUUUUUUUUUUUUUUUUU, +) +where + T: PartialEq; +struct Foo( + TTTTTTTTTTTTTTTTT, // Foo + UUUUUUUUUUUUUUUUUUUUUUUU, // Bar + // Baz + TTTTTTTTTTTTTTTTTTT, + // Qux (FIXME #572 - doc comment) + UUUUUUUUUUUUUUUUUUU, +); + +mod m { + struct X + where + T: Sized, + { + a: T, + } +} + +struct Foo( + TTTTTTTTTTTTTTTTTTT, + /// Qux + UUUUUUUUUUUUUUUUUUU, +); + +struct Issue677 { + pub ptr: *const libc::c_void, + pub trace: fn(obj: *const libc::c_void, tracer: *mut JSTracer), +} + +struct Foo {} +struct Foo {} +struct Foo { + // comment +} +struct Foo { + // trailing space -> +} +struct Foo { + // comment +} +struct Foo( + // comment +); + +struct LongStruct { + a: A, + the_quick_brown_fox_jumps_over_the_lazy_dog: + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +} + +struct Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: + node::Handle>, Type, NodeType>, +} + +struct Foo(String); + +// #1364 +fn foo() { + convex_shape.set_point(0, &Vector2f { x: 400.0, y: 100.0 }); + convex_shape.set_point(1, &Vector2f { x: 500.0, y: 70.0 }); + convex_shape.set_point(2, &Vector2f { x: 450.0, y: 100.0 }); + convex_shape.set_point(3, &Vector2f { x: 580.0, y: 150.0 }); +} + +// Vertical alignment +struct Foo { + aaaaa: u32, // a + + b: u32, // b + cc: u32, // cc + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 1 + yy: u32, // comment2 + zzz: u32, // comment3 + + aaaaaa: u32, // comment4 + bb: u32, // comment5 + // separate + dd: u32, // comment7 + c: u32, // comment6 + + aaaaaaa: u32, /* multi + * line + * comment + */ + b: u32, // hi + + do_not_push_this_comment1: u32, // comment1 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + please_do_not_push_this_comment3: u32, // comment3 + + do_not_push_this_comment1: u32, // comment1 + // separate + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + please_do_not_push_this_comment3: u32, // comment3 + + do_not_push_this_comment1: u32, // comment1 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + // separate + please_do_not_push_this_comment3: u32, // comment3 +} + +// structs with long identifier +struct Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong +{} +struct Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong +{} +struct Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong +{} +struct Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong +{ + x: i32, +} + +// structs with visibility, do not duplicate visibility (#2110). +pub(self) struct Foo {} +pub(super) struct Foo {} +pub(crate) struct Foo {} +pub(self) struct Foo(); +pub(super) struct Foo(); +pub(crate) struct Foo(); + +// #2125 +pub struct ReadinessCheckRegistry( + Mutex, Box ReadinessCheck + Sync + Send>>>, +); + +// #2144 unit struct with generics +struct MyBox; +struct MyBoxx +where + T: ?Sized, + S: Clone; + +// #2208 +struct Test { + /// foo + #[serde(default)] + pub join: Vec, + #[serde(default)] + pub tls: bool, +} + +// #2818 +struct Paren((i32)) +where + i32: Trait; +struct Parens((i32, i32)) +where + i32: Trait; diff --git a/src/tools/rustfmt/tests/target/trailing-comma-never.rs b/src/tools/rustfmt/tests/target/trailing-comma-never.rs new file mode 100644 index 0000000000..ea199f5ff2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/trailing-comma-never.rs @@ -0,0 +1,35 @@ +// rustfmt-trailing_comma: Never + +enum X { + A, + B +} + +enum Y { + A, + B +} + +enum TupX { + A(u32), + B(i32, u16) +} + +enum TupY { + A(u32), + B(i32, u16) +} + +enum StructX { + A { s: u16 }, + B { u: u32, i: i32 } +} + +enum StructY { + A { s: u16 }, + B { u: u32, i: i32 } +} + +static XXX: [i8; 64] = [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +]; diff --git a/src/tools/rustfmt/tests/target/trailing_commas.rs b/src/tools/rustfmt/tests/target/trailing_commas.rs new file mode 100644 index 0000000000..06f0a13b10 --- /dev/null +++ b/src/tools/rustfmt/tests/target/trailing_commas.rs @@ -0,0 +1,78 @@ +// rustfmt-match_block_trailing_comma: true +// rustfmt-trailing_comma: Always + +fn main() { + match foo { + x => {}, + y => { + foo(); + }, + _ => x, + } +} + +fn f(x: T, y: S,) -> T +where + T: P, + S: Q, +{ + x +} + +impl Trait for T +where + T: P, +{ + fn f(x: T,) -> T + where + T: Q + R, + { + x + } +} + +struct Pair +where + T: P, + S: P + Q, +{ + a: T, + b: S, +} + +struct TupPair(S, T,) +where + T: P, + S: P + Q; + +enum E +where + S: P, + T: P, +{ + A { a: T, }, +} + +type Double +where + T: P, + T: Q, += Pair; + +extern "C" { + fn f(x: T, y: S,) -> T + where + T: P, + S: Q; +} + +trait Q +where + T: P, + S: R, +{ + fn f(self, x: T, y: S, z: U,) -> Self + where + U: P, + V: P; +} diff --git a/src/tools/rustfmt/tests/target/trailing_comments/hard_tabs.rs b/src/tools/rustfmt/tests/target/trailing_comments/hard_tabs.rs new file mode 100644 index 0000000000..35e72f1aff --- /dev/null +++ b/src/tools/rustfmt/tests/target/trailing_comments/hard_tabs.rs @@ -0,0 +1,30 @@ +// rustfmt-version: Two +// rustfmt-wrap_comments: true +// rustfmt-hard_tabs: true + +impl Foo { + fn foo() { + bar(); // comment 1 + // comment 2 + // comment 3 + baz(); + } +} + +fn lorem_ipsum() { + let f = bar(); // Donec consequat mi. Quisque vitae dolor. Integer lobortis. Maecenas id nulla. Lorem. + // Id turpis. Nam posuere lectus vitae nibh. Etiam tortor orci, sagittis + // malesuada, rhoncus quis, hendrerit eget, libero. Quisque commodo nulla at + // nunc. Mauris consequat, enim vitae venenatis sollicitudin, dolor orci + // bibendum enim, a sagittis nulla nunc quis elit. Phasellus augue. Nunc + // suscipit, magna tincidunt lacinia faucibus, lacus tellus ornare purus, a + // pulvinar lacus orci eget nibh. Maecenas sed nibh non lacus tempor faucibus. + // In hac habitasse platea dictumst. Vivamus a orci at nulla tristique + // condimentum. Donec arcu quam, dictum accumsan, convallis accumsan, cursus sit + // amet, ipsum. In pharetra sagittis nunc. + let b = baz(); + + let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0] + // TODO(emilio): It may make sense to make this range [.01, 10.0], to align + // with css-fonts-4's range of [1, 1000]. +} diff --git a/src/tools/rustfmt/tests/target/trailing_comments/soft_tabs.rs b/src/tools/rustfmt/tests/target/trailing_comments/soft_tabs.rs new file mode 100644 index 0000000000..eba943042a --- /dev/null +++ b/src/tools/rustfmt/tests/target/trailing_comments/soft_tabs.rs @@ -0,0 +1,30 @@ +// rustfmt-version: Two +// rustfmt-wrap_comments: true + +pub const IFF_MULTICAST: ::c_int = 0x0000000800; // Supports multicast +// Multicast using broadcst. add. + +pub const SQ_CRETAB: u16 = 0x000e; // CREATE TABLE +pub const SQ_DRPTAB: u16 = 0x000f; // DROP TABLE +pub const SQ_CREIDX: u16 = 0x0010; // CREATE INDEX +//const SQ_DRPIDX: u16 = 0x0011; // DROP INDEX +//const SQ_GRANT: u16 = 0x0012; // GRANT +//const SQ_REVOKE: u16 = 0x0013; // REVOKE + +fn foo() { + let f = bar(); // Donec consequat mi. Quisque vitae dolor. Integer lobortis. Maecenas id nulla. Lorem. + // Id turpis. Nam posuere lectus vitae nibh. Etiam tortor orci, sagittis + // malesuada, rhoncus quis, hendrerit eget, libero. Quisque commodo nulla at + // nunc. Mauris consequat, enim vitae venenatis sollicitudin, dolor orci + // bibendum enim, a sagittis nulla nunc quis elit. Phasellus augue. Nunc + // suscipit, magna tincidunt lacinia faucibus, lacus tellus ornare purus, a + // pulvinar lacus orci eget nibh. Maecenas sed nibh non lacus tempor faucibus. + // In hac habitasse platea dictumst. Vivamus a orci at nulla tristique + // condimentum. Donec arcu quam, dictum accumsan, convallis accumsan, cursus sit + // amet, ipsum. In pharetra sagittis nunc. + let b = baz(); + + let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0] + // TODO(emilio): It may make sense to make this range [.01, 10.0], to align + // with css-fonts-4's range of [1, 1000]. +} diff --git a/src/tools/rustfmt/tests/target/trait.rs b/src/tools/rustfmt/tests/target/trait.rs new file mode 100644 index 0000000000..620046a71b --- /dev/null +++ b/src/tools/rustfmt/tests/target/trait.rs @@ -0,0 +1,213 @@ +// Test traits + +trait Foo { + fn bar(x: i32) -> Baz { + Baz::new() + } + + fn baz(a: AAAAAAAAAAAAAAAAAAAAAA, b: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB) -> RetType; + + fn foo( + a: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, // Another comment + b: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, + ) -> RetType; // Some comment + + fn baz(&mut self) -> i32; + + fn increment(&mut self, x: i32); + + fn read(&mut self, x: BufReader /* Used to be MemReader */) + where + R: Read; +} + +pub trait WriteMessage { + fn write_message(&mut self, &FrontendMessage) -> io::Result<()>; +} + +trait Runnable { + fn handler(self: &Runnable); +} + +trait TraitWithExpr { + fn fn_with_expr(x: [i32; 1]); +} + +trait Test { + fn read_struct(&mut self, s_name: &str, len: usize, f: F) -> Result + where + F: FnOnce(&mut Self) -> Result; +} + +trait T {} + +trait Foo { + type Bar: Baz; + type Inner: Foo = Box; +} + +trait ConstCheck: Foo +where + T: Baz, +{ + const J: i32; +} + +trait Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttt +where + T: Foo, +{ +} + +trait Ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt +where + T: Foo, +{ +} + +trait FooBar: Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt +where + J: Bar, +{ + fn test(); +} + +trait WhereList +where + T: Foo, + J: Bar, +{ +} + +trait X /* comment */ {} +trait Y // comment +{ +} + +// #2055 +pub trait Foo: +// A and C +A + C +// and B + + B +{} + +// #2158 +trait Foo { + type ItRev = > as UntypedTimeSeries>::IterRev; + type IteRev = + > as UntypedTimeSeries>::IterRev; +} + +// #2331 +trait MyTrait< + AAAAAAAAAAAAAAAAAAAA, + BBBBBBBBBBBBBBBBBBBB, + CCCCCCCCCCCCCCCCCCCC, + DDDDDDDDDDDDDDDDDDDD, +> +{ + fn foo() {} +} + +// Trait aliases +trait FooBar = Foo + Bar; +trait FooBar = Foo + Bar; +pub trait FooBar = Foo + Bar; +pub trait FooBar = Foo + Bar; +trait AAAAAAAAAAAAAAAAAA = BBBBBBBBBBBBBBBBBBB + CCCCCCCCCCCCCCCCCCCCCCCCCCCCC + DDDDDDDDDDDDDDDDDD; +pub trait AAAAAAAAAAAAAAAAAA = + BBBBBBBBBBBBBBBBBBB + CCCCCCCCCCCCCCCCCCCCCCCCCCCCC + DDDDDDDDDDDDDDDDDD; +trait AAAAAAAAAAAAAAAAAAA = + BBBBBBBBBBBBBBBBBBB + CCCCCCCCCCCCCCCCCCCCCCCCCCCCC + DDDDDDDDDDDDDDDDDD; +trait AAAAAAAAAAAAAAAAAA = + BBBBBBBBBBBBBBBBBBB + CCCCCCCCCCCCCCCCCCCCCCCCCCCCC + DDDDDDDDDDDDDDDDDDD; +trait AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA = + FooBar; +trait AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA< + A, + B, + C, + D, + E, +> = FooBar; +#[rustfmt::skip] +trait FooBar = Foo + + Bar; + +// #2637 +auto trait Example {} +pub auto trait PubExample {} +pub unsafe auto trait PubUnsafeExample {} + +// #3006 +trait Foo<'a> { + type Bar<'a>; +} + +impl<'a> Foo<'a> for i32 { + type Bar<'a> = i32; +} + +// #3092 +pub mod test { + pub trait ATraitWithALooongName {} + pub trait ATrait: + ATraitWithALooongName + + ATraitWithALooongName + + ATraitWithALooongName + + ATraitWithALooongName + { + } +} + +// Trait aliases with where clauses. +trait A = where for<'b> &'b Self: Send; + +trait B = where for<'b> &'b Self: Send + Clone + Copy + SomeTrait + AAAAAAAA + BBBBBBB + CCCCCCCCCC; +trait B = + where for<'b> &'b Self: Send + Clone + Copy + SomeTrait + AAAAAAAA + BBBBBBB + CCCCCCCCCCC; +trait B = where + for<'b> &'b Self: + Send + Clone + Copy + SomeTrait + AAAAAAAA + BBBBBBB + CCCCCCCCCCCCCCCCCCCCCCC; +trait B = where + for<'b> &'b Self: Send + + Clone + + Copy + + SomeTrait + + AAAAAAAA + + BBBBBBB + + CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC; + +trait B = where + for<'b> &'b Self: Send + + Clone + + Copy + + SomeTrait + + AAAAAAAA + + BBBBBBB + + CCCCCCCCC + + DDDDDDD + + DDDDDDDD + + DDDDDDDDD + + EEEEEEE; + +trait A<'a, 'b, 'c> = Debug + Foo where for<'b> &'b Self: Send; + +trait B<'a, 'b, 'c> = Debug + Foo +where + for<'b> &'b Self: Send + Clone + Copy + SomeTrait + AAAAAAAA + BBBBBBB + CCCCCCCCC + DDDDDDD; + +trait B<'a, 'b, 'c, T> = Debug<'a, T> +where + for<'b> &'b Self: Send + + Clone + + Copy + + SomeTrait + + AAAAAAAA + + BBBBBBB + + CCCCCCCCC + + DDDDDDD + + DDDDDDDD + + DDDDDDDDD + + EEEEEEE; diff --git a/src/tools/rustfmt/tests/target/try-conversion.rs b/src/tools/rustfmt/tests/target/try-conversion.rs new file mode 100644 index 0000000000..04992a0a0f --- /dev/null +++ b/src/tools/rustfmt/tests/target/try-conversion.rs @@ -0,0 +1,28 @@ +// rustfmt-use_try_shorthand: true + +fn main() { + let x = some_expr()?; + + let y = a + .very + .loooooooooooooooooooooooooooooooooooooong() + .chain() + .inside() + .weeeeeeeeeeeeeee()? + .test() + .0 + .x; +} + +fn test() { + a? +} + +fn issue1291() { + fs::create_dir_all(&gitfiledir).chain_err(|| { + format!( + "failed to create the {} submodule directory for the workarea", + name + ) + })?; +} diff --git a/src/tools/rustfmt/tests/target/try_block.rs b/src/tools/rustfmt/tests/target/try_block.rs new file mode 100644 index 0000000000..19a3f3e148 --- /dev/null +++ b/src/tools/rustfmt/tests/target/try_block.rs @@ -0,0 +1,29 @@ +// rustfmt-edition: 2018 + +fn main() -> Result<(), !> { + let _x: Option<_> = try { 4 }; + + try {} +} + +fn baz() -> Option { + if (1 == 1) { + return try { 5 }; + } + + // test + let x: Option<()> = try { + // try blocks are great + }; + + let y: Option = try { 6 }; // comment + + let x: Option = try { + baz()?; + baz()?; + baz()?; + 7 + }; + + return None; +} diff --git a/src/tools/rustfmt/tests/target/tuple.rs b/src/tools/rustfmt/tests/target/tuple.rs new file mode 100644 index 0000000000..68bb2f3bc2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/tuple.rs @@ -0,0 +1,100 @@ +// Test tuple litterals + +fn foo() { + let a = (a, a, a, a, a); + let aaaaaaaaaaaaaaaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + aaaaaaaaaaaaaa, + aaaaaaaaaaaaaa, + ); + let aaaaaaaaaaaaaaaaaaaaaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + aaaaaaaaaaaaaaaaaaaaaaaaa, + aaaa, + ); + let a = (a,); + + let b = ( + // This is a comment + b, // Comment + b, /* Trailing comment */ + ); + + // #1063 + foo(x.0 .0); +} + +fn a() { + (( + aaaaaaaa, + aaaaaaaaaaaaa, + aaaaaaaaaaaaaaaaa, + aaaaaaaaaaaaaa, + aaaaaaaaaaaaaaaa, + aaaaaaaaaaaaaa, + ),) +} + +fn b() { + ( + ( + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + ), + bbbbbbbbbbbbbbbbbb, + ) +} + +fn issue550() { + self.visitor.visit_volume( + self.level.sector_id(sector), + ( + floor_y, + if is_sky_flat(ceil_tex) { + from_wad_height(self.height_range.1) + } else { + ceil_y + }, + ), + ); +} + +fn issue775() { + if indent { + let a = mk_object(&[ + ("a".to_string(), Boolean(true)), + ( + "b".to_string(), + Array(vec![ + mk_object(&[("c".to_string(), String("\x0c\r".to_string()))]), + mk_object(&[("d".to_string(), String("".to_string()))]), + ]), + ), + ]); + } +} + +fn issue1725() { + bench_antialiased_lines!( + bench_draw_antialiased_line_segment_diagonal, + (10, 10), + (450, 450) + ); + bench_antialiased_lines!( + bench_draw_antialiased_line_segment_shallow, + (10, 10), + (450, 80) + ); +} + +fn issue_4355() { + let _ = ((1,),).0 .0; +} + +// https://github.com/rust-lang/rustfmt/issues/4410 +impl Drop for LockGuard { + fn drop(&mut self) { + LockMap::unlock(&self.0 .0, &self.0 .1); + } +} diff --git a/src/tools/rustfmt/tests/target/tuple_v2.rs b/src/tools/rustfmt/tests/target/tuple_v2.rs new file mode 100644 index 0000000000..ba653291c2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/tuple_v2.rs @@ -0,0 +1,5 @@ +// rustfmt-version: Two + +fn issue_4355() { + let _ = ((1,),).0.0; +} diff --git a/src/tools/rustfmt/tests/target/type-ascription.rs b/src/tools/rustfmt/tests/target/type-ascription.rs new file mode 100644 index 0000000000..a2f082ba4b --- /dev/null +++ b/src/tools/rustfmt/tests/target/type-ascription.rs @@ -0,0 +1,12 @@ +fn main() { + let xxxxxxxxxxx = + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: SomeTrait; + + let xxxxxxxxxxxxxxx = + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; + + let z = funk(yyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzz, wwwwww): + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; + + x: u32 - 1u32 / 10f32: u32 +} diff --git a/src/tools/rustfmt/tests/target/type.rs b/src/tools/rustfmt/tests/target/type.rs new file mode 100644 index 0000000000..e776125168 --- /dev/null +++ b/src/tools/rustfmt/tests/target/type.rs @@ -0,0 +1,183 @@ +// rustfmt-normalize_comments: true +fn types() { + let x: [Vec<_>] = []; + let y: *mut [SomeType; konst_funk()] = expr(); + let z: (/* #digits */ usize, /* exp */ i16) = funk(); + let z: (usize /* #digits */, i16 /* exp */) = funk(); +} + +struct F { + f: extern "C" fn(x: u8, ... /* comment */), + g: extern "C" fn(x: u8, /* comment */ ...), + h: extern "C" fn(x: u8, ...), + i: extern "C" fn( + x: u8, + // comment 4 + y: String, // comment 3 + z: Foo, + // comment + ... // comment 2 + ), +} + +fn issue_1006(def_id_to_string: for<'a, 'b> unsafe fn(TyCtxt<'b, 'tcx, 'tcx>, DefId) -> String) {} + +fn impl_trait_fn_1() -> impl Fn(i32) -> Option {} + +fn impl_trait_fn_2() -> impl Future {} + +fn issue_1234() { + do_parse!(name: take_while1!(is_token) >> (Header)) +} + +// #2510 +impl CombineTypes { + pub fn pop_callback( + &self, + query_id: Uuid, + ) -> Option<( + ProjectId, + Box () + Sync + Send>, + )> { + self.query_callbacks()(&query_id) + } +} + +// #2859 +pub fn do_something<'a, T: Trait1 + Trait2 + 'a>( + &fooo: u32, +) -> impl Future< + Item = ( + impl Future + 'a, + impl Future + 'a, + impl Future + 'a, + ), + Error = SomeError, +> + 'a { +} + +pub fn do_something<'a, T: Trait1 + Trait2 + 'a>( + &fooo: u32, +) -> impl Future< + Item = ( + impl Future + 'a, + impl Future + 'a, + impl Future + 'a, + ), + Error = SomeError, +> + Future< + Item = ( + impl Future + 'a, + impl Future + 'a, + impl Future + 'a, + ), + Error = SomeError, +> + Future< + Item = ( + impl Future + 'a, + impl Future + 'a, + impl Future + 'a, + ), + Error = SomeError, +> + 'a + 'b + 'c { +} + +// #3051 +token![impl]; +token![impl]; + +// #3060 +macro_rules! foo { + ($foo_api: ty) => { + type Target = ($foo_api) + 'static; + }; +} + +type Target = (FooAPI) + 'static; + +// #3137 +fn foo(t: T) +where + T: (FnOnce() -> ()) + Clone, + U: (FnOnce() -> ()) + 'static, +{ +} + +// #3117 +fn issue3117() { + { + { + { + { + { + { + { + { + let opt: &mut Option = + unsafe { &mut *self.future.get() }; + } + } + } + } + } + } + } + } +} + +// #3139 +fn issue3139() { + assert_eq!( + to_json_value(&None::).unwrap(), + json!({ "test": None:: }) + ); +} + +// #3180 +fn foo( + a: SomeLongComplexType, + b: SomeOtherLongComplexType, +) -> Box> { +} + +type MyFn = fn( + a: SomeLongComplexType, + b: SomeOtherLongComplexType, +) -> Box>; + +// Const opt-out + +trait T: ?const Super {} + +const fn maybe_const() -> i32 { + ::CONST +} + +struct S(std::marker::PhantomData); + +impl ?const T {} + +fn trait_object() -> &'static dyn ?const T { + &S +} + +fn i(_: impl IntoIterator>) {} + +fn apit(_: impl ?const T) {} + +fn rpit() -> impl ?const T { + S +} + +pub struct Foo(T); +impl Foo { + fn new(t: T) -> Self { + // not calling methods on `t`, so we opt out of requiring + // `` to have const methods via `?const` + Self(t) + } +} + +// #4357 +type T = typeof(1); +impl T for .. {} diff --git a/src/tools/rustfmt/tests/target/type_alias.rs b/src/tools/rustfmt/tests/target/type_alias.rs new file mode 100644 index 0000000000..862f9ecbee --- /dev/null +++ b/src/tools/rustfmt/tests/target/type_alias.rs @@ -0,0 +1,76 @@ +// rustfmt-normalize_comments: true + +type PrivateTest<'a, I> = ( + Box + 'a>, + Box + 'a>, +); + +pub type PublicTest<'a, I, O> = Result< + Vec, + Box + 'a>, + Box + 'a>, +>; + +pub type LongGenericListTest< + 'a, + 'b, + 'c, + 'd, + LONGPARAMETERNAME, + LONGPARAMETERNAME, + LONGPARAMETERNAME, + A, + B, + C, +> = Option>; + +pub type Exactly100CharsTest<'a, 'b, 'c, 'd, LONGPARAMETERNAME, LONGPARAMETERNAME, A, B> = Vec; + +pub type Exactly101CharsTest<'a, 'b, 'c, 'd, LONGPARAMETERNAME, LONGPARAMETERNAME, A, B> = + Vec; + +pub type Exactly100CharsToEqualTest<'a, 'b, 'c, 'd, LONGPARAMETERNAME, LONGPARAMETERNAME, A, B, C> = + Vec; + +pub type GenericsFitButNotEqualTest< + 'a, + 'b, + 'c, + 'd, + LONGPARAMETERNAME, + LONGPARAMETERNAME, + A1, + B, + C, +> = Vec; + +pub type CommentTest< + // Lifetime + 'a, + // Type + T, +> = (); + +pub type WithWhereClause +where + T: Clone, + LONGPARAMETERNAME: Clone + Eq + OtherTrait, += Option; + +pub type Exactly100CharstoEqualWhereTest +where + T: Clone + Ord + Eq + SomeOtherTrait, += Option; + +pub type Exactly101CharstoEqualWhereTest +where + T: Clone + Ord + Eq + SomeOtherTrait, += Option; + +type RegisterPlugin = unsafe fn(pt: *const c_char, plugin: *mut c_void, data: *mut CallbackData); + +// #1683 +pub type Between = + super::operators::Between, AsExpr>>; +pub type NotBetween = + super::operators::NotBetween, AsExpr>>; diff --git a/src/tools/rustfmt/tests/target/unicode.rs b/src/tools/rustfmt/tests/target/unicode.rs new file mode 100644 index 0000000000..34a4f46347 --- /dev/null +++ b/src/tools/rustfmt/tests/target/unicode.rs @@ -0,0 +1,30 @@ +// rustfmt-wrap_comments: true + +fn foo() { + let s = "this line goes to 100: ͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶ"; + let s = 42; + + // a comment of length 80, with the starting sigil: ҘҘҘҘҘҘҘҘҘҘ ҘҘҘҘҘҘҘҘҘҘҘҘҘҘ + let s = 42; +} + +pub fn bar(config: &Config) { + let csv = RefCell::new(create_csv(config, "foo")); + { + let mut csv = csv.borrow_mut(); + for (i1, i2, i3) in iproduct!(0..2, 0..3, 0..3) { + csv.write_field(format!("γ[{}.{}.{}]", i1, i2, i3)).unwrap(); + csv.write_field(format!("d[{}.{}.{}]", i1, i2, i3)).unwrap(); + csv.write_field(format!("i[{}.{}.{}]", i1, i2, i3)).unwrap(); + } + csv.write_record(None::<&[u8]>).unwrap(); + } +} + +// The NotUnicode line is below 100 wrt chars but over it wrt String::len +fn baz() { + let our_error_b = result_b_from_func.or_else(|e| match e { + NotPresent => Err(e).chain_err(|| "env var wasn't provided"), + NotUnicode(_) => Err(e).chain_err(|| "env var was very very very bork文字化ã"), + }); +} diff --git a/src/tools/rustfmt/tests/target/unindent_if_else_cond_comment.rs b/src/tools/rustfmt/tests/target/unindent_if_else_cond_comment.rs new file mode 100644 index 0000000000..98621b1eed --- /dev/null +++ b/src/tools/rustfmt/tests/target/unindent_if_else_cond_comment.rs @@ -0,0 +1,27 @@ +// Comments on else block. See #1575. + +fn example() { + // `if` comment + if x { + foo(); + // `else if` comment + } else if y { + foo(); + // Comment on `else if`. + // Comment on `else if`. + } else if z { + bar(); + /* + * Multi line comment on `else if` + */ + } else if xx { + bar(); + /* Single line comment on `else if` */ + } else if yy { + foo(); + // `else` comment + } else { + foo(); + // Comment at the end of `else` block + }; +} diff --git a/src/tools/rustfmt/tests/target/unions.rs b/src/tools/rustfmt/tests/target/unions.rs new file mode 100644 index 0000000000..8ed16b269c --- /dev/null +++ b/src/tools/rustfmt/tests/target/unions.rs @@ -0,0 +1,198 @@ +// rustfmt-normalize_comments: true +// rustfmt-wrap_comments: true + +/// A Doc comment +#[AnAttribute] +pub union Foo { + #[rustfmt::skip] + f : SomeType, // Comment beside a field + f: SomeType, // Comment beside a field + // Comment on a field + #[AnAttribute] + g: SomeOtherType, + /// A doc comment on a field + h: AThirdType, + pub i: TypeForPublicField, +} + +// #1029 +pub union Foo { + #[doc(hidden)] + // This will NOT get deleted! + bar: String, // hi +} + +// #1029 +union X { + // `x` is an important number. + #[allow(unused)] // TODO: use + x: u32, +} + +// #410 +#[allow(missing_docs)] +pub union Writebatch { + #[allow(dead_code)] // only used for holding the internal pointer + writebatch: RawWritebatch, + marker: PhantomData, +} + +// With a where-clause and generics. +pub union Foo<'a, Y: Baz> +where + X: Whatever, +{ + f: SomeType, // Comment beside a field +} + +union Baz { + a: A, // Comment A + b: B, // Comment B + c: C, // Comment C +} + +union Baz { + a: A, // Comment A + + b: B, // Comment B + + c: C, // Comment C +} + +union Baz { + a: A, + + b: B, + c: C, + + d: D, +} + +union Baz { + // Comment A + a: A, + + // Comment B + b: B, + // Comment C + c: C, +} + +pub union State time::Timespec> { + now: F, +} + +pub union State ()> { + now: F, +} + +pub union State { + now: F, +} + +union Palette { + /// A map of indices in the palette to a count of pixels in approximately + /// that color + foo: i32, +} + +// Splitting a single line comment into a block previously had a misalignment +// when the field had attributes +union FieldsWithAttributes { + // Pre Comment + #[rustfmt::skip] pub host:String, /* Post comment BBBBBBBBBBBBBB BBBBBBBBBBBBBBBB + * BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBB BBBBBBBBBBB */ + // Another pre comment + #[attr1] + #[attr2] + pub id: usize, /* CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCC + * CCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCC CCCCCCCCCCCC */ +} + +union Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: + node::Handle>, Type, NodeType>, +} + +mod m { + union X + where + T: Sized, + { + a: T, + } +} + +union Issue677 { + pub ptr: *const libc::c_void, + pub trace: fn(obj: *const libc::c_void, tracer: *mut JSTracer), +} + +union Foo {} +union Foo {} +union Foo { + // comment +} +union Foo { + // trailing space -> +} +union Foo { + // comment +} + +union LongUnion { + a: A, + the_quick_brown_fox_jumps_over_the_lazy_dog: + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +} + +union Deep { + deeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep: + node::Handle>, Type, NodeType>, +} + +// #1364 +fn foo() { + convex_shape.set_point(0, &Vector2f { x: 400.0, y: 100.0 }); + convex_shape.set_point(1, &Vector2f { x: 500.0, y: 70.0 }); + convex_shape.set_point(2, &Vector2f { x: 450.0, y: 100.0 }); + convex_shape.set_point(3, &Vector2f { x: 580.0, y: 150.0 }); +} + +// Vertical alignment +union Foo { + aaaaa: u32, // a + + b: u32, // b + cc: u32, // cc + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 1 + yy: u32, // comment2 + zzz: u32, // comment3 + + aaaaaa: u32, // comment4 + bb: u32, // comment5 + // separate + dd: u32, // comment7 + c: u32, // comment6 + + aaaaaaa: u32, /* multi + * line + * comment + */ + b: u32, // hi + + do_not_push_this_comment1: u32, // comment1 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + please_do_not_push_this_comment3: u32, // comment3 + + do_not_push_this_comment1: u32, // comment1 + // separate + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + please_do_not_push_this_comment3: u32, // comment3 + + do_not_push_this_comment1: u32, // comment1 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: u32, // 2 + // separate + please_do_not_push_this_comment3: u32, // comment3 +} diff --git a/src/tools/rustfmt/tests/target/unsafe-mod.rs b/src/tools/rustfmt/tests/target/unsafe-mod.rs new file mode 100644 index 0000000000..05ba2f54fa --- /dev/null +++ b/src/tools/rustfmt/tests/target/unsafe-mod.rs @@ -0,0 +1,7 @@ +// These are supported by rustc syntactically but not semantically. + +#[cfg(any())] +unsafe mod m {} + +#[cfg(any())] +unsafe extern "C++" {} diff --git a/src/tools/rustfmt/tests/target/visibility.rs b/src/tools/rustfmt/tests/target/visibility.rs new file mode 100644 index 0000000000..ca078422c1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/visibility.rs @@ -0,0 +1,8 @@ +// #2398 +pub mod outer_mod { + pub mod inner_mod { + pub(in outer_mod) fn outer_mod_visible_fn() {} + pub(super) fn super_mod_visible_fn() {} + pub(self) fn inner_mod_visible_fn() {} + } +} diff --git a/src/tools/rustfmt/tests/target/visual-fn-type.rs b/src/tools/rustfmt/tests/target/visual-fn-type.rs new file mode 100644 index 0000000000..052acde020 --- /dev/null +++ b/src/tools/rustfmt/tests/target/visual-fn-type.rs @@ -0,0 +1,9 @@ +// rustfmt-indent_style: Visual +type CNodeSetAtts = unsafe extern "C" fn(node: *const RsvgNode, + node_impl: *const RsvgCNodeImpl, + handle: *const RsvgHandle, + pbag: *const PropertyBag); +type CNodeDraw = unsafe extern "C" fn(node: *const RsvgNode, + node_impl: *const RsvgCNodeImpl, + draw_ctx: *const RsvgDrawingCtx, + dominate: i32); diff --git a/src/tools/rustfmt/tests/target/where-clause-rfc.rs b/src/tools/rustfmt/tests/target/where-clause-rfc.rs new file mode 100644 index 0000000000..9c43e91d37 --- /dev/null +++ b/src/tools/rustfmt/tests/target/where-clause-rfc.rs @@ -0,0 +1,156 @@ +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) +where + T: FOo, + U: Bar, +{ + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) +where + T: FOo, +{ + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule( + node: &CompoundNode, + rule: &Rule, + args: &[Arg], + shape: &Shape, + shape: &Shape, +) where + T: FOo, + U: Bar, +{ + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule( + node: &CompoundNode, + rule: &Rule, + args: &[Arg], + shape: &Shape, + shape: &Shape, +) where + T: FOo, +{ + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule( + node: &CompoundNode, + rule: &Rule, + args: &[Arg], + shape: &Shape, +) -> Option +where + T: FOo, + U: Bar, +{ + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule( + node: &CompoundNode, + rule: &Rule, + args: &[Arg], + shape: &Shape, +) -> Option +where + T: FOo, +{ + let mut effects = HashMap::new(); +} + +pub trait Test { + fn very_long_method_name(self, f: F) -> MyVeryLongReturnType + where + F: FnMut(Self::Item) -> bool; + + fn exactly_100_chars1(self, f: F) -> MyVeryLongReturnType + where + F: FnMut(Self::Item) -> bool; +} + +fn very_long_function_name(very_long_argument: F) -> MyVeryLongReturnType +where + F: FnMut(Self::Item) -> bool, +{ +} + +struct VeryLongTupleStructName(LongLongTypename, LongLongTypename, i32, i32) +where + A: LongTrait; + +struct Exactly100CharsToSemicolon(LongLongTypename, i32, i32) +where + A: LongTrait1234; + +struct AlwaysOnNextLine +where + A: LongTrait, +{ + x: i32, +} + +pub trait SomeTrait +where + T: Something + + Sync + + Send + + Display + + Debug + + Copy + + Hash + + Debug + + Display + + Write + + Read + + FromStr, +{ +} + +// #2020 +impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> { + fn elaborate_bounds(&mut self, bounds: &[ty::PolyTraitRef<'tcx>], mut mk_cand: F) + where + F: for<'b> FnMut( + &mut ProbeContext<'b, 'gcx, 'tcx>, + ty::PolyTraitRef<'tcx>, + ty::AssociatedItem, + ), + { + // ... + } +} + +// #2497 +fn handle_update<'a, Tab, Conn, R, C>( + executor: &Executor>>, + change_set: &'a C, +) -> ExecutionResult +where + &'a C: Identifiable + AsChangeset + HasTable
, + <&'a C as AsChangeset>::Changeset: QueryFragment, + Tab: Table + HasTable
, + Tab::PrimaryKey: EqAll<<&'a C as Identifiable>::Id>, + Tab::FromClause: QueryFragment, + Tab: FindDsl<<&'a C as Identifiable>::Id>, + Find::Id>: IntoUpdateTarget
, + ::Id> as IntoUpdateTarget>::WhereClause: + QueryFragment, + Tab::Query: FilterDsl<::Id>>::Output>, + Filter::Id>>::Output>: LimitDsl, + Limit::Id>>::Output>>: + QueryDsl + + BoxedDsl< + 'a, + Conn::Backend, + Output = BoxedSelectStatement<'a, R::SqlType, Tab, Conn::Backend>, + >, + R: LoadingHandler + + GraphQLType, +{ + unimplemented!() +} diff --git a/src/tools/rustfmt/tests/target/where-clause.rs b/src/tools/rustfmt/tests/target/where-clause.rs new file mode 100644 index 0000000000..eb2f8d5e6e --- /dev/null +++ b/src/tools/rustfmt/tests/target/where-clause.rs @@ -0,0 +1,107 @@ +// rustfmt-indent_style: Visual + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) + where T: FOo, + U: Bar +{ + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, rule: &Rule, args: &[Arg], shape: &Shape) + where T: FOo +{ + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, + rule: &Rule, + args: &[Arg], + shape: &Shape, + shape: &Shape) + where T: FOo, + U: Bar +{ + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, + rule: &Rule, + args: &[Arg], + shape: &Shape, + shape: &Shape) + where T: FOo +{ + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, + rule: &Rule, + args: &[Arg], + shape: &Shape) + -> Option + where T: FOo, + U: Bar +{ + let mut effects = HashMap::new(); +} + +fn reflow_list_node_with_rule(node: &CompoundNode, + rule: &Rule, + args: &[Arg], + shape: &Shape) + -> Option + where T: FOo +{ + let mut effects = HashMap::new(); +} + +pub trait Test { + fn very_long_method_name(self, f: F) -> MyVeryLongReturnType + where F: FnMut(Self::Item) -> bool; + + fn exactly_100_chars1(self, f: F) -> MyVeryLongReturnType + where F: FnMut(Self::Item) -> bool; +} + +fn very_long_function_name(very_long_argument: F) -> MyVeryLongReturnType + where F: FnMut(Self::Item) -> bool +{ +} + +struct VeryLongTupleStructName(LongLongTypename, LongLongTypename, i32, i32) + where A: LongTrait; + +struct Exactly100CharsToSemicolon(LongLongTypename, i32, i32) where A: LongTrait1234; + +struct AlwaysOnNextLine + where A: LongTrait +{ + x: i32, +} + +pub trait SomeTrait + where T: Something + + Sync + + Send + + Display + + Debug + + Copy + + Hash + + Debug + + Display + + Write + + Read + + FromStr +{ +} + +// #2020 +impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> { + fn elaborate_bounds(&mut self, bounds: &[ty::PolyTraitRef<'tcx>], mut mk_cand: F) + where F: for<'b> FnMut(&mut ProbeContext<'b, 'gcx, 'tcx>, + ty::PolyTraitRef<'tcx>, + ty::AssociatedItem) + { + // ... + } +} diff --git a/src/tools/rustfmt/tests/target/width-heuristics.rs b/src/tools/rustfmt/tests/target/width-heuristics.rs new file mode 100644 index 0000000000..e177a2152e --- /dev/null +++ b/src/tools/rustfmt/tests/target/width-heuristics.rs @@ -0,0 +1,24 @@ +// rustfmt-max_width: 120 + +// elems on multiple lines for max_width 100, but same line for max_width 120 +fn foo(e: Enum) { + match e { + Enum::Var { elem1, elem2, elem3 } => { + return; + } + } +} + +// elems not on same line for either max_width 100 or 120 +fn bar(e: Enum) { + match e { + Enum::Var { + elem1, + elem2, + elem3, + elem4, + } => { + return; + } + } +} diff --git a/src/tools/rustfmt/tests/target/wrap_comments_should_not_imply_format_doc_comments.rs b/src/tools/rustfmt/tests/target/wrap_comments_should_not_imply_format_doc_comments.rs new file mode 100644 index 0000000000..d61d4d7c21 --- /dev/null +++ b/src/tools/rustfmt/tests/target/wrap_comments_should_not_imply_format_doc_comments.rs @@ -0,0 +1,16 @@ +// rustfmt-wrap_comments: true + +/// Foo +/// +/// # Example +/// ``` +/// # #![cfg_attr(not(dox), feature(cfg_target_feature, target_feature, stdsimd))] +/// # #![cfg_attr(not(dox), no_std)] +/// fn foo() { } +/// ``` +fn foo() {} + +/// A long commment for wrapping +/// This is a long long long long long long long long long long long long long +/// long long long long long long long sentence. +fn bar() {} diff --git a/src/tools/rustfmt/tests/writemode/source/fn-single-line.rs b/src/tools/rustfmt/tests/writemode/source/fn-single-line.rs new file mode 100644 index 0000000000..ab1e13e17a --- /dev/null +++ b/src/tools/rustfmt/tests/writemode/source/fn-single-line.rs @@ -0,0 +1,80 @@ +// rustfmt-fn_single_line: true +// rustfmt-emit_mode: checkstyle +// Test single-line functions. + +fn foo_expr() { + 1 +} + +fn foo_stmt() { + foo(); +} + +fn foo_decl_local() { + let z = 5; + } + +fn foo_decl_item(x: &mut i32) { + x = 3; +} + + fn empty() { + +} + +fn foo_return() -> String { + "yay" +} + +fn foo_where() -> T where T: Sync { + let x = 2; +} + +fn fooblock() { + { + "inner-block" + } +} + +fn fooblock2(x: i32) { + let z = match x { + _ => 2, + }; +} + +fn comment() { + // this is a test comment + 1 +} + +fn comment2() { + // multi-line comment + let z = 2; + 1 +} + +fn only_comment() { + // Keep this here +} + +fn aaaaaaaaaaaaaaaaa_looooooooooooooooooooooong_name() { + let z = "aaaaaaawwwwwwwwwwwwwwwwwwwwwwwwwwww"; +} + +fn lots_of_space () { + 1 +} + +fn mac() -> Vec { vec![] } + +trait CoolTypes { + fn dummy(&self) { + } +} + +trait CoolerTypes { fn dummy(&self) { +} +} + +fn Foo() where T: Bar { +} diff --git a/src/tools/rustfmt/tests/writemode/source/json.rs b/src/tools/rustfmt/tests/writemode/source/json.rs new file mode 100644 index 0000000000..89dcf69418 --- /dev/null +++ b/src/tools/rustfmt/tests/writemode/source/json.rs @@ -0,0 +1,80 @@ +// rustfmt-fn_single_line: true +// rustfmt-emit_mode: json +// Test single-line functions. + +fn foo_expr() { + 1 +} + +fn foo_stmt() { + foo(); +} + +fn foo_decl_local() { + let z = 5; + } + +fn foo_decl_item(x: &mut i32) { + x = 3; +} + + fn empty() { + +} + +fn foo_return() -> String { + "yay" +} + +fn foo_where() -> T where T: Sync { + let x = 2; +} + +fn fooblock() { + { + "inner-block" + } +} + +fn fooblock2(x: i32) { + let z = match x { + _ => 2, + }; +} + +fn comment() { + // this is a test comment + 1 +} + +fn comment2() { + // multi-line comment + let z = 2; + 1 +} + +fn only_comment() { + // Keep this here +} + +fn aaaaaaaaaaaaaaaaa_looooooooooooooooooooooong_name() { + let z = "aaaaaaawwwwwwwwwwwwwwwwwwwwwwwwwwww"; +} + +fn lots_of_space () { + 1 +} + +fn mac() -> Vec { vec![] } + +trait CoolTypes { + fn dummy(&self) { + } +} + +trait CoolerTypes { fn dummy(&self) { +} +} + +fn Foo() where T: Bar { +} diff --git a/src/tools/rustfmt/tests/writemode/source/modified.rs b/src/tools/rustfmt/tests/writemode/source/modified.rs new file mode 100644 index 0000000000..948beb348d --- /dev/null +++ b/src/tools/rustfmt/tests/writemode/source/modified.rs @@ -0,0 +1,14 @@ +// rustfmt-write_mode: modified +// Test "modified" output + +fn +blah +() +{ } + + +#[cfg +( a , b +)] +fn +main() {} diff --git a/src/tools/rustfmt/tests/writemode/target/checkstyle.xml b/src/tools/rustfmt/tests/writemode/target/checkstyle.xml new file mode 100644 index 0000000000..05bc3a2525 --- /dev/null +++ b/src/tools/rustfmt/tests/writemode/target/checkstyle.xml @@ -0,0 +1,2 @@ + + diff --git a/src/tools/rustfmt/tests/writemode/target/modified.txt b/src/tools/rustfmt/tests/writemode/target/modified.txt new file mode 100644 index 0000000000..5c0539a665 --- /dev/null +++ b/src/tools/rustfmt/tests/writemode/target/modified.txt @@ -0,0 +1,5 @@ +4 4 1 +fn blah() {} +10 5 2 +#[cfg(a, b)] +fn main() {} diff --git a/src/tools/rustfmt/tests/writemode/target/output.json b/src/tools/rustfmt/tests/writemode/target/output.json new file mode 100644 index 0000000000..b5f327b0a1 --- /dev/null +++ b/src/tools/rustfmt/tests/writemode/target/output.json @@ -0,0 +1 @@ +[{"name":"tests/writemode/source/json.rs","mismatches":[{"original_begin_line":5,"original_end_line":7,"expected_begin_line":5,"expected_end_line":5,"original":"fn foo_expr() {\n 1\n}","expected":"fn foo_expr() { 1 }"},{"original_begin_line":9,"original_end_line":11,"expected_begin_line":7,"expected_end_line":7,"original":"fn foo_stmt() {\n foo();\n}","expected":"fn foo_stmt() { foo(); }"},{"original_begin_line":13,"original_end_line":15,"expected_begin_line":9,"expected_end_line":9,"original":"fn foo_decl_local() {\n let z = 5;\n }","expected":"fn foo_decl_local() { let z = 5; }"},{"original_begin_line":17,"original_end_line":19,"expected_begin_line":11,"expected_end_line":11,"original":"fn foo_decl_item(x: &mut i32) {\n x = 3;\n}","expected":"fn foo_decl_item(x: &mut i32) { x = 3; }"},{"original_begin_line":21,"original_end_line":21,"expected_begin_line":13,"expected_end_line":13,"original":" fn empty() {","expected":"fn empty() {}"},{"original_begin_line":23,"original_end_line":23,"expected_begin_line":15,"expected_end_line":15,"original":"}","expected":"fn foo_return() -> String { \"yay\" }"},{"original_begin_line":25,"original_end_line":29,"expected_begin_line":17,"expected_end_line":20,"original":"fn foo_return() -> String {\n \"yay\"\n}\n\nfn foo_where() -> T where T: Sync {","expected":"fn foo_where() -> T\nwhere\n T: Sync,\n{"},{"original_begin_line":64,"original_end_line":66,"expected_begin_line":55,"expected_end_line":55,"original":"fn lots_of_space () {\n 1 \n}","expected":"fn lots_of_space() { 1 }"},{"original_begin_line":71,"original_end_line":72,"expected_begin_line":60,"expected_end_line":60,"original":" fn dummy(&self) {\n }","expected":" fn dummy(&self) {}"},{"original_begin_line":75,"original_end_line":75,"expected_begin_line":63,"expected_end_line":64,"original":"trait CoolerTypes { fn dummy(&self) { ","expected":"trait CoolerTypes {\n fn dummy(&self) {}"},{"original_begin_line":77,"original_end_line":77,"expected_begin_line":66,"expected_end_line":66,"original":"}","expected":""},{"original_begin_line":79,"original_end_line":79,"expected_begin_line":67,"expected_end_line":70,"original":"fn Foo() where T: Bar {","expected":"fn Foo()\nwhere\n T: Bar,\n{"}]}] \ No newline at end of file diff --git a/src/version b/src/version index a63cb35e6f..154cb93b2c 100644 --- a/src/version +++ b/src/version @@ -1 +1 @@ -1.52.0 +1.52.1 diff --git a/vendor/annotate-snippets-0.6.1/.cargo-checksum.json b/vendor/annotate-snippets-0.6.1/.cargo-checksum.json new file mode 100644 index 0000000000..13074988fc --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"230269942a7482e8a1c77dd30e22db58b4c7e3fa42dd5203e40e93d6bbcea175","Cargo.toml":"984545ef81f26c2d0087367044b65f9eb1f8c75c6a160c717d37caea5058452b","LICENSE-APACHE":"c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4","LICENSE-MIT":"49c0b000c03731d9e3970dc059ad4ca345d773681f4a612b0024435b663e0220","README.md":"8974c4d36d39dff6dabea6fcef6d04c001bd4fe46ac28e6cd18206a7d96ff9b1","examples/expected_type.rs":"cc8e81dcbdbe31cb1e3d2b2b951c8eeb975789c68c8511f1436b9464752396df","examples/footer.rs":"485dd393b03ad1958549db08b521be9f67ee61400e3181793353e927ec2aa414","examples/format.rs":"a6f1e6b51b13b883f0636aca177fa119db8095faaff58c3c5f2712537d9b08bc","examples/multislice.rs":"0afeed0bb0af412c2f1b5709e536a42186f492602d11869f4c058e8ccb30ceda","src/display_list/from_snippet.rs":"354ed2170db40e8324d3e2c28fe938623b45e4993d6124d3d4979a1546664d09","src/display_list/mod.rs":"e1810ba12bf48515d063075b9cdda3ede667d58bb33e75b1de27c392fbe11836","src/display_list/structs.rs":"d6a13ba21ced6032db262eca1aa50c0a6f738b9f0a778deb6b88a072fc54f335","src/formatter/mod.rs":"8b8414e7a0abc24c2d22d043d59b09da4f0f3cfdebed517e769de9e453765eac","src/formatter/style.rs":"6f3049e6d18fb9629a752f1bdc4ef720b1675ff69f07db55c3e7debb3885ad87","src/lib.rs":"fd655cb0518b4d11d5ff3a92eceefc0f45697c430197e95d3b8bed3bbf90325f","src/snippet.rs":"904d642357a4ed5d2a0387e7e9253ec4631ef852c7188507f98e474b16fd5adf","src/stylesheets/color.rs":"081f025ab42091dea42eda719d997e7fdf6a949885ead46e0d614dfa47c6a3a4","src/stylesheets/mod.rs":"ef8038360631561f53a4472cffe45e1fbde04f4aed8d4738426da88214b94576","src/stylesheets/no_color.rs":"0d25c1253658141b24fab1440b2b39a3fffe71952fc3667d2e2804a471766cf9","tests/diff/mod.rs":"44a8f8ef3b90db894227d6451b3bcbaa158c6daad5e8951456b979de6687b8e1","tests/dl_from_snippet.rs":"468eea5c1811b0578e923cdeaa6b7d8c159d4ac517c8802e332554e709df7038","tests/fixtures.rs":"25af2bd1891997b07bae8425e82a1ec89c04de29645188e83d6eb506948fb1b7","tests/fixtures/no-color/multiline_annotation.txt":"b8d36204f260d91e99672fe11839e1fe416f6a18b6de46c923d43c0430a97ee7","tests/fixtures/no-color/multiline_annotation.yaml":"fab49391fa0c53ffe3b997f70c5615c0b767c21de105b0a3805a692085e641ae","tests/fixtures/no-color/multiline_annotation2.txt":"391da0ef9bec5de45a30a0869ecc3227a1d3272872508af1d7a294fa431c8dd2","tests/fixtures/no-color/multiline_annotation2.yaml":"aa2fb2c8666f1c625c92dd1630dd643ba304617334c077b169e5a27be0c116af","tests/fixtures/no-color/multiple_annotations.txt":"b284ab83d9de3aa9d014d3d04ac199517967885bc064f0462c15d6a4d71554aa","tests/fixtures/no-color/multiple_annotations.yaml":"7892a2eb95a0442e3926c31482202410d867a87ef3d35c6c4599ebd6e5c33d31","tests/fixtures/no-color/simple.txt":"cb9b7fab003c58e03ce87cae4fd04a40983ff3f8ee329b494dcb3abcf576fccd","tests/fixtures/no-color/simple.yaml":"9650f0a4ff157c7daf6cc3fc33179173f1cd4c1ace298501efca3da134aec1e9","tests/formatter.rs":"9a6be74db49bb3d5d91d3c42430f7d0585911b6702fb017063ed02063c50507d","tests/snippet/mod.rs":"e2f2b073590ba2632196ea2bdc26832a9ab986e1955721a578db7a723e21a37e"},"package":"c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7"} \ No newline at end of file diff --git a/vendor/annotate-snippets-0.6.1/CHANGELOG.md b/vendor/annotate-snippets-0.6.1/CHANGELOG.md new file mode 100644 index 0000000000..a125e8fca3 --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +## Unreleased + + - … + +## annotate-snippets 0.6.1 (July 23, 2019) + + - Fix too many anonymized line numbers (#5) + +## annotate-snippets 0.6.0 (June 26, 2019) + + - Add an option to anonymize line numbers (#3) + - Transition the crate to rust-lang org. + - Update the syntax to Rust 2018 idioms. (#4) diff --git a/vendor/annotate-snippets-0.6.1/Cargo.toml b/vendor/annotate-snippets-0.6.1/Cargo.toml new file mode 100644 index 0000000000..1f157d28ca --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/Cargo.toml @@ -0,0 +1,55 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "annotate-snippets" +version = "0.6.1" +authors = ["Zibi Braniecki "] +description = "Library for building code annotations" +readme = "README.md" +keywords = ["code", "analysis", "ascii", "errors", "debug"] +license = "Apache-2.0/MIT" +repository = "https://github.com/rust-lang/annotate-snippets-rs" +[dependencies.ansi_term] +version = "0.11.0" +optional = true +[dev-dependencies.ansi_term] +version = "^0.12" + +[dev-dependencies.difference] +version = "^2.0" + +[dev-dependencies.glob] +version = "^0.3" + +[dev-dependencies.serde] +version = "^1.0" +features = ["derive"] + +[dev-dependencies.serde_yaml] +version = "^0.8" + +[features] +color = ["ansi_term"] +default = [] +[badges.coveralls] +branch = "master" +repository = "rust-lang/annotate-snippets-rs" +service = "github" + +[badges.maintenance] +status = "actively-developed" + +[badges.travis-ci] +branch = "master" +repository = "rust-lang/annotate-snippets-rs" diff --git a/vendor/annotate-snippets-0.6.1/LICENSE-APACHE b/vendor/annotate-snippets-0.6.1/LICENSE-APACHE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/annotate-snippets-0.6.1/LICENSE-MIT b/vendor/annotate-snippets-0.6.1/LICENSE-MIT new file mode 100644 index 0000000000..5655fa311c --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright 2017 Mozilla + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/annotate-snippets-0.6.1/README.md b/vendor/annotate-snippets-0.6.1/README.md new file mode 100644 index 0000000000..a690007b0f --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/README.md @@ -0,0 +1,88 @@ +# annotate-snippets + +`annotate-snippets` is a Rust library for annotation of programming code slices. + +[![crates.io](http://meritbadge.herokuapp.com/annotate-snippets)](https://crates.io/crates/annotate-snippets) +[![Build Status](https://travis-ci.org/rust-lang/annotate-snippets-rs.svg?branch=master)](https://travis-ci.org/rust-lang/annotate-snippets-rs) +[![Coverage Status](https://coveralls.io/repos/github/rust-lang/annotate-snippets-rs/badge.svg?branch=master)](https://coveralls.io/github/rust-lang/annotate-snippets-rs?branch=master) + +The library helps visualize meta information annotating source code slices. +It takes a data structure called `Snippet` on the input and produces a `String` +which may look like this: + +```text +error[E0308]: mismatched types + --> src/format.rs:52:1 + | +51 | ) -> Option { + | -------------- expected `Option` because of return type +52 | / for ann in annotations { +53 | | match (ann.range.0, ann.range.1) { +54 | | (None, None) => continue, +55 | | (Some(start), Some(end)) if start > end_index => continue, +... | +71 | | } +72 | | } + | |_____^ expected enum `std::option::Option`, found () +``` + +[Documentation][] + +[Documentation]: https://docs.rs/annotate-snippets/ + +Usage +----- + +```rust +use annotate_snippets::snippet; + +fn main() { + let snippet = Snippet { + title: Some(Annotation { + label: Some("expected type, found `22`".to_string()), + id: None, + annotation_type: AnnotationType::Error, + }), + footer: vec![], + slices: vec![ + Slice { + source: r#" +This is an example +content of the slice +which will be annotated +with the list of annotations below. + "#.to_string(), + line_start: 26, + origin: Some("examples/example.txt".to_string()), + fold: false, + annotations: vec![ + SourceAnnotation { + label: "Example error annotation".to_string(), + annotation_type: AnnotationType::Error, + range: (13, 18), + }, + SourceAnnotation { + label: "and here's a warning".to_string(), + annotation_type: AnnotationType::Warning, + range: (34, 50), + }, + ], + }, + ], + }; + + let dl = DisplayList::from(snippet); + let dlf = DisplayListFormatter::new(true); + dlf.format(&dl); +} +``` + +Local Development +----------------- + + cargo build + cargo test + +When submitting a PR please use [`cargo fmt`][] (nightly). + +[`cargo fmt`]: https://github.com/rust-lang-nursery/rustfmt diff --git a/vendor/annotate-snippets-0.6.1/examples/expected_type.rs b/vendor/annotate-snippets-0.6.1/examples/expected_type.rs new file mode 100644 index 0000000000..20cda66889 --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/examples/expected_type.rs @@ -0,0 +1,40 @@ +use annotate_snippets::display_list::DisplayList; +use annotate_snippets::formatter::DisplayListFormatter; +use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; + +fn main() { + let snippet = Snippet { + title: Some(Annotation { + label: Some("expected type, found `22`".to_string()), + id: None, + annotation_type: AnnotationType::Error, + }), + footer: vec![], + slices: vec![Slice { + source: r#" annotations: vec![SourceAnnotation { + label: "expected struct `annotate_snippets::snippet::Slice`, found reference" + .to_string(), + range: <22, 25>,"# + .to_string(), + line_start: 26, + origin: Some("examples/footer.rs".to_string()), + fold: true, + annotations: vec![ + SourceAnnotation { + label: "".to_string(), + annotation_type: AnnotationType::Error, + range: (208, 210), + }, + SourceAnnotation { + label: "while parsing this struct".to_string(), + annotation_type: AnnotationType::Info, + range: (34, 50), + }, + ], + }], + }; + + let dl = DisplayList::from(snippet); + let dlf = DisplayListFormatter::new(true, false); + println!("{}", dlf.format(&dl)); +} diff --git a/vendor/annotate-snippets-0.6.1/examples/footer.rs b/vendor/annotate-snippets-0.6.1/examples/footer.rs new file mode 100644 index 0000000000..1b03910fe6 --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/examples/footer.rs @@ -0,0 +1,37 @@ +use annotate_snippets::display_list::DisplayList; +use annotate_snippets::formatter::DisplayListFormatter; +use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; + +fn main() { + let snippet = Snippet { + title: Some(Annotation { + label: Some("mismatched types".to_string()), + id: Some("E0308".to_string()), + annotation_type: AnnotationType::Error, + }), + footer: vec![Annotation { + label: Some( + "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`" + .to_string(), + ), + id: None, + annotation_type: AnnotationType::Note, + }], + slices: vec![Slice { + source: " slices: vec![\"A\",".to_string(), + line_start: 13, + origin: Some("src/multislice.rs".to_string()), + fold: false, + annotations: vec![SourceAnnotation { + label: "expected struct `annotate_snippets::snippet::Slice`, found reference" + .to_string(), + range: (21, 24), + annotation_type: AnnotationType::Error, + }], + }], + }; + + let dl = DisplayList::from(snippet); + let dlf = DisplayListFormatter::new(true, false); + println!("{}", dlf.format(&dl)); +} diff --git a/vendor/annotate-snippets-0.6.1/examples/format.rs b/vendor/annotate-snippets-0.6.1/examples/format.rs new file mode 100644 index 0000000000..7e41802eb8 --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/examples/format.rs @@ -0,0 +1,58 @@ +use annotate_snippets::display_list::DisplayList; +use annotate_snippets::formatter::DisplayListFormatter; +use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; + +fn main() { + let snippet = Snippet { + slices: vec![Slice { + source: r#") -> Option { + for ann in annotations { + match (ann.range.0, ann.range.1) { + (None, None) => continue, + (Some(start), Some(end)) if start > end_index => continue, + (Some(start), Some(end)) if start >= start_index => { + let label = if let Some(ref label) = ann.label { + format!(" {}", label) + } else { + String::from("") + }; + + return Some(format!( + "{}{}{}", + " ".repeat(start - start_index), + "^".repeat(end - start), + label + )); + } + _ => continue, + } + }"# + .to_string(), + line_start: 51, + origin: Some("src/format.rs".to_string()), + fold: false, + annotations: vec![ + SourceAnnotation { + label: "expected `Option` because of return type".to_string(), + annotation_type: AnnotationType::Warning, + range: (5, 19), + }, + SourceAnnotation { + label: "expected enum `std::option::Option`".to_string(), + annotation_type: AnnotationType::Error, + range: (23, 745), + }, + ], + }], + title: Some(Annotation { + label: Some("mismatched types".to_string()), + id: Some("E0308".to_string()), + annotation_type: AnnotationType::Error, + }), + footer: vec![], + }; + + let dl = DisplayList::from(snippet); + let dlf = DisplayListFormatter::new(true, false); + println!("{}", dlf.format(&dl)); +} diff --git a/vendor/annotate-snippets-0.6.1/examples/multislice.rs b/vendor/annotate-snippets-0.6.1/examples/multislice.rs new file mode 100644 index 0000000000..af1cb152bc --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/examples/multislice.rs @@ -0,0 +1,34 @@ +use annotate_snippets::display_list::DisplayList; +use annotate_snippets::formatter::DisplayListFormatter; +use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet}; + +fn main() { + let snippet = Snippet { + title: Some(Annotation { + label: Some("mismatched types".to_string()), + id: None, + annotation_type: AnnotationType::Error, + }), + footer: vec![], + slices: vec![ + Slice { + source: "Foo".to_string(), + line_start: 51, + origin: Some("src/format.rs".to_string()), + fold: false, + annotations: vec![], + }, + Slice { + source: "Faa".to_string(), + line_start: 129, + origin: Some("src/display.rs".to_string()), + fold: false, + annotations: vec![], + }, + ], + }; + + let dl = DisplayList::from(snippet); + let dlf = DisplayListFormatter::new(true, false); + println!("{}", dlf.format(&dl)); +} diff --git a/vendor/annotate-snippets-0.6.1/src/display_list/from_snippet.rs b/vendor/annotate-snippets-0.6.1/src/display_list/from_snippet.rs new file mode 100644 index 0000000000..e3d79ed4b1 --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/src/display_list/from_snippet.rs @@ -0,0 +1,403 @@ +//! Trait for converting `Snippet` to `DisplayList`. +use super::*; +use crate::snippet; + +fn format_label(label: Option<&str>, style: Option) -> Vec { + let mut result = vec![]; + if let Some(label) = label { + let elements: Vec<&str> = label.split("__").collect(); + for (idx, element) in elements.iter().enumerate() { + let element_style = match style { + Some(s) => s, + None => { + if idx % 2 == 0 { + DisplayTextStyle::Regular + } else { + DisplayTextStyle::Emphasis + } + } + }; + result.push(DisplayTextFragment { + content: element.to_string(), + style: element_style, + }); + } + } + result +} + +fn format_title(annotation: &snippet::Annotation) -> DisplayLine { + let label = annotation.label.clone().unwrap_or_default(); + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::from(annotation.annotation_type), + id: annotation.id.clone(), + label: format_label(Some(&label), Some(DisplayTextStyle::Emphasis)), + }, + source_aligned: false, + continuation: false, + }) +} + +fn format_annotation(annotation: &snippet::Annotation) -> Vec { + let mut result = vec![]; + let label = annotation.label.clone().unwrap_or_default(); + for (i, line) in label.lines().enumerate() { + result.push(DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::from(annotation.annotation_type), + id: None, + label: format_label(Some(line), None), + }, + source_aligned: true, + continuation: i != 0, + })); + } + result +} + +fn format_slice(slice: &snippet::Slice, is_first: bool, has_footer: bool) -> Vec { + let mut body = format_body(slice, has_footer); + let mut result = vec![]; + + let header = format_header(slice, &body, is_first); + if let Some(header) = header { + result.push(header); + } + result.append(&mut body); + result +} + +fn format_header( + slice: &snippet::Slice, + body: &[DisplayLine], + is_first: bool, +) -> Option { + let main_annotation = slice.annotations.get(0); + + let display_header = if is_first { + DisplayHeaderType::Initial + } else { + DisplayHeaderType::Continuation + }; + + if let Some(annotation) = main_annotation { + let mut col = 1; + let mut row = slice.line_start; + + for item in body.iter() { + if let DisplayLine::Source { + line: DisplaySourceLine::Content { range, .. }, + .. + } = item + { + if annotation.range.0 >= range.0 && annotation.range.0 <= range.1 { + col = annotation.range.0 - range.0; + break; + } + row += 1; + } + } + if let Some(ref path) = slice.origin { + return Some(DisplayLine::Raw(DisplayRawLine::Origin { + path: path.to_string(), + pos: Some((row, col)), + header_type: display_header, + })); + } + } + if let Some(ref path) = slice.origin { + return Some(DisplayLine::Raw(DisplayRawLine::Origin { + path: path.to_string(), + pos: None, + header_type: display_header, + })); + } + None +} + +fn fold_body(body: &[DisplayLine]) -> Vec { + let mut new_body = vec![]; + + let mut no_annotation_lines_counter = 0; + let mut idx = 0; + + while idx < body.len() { + match body[idx] { + DisplayLine::Source { + line: DisplaySourceLine::Annotation { .. }, + ref inline_marks, + .. + } => { + if no_annotation_lines_counter > 2 { + let fold_start = idx - no_annotation_lines_counter; + let fold_end = idx; + let pre_len = if no_annotation_lines_counter > 8 { + 4 + } else { + 0 + }; + let post_len = if no_annotation_lines_counter > 8 { + 2 + } else { + 1 + }; + for item in body.iter().take(fold_start + pre_len).skip(fold_start) { + new_body.push(item.clone()); + } + new_body.push(DisplayLine::Fold { + inline_marks: inline_marks.clone(), + }); + for item in body.iter().take(fold_end).skip(fold_end - post_len) { + new_body.push(item.clone()); + } + } else { + let start = idx - no_annotation_lines_counter; + for item in body.iter().take(idx).skip(start) { + new_body.push(item.clone()); + } + } + no_annotation_lines_counter = 0; + } + DisplayLine::Source { .. } => { + no_annotation_lines_counter += 1; + idx += 1; + continue; + } + _ => { + no_annotation_lines_counter += 1; + } + } + new_body.push(body[idx].clone()); + idx += 1; + } + + new_body +} + +fn format_body(slice: &snippet::Slice, has_footer: bool) -> Vec { + let mut body = vec![]; + + let mut current_line = slice.line_start; + let mut current_index = 0; + let mut line_index_ranges = vec![]; + + for line in slice.source.lines() { + let line_length = line.chars().count() + 1; + let line_range = (current_index, current_index + line_length); + body.push(DisplayLine::Source { + lineno: Some(current_line), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: line.to_string(), + range: line_range, + }, + }); + line_index_ranges.push(line_range); + current_line += 1; + current_index += line_length + 1; + } + + let mut annotation_line_count = 0; + let mut annotations = slice.annotations.clone(); + for idx in 0..body.len() { + let (line_start, line_end) = line_index_ranges[idx]; + // It would be nice to use filter_drain here once it's stable. + annotations = annotations + .into_iter() + .filter(|annotation| { + let body_idx = idx + annotation_line_count; + let annotation_type = match annotation.annotation_type { + snippet::AnnotationType::Error => DisplayAnnotationType::None, + snippet::AnnotationType::Warning => DisplayAnnotationType::None, + _ => DisplayAnnotationType::from(annotation.annotation_type), + }; + match annotation.range { + (start, _) if start > line_end => true, + (start, end) if start >= line_start && end <= line_end + 1 => { + let range = (start - line_start, end - line_start); + body.insert( + body_idx + 1, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type, + id: None, + label: format_label(Some(&annotation.label), None), + }, + range, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + ); + annotation_line_count += 1; + false + } + (start, end) if start >= line_start && start <= line_end && end > line_end => { + if start - line_start == 0 { + if let DisplayLine::Source { + ref mut inline_marks, + .. + } = body[body_idx] + { + inline_marks.push(DisplayMark { + mark_type: DisplayMarkType::AnnotationStart, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }); + } + } else { + let range = (start - line_start, start - line_start + 1); + body.insert( + body_idx + 1, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![], + }, + range, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + annotation_part: DisplayAnnotationPart::MultilineStart, + }, + }, + ); + annotation_line_count += 1; + } + true + } + (start, end) if start < line_start && end > line_end => { + if let DisplayLine::Source { + ref mut inline_marks, + .. + } = body[body_idx] + { + inline_marks.push(DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }); + } + true + } + (start, end) if start < line_start && end >= line_start && end <= line_end => { + if let DisplayLine::Source { + ref mut inline_marks, + .. + } = body[body_idx] + { + inline_marks.push(DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }); + } + let range = (end - line_start, end - line_start + 1); + body.insert( + body_idx + 1, + DisplayLine::Source { + lineno: None, + inline_marks: vec![DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type, + id: None, + label: format_label(Some(&annotation.label), None), + }, + range, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + annotation_part: DisplayAnnotationPart::MultilineEnd, + }, + }, + ); + annotation_line_count += 1; + false + } + _ => true, + } + }) + .collect(); + } + + if slice.fold { + body = fold_body(&body); + } + + body.insert( + 0, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ); + if has_footer { + body.push(DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }); + } else if let Some(DisplayLine::Source { .. }) = body.last() { + body.push(DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }); + } + body +} + +impl From for DisplayList { + fn from(snippet: snippet::Snippet) -> Self { + let mut body = vec![]; + if let Some(annotation) = snippet.title { + body.push(format_title(&annotation)); + } + + for (idx, slice) in snippet.slices.iter().enumerate() { + body.append(&mut format_slice( + &slice, + idx == 0, + !snippet.footer.is_empty(), + )); + } + + for annotation in snippet.footer { + body.append(&mut format_annotation(&annotation)); + } + + Self { body } + } +} + +impl From for DisplayAnnotationType { + fn from(at: snippet::AnnotationType) -> Self { + match at { + snippet::AnnotationType::Error => DisplayAnnotationType::Error, + snippet::AnnotationType::Warning => DisplayAnnotationType::Warning, + snippet::AnnotationType::Info => DisplayAnnotationType::Info, + snippet::AnnotationType::Note => DisplayAnnotationType::Note, + snippet::AnnotationType::Help => DisplayAnnotationType::Help, + } + } +} diff --git a/vendor/annotate-snippets-0.6.1/src/display_list/mod.rs b/vendor/annotate-snippets-0.6.1/src/display_list/mod.rs new file mode 100644 index 0000000000..5e0b393d66 --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/src/display_list/mod.rs @@ -0,0 +1,124 @@ +//! display_list module stores the output model for the snippet. +//! +//! `DisplayList` is a central structure in the crate, which contains +//! the structured list of lines to be displayed. +//! +//! It is made of two types of lines: `Source` and `Raw`. All `Source` lines +//! are structured using four columns: +//! +//! ```text +//! /------------ (1) Line number column. +//! | /--------- (2) Line number column delimiter. +//! | | /------- (3) Inline marks column. +//! | | | /--- (4) Content column with the source and annotations for slices. +//! | | | | +//! ============================================================================= +//! error[E0308]: mismatched types +//! --> src/format.rs:51:5 +//! | +//! 151 | / fn test() -> String { +//! 152 | | return "test"; +//! 153 | | } +//! | |___^ error: expected `String`, for `&str`. +//! | +//! ``` +//! +//! The first two lines of the example above are `Raw` lines, while the rest +//! are `Source` lines. +//! +//! `DisplayList` does not store column alignment information, and those are +//! only calculated by the `DisplayListFormatter` using information such as +//! styling. +//! +//! The above snippet has been built out of the following structure: +//! +//! ``` +//! use annotate_snippets::display_list::*; +//! +//! let dl = DisplayList { +//! body: vec![ +//! DisplayLine::Raw(DisplayRawLine::Annotation { +//! annotation: Annotation { +//! annotation_type: DisplayAnnotationType::Error, +//! id: Some("E0308".to_string()), +//! label: vec![ +//! DisplayTextFragment { +//! content: "mismatched types".to_string(), +//! style: DisplayTextStyle::Regular, +//! } +//! ] +//! }, +//! source_aligned: false, +//! continuation: false, +//! }), +//! DisplayLine::Raw(DisplayRawLine::Origin { +//! path: "src/format.rs".to_string(), +//! pos: Some((51, 5)), +//! header_type: DisplayHeaderType::Initial, +//! }), +//! DisplayLine::Source { +//! lineno: Some(151), +//! inline_marks: vec![ +//! DisplayMark { +//! mark_type: DisplayMarkType::AnnotationStart, +//! annotation_type: DisplayAnnotationType::Error, +//! } +//! ], +//! line: DisplaySourceLine::Content { +//! text: " fn test() -> String {".to_string(), +//! range: (0, 24) +//! } +//! }, +//! DisplayLine::Source { +//! lineno: Some(152), +//! inline_marks: vec![ +//! DisplayMark { +//! mark_type: DisplayMarkType::AnnotationThrough, +//! annotation_type: DisplayAnnotationType::Error, +//! } +//! ], +//! line: DisplaySourceLine::Content { +//! text: " return \"test\";".to_string(), +//! range: (25, 46) +//! } +//! }, +//! DisplayLine::Source { +//! lineno: Some(153), +//! inline_marks: vec![ +//! DisplayMark { +//! mark_type: DisplayMarkType::AnnotationThrough, +//! annotation_type: DisplayAnnotationType::Error, +//! } +//! ], +//! line: DisplaySourceLine::Content { +//! text: " }".to_string(), +//! range: (47, 51) +//! } +//! }, +//! DisplayLine::Source { +//! lineno: None, +//! inline_marks: vec![], +//! line: DisplaySourceLine::Annotation { +//! annotation: Annotation { +//! annotation_type: DisplayAnnotationType::Error, +//! id: None, +//! label: vec![ +//! DisplayTextFragment { +//! content: "expected `String`, for `&str`.".to_string(), +//! style: DisplayTextStyle::Regular, +//! } +//! ] +//! }, +//! range: (3, 4), +//! annotation_type: DisplayAnnotationType::Error, +//! annotation_part: DisplayAnnotationPart::MultilineEnd, +//! } +//! +//! } +//! ] +//! }; +//! ``` +mod from_snippet; +mod structs; + +pub use self::structs::*; diff --git a/vendor/annotate-snippets-0.6.1/src/display_list/structs.rs b/vendor/annotate-snippets-0.6.1/src/display_list/structs.rs new file mode 100644 index 0000000000..0d3e0bc5b6 --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/src/display_list/structs.rs @@ -0,0 +1,253 @@ +/// List of lines to be displayed. +#[derive(Debug, Clone, PartialEq)] +pub struct DisplayList { + pub body: Vec, +} + +impl From> for DisplayList { + fn from(body: Vec) -> Self { + Self { body } + } +} + +/// Inline annotation which can be used in either Raw or Source line. +#[derive(Debug, Clone, PartialEq)] +pub struct Annotation { + pub annotation_type: DisplayAnnotationType, + pub id: Option, + pub label: Vec, +} + +/// A single line used in `DisplayList`. +#[derive(Debug, Clone, PartialEq)] +pub enum DisplayLine { + /// A line with `lineno` portion of the slice. + Source { + lineno: Option, + inline_marks: Vec, + line: DisplaySourceLine, + }, + + /// A line indicating a folded part of the slice. + Fold { inline_marks: Vec }, + + /// A line which is displayed outside of slices. + Raw(DisplayRawLine), +} + +/// A source line. +#[derive(Debug, Clone, PartialEq)] +pub enum DisplaySourceLine { + /// A line with the content of the Slice. + Content { + text: String, + range: (usize, usize), // meta information for annotation placement. + }, + + /// An annotation line which is displayed in context of the slice. + Annotation { + annotation: Annotation, + range: (usize, usize), + annotation_type: DisplayAnnotationType, + annotation_part: DisplayAnnotationPart, + }, + + /// An empty source line. + Empty, +} + +/// Raw line - a line which does not have the `lineno` part and is not considered +/// a part of the snippet. +#[derive(Debug, Clone, PartialEq)] +pub enum DisplayRawLine { + /// A line which provides information about the location of the given + /// slice in the project structure. + Origin { + path: String, + pos: Option<(usize, usize)>, + header_type: DisplayHeaderType, + }, + + /// An annotation line which is not part of any snippet. + Annotation { + annotation: Annotation, + + /// If set to `true`, the annotation will be aligned to the + /// lineno delimiter of the snippet. + source_aligned: bool, + /// If set to `true`, only the label of the `Annotation` will be + /// displayed. It allows for a multiline annotation to be aligned + /// without displaing the meta information (`type` and `id`) to be + /// displayed on each line. + continuation: bool, + }, +} + +/// An inline text fragment which any label is composed of. +#[derive(Debug, Clone, PartialEq)] +pub struct DisplayTextFragment { + pub content: String, + pub style: DisplayTextStyle, +} + +/// A style for the `DisplayTextFragment` which can be visually formatted. +/// +/// This information may be used to emphasis parts of the label. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DisplayTextStyle { + Regular, + Emphasis, +} + +/// An indicator of what part of the annotation a given `Annotation` is. +#[derive(Debug, Clone, PartialEq)] +pub enum DisplayAnnotationPart { + /// A standalone, single-line annotation. + Standalone, + /// A continuation of a multi-line label of an annotation. + LabelContinuation, + /// A consequitive annotation in case multiple annotations annotate a single line. + Consequitive, + /// A line starting a multiline annotation. + MultilineStart, + /// A line ending a multiline annotation. + MultilineEnd, +} + +/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`. +#[derive(Debug, Clone, PartialEq)] +pub struct DisplayMark { + pub mark_type: DisplayMarkType, + pub annotation_type: DisplayAnnotationType, +} + +/// A type of the `DisplayMark`. +#[derive(Debug, Clone, PartialEq)] +pub enum DisplayMarkType { + /// A mark indicating a multiline annotation going through the current line. + /// + /// Example: + /// ``` + /// use annotate_snippets::display_list::*; + /// use annotate_snippets::formatter::DisplayListFormatter; + /// + /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors + /// + /// let dl = DisplayList { + /// body: vec![ + /// DisplayLine::Source { + /// lineno: Some(51), + /// inline_marks: vec![ + /// DisplayMark { + /// mark_type: DisplayMarkType::AnnotationThrough, + /// annotation_type: DisplayAnnotationType::Error, + /// } + /// ], + /// line: DisplaySourceLine::Content { + /// text: "Example".to_string(), + /// range: (0, 7), + /// } + /// } + /// ] + /// }; + /// assert_eq!(dlf.format(&dl), "51 | | Example"); + /// ``` + AnnotationThrough, + + /// A mark indicating a multiline annotation starting on the given line. + /// + /// Example: + /// ``` + /// use annotate_snippets::display_list::*; + /// use annotate_snippets::formatter::DisplayListFormatter; + /// + /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors + /// + /// let dl = DisplayList { + /// body: vec![ + /// DisplayLine::Source { + /// lineno: Some(51), + /// inline_marks: vec![ + /// DisplayMark { + /// mark_type: DisplayMarkType::AnnotationStart, + /// annotation_type: DisplayAnnotationType::Error, + /// } + /// ], + /// line: DisplaySourceLine::Content { + /// text: "Example".to_string(), + /// range: (0, 7), + /// } + /// } + /// ] + /// }; + /// assert_eq!(dlf.format(&dl), "51 | / Example"); + /// ``` + AnnotationStart, +} + +/// A type of the `Annotation` which may impact the sigils, style or text displayed. +/// +/// There are several ways in which the `DisplayListFormatter` uses this information +/// when formatting the `DisplayList`: +/// +/// * An annotation may display the name of the type like `error` or `info`. +/// * An underline for `Error` may be `^^^` while for `Warning` it coule be `---`. +/// * `ColorStylesheet` may use different colors for different annotations. +#[derive(Debug, Clone, PartialEq)] +pub enum DisplayAnnotationType { + None, + Error, + Warning, + Info, + Note, + Help, +} + +/// Information whether the header is the initial one or a consequitive one +/// for multi-slice cases. +#[derive(Debug, Clone, PartialEq)] +pub enum DisplayHeaderType { + /// Initial header is the first header in the snippet. + /// + /// Example: + /// ``` + /// use annotate_snippets::display_list::*; + /// use annotate_snippets::formatter::DisplayListFormatter; + /// + /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors + /// + /// let dl = DisplayList { + /// body: vec![ + /// DisplayLine::Raw(DisplayRawLine::Origin { + /// path: "file1.rs".to_string(), + /// pos: Some((51, 5)), + /// header_type: DisplayHeaderType::Initial, + /// }) + /// ] + /// }; + /// assert_eq!(dlf.format(&dl), "--> file1.rs:51:5"); + /// ``` + Initial, + + /// Continuation marks all headers of following slices in the snippet. + /// + /// Example: + /// ``` + /// use annotate_snippets::display_list::*; + /// use annotate_snippets::formatter::DisplayListFormatter; + /// + /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors + /// + /// let dl = DisplayList { + /// body: vec![ + /// DisplayLine::Raw(DisplayRawLine::Origin { + /// path: "file1.rs".to_string(), + /// pos: Some((51, 5)), + /// header_type: DisplayHeaderType::Continuation, + /// }) + /// ] + /// }; + /// assert_eq!(dlf.format(&dl), "::: file1.rs:51:5"); + /// ``` + Continuation, +} diff --git a/vendor/annotate-snippets-0.6.1/src/formatter/mod.rs b/vendor/annotate-snippets-0.6.1/src/formatter/mod.rs new file mode 100644 index 0000000000..654b51893b --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/src/formatter/mod.rs @@ -0,0 +1,367 @@ +//! DisplayListFormatter is a module handling the formatting of a +//! `DisplayList` into a formatted string. +//! +//! Besides formatting into a string it also uses a `style::Stylesheet` to +//! provide additional styling like colors and emphasis to the text. + +pub mod style; + +use self::style::{Style, StyleClass, Stylesheet}; +use crate::display_list::*; +use std::cmp; + +use crate::stylesheets::no_color::NoColorStylesheet; +#[cfg(feature = "ansi_term")] +use crate::stylesheets::color::AnsiTermStylesheet; + +fn repeat_char(c: char, n: usize) -> String { + let mut s = String::with_capacity(c.len_utf8()); + s.push(c); + s.repeat(n) +} + +/// DisplayListFormatter' constructor accepts two arguments: +/// +/// * `color` allows the formatter to optionally apply colors and emphasis +/// using the `ansi_term` crate. +/// * `anonymized_line_numbers` will replace line numbers in the left column with the text `LL`. +/// +/// Example: +/// +/// ``` +/// use annotate_snippets::formatter::DisplayListFormatter; +/// use annotate_snippets::display_list::{DisplayList, DisplayLine, DisplaySourceLine}; +/// +/// let dlf = DisplayListFormatter::new(false, false); // Don't use colors, Don't anonymize line numbers +/// +/// let dl = DisplayList { +/// body: vec![ +/// DisplayLine::Source { +/// lineno: Some(192), +/// inline_marks: vec![], +/// line: DisplaySourceLine::Content { +/// text: "Example line of text".into(), +/// range: (0, 21) +/// } +/// } +/// ] +/// }; +/// assert_eq!(dlf.format(&dl), "192 | Example line of text"); +/// ``` +pub struct DisplayListFormatter { + stylesheet: Box, + anonymized_line_numbers: bool, +} + +impl DisplayListFormatter { + const ANONYMIZED_LINE_NUM: &'static str = "LL"; + + /// Constructor for the struct. + /// + /// The argument `color` selects the stylesheet depending on the user preferences and + /// `ansi_term` crate availability. + /// + /// The argument `anonymized_line_numbers` will replace line numbers in the left column with + /// the text `LL`. This can be useful to enable when running UI tests, such as in the Rust + /// test suite. + pub fn new(color: bool, anonymized_line_numbers: bool) -> Self { + if color { + Self { + #[cfg(feature = "ansi_term")] + stylesheet: Box::new(AnsiTermStylesheet {}), + #[cfg(not(feature = "ansi_term"))] + stylesheet: Box::new(NoColorStylesheet {}), + anonymized_line_numbers, + } + } else { + Self { + stylesheet: Box::new(NoColorStylesheet {}), + anonymized_line_numbers, + } + } + } + + /// Formats a `DisplayList` into a String. + pub fn format(&self, dl: &DisplayList) -> String { + let lineno_width = dl.body.iter().fold(0, |max, line| match line { + DisplayLine::Source { + lineno: Some(lineno), + .. + } => { + if self.anonymized_line_numbers { + Self::ANONYMIZED_LINE_NUM.len() + } else { + cmp::max(lineno.to_string().len(), max) + } + }, + _ => max, + }); + let inline_marks_width = dl.body.iter().fold(0, |max, line| match line { + DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max), + _ => max, + }); + + dl.body + .iter() + .map(|line| self.format_line(line, lineno_width, inline_marks_width)) + .collect::>() + .join("\n") + } + + fn format_annotation_type(&self, annotation_type: &DisplayAnnotationType) -> &'static str { + match annotation_type { + DisplayAnnotationType::Error => "error", + DisplayAnnotationType::Warning => "warning", + DisplayAnnotationType::Info => "info", + DisplayAnnotationType::Note => "note", + DisplayAnnotationType::Help => "help", + DisplayAnnotationType::None => "", + } + } + + fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box { + self.stylesheet.get_style(match annotation_type { + DisplayAnnotationType::Error => StyleClass::Error, + DisplayAnnotationType::Warning => StyleClass::Warning, + DisplayAnnotationType::Info => StyleClass::Info, + DisplayAnnotationType::Note => StyleClass::Note, + DisplayAnnotationType::Help => StyleClass::Help, + DisplayAnnotationType::None => StyleClass::None, + }) + } + + fn format_label(&self, label: &[DisplayTextFragment]) -> String { + let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis); + label + .iter() + .map(|fragment| match fragment.style { + DisplayTextStyle::Regular => fragment.content.clone(), + DisplayTextStyle::Emphasis => emphasis_style.paint(&fragment.content), + }) + .collect::>() + .join("") + } + + fn format_annotation( + &self, + annotation: &Annotation, + continuation: bool, + in_source: bool, + ) -> String { + let color = self.get_annotation_style(&annotation.annotation_type); + let formatted_type = if let Some(ref id) = annotation.id { + format!( + "{}[{}]", + self.format_annotation_type(&annotation.annotation_type), + id + ) + } else { + self.format_annotation_type(&annotation.annotation_type) + .to_string() + }; + let label = self.format_label(&annotation.label); + + let label_part = if label.is_empty() { + "".to_string() + } else if in_source { + color.paint(&format!(": {}", self.format_label(&annotation.label))) + } else { + format!(": {}", self.format_label(&annotation.label)) + }; + if continuation { + let indent = formatted_type.len() + 2; + return format!("{}{}", repeat_char(' ', indent), label); + } + if !formatted_type.is_empty() { + format!("{}{}", color.paint(&formatted_type), label_part) + } else { + label + } + } + + fn format_source_line(&self, line: &DisplaySourceLine) -> Option { + match line { + DisplaySourceLine::Empty => None, + DisplaySourceLine::Content { text, .. } => Some(format!(" {}", text)), + DisplaySourceLine::Annotation { + range, + annotation, + annotation_type, + annotation_part, + } => { + let indent_char = match annotation_part { + DisplayAnnotationPart::Standalone => ' ', + DisplayAnnotationPart::LabelContinuation => ' ', + DisplayAnnotationPart::Consequitive => ' ', + DisplayAnnotationPart::MultilineStart => '_', + DisplayAnnotationPart::MultilineEnd => '_', + }; + let mark = match annotation_type { + DisplayAnnotationType::Error => '^', + DisplayAnnotationType::Warning => '-', + DisplayAnnotationType::Info => '-', + DisplayAnnotationType::Note => '-', + DisplayAnnotationType::Help => '-', + DisplayAnnotationType::None => ' ', + }; + let color = self.get_annotation_style(annotation_type); + let indent_length = match annotation_part { + DisplayAnnotationPart::LabelContinuation => range.1, + DisplayAnnotationPart::Consequitive => range.1, + _ => range.0, + }; + let indent = color.paint(&repeat_char(indent_char, indent_length + 1)); + let marks = color.paint(&repeat_char(mark, range.1 - indent_length)); + let annotation = self.format_annotation( + annotation, + annotation_part == &DisplayAnnotationPart::LabelContinuation, + true, + ); + if annotation.is_empty() { + return Some(format!("{}{}", indent, marks)); + } + Some(format!("{}{} {}", indent, marks, color.paint(&annotation))) + } + } + } + + fn format_lineno(&self, lineno: Option, lineno_width: usize) -> String { + match lineno { + Some(n) => format!("{:>width$}", n, width = lineno_width), + None => repeat_char(' ', lineno_width), + } + } + + fn format_raw_line(&self, line: &DisplayRawLine, lineno_width: usize) -> String { + match line { + DisplayRawLine::Origin { + path, + pos, + header_type, + } => { + let header_sigil = match header_type { + DisplayHeaderType::Initial => "-->", + DisplayHeaderType::Continuation => ":::", + }; + let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + + if let Some((col, row)) = pos { + format!( + "{}{} {}:{}:{}", + repeat_char(' ', lineno_width), + lineno_color.paint(header_sigil), + path, + col, + row + ) + } else { + format!( + "{}{} {}", + repeat_char(' ', lineno_width), + lineno_color.paint(header_sigil), + path + ) + } + } + DisplayRawLine::Annotation { + annotation, + source_aligned, + continuation, + } => { + if *source_aligned { + if *continuation { + format!( + "{}{}", + repeat_char(' ', lineno_width + 3), + self.format_annotation(annotation, *continuation, false) + ) + } else { + let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + format!( + "{} {} {}", + repeat_char(' ', lineno_width), + lineno_color.paint("="), + self.format_annotation(annotation, *continuation, false) + ) + } + } else { + self.format_annotation(annotation, *continuation, false) + } + } + } + } + + fn format_line( + &self, + dl: &DisplayLine, + lineno_width: usize, + inline_marks_width: usize, + ) -> String { + match dl { + DisplayLine::Source { + lineno, + inline_marks, + line, + } => { + let lineno = if self.anonymized_line_numbers && lineno.is_some() { + Self::ANONYMIZED_LINE_NUM.to_string() + } else { + self.format_lineno(*lineno, lineno_width) + }; + let marks = self.format_inline_marks(inline_marks, inline_marks_width); + let lf = self.format_source_line(line); + let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + + let mut prefix = lineno_color.paint(&format!("{} |", lineno)); + + match lf { + Some(lf) => { + if !marks.is_empty() { + prefix.push_str(&format!(" {}", marks)); + } + format!("{}{}", prefix, lf) + } + None => { + if !marks.trim().is_empty() { + prefix.push_str(&format!(" {}", marks)); + } + prefix + } + } + } + DisplayLine::Fold { inline_marks } => { + let marks = self.format_inline_marks(inline_marks, inline_marks_width); + let indent = lineno_width; + if marks.trim().is_empty() { + String::from("...") + } else { + format!("...{}{}", repeat_char(' ', indent), marks) + } + } + DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width), + } + } + + fn format_inline_marks( + &self, + inline_marks: &[DisplayMark], + inline_marks_width: usize, + ) -> String { + format!( + "{}{}", + " ".repeat(inline_marks_width - inline_marks.len()), + inline_marks + .iter() + .map(|mark| { + let sigil = match mark.mark_type { + DisplayMarkType::AnnotationThrough => "|", + DisplayMarkType::AnnotationStart => "/", + }; + let color = self.get_annotation_style(&mark.annotation_type); + color.paint(sigil) + }) + .collect::>() + .join(""), + ) + } +} diff --git a/vendor/annotate-snippets-0.6.1/src/formatter/style.rs b/vendor/annotate-snippets-0.6.1/src/formatter/style.rs new file mode 100644 index 0000000000..c1b90467e3 --- /dev/null +++ b/vendor/annotate-snippets-0.6.1/src/formatter/style.rs @@ -0,0 +1,98 @@ +//! Set of structures required to implement a stylesheet for +//! [DisplayListFormatter](super::DisplayListFormatter). +//! +//! In order to provide additional styling information for the +//! formatter, a structs can implement `Stylesheet` and `Style` +//! traits. +//! +//! Example: +//! +//! ``` +//! use annotate_snippets::formatter::style::{Stylesheet, StyleClass, Style}; +//! +//! struct HTMLStyle { +//! prefix: String, +//! postfix: String, +//! }; +//! +//! impl HTMLStyle { +//! fn new(prefix: &str, postfix: &str) -> Self { +//! HTMLStyle { +//! prefix: prefix.into(), +//! postfix: postfix.into() +//! } +//! } +//! }; +//! +//! impl Style for HTMLStyle { +//! fn paint(&self, text: &str) -> String { +//! format!("{}{}{}", self.prefix, text, self.postfix) +//! } +//! +//! fn bold(&self) -> Box