--- /dev/null
- *What benefit of this lint over old code?*
+---
+name: New lint suggestion
+about: Suggest a new Clippy lint.
+labels: L-lint
+---
+
+### What it does
+
+*What does this lint do?*
+
+### Categories (optional)
+
+- Kind: *See <https://github.com/rust-lang/rust-clippy/blob/master/README.md#clippy> 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
+<code>
+```
+
+Could be written as:
+
+```rust
+<code>
+```
--- /dev/null
- - 'chronotope/chrono'
+name: Clippy Test (bors)
+
+on:
+ push:
+ branches:
+ - auto
+ - try
+
+env:
+ RUST_BACKTRACE: 1
+ CARGO_TARGET_DIR: '${{ github.workspace }}/target'
+ NO_FMT_TEST: 1
+
+jobs:
+ changelog:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
+ with:
+ github_token: "${{ secrets.github_token }}"
+ - name: Checkout
+ uses: actions/checkout@v2.0.0
+ with:
+ ref: ${{ github.ref }}
+
+ # Run
+ - name: Check Changelog
+ run: |
+ MESSAGE=$(git log --format=%B -n 1)
+ PR=$(echo "$MESSAGE" | grep -o "#[0-9]*" | head -1 | sed -e 's/^#//')
+ output=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/rust-lang/rust-clippy/pulls/$PR" | \
+ python -c "import sys, json; print(json.load(sys.stdin)['body'])" | \
+ grep "^changelog: " | \
+ sed "s/changelog: //g")
+ if [[ -z "$output" ]]; then
+ echo "ERROR: PR body must contain 'changelog: ...'"
+ exit 1
+ elif [[ "$output" = "none" ]]; then
+ echo "WARNING: changelog is 'none'"
+ fi
+ env:
+ PYTHONIOENCODING: 'utf-8'
+ base:
+ needs: changelog
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ host: [x86_64-unknown-linux-gnu, i686-unknown-linux-gnu, x86_64-apple-darwin, x86_64-pc-windows-msvc]
+ exclude:
+ - os: ubuntu-latest
+ host: x86_64-apple-darwin
+ - os: ubuntu-latest
+ host: x86_64-pc-windows-msvc
+ - os: macos-latest
+ host: x86_64-unknown-linux-gnu
+ - os: macos-latest
+ host: i686-unknown-linux-gnu
+ - os: macos-latest
+ host: x86_64-pc-windows-msvc
+ - os: windows-latest
+ host: x86_64-unknown-linux-gnu
+ - os: windows-latest
+ host: i686-unknown-linux-gnu
+ - os: windows-latest
+ host: x86_64-apple-darwin
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ # Setup
+ - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
+ with:
+ github_token: "${{ secrets.github_token }}"
+
+ - name: Install dependencies (Linux-i686)
+ run: |
+ sudo dpkg --add-architecture i386
+ sudo apt-get update
+ sudo apt-get install gcc-multilib libssl-dev:i386 libgit2-dev:i386
+ if: matrix.host == 'i686-unknown-linux-gnu'
+
+ - name: rust-toolchain
+ uses: actions-rs/toolchain@v1.0.3
+ with:
+ toolchain: nightly
+ target: ${{ matrix.host }}
+ profile: minimal
+
+ - name: Checkout
+ uses: actions/checkout@v2.0.0
+
+ - name: Run cargo update
+ run: cargo update
+
+ - name: Cache cargo dir
+ uses: actions/cache@v2
+ with:
+ path: ~/.cargo
+ key: ${{ runner.os }}-${{ matrix.host }}-${{ hashFiles('Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-${{ matrix.host }}
+
+ - name: Master Toolchain Setup
+ run: bash setup-toolchain.sh
+ env:
+ HOST_TOOLCHAIN: ${{ matrix.host }}
+ shell: bash
+
+ # Run
+ - name: Set LD_LIBRARY_PATH (Linux)
+ if: runner.os == 'Linux'
+ run: |
+ SYSROOT=$(rustc --print sysroot)
+ echo "::set-env name=LD_LIBRARY_PATH::${SYSROOT}/lib${LD_LIBRARY_PATH+:${LD_LIBRARY_PATH}}"
+ - name: Link rustc dylib (MacOS)
+ if: runner.os == 'macOS'
+ run: |
+ SYSROOT=$(rustc --print sysroot)
+ sudo mkdir -p /usr/local/lib
+ sudo find "${SYSROOT}/lib" -maxdepth 1 -name '*dylib' -exec ln -s {} /usr/local/lib \;
+ - name: Set PATH (Windows)
+ if: runner.os == 'Windows'
+ run: |
+ $sysroot = rustc --print sysroot
+ $env:PATH += ';' + $sysroot + '\bin'
+ echo "::set-env name=PATH::$env:PATH"
+
+ - name: Build
+ run: cargo build --features deny-warnings
+ shell: bash
+
+ - name: Test
+ run: cargo test --features deny-warnings
+ shell: bash
+
+ - name: Test clippy_lints
+ run: cargo test --features deny-warnings
+ shell: bash
+ working-directory: clippy_lints
+
+ - name: Test rustc_tools_util
+ run: cargo test --features deny-warnings
+ shell: bash
+ working-directory: rustc_tools_util
+
+ - name: Test clippy_dev
+ run: cargo test --features deny-warnings
+ shell: bash
+ working-directory: clippy_dev
+
+ - name: Test cargo-clippy
+ run: ../target/debug/cargo-clippy
+ shell: bash
+ working-directory: clippy_workspace_tests
+
+ - name: Test clippy-driver
+ run: bash .github/driver.sh
+ shell: bash
+ env:
+ OS: ${{ runner.os }}
+
+ # Cleanup
+ - name: Run cargo-cache --autoclean
+ run: |
+ cargo +nightly install cargo-cache --no-default-features --features ci-autoclean cargo-cache
+ cargo cache
+ shell: bash
+ integration_build:
+ needs: changelog
+ runs-on: ubuntu-latest
+
+ steps:
+ # Setup
+ - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
+ with:
+ github_token: "${{ secrets.github_token }}"
+
+ - name: rust-toolchain
+ uses: actions-rs/toolchain@v1.0.3
+ with:
+ toolchain: nightly
+ target: x86_64-unknown-linux-gnu
+ profile: minimal
+
+ - name: Checkout
+ uses: actions/checkout@v2.0.0
+
+ - name: Run cargo update
+ run: cargo update
+
+ - name: Cache cargo dir
+ uses: actions/cache@v2
+ with:
+ path: ~/.cargo
+ key: ${{ runner.os }}-x86_64-unknown-linux-gnu-${{ hashFiles('Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-x86_64-unknown-linux-gnu
+
+ - name: Master Toolchain Setup
+ run: bash setup-toolchain.sh
+
+ # Run
+ - name: Build Integration Test
+ run: cargo test --test integration --features integration --no-run
+
+ # Upload
+ - name: Extract Binaries
+ run: |
+ DIR=$CARGO_TARGET_DIR/debug
+ rm $DIR/deps/integration-*.d
+ mv $DIR/deps/integration-* $DIR/integration
+ find $DIR ! -executable -o -type d ! -path $DIR | xargs rm -rf
+ rm -rf $CARGO_TARGET_DIR/release
+
+ - name: Upload Binaries
+ uses: actions/upload-artifact@v1
+ with:
+ name: target
+ path: target
+
+ # Cleanup
+ - name: Run cargo-cache --autoclean
+ run: |
+ cargo +nightly install cargo-cache --no-default-features --features ci-autoclean cargo-cache
+ cargo cache
+ integration:
+ needs: integration_build
+ strategy:
+ fail-fast: false
+ max-parallel: 6
+ matrix:
+ integration:
+ - 'rust-lang/cargo'
+ # 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: rust-toolchain
+ uses: actions-rs/toolchain@v1.0.3
+ with:
+ toolchain: nightly
+ target: x86_64-unknown-linux-gnu
+ profile: minimal
+
+ - name: Checkout
+ uses: actions/checkout@v2.0.0
+
+ - name: Run cargo update
+ run: cargo update
+
+ - name: Cache cargo dir
+ uses: actions/cache@v2
+ with:
+ path: ~/.cargo
+ key: ${{ runner.os }}-x86_64-unknown-linux-gnu-${{ hashFiles('Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-x86_64-unknown-linux-gnu
+
+ - name: Master Toolchain Setup
+ run: bash setup-toolchain.sh
+
+ # Download
+ - name: Download target dir
+ uses: actions/download-artifact@v1
+ with:
+ name: target
+ path: target
+
+ - name: Make Binaries Executable
+ run: chmod +x $CARGO_TARGET_DIR/debug/*
+
+ # Run
+ - name: Test ${{ matrix.integration }}
+ run: $CARGO_TARGET_DIR/debug/integration
+ env:
+ INTEGRATION: ${{ matrix.integration }}
+ RUSTUP_TOOLCHAIN: master
+
+ # Cleanup
+ - name: Run cargo-cache --autoclean
+ run: |
+ cargo +nightly install cargo-cache --no-default-features --features ci-autoclean cargo-cache
+ cargo cache
+
+ # These jobs doesn't actually test anything, but they're only used to tell
+ # bors the build completed, as there is no practical way to detect when a
+ # workflow is successful listening to webhooks only.
+ #
+ # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
+
+ end-success:
+ name: bors test finished
+ if: github.event.pusher.name == 'bors' && success()
+ runs-on: ubuntu-latest
+ needs: [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
--- /dev/null
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Changelog Update](doc/changelog_update.md) if you want to update this
+document.
+
+## Unreleased / In Rust Nightly
+
+[7ea7cd1...master](https://github.com/rust-lang/rust-clippy/compare/7ea7cd1...master)
+
+## Rust 1.45
+
+Current beta, release 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
+
+Current stable, 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
+
+<!-- lint disable no-unused-definitions -->
+<!-- begin autogenerated links to lint list -->
+[`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons
+[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
+[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
+[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
+[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants
+[`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern
+[`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops
+[`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock
+[`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask
+[`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
+[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
+[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
+[`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation
+[`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap
+[`cast_precision_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_precision_loss
+[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
+[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut
+[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
+[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
+[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
+[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
+[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions
+[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref
+[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
+[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
+[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan
+[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null
+[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
+[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity
+[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
+[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
+[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
+[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
+[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
+[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
+[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
+[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
+[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access
+[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr
+[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver
+[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof
+[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
+[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
+[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
+[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
+[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
+[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg
+[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens
+[`drop_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_bounds
+[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy
+[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
+[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
+[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
+[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else
+[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
+[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
+[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
+[`enum_clike_unportable_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_clike_unportable_variant
+[`enum_glob_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_glob_use
+[`enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names
+[`eq_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#eq_op
+[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
+[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
+[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
+[`exit`]: https://rust-lang.github.io/rust-clippy/master/index.html#exit
+[`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call
+[`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
+[`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file
+[`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map
+[`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next
+[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next
+[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map
+[`flat_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_identity
+[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
+[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
+[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
+[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
+[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
+[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
+[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation
+[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map
+[`for_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
+[`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_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher
+[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return
+[`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub
+[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
+[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
+[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
+[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
+[`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string
+[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match
+[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter
+[`inherent_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string
+[`inherent_to_string_shadow_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string_shadow_display
+[`inline_always`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_always
+[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body
+[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
+[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
+[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division
+[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array
+[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
+[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering
+[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref
+[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
+[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
+[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
+[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
+[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
+[`iter_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
+[`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty
+[`len_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_zero
+[`let_and_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return
+[`let_underscore_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_lock
+[`let_underscore_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_must_use
+[`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value
+[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
+[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
+[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
+[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
+[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
+[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
+[`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_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
+[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap
+[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names
+[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
+[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry
+[`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten
++[`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_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
+[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
+[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
+[`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception
+[`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions
+[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
+[`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one
+[`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions
+[`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl
+[`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate
+[`must_use_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_unit
+[`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref
+[`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut
+[`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
+[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
+[`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic
+[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
+[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount
+[`needless_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool
+[`needless_borrow`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
+[`needless_borrowed_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrowed_reference
+[`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect
+[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue
+[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
+[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
+[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
+[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
+[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
+[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
+[`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord
+[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply
+[`never_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_loop
+[`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self
+[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
+[`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect
+[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
+[`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool
+[`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options
+[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
+[`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect
+[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
+[`option_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_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_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout
+[`print_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_with_newline
+[`println_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string
+[`ptr_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg
+[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
+[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
+[`question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark
+[`range_minus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_minus_one
+[`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one
+[`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero
+[`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len
+[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
+[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
+[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
+[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call
+[`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
+[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
+[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
+[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
+[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
+[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
+[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
+[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
++[`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
+[`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
+[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
+[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
+[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse
+[`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same
+[`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated
+[`short_circuit_statement`]: https://rust-lang.github.io/rust-clippy/master/index.html#short_circuit_statement
+[`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq
+[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
+[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
+[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
+[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
+[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
+[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
+[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
+[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
+[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
+[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
+[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign
+[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars
+[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes
+[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
+[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools
+[`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops
+[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
+[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting
+[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting
+[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
+[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
+[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
+[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
+[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
+[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
+[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
+[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
+[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
+[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
+[`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg
+[`transmute_bytes_to_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_bytes_to_str
+[`transmute_float_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_float_to_int
+[`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool
+[`transmute_int_to_char`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_char
+[`transmute_int_to_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_float
+[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
+[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
+[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null
+[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
+[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
+[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
+[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
+[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
+[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
+[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented
+[`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init
+[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
+[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
+[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
+[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
+[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
+[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
+[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
+[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
+[`unnecessary_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
+[`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
+[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
+[`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_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_width_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_width_space
+[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset
+<!-- end autogenerated links to lint list -->
--- /dev/null
- [subtree]: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#external-dependencies-subtree
+# Contributing to Clippy
+
+Hello fellow Rustacean! Great to see your interest in compiler internals and lints!
+
+**First**: if you're unsure or afraid of _anything_, just ask or submit the issue or pull request anyway. You won't be
+yelled at for giving it your best effort. The worst that can happen is that you'll be politely asked to change
+something. We appreciate any sort of contributions, and don't want a wall of rules to get in the way of that.
+
+Clippy welcomes contributions from everyone. There are many ways to contribute to Clippy and the following document
+explains how you can contribute and how to get started. If you have any questions about contributing or need help with
+anything, feel free to ask questions on issues or visit the `#clippy` on [Discord].
+
+All contributors are expected to follow the [Rust Code of Conduct].
+
+- [Contributing to Clippy](#contributing-to-clippy)
+ - [Getting started](#getting-started)
+ - [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)
+ - [Fixing build failures caused by Rust](#fixing-build-failures-caused-by-rust)
+ - [Issue and PR triage](#issue-and-pr-triage)
+ - [Bors and Homu](#bors-and-homu)
+ - [Contributions](#contributions)
+
+[Discord]: https://discord.gg/rust-lang
+[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct
+
+## Getting started
+
+High level approach:
+
+1. Find something to fix/improve
+2. Change code (likely some file in `clippy_lints/src/`)
+3. Follow the instructions in the [docs for writing lints](doc/adding_lints.md) such as running the `setup-toolchain.sh` script
+4. Run `cargo test` in the root directory and wiggle code until it passes
+5. Open a PR (also can be done after 2. if you run into problems)
+
+### Finding something to fix/improve
+
+All issues on Clippy are mentored, if you want help with a bug just ask
+@Manishearth, @flip1995, @phansch or @yaahc.
+
+Some issues are easier than others. The [`good first issue`] label can be used to find the easy issues.
+If you want to work on an issue, please leave a comment so that we can assign it to you!
+
+There are also some abandoned PRs, marked with [`S-inactive-closed`].
+Pretty often these PRs are nearly completed and just need some extra steps
+(formatting, addressing review comments, ...) to be merged. If you want to
+complete such a PR, please leave a comment in the PR and open a new one based
+on it.
+
+Issues marked [`T-AST`] involve simple matching of the syntax tree structure,
+and are generally easier than [`T-middle`] issues, which involve types
+and resolved paths.
+
+[`T-AST`] issues will generally need you to match against a predefined syntax structure.
+To figure out how this syntax structure is encoded in the AST, it is recommended to run
+`rustc -Z ast-json` on an example of the structure and compare with the [nodes in the AST docs].
+Usually the lint will end up to be a nested series of matches and ifs, [like so][deep-nesting].
+But we can make it nest-less by using [if_chain] macro, [like this][nest-less].
+
+[`E-medium`] issues are generally pretty easy too, though it's recommended you work on an E-easy issue first.
+They are mostly classified as [`E-medium`], since they might be somewhat involved code wise,
+but not difficult per-se.
+
+[`T-middle`] issues can be more involved and require verifying types. The [`ty`] module contains a
+lot of methods that are useful, though one of the most useful would be `expr_ty` (gives the type of
+an AST expression). `match_def_path()` in Clippy's `utils` module can also be useful.
+
+[`good first issue`]: https://github.com/rust-lang/rust-clippy/labels/good%20first%20issue
+[`S-inactive-closed`]: https://github.com/rust-lang/rust-clippy/pulls?q=is%3Aclosed+label%3AS-inactive-closed
+[`T-AST`]: https://github.com/rust-lang/rust-clippy/labels/T-AST
+[`T-middle`]: https://github.com/rust-lang/rust-clippy/labels/T-middle
+[`E-medium`]: https://github.com/rust-lang/rust-clippy/labels/E-medium
+[`ty`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty
+[nodes in the AST docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/
+[deep-nesting]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/mem_forget.rs#L29-L43
+[if_chain]: https://docs.rs/if_chain/*/if_chain
+[nest-less]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/bit_mask.rs#L124-L150
+
+## Writing code
+
+Have a look at the [docs for writing lints][adding_lints] for more details.
+
+If you want to add a new lint or change existing ones apart from bugfixing, it's
+also a good idea to give the [stability guarantees][rfc_stability] and
+[lint categories][rfc_lint_cats] sections of the [Clippy 1.0 RFC][clippy_rfc] a
+quick read.
+
+[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md
+[clippy_rfc]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md
+[rfc_stability]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#stability-guarantees
+[rfc_lint_cats]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories
+
+## 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 <repo-path>` where `<repo-path>` 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
+
+## Fixing build failures caused by Rust
+
+Clippy currently gets built with `rustc` of the `rust-lang/rust` `master`
+branch. Most of the times we have to adapt to the changes and only very rarely
+there's an actual bug in Rust.
+
+If you decide to make Clippy work again with a Rust commit that breaks it, you
+have to sync the `rust-lang/rust-clippy` repository with the `subtree` copy of
+Clippy in the `rust-lang/rust` repository.
+
+For general information about `subtree`s in the Rust repository see [Rust's
+`CONTRIBUTING.md`][subtree].
+
+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
+2. 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
+ ```
+3. 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 [Discord] channel.)
+4. 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
+ ```
+5. Open a PR to [`rust-lang/rust`]
+
+Also, 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
+```
+
+_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`. For
+this to work, you will need the fix of `git subtree` available
+[here][gitgitgadget-pr].
+
+[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]. We don't
+want Clippy to crash on your code and we want it to be as reliable as the
+suggestions from Rust compiler errors.
+
+## Bors and Homu
+
+We use a bot powered by [Homu][homu] to help automate testing and landing of pull
+requests in Clippy. The bot's username is @bors.
+
+You can find the Clippy bors queue [here][homu_queue].
+
+If you have @bors permissions, you can find an overview of the available
+commands [here][homu_instructions].
+
+[triage]: https://forge.rust-lang.org/release/triage-procedure.html
+[l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash%20%3Aboom%3A
+[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug%20%3Abeetle%3A
+[homu]: https://github.com/rust-lang/homu
+[homu_instructions]: https://buildbot2.rust-lang.org/homu/
+[homu_queue]: https://buildbot2.rust-lang.org/homu/queue/clippy
+
+## Contributions
+
+Contributions to Clippy should be made in the form of GitHub pull requests. Each pull request will
+be reviewed by a core contributor (someone with permission to land patches) and either landed in the
+main tree or given feedback for changes that would be required.
+
+All code in this repository is under the [Apache-2.0] or the [MIT] license.
+
+<!-- adapted from https://github.com/servo/servo/blob/master/CONTRIBUTING.md -->
+
+[Apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0
+[MIT]: https://opensource.org/licenses/MIT
--- /dev/null
- first_line_of_span, is_present_in_source, match_def_path, paths, snippet_opt, span_lint, span_lint_and_sugg,
- span_lint_and_then, without_block_comments,
+//! checks for attributes
+
+use crate::reexport::Name;
+use crate::utils::{
- use rustc_span::symbol::Symbol;
++ first_line_of_span, is_present_in_source, match_def_path, paths, 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::ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
+use rustc_ast::util::lev_distance::find_best_match_for_name;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
+};
+use rustc_lint::{CheckLintNameResult, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
- match &*ident.as_str() {
++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] = &["cloudabi", "hermit", "none", "wasi"];
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for items annotated with `#[inline(always)]`,
+ /// unless the annotated function is empty or simply panics.
+ ///
+ /// **Why is this bad?** While there are valid uses of this annotation (and once
+ /// you know when to use it, by all means `allow` this lint), it's a common
+ /// newbie-mistake to pepper one's code with it.
+ ///
+ /// As a rule of thumb, before slapping `#[inline(always)]` on a function,
+ /// measure if that additional function call really affects your runtime profile
+ /// sufficiently to make up for the increase in compile time.
+ ///
+ /// **Known problems:** False positives, big time. This lint is meant to be
+ /// deactivated by everyone doing serious performance work. This means having
+ /// done the measurement.
+ ///
+ /// **Example:**
+ /// ```ignore
+ /// #[inline(always)]
+ /// fn not_quite_hot_code(..) { ... }
+ /// ```
+ pub INLINE_ALWAYS,
+ pedantic,
+ "use of `#[inline(always)]`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `extern crate` and `use` items annotated with
+ /// lint attributes.
+ ///
+ /// This lint permits `#[allow(unused_imports)]`, `#[allow(deprecated)]` and
+ /// `#[allow(unreachable_pub)]` on `use` items and `#[allow(unused_imports)]` on
+ /// `extern crate` items with a `#[macro_use]` attribute.
+ ///
+ /// **Why is this bad?** Lint attributes have no effect on crate imports. Most
+ /// likely a `!` was forgotten.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```ignore
+ /// // Bad
+ /// #[deny(dead_code)]
+ /// extern crate foo;
+ /// #[forbid(dead_code)]
+ /// use foo::bar;
+ ///
+ /// // Ok
+ /// #[allow(unused_imports)]
+ /// use foo::baz;
+ /// #[allow(unused_imports)]
+ /// #[macro_use]
+ /// extern crate baz;
+ /// ```
+ pub USELESS_ATTRIBUTE,
+ correctness,
+ "use of lint attributes on `extern crate` items"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `#[deprecated]` annotations with a `since`
+ /// field that is not a valid semantic version.
+ ///
+ /// **Why is this bad?** For checking the version of the deprecation, it must be
+ /// a valid semver. Failing that, the contained information is useless.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// #[deprecated(since = "forever")]
+ /// fn something_else() { /* ... */ }
+ /// ```
+ pub DEPRECATED_SEMVER,
+ correctness,
+ "use of `#[deprecated(since = \"x\")]` where x is not semver"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for empty lines after outer attributes
+ ///
+ /// **Why is this bad?**
+ /// Most likely the attribute was meant to be an inner attribute using a '!'.
+ /// If it was meant to be an outer attribute, then the following item
+ /// should not be separated by empty lines.
+ ///
+ /// **Known problems:** Can cause false positives.
+ ///
+ /// From the clippy side it's difficult to detect empty lines between an attributes and the
+ /// following item because empty lines and comments are not part of the AST. The parsing
+ /// currently works for basic cases but is not perfect.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// // Good (as inner attribute)
+ /// #![inline(always)]
+ ///
+ /// fn this_is_fine() { }
+ ///
+ /// // Bad
+ /// #[inline(always)]
+ ///
+ /// fn not_quite_good_code() { }
+ ///
+ /// // Good (as outer attribute)
+ /// #[inline(always)]
+ /// fn this_is_fine_too() { }
+ /// ```
+ pub EMPTY_LINE_AFTER_OUTER_ATTR,
+ nursery,
+ "empty line after outer attribute"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `allow`/`warn`/`deny`/`forbid` attributes with scoped clippy
+ /// lints and if those lints exist in clippy. If there is an uppercase letter in the lint name
+ /// (not the tool name) and a lowercase version of this lint exists, it will suggest to lowercase
+ /// the lint name.
+ ///
+ /// **Why is this bad?** A lint attribute with a mistyped lint name won't have an effect.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// Bad:
+ /// ```rust
+ /// #![warn(if_not_els)]
+ /// #![deny(clippy::All)]
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// #![warn(if_not_else)]
+ /// #![deny(clippy::all)]
+ /// ```
+ pub UNKNOWN_CLIPPY_LINTS,
+ style,
+ "unknown_lints for scoped Clippy lints"
+}
+
++declare_clippy_lint! {
++ /// **What it does:** Checks for `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,
+ UNKNOWN_CLIPPY_LINTS,
++ 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() {
- check_clippy_lint_names(cx, items);
++ let ident = &*ident.as_str();
++ match ident {
+ "allow" | "warn" | "deny" | "forbid" => {
- #[allow(clippy::single_match_else)]
- fn check_clippy_lint_names(cx: &LateContext<'_>, items: &[NestedMetaItem]) {
- let lint_store = cx.lints();
- for lint in items {
++ check_clippy_lint_names(cx, ident, items);
+ },
+ _ => {},
+ }
+ if items.is_empty() || !attr.check_name(sym!(deprecated)) {
+ return;
+ }
+ for item in items {
+ if_chain! {
+ if let NestedMetaItem::MetaItem(mi) = &item;
+ if let MetaItemKind::NameValue(lit) = &mi.kind;
+ if mi.check_name(sym!(since));
+ then {
+ check_semver(cx, item.span(), lit);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if is_relevant_item(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, &item.attrs)
+ }
+ match item.kind {
+ ItemKind::ExternCrate(..) | ItemKind::Use(..) => {
+ let skip_unused_imports = item.attrs.iter().any(|attr| attr.check_name(sym!(macro_use)));
+
+ for attr in item.attrs {
+ if in_external_macro(cx.sess(), attr.span) {
+ return;
+ }
+ if let Some(lint_list) = &attr.meta_item_list() {
+ if let Some(ident) = attr.ident() {
+ match &*ident.as_str() {
+ "allow" | "warn" | "deny" | "forbid" => {
+ // permit `unused_imports`, `deprecated` and `unreachable_pub` for `use` items
+ // and `unused_imports` for `extern crate` items with `macro_use`
+ for lint in lint_list {
+ match item.kind {
+ ItemKind::Use(..) => {
+ if is_word(lint, sym!(unused_imports))
+ || is_word(lint, sym!(deprecated))
+ || is_word(lint, sym!(unreachable_pub))
+ || is_word(lint, sym!(unused))
+ {
+ return;
+ }
+ },
+ ItemKind::ExternCrate(..) => {
+ if is_word(lint, sym!(unused_imports)) && skip_unused_imports {
+ return;
+ }
+ if is_word(lint, sym!(unused_extern_crates)) {
+ return;
+ }
+ },
+ _ => {},
+ }
+ }
+ let line_span = first_line_of_span(cx, attr.span);
+
+ if let Some(mut sugg) = snippet_opt(cx, line_span) {
+ if sugg.contains("#[") {
+ span_lint_and_then(
+ cx,
+ USELESS_ATTRIBUTE,
+ line_span,
+ "useless lint attribute",
+ |diag| {
+ sugg = sugg.replacen("#[", "#![", 1);
+ diag.span_suggestion(
+ line_span,
+ "if you just forgot a `!`, use",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ if is_relevant_impl(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, &item.attrs)
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if is_relevant_trait(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, &item.attrs)
+ }
+ }
+}
+
- let name = meta_item.path.segments.last().unwrap().ident.name;
- if let CheckLintNameResult::Tool(Err((None, _))) = lint_store.check_lint_name(
- &name.as_str(),
- Some(tool_name.name),
- );
++fn check_clippy_lint_names(cx: &LateContext<'_>, ident: &str, items: &[NestedMetaItem]) {
++ fn extract_name(lint: &NestedMetaItem) -> Option<SymbolStr> {
+ if_chain! {
+ if let Some(meta_item) = lint.meta_item();
+ if meta_item.path.segments.len() > 1;
+ if let tool_name = meta_item.path.segments[0].ident;
+ if tool_name.as_str() == "clippy";
- &format!("unknown clippy lint: clippy::{}", name),
++ let lint_name = meta_item.path.segments.last().unwrap().ident.name;
+ then {
++ return Some(lint_name.as_str());
++ }
++ }
++ None
++ }
++
++ let lint_store = cx.lints();
++ for lint in items {
++ if let Some(lint_name) = extract_name(lint) {
++ if let CheckLintNameResult::Tool(Err((None, _))) =
++ lint_store.check_lint_name(&lint_name, Some(sym!(clippy)))
++ {
+ span_lint_and_then(
+ cx,
+ UNKNOWN_CLIPPY_LINTS,
+ lint.span(),
- let name_lower = name.as_str().to_lowercase();
- let symbols = lint_store.get_lints().iter().map(
- |l| Symbol::intern(&l.name_lower())
- ).collect::<Vec<_>>();
- let sugg = find_best_match_for_name(
- symbols.iter(),
- &format!("clippy::{}", name_lower),
- None,
- );
- if name.as_str().chars().any(char::is_uppercase)
- && lint_store.find_lints(&format!("clippy::{}", name_lower)).is_ok() {
++ &format!("unknown clippy lint: clippy::{}", lint_name),
+ |diag| {
- }
++ let name_lower = lint_name.to_lowercase();
++ let symbols = lint_store
++ .get_lints()
++ .iter()
++ .map(|l| Symbol::intern(&l.name_lower()))
++ .collect::<Vec<_>>();
++ let sugg = find_best_match_for_name(symbols.iter(), &format!("clippy::{}", name_lower), None);
++ if lint_name.chars().any(char::is_uppercase)
++ && lint_store.find_lints(&format!("clippy::{}", name_lower)).is_ok()
++ {
+ diag.span_suggestion(
+ lint.span(),
+ "lowercase the lint name",
+ format!("clippy::{}", name_lower),
+ Applicability::MachineApplicable,
+ );
+ } else if let Some(sugg) = sugg {
+ diag.span_suggestion(
+ lint.span(),
+ "did you mean",
+ sugg.to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
- };
++ },
++ );
++ } else 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",
+ );
+ }
- if let Some(stmt) = block.stmts.first() {
- match &stmt.kind {
++ }
+ }
+}
+
+fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
+ if let ItemKind::Fn(_, _, eid) = item.kind {
+ is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value)
+ } else {
+ true
+ }
+}
+
+fn is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool {
+ match item.kind {
+ ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value),
+ _ => false,
+ }
+}
+
+fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool {
+ match item.kind {
+ TraitItemKind::Fn(_, TraitFn::Required(_)) => true,
+ TraitItemKind::Fn(_, TraitFn::Provided(eid)) => {
+ is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value)
+ },
+ _ => false,
+ }
+}
+
+fn is_relevant_block(cx: &LateContext<'_>, tables: &ty::TypeckTables<'_>, block: &Block<'_>) -> bool {
- }
- } else {
- block.expr.as_ref().map_or(false, |e| is_relevant_expr(cx, tables, e))
- }
++ block.stmts.first().map_or(
++ block.expr.as_ref().map_or(false, |e| is_relevant_expr(cx, tables, e)),
++ |stmt| match &stmt.kind {
+ StmtKind::Local(_) => true,
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, tables, expr),
+ _ => false,
- if let Some(fun_id) = tables.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
- !match_def_path(cx, fun_id, &paths::BEGIN_PANIC)
- } else {
- true
- }
++ },
++ )
+}
+
+fn is_relevant_expr(cx: &LateContext<'_>, tables: &ty::TypeckTables<'_>, expr: &Expr<'_>) -> bool {
+ match &expr.kind {
+ ExprKind::Block(block, _) => is_relevant_block(cx, tables, block),
+ ExprKind::Ret(Some(e)) => is_relevant_expr(cx, tables, e),
+ ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
+ ExprKind::Call(path_expr, _) => {
+ if let ExprKind::Path(qpath) = &path_expr.kind {
++ tables
++ .qpath_res(qpath, path_expr.hir_id)
++ .opt_def_id()
++ .map_or(true, |fun_id| !match_def_path(cx, fun_id, &paths::BEGIN_PANIC))
+ } else {
+ true
+ }
+ },
+ _ => true,
+ }
+}
+
+fn check_attrs(cx: &LateContext<'_>, span: Span, name: Name, attrs: &[Attribute]) {
+ if span.from_expansion() {
+ return;
+ }
+
+ for attr in attrs {
+ if let Some(values) = attr.meta_item_list() {
+ if values.len() != 1 || !attr.check_name(sym!(inline)) {
+ continue;
+ }
+ if is_word(&values[0], sym!(always)) {
+ span_lint(
+ cx,
+ INLINE_ALWAYS,
+ attr.span,
+ &format!(
+ "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
+ name
+ ),
+ );
+ }
+ }
+ }
+}
+
+fn check_semver(cx: &LateContext<'_>, span: Span, lit: &Lit) {
+ if let LitKind::Str(is, _) = lit.kind {
+ if Version::parse(&is.as_str()).is_ok() {
+ return;
+ }
+ }
+ span_lint(
+ cx,
+ DEPRECATED_SEMVER,
+ span,
+ "the since field must contain a semver-compliant version",
+ );
+}
+
+fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
+ if let NestedMetaItem::MetaItem(mi) = &nmi {
+ mi.is_word() && mi.check_name(expected)
+ } else {
+ false
+ }
+}
+
+declare_lint_pass!(EarlyAttributes => [
+ DEPRECATED_CFG_ATTR,
+ MISMATCHED_TARGET_OS,
+ EMPTY_LINE_AFTER_OUTER_ATTR,
+]);
+
+impl EarlyLintPass for EarlyAttributes {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::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::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::<Vec<_>>();
+ let lines = without_block_comments(lines);
+
+ if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
+ span_lint(
+ cx,
+ EMPTY_LINE_AFTER_OUTER_ATTR,
+ begin_of_attr_to_item,
+ "Found an empty line after an outer attribute. \
+ Perhaps you forgot to add a `!` to make it an inner attribute?",
+ );
+ }
+ }
+ }
+ }
+}
+
+fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute) {
+ if_chain! {
+ // check cfg_attr
+ if attr.check_name(sym!(cfg_attr));
+ if let Some(items) = attr.meta_item_list();
+ if items.len() == 2;
+ // check for `rustfmt`
+ if let Some(feature_item) = items[0].meta_item();
+ if feature_item.check_name(sym!(rustfmt));
+ // check for `rustfmt_skip` and `rustfmt::skip`
+ if let Some(skip_item) = &items[1].meta_item();
+ if skip_item.check_name(sym!(rustfmt_skip)) ||
+ skip_item.path.segments.last().expect("empty path in attribute").ident.name == sym!(skip);
+ // Only lint outer attributes, because custom inner attributes are unstable
+ // Tracking issue: https://github.com/rust-lang/rust/issues/54726
+ if let AttrStyle::Outer = attr.style;
+ then {
+ span_lint_and_sugg(
+ cx,
+ DEPRECATED_CFG_ATTR,
+ attr.span,
+ "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
+ "use",
+ "#[rustfmt::skip]".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
+ fn find_os(name: &str) -> Option<&'static str> {
+ UNIX_SYSTEMS
+ .iter()
+ .chain(NON_UNIX_SYSTEMS.iter())
+ .find(|&&os| os == name)
+ .copied()
+ }
+
+ fn is_unix(name: &str) -> bool {
+ UNIX_SYSTEMS.iter().any(|&os| os == name)
+ }
+
+ fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> {
+ let mut mismatched = Vec::new();
+
+ for item in items {
+ if let NestedMetaItem::MetaItem(meta) = item {
+ match &meta.kind {
+ MetaItemKind::List(list) => {
+ mismatched.extend(find_mismatched_target_os(&list));
+ },
+ MetaItemKind::Word => {
+ if_chain! {
+ if let Some(ident) = meta.ident();
+ if let Some(os) = find_os(&*ident.name.as_str());
+ then {
+ mismatched.push((os, ident.span));
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+
+ mismatched
+ }
+
+ if_chain! {
+ if attr.check_name(sym!(cfg));
+ if let Some(list) = attr.meta_item_list();
+ let mismatched = find_mismatched_target_os(&list);
+ if !mismatched.is_empty();
+ then {
+ let mess = "operating system used in target family position";
+
+ span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, &mess, |diag| {
+ // Avoid showing the unix suggestion multiple times in case
+ // we have more than one mismatch for unix-like systems
+ let mut unix_suggested = false;
+
+ for (os, span) in mismatched {
+ let sugg = format!("target_os = \"{}\"", os);
+ diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
+
+ if !unix_suggested && is_unix(os) {
+ diag.help("Did you mean `unix`?");
+ unix_suggested = true;
+ }
+ }
+ });
+ }
+ }
+}
--- /dev/null
- /// are not designed to operator in an async context across await points.
+use crate::utils::{match_def_path, paths, span_lint_and_note};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::GeneratorInteriorTypeCause;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calls to await while holding a
+ /// non-async-aware MutexGuard.
+ ///
+ /// **Why is this bad?** The Mutex types found in syd::sync and parking_lot
++ /// are not designed to 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:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust,ignore
+ /// use std::sync::Mutex;
+ ///
+ /// async fn foo(x: &Mutex<u32>) {
+ /// let guard = x.lock().unwrap();
+ /// *guard += 1;
+ /// bar.await;
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// use std::sync::Mutex;
+ ///
+ /// async fn foo(x: &Mutex<u32>) {
+ /// {
+ /// let guard = x.lock().unwrap();
+ /// *guard += 1;
+ /// }
+ /// bar.await;
+ /// }
+ /// ```
+ pub AWAIT_HOLDING_LOCK,
+ pedantic,
+ "Inside an async function, holding a MutexGuard while calling await"
+}
+
+declare_lint_pass!(AwaitHoldingLock => [AWAIT_HOLDING_LOCK]);
+
+impl LateLintPass<'_> for AwaitHoldingLock {
+ fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
+ use AsyncGeneratorKind::{Block, Closure, Fn};
+ if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
+ let body_id = BodyId {
+ hir_id: body.value.hir_id,
+ };
+ let def_id = cx.tcx.hir().body_owner_def_id(body_id);
+ let tables = cx.tcx.typeck_tables_of(def_id);
+ check_interior_types(cx, &tables.generator_interior_types, body.value.span);
+ }
+ }
+}
+
+fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
+ for ty_cause in ty_causes {
+ if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind {
+ if is_mutex_guard(cx, adt.did) {
+ span_lint_and_note(
+ cx,
+ AWAIT_HOLDING_LOCK,
+ ty_cause.span,
+ "this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await.",
+ ty_cause.scope_span.or(Some(span)),
+ "these are all the await points this lock is held through",
+ );
+ }
+ }
+ }
+}
+
+fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ match_def_path(cx, def_id, &paths::MUTEX_GUARD)
+ || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
+ || match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD)
+ || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
+ || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
+ || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
+}
--- /dev/null
- "try",
+//! Checks for if expressions that contain only an if expression.
+//!
+//! For example, the lint would catch:
+//!
+//! ```rust,ignore
+//! if x {
+//! if y {
+//! println!("Hello world");
+//! }
+//! }
+//! ```
+//!
+//! This lint is **warn** by default
+
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+use crate::utils::sugg::Sugg;
+use crate::utils::{snippet_block, snippet_block_with_applicability, span_lint_and_sugg, span_lint_and_then};
+use rustc_errors::Applicability;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for nested `if` statements which can be collapsed
+ /// by `&&`-combining their conditions and for `else { if ... }` expressions
+ /// that
+ /// can be collapsed to `else if ...`.
+ ///
+ /// **Why is this bad?** Each `if`-statement adds one level of nesting, which
+ /// makes code look more complex than it really is.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// if x {
+ /// if y {
+ /// …
+ /// }
+ /// }
+ ///
+ /// // or
+ ///
+ /// if x {
+ /// …
+ /// } else {
+ /// if y {
+ /// …
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Should be written:
+ ///
+ /// ```rust.ignore
+ /// if x && y {
+ /// …
+ /// }
+ ///
+ /// // or
+ ///
+ /// if x {
+ /// …
+ /// } else if y {
+ /// …
+ /// }
+ /// ```
+ pub COLLAPSIBLE_IF,
+ style,
+ "`if`s that can be collapsed (e.g., `if x { if y { ... } }` and `else { if x { ... } }`)"
+}
+
+declare_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF]);
+
+impl EarlyLintPass for CollapsibleIf {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ if !expr.span.from_expansion() {
+ check_if(cx, expr)
+ }
+ }
+}
+
+fn check_if(cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ if let ast::ExprKind::If(check, then, else_) = &expr.kind {
+ if let Some(else_) = else_ {
+ check_collapsible_maybe_if_let(cx, else_);
+ } else if let ast::ExprKind::Let(..) = check.kind {
+ // Prevent triggering on `if let a = b { if c { .. } }`.
+ } else {
+ check_collapsible_no_if_let(cx, expr, check, then);
+ }
+ }
+}
+
+fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
+ // We trim all opening braces and whitespaces and then check if the next string is a comment.
+ let trimmed_block_text = snippet_block(cx, expr.span, "..", None)
+ .trim_start_matches(|c: char| c.is_whitespace() || c == '{')
+ .to_owned();
+ trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
+}
+
+fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
+ if_chain! {
+ if let ast::ExprKind::Block(ref block, _) = else_.kind;
+ if !block_starts_with_comment(cx, block);
+ if let Some(else_) = expr_block(block);
+ if !else_.span.from_expansion();
+ if let ast::ExprKind::If(..) = else_.kind;
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_IF,
+ block.span,
+ "this `else { if .. }` block can be collapsed",
- "try",
++ "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 let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind;
+ then {
+ if let ast::ExprKind::Let(..) = check_inner.kind {
+ // Prevent triggering on `if c { if let a = b { .. } }`.
+ return;
+ }
+
+ if expr.span.ctxt() != inner.span.ctxt() {
+ return;
+ }
+ span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this `if` statement can be collapsed", |diag| {
+ let lhs = Sugg::ast(cx, check, "..");
+ let rhs = Sugg::ast(cx, check_inner, "..");
+ diag.span_suggestion(
+ expr.span,
++ "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
+ }
+}
--- /dev/null
- match kind {
- BinOpKind::Lt | BinOpKind::Gt | BinOpKind::Eq => true,
- _ => false,
- }
+use crate::utils::{
+ get_trait_def_id, if_sequence, implements_trait, parent_node_is_if_expr, paths, span_lint_and_help, SpanlessEq,
+};
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks comparison chains written with `if` that can be
+ /// rewritten with `match` and `cmp`.
+ ///
+ /// **Why is this bad?** `if` is not guaranteed to be exhaustive and conditionals can get
+ /// repetitive
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// # fn a() {}
+ /// # fn b() {}
+ /// # fn c() {}
+ /// fn f(x: u8, y: u8) {
+ /// if x > y {
+ /// a()
+ /// } else if x < y {
+ /// b()
+ /// } else {
+ /// c()
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust,ignore
+ /// use std::cmp::Ordering;
+ /// # fn a() {}
+ /// # fn b() {}
+ /// # fn c() {}
+ /// fn f(x: u8, y: u8) {
+ /// match x.cmp(&y) {
+ /// Ordering::Greater => a(),
+ /// Ordering::Less => b(),
+ /// Ordering::Equal => c()
+ /// }
+ /// }
+ /// ```
+ pub COMPARISON_CHAIN,
+ style,
+ "`if`s that can be rewritten with `match` and `cmp`"
+}
+
+declare_lint_pass!(ComparisonChain => [COMPARISON_CHAIN]);
+
+impl<'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.tables().expr_ty(lhs1);
+ let is_ord = get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[]));
+
+ if !is_ord {
+ return;
+ }
+ } else {
+ // We only care about comparison chains
+ return;
+ }
+ }
+ span_lint_and_help(
+ cx,
+ COMPARISON_CHAIN,
+ expr.span,
+ "`if` chain can be rewritten with `match`",
+ None,
+ "Consider rewriting the `if` chain to use `cmp` and `match`.",
+ )
+ }
+}
+
+fn kind_is_cmp(kind: BinOpKind) -> bool {
++ matches!(kind, BinOpKind::Lt | BinOpKind::Gt | BinOpKind::Eq)
+}
--- /dev/null
- "associated-constants `MIN`/`MAX` of integers are prefer to `{min,max}_value()` and module constants"
+macro_rules! declare_deprecated_lint {
+ (pub $name: ident, $_reason: expr) => {
+ declare_lint!(pub $name, Allow, "deprecated lint")
+ }
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This used to check for `assert!(a == b)` and recommend
+ /// replacement with `assert_eq!(a, b)`, but this is no longer needed after RFC 2011.
+ pub SHOULD_ASSERT_EQ,
+ "`assert!()` will be more flexible with RFC 2011"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This used to check for `Vec::extend`, which was slower than
+ /// `Vec::extend_from_slice`. Thanks to specialization, this is no longer true.
+ pub EXTEND_FROM_SLICE,
+ "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** `Range::step_by(0)` used to be linted since it's
+ /// an infinite iterator, which is better expressed by `iter::repeat`,
+ /// but the method has been removed for `Iterator::step_by` which panics
+ /// if given a zero
+ pub RANGE_STEP_BY_ZERO,
+ "`iterator.step_by(0)` panics nowadays"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This used to check for `Vec::as_slice`, which was unstable with good
+ /// stable alternatives. `Vec::as_slice` has now been stabilized.
+ pub UNSTABLE_AS_SLICE,
+ "`Vec::as_slice` has been stabilized in 1.7"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This used to check for `Vec::as_mut_slice`, which was unstable with good
+ /// stable alternatives. `Vec::as_mut_slice` has now been stabilized.
+ pub UNSTABLE_AS_MUT_SLICE,
+ "`Vec::as_mut_slice` has been stabilized in 1.7"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This used to check for `.to_string()` method calls on values
+ /// of type `&str`. This is not unidiomatic and with specialization coming, `to_string` could be
+ /// specialized to be as efficient as `to_owned`.
+ pub STR_TO_STRING,
+ "using `str::to_string` is common even today and specialization will likely happen soon"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This used to check for `.to_string()` method calls on values
+ /// of type `String`. This is not unidiomatic and with specialization coming, `to_string` could be
+ /// specialized to be as efficient as `clone`.
+ pub STRING_TO_STRING,
+ "using `string::to_string` is common even today and specialization will likely happen soon"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This lint should never have applied to non-pointer types, as transmuting
+ /// between non-pointer types of differing alignment is well-defined behavior (it's semantically
+ /// equivalent to a memcpy). This lint has thus been refactored into two separate lints:
+ /// cast_ptr_alignment and transmute_ptr_to_ptr.
+ pub MISALIGNED_TRANSMUTE,
+ "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This lint is too subjective, not having a good reason for being in clippy.
+ /// Additionally, compound assignment operators may be overloaded separately from their non-assigning
+ /// counterparts, so this lint may suggest a change in behavior or the code may not compile.
+ pub ASSIGN_OPS,
+ "using compound assignment operators (e.g., `+=`) is harmless"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** The original rule will only lint for `if let`. After
+ /// making it support to lint `match`, naming as `if let` is not suitable for it.
+ /// So, this lint is deprecated.
+ pub IF_LET_REDUNDANT_PATTERN_MATCHING,
+ "this lint has been changed to redundant_pattern_matching"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This lint used to suggest replacing `let mut vec =
+ /// Vec::with_capacity(n); vec.set_len(n);` with `let vec = vec![0; n];`. The
+ /// replacement has very different performance characteristics so the lint is
+ /// deprecated.
+ pub UNSAFE_VECTOR_INITIALIZATION,
+ "the replacement suggested by this lint had substantially different behavior"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This lint has been superseded by the warn-by-default
+ /// `invalid_value` rustc lint.
+ pub INVALID_REF,
+ "superseded by rustc lint `invalid_value`"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This lint has been superseded by #[must_use] in rustc.
+ pub UNUSED_COLLECT,
+ "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This lint has been uplifted to rustc and is now called
+ /// `array_into_iter`.
+ pub INTO_ITER_ON_ARRAY,
+ "this lint has been uplifted to rustc and is now called `array_into_iter`"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** This lint has been uplifted to rustc and is now called
+ /// `unused_labels`.
+ pub UNUSED_LABEL,
+ "this lint has been uplifted to rustc and is now called `unused_labels`"
+}
+
+declare_deprecated_lint! {
+ /// **What it does:** Nothing. This lint has been deprecated.
+ ///
+ /// **Deprecation reason:** Associated-constants are now preferred.
+ pub REPLACE_CONSTS,
++ "associated-constants `MIN`/`MAX` of integers are prefered 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"
+}
--- /dev/null
- if cx.tcx.lang_items().deref_trait().map_or(false, |id| {
+use crate::utils::{get_parent_expr, implements_trait, snippet, span_lint_and_sugg};
+use if_chain::if_chain;
+use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX, PREC_PREFIX};
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for explicit `deref()` or `deref_mut()` method calls.
+ ///
+ /// **Why is this bad?** Derefencing by `&*x` or `&mut *x` is clearer and more concise,
+ /// when not part of a method chain.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// use std::ops::Deref;
+ /// let a: &mut String = &mut String::from("foo");
+ /// let b: &str = a.deref();
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// let a: &mut String = &mut String::from("foo");
+ /// let b = &*a;
+ /// ```
+ ///
+ /// This lint excludes
+ /// ```rust,ignore
+ /// let _ = d.unwrap().deref();
+ /// ```
+ pub EXPLICIT_DEREF_METHODS,
+ pedantic,
+ "Explicit use of deref or deref_mut method while not in a method chain."
+}
+
+declare_lint_pass!(Dereferencing => [
+ EXPLICIT_DEREF_METHODS
+]);
+
+impl<'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.tables().expr_ty(&call_expr), id, &[])
- if cx.tcx.lang_items().deref_mut_trait().map_or(false, |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.tables().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,
+ );
+ }
+ },
+ _ => (),
+ }
+}
--- /dev/null
- match op.node {
+use crate::utils::{
+ implements_trait, in_macro, is_copy, multispan_sugg, snippet, span_lint, span_lint_and_then, SpanlessEq,
+};
+use rustc_errors::Applicability;
+use rustc_hir::{BinOp, BinOpKind, BorrowKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for equal operands to comparison, logical and
+ /// bitwise, difference and division binary operators (`==`, `>`, etc., `&&`,
+ /// `||`, `&`, `|`, `^`, `-` and `/`).
+ ///
+ /// **Why is this bad?** This is usually just a typo or a copy and paste error.
+ ///
+ /// **Known problems:** False negatives: We had some false positives regarding
+ /// calls (notably [racer](https://github.com/phildawes/racer) had one instance
+ /// of `x.pop() && x.pop()`), so we removed matching any function or method
+ /// calls. We may introduce a list of known pure functions in the future.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let x = 1;
+ /// if x + 1 == x + 1 {}
+ /// ```
+ pub EQ_OP,
+ correctness,
+ "equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for arguments to `==` which have their address
+ /// taken to satisfy a bound
+ /// and suggests to dereference the other argument instead
+ ///
+ /// **Why is this bad?** It is more idiomatic to dereference the other argument.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```ignore
+ /// // 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]);
+
+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::Binary(op, ref left, ref right) = e.kind {
+ if e.span.from_expansion() {
+ return;
+ }
+ let macro_with_not_op = |expr_kind: &ExprKind<'_>| {
+ if let ExprKind::Unary(_, ref expr) = *expr_kind {
+ in_macro(expr.span)
+ } else {
+ false
+ }
+ };
+ if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) {
+ return;
+ }
+ if is_valid_operator(op) && SpanlessEq::new(cx).ignore_fn().eq_expr(left, right) {
+ span_lint(
+ cx,
+ EQ_OP,
+ e.span,
+ &format!("equal expressions as operands to `{}`", op.node.as_str()),
+ );
+ return;
+ }
+ let (trait_id, requires_ref) = match op.node {
+ BinOpKind::Add => (cx.tcx.lang_items().add_trait(), false),
+ BinOpKind::Sub => (cx.tcx.lang_items().sub_trait(), false),
+ BinOpKind::Mul => (cx.tcx.lang_items().mul_trait(), false),
+ BinOpKind::Div => (cx.tcx.lang_items().div_trait(), false),
+ BinOpKind::Rem => (cx.tcx.lang_items().rem_trait(), false),
+ // don't lint short circuiting ops
+ BinOpKind::And | BinOpKind::Or => return,
+ BinOpKind::BitXor => (cx.tcx.lang_items().bitxor_trait(), false),
+ BinOpKind::BitAnd => (cx.tcx.lang_items().bitand_trait(), false),
+ BinOpKind::BitOr => (cx.tcx.lang_items().bitor_trait(), false),
+ BinOpKind::Shl => (cx.tcx.lang_items().shl_trait(), false),
+ BinOpKind::Shr => (cx.tcx.lang_items().shr_trait(), false),
+ BinOpKind::Ne | BinOpKind::Eq => (cx.tcx.lang_items().eq_trait(), true),
+ BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ge | BinOpKind::Gt => {
+ (cx.tcx.lang_items().partial_ord_trait(), true)
+ },
+ };
+ if let Some(trait_id) = trait_id {
+ #[allow(clippy::match_same_arms)]
+ match (&left.kind, &right.kind) {
+ // do not suggest to dereference literals
+ (&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {},
+ // &foo == &bar
+ (&ExprKind::AddrOf(BorrowKind::Ref, _, ref l), &ExprKind::AddrOf(BorrowKind::Ref, _, ref r)) => {
+ let lty = cx.tables().expr_ty(l);
+ let rty = cx.tables().expr_ty(r);
+ let lcpy = is_copy(cx, lty);
+ let rcpy = is_copy(cx, rty);
+ // either operator autorefs or both args are copyable
+ if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of both operands",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ multispan_sugg(
+ diag,
+ "use the values directly",
+ vec![(left.span, lsnip), (right.span, rsnip)],
+ );
+ },
+ )
+ } else if lcpy
+ && !rcpy
+ && implements_trait(cx, lty, trait_id, &[cx.tables().expr_ty(right).into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of left operand",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ diag.span_suggestion(
+ left.span,
+ "use the left value directly",
+ lsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ )
+ } else if !lcpy
+ && rcpy
+ && implements_trait(cx, cx.tables().expr_ty(left), trait_id, &[rty.into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of right operand",
+ |diag| {
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ diag.span_suggestion(
+ right.span,
+ "use the right value directly",
+ rsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ )
+ }
+ },
+ // &foo == bar
+ (&ExprKind::AddrOf(BorrowKind::Ref, _, ref l), _) => {
+ let lty = cx.tables().expr_ty(l);
+ let lcpy = is_copy(cx, lty);
+ if (requires_ref || lcpy)
+ && implements_trait(cx, lty, trait_id, &[cx.tables().expr_ty(right).into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of left operand",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ diag.span_suggestion(
+ left.span,
+ "use the left value directly",
+ lsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ )
+ }
+ },
+ // foo == &bar
+ (_, &ExprKind::AddrOf(BorrowKind::Ref, _, ref r)) => {
+ let rty = cx.tables().expr_ty(r);
+ let rcpy = is_copy(cx, rty);
+ if (requires_ref || rcpy)
+ && implements_trait(cx, cx.tables().expr_ty(left), trait_id, &[rty.into()])
+ {
+ span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| {
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ diag.span_suggestion(
+ right.span,
+ "use the right value directly",
+ rsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ })
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+}
+
+fn is_valid_operator(op: BinOp) -> bool {
- | BinOpKind::Div
- | BinOpKind::Eq
- | BinOpKind::Lt
- | BinOpKind::Le
- | BinOpKind::Gt
- | BinOpKind::Ge
- | BinOpKind::Ne
- | BinOpKind::And
- | BinOpKind::Or
- | BinOpKind::BitXor
- | BinOpKind::BitAnd
- | BinOpKind::BitOr => true,
- _ => false,
- }
++ matches!(
++ op.node,
+ BinOpKind::Sub
++ | BinOpKind::Div
++ | BinOpKind::Eq
++ | BinOpKind::Lt
++ | BinOpKind::Le
++ | BinOpKind::Gt
++ | BinOpKind::Ge
++ | BinOpKind::Ne
++ | BinOpKind::And
++ | BinOpKind::Or
++ | BinOpKind::BitXor
++ | BinOpKind::BitAnd
++ | BinOpKind::BitOr
++ )
+}
--- /dev/null
- match map.find(map.get_parent_node(id)) {
- Some(Node::Param(_)) => true,
- _ => false,
- }
+use rustc_hir::intravisit;
+use rustc_hir::{self, Body, FnDecl, HirId, HirIdSet, ItemKind, Node};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_target::abi::LayoutOf;
+use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+
+use crate::utils::span_lint;
+
+#[derive(Copy, Clone)]
+pub struct BoxedLocal {
+ pub too_large_for_stack: u64,
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `Box<T>` where an unboxed `T` would
+ /// work fine.
+ ///
+ /// **Why is this bad?** This is an unnecessary allocation, and bad for
+ /// performance. It is only necessary to allocate if you wish to move the box
+ /// into something.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # fn foo(bar: usize) {}
+ ///
+ /// // Bad
+ /// let x = Box::new(1);
+ /// foo(*x);
+ /// println!("{}", *x);
+ ///
+ /// // Good
+ /// let x = 1;
+ /// foo(x);
+ /// println!("{}", x);
+ /// ```
+ pub BOXED_LOCAL,
+ perf,
+ "using `Box<T>` where unnecessary"
+}
+
+fn is_non_trait_box(ty: Ty<'_>) -> bool {
+ ty.is_box() && !ty.boxed_ty().is_trait()
+}
+
+struct EscapeDelegate<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ set: HirIdSet,
+ 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>,
+ _: intravisit::FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ hir_id: HirId,
+ ) {
+ // If the method is an impl for a trait, don't warn.
+ let parent_id = cx.tcx.hir().get_parent_item(hir_id);
+ let parent_node = cx.tcx.hir().find(parent_id);
+
+ if let Some(Node::Item(item)) = parent_node {
+ if let ItemKind::Impl { of_trait: Some(_), .. } = item.kind {
+ return;
+ }
+ }
+
+ let mut v = EscapeDelegate {
+ cx,
+ set: HirIdSet::default(),
+ too_large_for_stack: self.too_large_for_stack,
+ };
+
+ let fn_def_id = cx.tcx.hir().local_def_id(hir_id);
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ ExprUseVisitor::new(&mut v, &infcx, fn_def_id, cx.param_env, cx.tables()).consume_body(body);
+ });
+
+ for node in v.set {
+ span_lint(
+ cx,
+ BOXED_LOCAL,
+ cx.tcx.hir().span(node),
+ "local variable doesn't need to be boxed here",
+ );
+ }
+ }
+}
+
+// TODO: Replace with Map::is_argument(..) when it's fixed
+fn is_argument(map: rustc_middle::hir::map::Map<'_>, id: HirId) -> bool {
+ match map.find(id) {
+ Some(Node::Binding(_)) => (),
+ _ => return false,
+ }
+
++ 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>, 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>, _: 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>) {
+ 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;
+ }
+
+ if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) {
+ self.set.insert(cmt.hir_id);
+ }
+ return;
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> EscapeDelegate<'a, 'tcx> {
+ fn is_large_box(&self, ty: Ty<'tcx>) -> bool {
+ // Large types need to be boxed to avoid stack overflows.
+ if ty.is_box() {
+ self.cx.layout_of(ty.boxed_ty()).map_or(0, |l| l.size.bytes()) > self.too_large_for_stack
+ } else {
+ false
+ }
+ }
+}
--- /dev/null
- (l, r) => match (l, r) {
- (ty::Ref(_, _, _), _) | (_, ty::Ref(_, _, _)) => false,
- (_, _) => true,
- },
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{def_id, Expr, ExprKind, Param, PatKind, QPath};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+use crate::utils::{
+ implements_trait, is_adjusted, iter_input_pats, snippet_opt, span_lint_and_sugg, span_lint_and_then,
+ type_is_unsafe_function,
+};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for closures which just call another function where
+ /// the function can be called directly. `unsafe` functions or calls where types
+ /// get adjusted are ignored.
+ ///
+ /// **Why is this bad?** Needlessly creating a closure adds code for no benefit
+ /// and gives the optimizer more work.
+ ///
+ /// **Known problems:** If creating the closure inside the closure has a side-
+ /// effect then moving the closure creation out will change when that side-
+ /// effect runs.
+ /// See rust-lang/rust-clippy#1439 for more details.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// // 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:** rust-lang/rust-clippy#3071, rust-lang/rust-clippy#4002,
+ /// rust-lang/rust-clippy#3942
+ ///
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// Some('a').map(|s| s.to_uppercase());
+ /// ```
+ /// may be rewritten as
+ /// ```rust,ignore
+ /// Some('a').map(char::to_uppercase);
+ /// ```
+ pub REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
+ pedantic,
+ "redundant closures for method calls"
+}
+
+declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]);
+
+impl<'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 {
+ check_closure(cx, arg)
+ }
+ },
+ _ => (),
+ }
+ }
+}
+
+fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let ExprKind::Closure(_, ref decl, eid, _, _) = expr.kind {
+ let body = cx.tcx.hir().body(eid);
+ let ex = &body.value;
+
+ if_chain!(
+ if let ExprKind::Call(ref caller, ref args) = ex.kind;
+
+ if let ExprKind::Path(_) = caller.kind;
+
+ // Not the same number of arguments, there is no way the closure is the same as the function return;
+ if args.len() == decl.inputs.len();
+
+ // Are the expression or the arguments type-adjusted? Then we need the closure
+ if !(is_adjusted(cx, ex) || args.iter().any(|arg| is_adjusted(cx, arg)));
+
+ let fn_ty = cx.tables().expr_ty(caller);
+
+ if matches!(fn_ty.kind, ty::FnDef(_, _) | ty::FnPtr(_) | ty::Closure(_, _));
+
+ if !type_is_unsafe_function(cx, fn_ty);
+
+ if compare_inputs(&mut iter_input_pats(decl, body), &mut args.iter());
+
+ then {
+ span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure found", |diag| {
+ if let Some(snippet) = snippet_opt(cx, caller.span) {
+ diag.span_suggestion(
+ expr.span,
+ "remove closure as shown",
+ snippet,
+ Applicability::MachineApplicable,
+ );
+ }
+ });
+ }
+ );
+
+ if_chain!(
+ if let ExprKind::MethodCall(ref path, _, ref args, _) = ex.kind;
+
+ // Not the same number of arguments, there is no way the closure is the same as the function return;
+ if args.len() == decl.inputs.len();
+
+ // Are the expression or the arguments type-adjusted? Then we need the closure
+ if !(is_adjusted(cx, ex) || args.iter().skip(1).any(|arg| is_adjusted(cx, arg)));
+
+ let method_def_id = cx.tables().type_dependent_def_id(ex.hir_id).unwrap();
+ if !type_is_unsafe_function(cx, cx.tcx.type_of(method_def_id));
+
+ if compare_inputs(&mut iter_input_pats(decl, body), &mut args.iter());
+
+ if let Some(name) = get_ufcs_type_name(cx, method_def_id, &args[0]);
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
+ expr.span,
+ "redundant closure found",
+ "remove closure as shown",
+ format!("{}::{}", name, path.ident.name),
+ Applicability::MachineApplicable,
+ );
+ }
+ );
+ }
+}
+
+/// Tries to determine the type for universal function call to be used instead of the closure
+fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: def_id::DefId, self_arg: &Expr<'_>) -> Option<String> {
+ let expected_type_of_self = &cx.tcx.fn_sig(method_def_id).inputs_and_output().skip_binder()[0];
+ let actual_type_of_self = &cx.tables().node_type(self_arg.hir_id);
+
+ if let Some(trait_id) = cx.tcx.trait_of_item(method_def_id) {
+ if match_borrow_depth(expected_type_of_self, &actual_type_of_self)
+ && implements_trait(cx, actual_type_of_self, trait_id, &[])
+ {
+ return Some(cx.tcx.def_path_str(trait_id));
+ }
+ }
+
+ cx.tcx.impl_of_method(method_def_id).and_then(|_| {
+ //a type may implicitly implement other type's methods (e.g. Deref)
+ if match_types(expected_type_of_self, &actual_type_of_self) {
+ return Some(get_type_name(cx, &actual_type_of_self));
+ }
+ None
+ })
+}
+
+fn match_borrow_depth(lhs: Ty<'_>, rhs: Ty<'_>) -> bool {
+ match (&lhs.kind, &rhs.kind) {
+ (ty::Ref(_, t1, mut1), ty::Ref(_, t2, mut2)) => mut1 == mut2 && match_borrow_depth(&t1, &t2),
++ (l, r) => !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<Item = &Param<'_>>,
+ call_args: &mut dyn Iterator<Item = &Expr<'_>>,
+) -> bool {
+ for (closure_input, function_arg) in closure_inputs.zip(call_args) {
+ if let PatKind::Binding(_, _, ident, _) = closure_input.pat.kind {
+ // XXXManishearth Should I be checking the binding mode here?
+ if let ExprKind::Path(QPath::Resolved(None, ref p)) = function_arg.kind {
+ if p.segments.len() != 1 {
+ // If it's a proper path, it can't be a local variable
+ return false;
+ }
+ if p.segments[0].ident.name != ident.name {
+ // The two idents should be the same
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ true
+}
--- /dev/null
- Constant::{F32, F64},
+use crate::consts::{
+ constant, constant_simple, Constant,
- use crate::utils::{higher, numeric_literal, span_lint_and_sugg, sugg, SpanlessEq};
++ Constant::{Int, F32, F64},
+};
- use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
++use crate::utils::{get_parent_expr, higher, numeric_literal, span_lint_and_sugg, sugg, SpanlessEq};
+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.tables(), base) {
+ if F32(2.0) == value || F64(2.0) == value {
+ return Some("log2");
+ } else if F32(10.0) == value || F64(10.0) == value {
+ return Some("log10");
+ } else if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
+ return Some("ln");
+ }
+ }
+
+ None
+}
+
+// Adds type suffixes and parenthesis to method receivers if necessary
+fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Sugg<'a> {
+ let mut suggestion = Sugg::hir(cx, expr, "..");
+
+ if let ExprKind::Unary(UnOp::UnNeg, inner_expr) = &expr.kind {
+ expr = &inner_expr;
+ }
+
+ if_chain! {
+ // if the expression is a float literal and it is unsuffixed then
+ // add a suffix so the suggestion is valid and unambiguous
+ if let ty::Float(float_ty) = cx.tables().expr_ty(expr).kind;
+ if let ExprKind::Lit(lit) = &expr.kind;
+ if let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node;
+ then {
+ let op = format!(
+ "{}{}{}",
+ suggestion,
+ // Check for float literals without numbers following the decimal
+ // separator such as `2.` and adds a trailing zero
+ if sym.as_str().ends_with('.') {
+ "0"
+ } else {
+ ""
+ },
+ float_ty.name_str()
+ ).into();
+
+ suggestion = match suggestion {
+ Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
+ _ => Sugg::NonParen(op)
+ };
+ }
+ }
+
+ suggestion.maybe_par()
+}
+
+fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ if let Some(method) = get_specialized_log_method(cx, &args[1]) {
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "logarithm for bases 2, 10 and e can be computed more accurately",
+ "consider using",
+ format!("{}.{}()", Sugg::hir(cx, &args[0], ".."), method),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and
+// suggest usage of `(x + (y - 1)).ln_1p()` instead
+fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ rhs,
+ ) = &args[0].kind
+ {
+ let recv = match (constant(cx, cx.tables(), lhs), constant(cx, cx.tables(), rhs)) {
+ (Some((value, _)), _) if F32(1.0) == value || F64(1.0) == value => rhs,
+ (_, Some((value, _))) if F32(1.0) == value || F64(1.0) == value => lhs,
+ _ => return,
+ };
+
+ span_lint_and_sugg(
+ cx,
+ IMPRECISE_FLOPS,
+ expr.span,
+ "ln(1 + x) can be computed more accurately",
+ "consider using",
+ format!("{}.ln_1p()", prepare_receiver_sugg(cx, recv)),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// Returns an integer if the float constant is a whole number and it can be
+// converted to an integer without loss of precision. For now we only check
+// ranges [-16777215, 16777216) for type f32 as whole number floats outside
+// this range are lossy and ambiguous.
+#[allow(clippy::cast_possible_truncation)]
+fn get_integer_from_float_constant(value: &Constant) -> Option<i32> {
+ match value {
+ F32(num) if num.fract() == 0.0 => {
+ if (-16_777_215.0..16_777_216.0).contains(num) {
+ Some(num.round() as i32)
+ } else {
+ None
+ }
+ },
+ F64(num) if num.fract() == 0.0 => {
+ if (-2_147_483_648.0..2_147_483_648.0).contains(num) {
+ Some(num.round() as i32)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ // Check receiver
+ if let Some((value, _)) = constant(cx, cx.tables(), &args[0]) {
+ let method = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
+ "exp"
+ } else if F32(2.0) == value || F64(2.0) == value {
+ "exp2"
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "exponent for bases 2 and e can be computed more accurately",
+ "consider using",
+ format!("{}.{}()", prepare_receiver_sugg(cx, &args[1]), method),
+ Applicability::MachineApplicable,
+ );
+ }
+
+ // Check argument
+ if let Some((value, _)) = constant(cx, cx.tables(), &args[1]) {
+ let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
+ (
+ SUBOPTIMAL_FLOPS,
+ "square-root of a number can be computed more efficiently and accurately",
+ format!("{}.sqrt()", Sugg::hir(cx, &args[0], "..")),
+ )
+ } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
+ (
+ IMPRECISE_FLOPS,
+ "cube-root of a number can be computed more accurately",
+ format!("{}.cbrt()", Sugg::hir(cx, &args[0], "..")),
+ )
+ } else if let Some(exponent) = get_integer_from_float_constant(&value) {
+ (
+ SUBOPTIMAL_FLOPS,
+ "exponentiation with integer powers can be computed more efficiently",
+ format!(
+ "{}.powi({})",
+ Sugg::hir(cx, &args[0], ".."),
+ numeric_literal::format(&exponent.to_string(), None, false)
+ ),
+ )
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ expr.span,
+ help,
+ "consider using",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
++fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
++ if let Some((value, _)) = constant(cx, cx.tables(), &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<String> {
++ 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 are_exprs_equal(cx, lmul_lhs, lmul_rhs);
++ if are_exprs_equal(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.tables(), &largs[1]);
++ if let Some((rvalue, _)) = constant(cx, cx.tables(), &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.tables().expr_ty(lhs).is_floating_point();
+ if let Some((value, _)) = constant(cx, cx.tables(), rhs);
+ if F32(1.0) == value || F64(1.0) == value;
+ if let ExprKind::MethodCall(ref path, _, ref method_args, _) = lhs.kind;
+ if cx.tables().expr_ty(&method_args[0]).is_floating_point();
+ if path.ident.name.as_str() == "exp";
+ then {
+ span_lint_and_sugg(
+ cx,
+ IMPRECISE_FLOPS,
+ expr.span,
+ "(e.pow(x) - 1) can be computed more accurately",
+ "consider using",
+ format!(
+ "{}.exp_m1()",
+ Sugg::hir(cx, &method_args[0], "..")
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lhs, ref rhs) = &expr.kind;
+ if cx.tables().expr_ty(lhs).is_floating_point();
+ if cx.tables().expr_ty(rhs).is_floating_point();
+ then {
+ return Some((lhs, rhs));
+ }
+ }
+
+ None
+}
+
+// TODO: Fix rust-lang/rust-clippy#4735
+fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ rhs,
+ ) = &expr.kind
+ {
++ 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) && are_exprs_equal(cx, left, test),
+ BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && are_exprs_equal(cx, right, test),
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
+
+/// See [`is_testing_positive`]
+fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
+ if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
+ match op {
+ BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && are_exprs_equal(cx, right, test),
+ BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && are_exprs_equal(cx, left, test),
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
+
+fn are_exprs_equal(cx: &LateContext<'_>, expr1: &Expr<'_>, expr2: &Expr<'_>) -> bool {
+ SpanlessEq::new(cx).ignore_fn().eq_expr(expr1, expr2)
+}
+
+/// Returns true iff expr is some zero literal
+fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ match constant_simple(cx, cx.tables(), expr) {
+ Some(Constant::Int(i)) => i == 0,
+ Some(Constant::F32(f)) => f == 0.0,
+ Some(Constant::F64(f)) => f == 0.0,
+ _ => false,
+ }
+}
+
+/// If the two expressions are negations of each other, then it returns
+/// a tuple, in which the first element is true iff expr1 is the
+/// positive expressions, and the second element is the positive
+/// one of the two expressions
+/// If the two expressions are not negations of each other, then it
+/// returns None.
+fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
+ if let ExprKind::Unary(UnOp::UnNeg, expr1_negated) = &expr1.kind {
+ if are_exprs_equal(cx, expr1_negated, expr2) {
+ return Some((false, expr2));
+ }
+ }
+ if let ExprKind::Unary(UnOp::UnNeg, expr2_negated) = &expr2.kind {
+ if are_exprs_equal(cx, expr1, expr2_negated) {
+ return Some((true, expr1));
+ }
+ }
+ None
+}
+
+fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some((cond, body, Some(else_body))) = higher::if_block(&expr);
+ if let ExprKind::Block(block, _) = body.kind;
+ if block.stmts.is_empty();
+ if let Some(if_body_expr) = block.expr;
+ if let ExprKind::Block(else_block, _) = else_body.kind;
+ if else_block.stmts.is_empty();
+ if let Some(else_body_expr) = else_block.expr;
+ if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr);
+ then {
+ let positive_abs_sugg = (
+ "manual implementation of `abs` method",
+ format!("{}.abs()", Sugg::hir(cx, body, "..")),
+ );
+ let negative_abs_sugg = (
+ "manual implementation of negation of `abs` method",
+ format!("-{}.abs()", Sugg::hir(cx, body, "..")),
+ );
+ let sugg = if is_testing_positive(cx, cond, body) {
+ if if_expr_positive {
+ positive_abs_sugg
+ } else {
+ negative_abs_sugg
+ }
+ } else if is_testing_negative(cx, cond, body) {
+ if if_expr_positive {
+ negative_abs_sugg
+ } else {
+ positive_abs_sugg
+ }
+ } else {
+ return;
+ };
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ sugg.0,
+ "try",
+ sugg.1,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
++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 && are_exprs_equal(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.tables(), div_rhs);
++ if let Some((lvalue, _)) = constant(cx, cx.tables(), 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.tables().expr_ty(&args[0]);
+
+ if recv_ty.is_floating_point() {
+ match &*path.ident.name.as_str() {
+ "ln" => check_ln1p(cx, expr, args),
+ "log" => check_log_base(cx, expr, args),
+ "powf" => check_powf(cx, expr, args),
++ "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);
+ }
+ }
+}
--- /dev/null
- if let ExprKind::Block(..) = expr.kind {
- true
- } else {
- false
- }
+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 {
- if let ExprKind::If(..) = expr.kind {
- true
- } else {
- false
- }
++ 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(..))
+}
--- /dev/null
- Path(ref qpath) => {
- if let Res::Local(_) = qpath_res(cx, qpath, e.hir_id) {
- false
- } else {
- true
- }
- },
+use crate::utils::{
+ attr_by_name, attrs::is_proc_macro, is_must_use_ty, is_trait_impl_item, iter_input_pats, match_def_path,
+ must_use_attr, qpath_res, return_ty, snippet, snippet_opt, span_lint, span_lint_and_help, span_lint_and_then,
+ trait_ref_of_method, type_is_unsafe_function,
+};
+use rustc_ast::ast::Attribute;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit;
+use rustc_hir::{def::Res, def_id::DefId};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_target::spec::abi::Abi;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for functions with too many parameters.
+ ///
+ /// **Why is this bad?** Functions with lots of parameters are considered bad
+ /// style and reduce readability (“what does the 5th parameter mean?”). Consider
+ /// grouping some parameters into a new type.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # struct Color;
+ /// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) {
+ /// // ..
+ /// }
+ /// ```
+ pub TOO_MANY_ARGUMENTS,
+ complexity,
+ "functions with too many arguments"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for functions with a large amount of lines.
+ ///
+ /// **Why is this bad?** Functions with a lot of lines are harder to understand
+ /// due to having to look at a larger amount of code to understand what the
+ /// function is doing. Consider splitting the body of the function into
+ /// multiple functions.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// fn im_too_long() {
+ /// println!("");
+ /// // ... 100 more LoC
+ /// println!("");
+ /// }
+ /// ```
+ pub TOO_MANY_LINES,
+ pedantic,
+ "functions with too many lines"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for public functions that dereference raw pointer
+ /// arguments but are not marked unsafe.
+ ///
+ /// **Why is this bad?** The function should probably be marked `unsafe`, since
+ /// for an arbitrary raw pointer, there is no way of telling for sure if it is
+ /// valid.
+ ///
+ /// **Known problems:**
+ ///
+ /// * It does not check functions recursively so if the pointer is passed to a
+ /// private non-`unsafe` function which does the dereferencing, the lint won't
+ /// trigger.
+ /// * It only checks for arguments whose type are raw pointers, not raw pointers
+ /// got from an argument in some other way (`fn foo(bar: &[*const u8])` or
+ /// `some_argument.get_raw_ptr()`).
+ ///
+ /// **Example:**
+ /// ```rust,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 { t }
+ /// ```
+ pub MUST_USE_CANDIDATE,
+ pedantic,
+ "function or method that could take a `#[must_use]` attribute"
+}
+
+#[derive(Copy, Clone)]
+pub struct Functions {
+ threshold: u64,
+ max_lines: u64,
+}
+
+impl Functions {
+ pub fn new(threshold: u64, max_lines: u64) -> Self {
+ Self { threshold, max_lines }
+ }
+}
+
+impl_lint_pass!(Functions => [
+ TOO_MANY_ARGUMENTS,
+ TOO_MANY_LINES,
+ NOT_UNSAFE_PTR_ARG_DEREF,
+ MUST_USE_UNIT,
+ DOUBLE_MUST_USE,
+ MUST_USE_CANDIDATE,
+]);
+
+impl<'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 attr = must_use_attr(&item.attrs);
+ if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
+ if let Some(attr) = attr {
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr);
+ return;
+ }
+ if cx.access_levels.is_exported(item.hir_id)
+ && !is_proc_macro(&item.attrs)
+ && attr_by_name(&item.attrs, "no_mangle").is_none()
+ {
+ check_must_use_candidate(
+ cx,
+ &sig.decl,
+ cx.tcx.hir().body(*body_id),
+ item.span,
+ item.hir_id,
+ item.span.with_hi(sig.decl.output.span().hi()),
+ "this function could have a `#[must_use]` attribute",
+ );
+ }
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
+ if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind {
+ let attr = must_use_attr(&item.attrs);
+ if let Some(attr) = attr {
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr);
+ } else if cx.access_levels.is_exported(item.hir_id)
+ && !is_proc_macro(&item.attrs)
+ && trait_ref_of_method(cx, item.hir_id).is_none()
+ {
+ check_must_use_candidate(
+ cx,
+ &sig.decl,
+ cx.tcx.hir().body(*body_id),
+ item.span,
+ item.hir_id,
+ item.span.with_hi(sig.decl.output.span().hi()),
+ "this method could have a `#[must_use]` attribute",
+ );
+ }
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind {
+ // don't lint extern functions decls, it's not their fault
+ if sig.header.abi == Abi::Rust {
+ self.check_arg_number(cx, &sig.decl, item.span.with_hi(sig.decl.output.span().hi()));
+ }
+
+ let attr = must_use_attr(&item.attrs);
+ if let Some(attr) = attr {
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr);
+ }
+ if let hir::TraitFn::Provided(eid) = *eid {
+ let body = cx.tcx.hir().body(eid);
+ Self::check_raw_ptr(cx, sig.header.unsafety, &sig.decl, body, item.hir_id);
+
+ if attr.is_none() && cx.access_levels.is_exported(item.hir_id) && !is_proc_macro(&item.attrs) {
+ check_must_use_candidate(
+ cx,
+ &sig.decl,
+ body,
+ item.span,
+ item.hir_id,
+ item.span.with_hi(sig.decl.output.span().hi()),
+ "this method could have a `#[must_use]` attribute",
+ );
+ }
+ }
+ }
+ }
+}
+
+impl<'tcx> Functions {
+ fn check_arg_number(self, cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, fn_span: Span) {
+ let args = decl.inputs.len() as u64;
+ if args > self.threshold {
+ span_lint(
+ cx,
+ TOO_MANY_ARGUMENTS,
+ fn_span,
+ &format!("this function has too many arguments ({}/{})", args, self.threshold),
+ );
+ }
+ }
+
+ fn check_line_number(self, cx: &LateContext<'_>, span: Span, body: &'tcx hir::Body<'_>) {
+ if in_external_macro(cx.sess(), span) {
+ return;
+ }
+
+ let code_snippet = snippet(cx, body.value.span, "..");
+ let mut line_count: u64 = 0;
+ let mut in_comment = false;
+ let mut code_in_line;
+
+ // Skip the surrounding function decl.
+ let start_brace_idx = code_snippet.find('{').map_or(0, |i| i + 1);
+ let end_brace_idx = code_snippet.rfind('}').unwrap_or_else(|| code_snippet.len());
+ let function_lines = code_snippet[start_brace_idx..end_brace_idx].lines();
+
+ for mut line in function_lines {
+ code_in_line = false;
+ loop {
+ line = line.trim_start();
+ if line.is_empty() {
+ break;
+ }
+ if in_comment {
+ match line.find("*/") {
+ Some(i) => {
+ line = &line[i + 2..];
+ in_comment = false;
+ continue;
+ },
+ None => break,
+ }
+ } else {
+ let multi_idx = line.find("/*").unwrap_or_else(|| line.len());
+ let single_idx = line.find("//").unwrap_or_else(|| line.len());
+ code_in_line |= multi_idx > 0 && single_idx > 0;
+ // Implies multi_idx is below line.len()
+ if multi_idx < single_idx {
+ line = &line[multi_idx + 2..];
+ in_comment = true;
+ continue;
+ }
+ break;
+ }
+ }
+ if code_in_line {
+ line_count += 1;
+ }
+ }
+
+ if line_count > self.max_lines {
+ span_lint(cx, TOO_MANY_LINES, span, "This function has a large number of lines.")
+ }
+ }
+
+ fn check_raw_ptr(
+ cx: &LateContext<'tcx>,
+ unsafety: hir::Unsafety,
+ decl: &'tcx hir::FnDecl<'_>,
+ body: &'tcx hir::Body<'_>,
+ hir_id: hir::HirId,
+ ) {
+ let expr = &body.value;
+ if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(hir_id) {
+ let raw_ptrs = iter_input_pats(decl, body)
+ .zip(decl.inputs.iter())
+ .filter_map(|(arg, ty)| raw_ptr_arg(arg, ty))
+ .collect::<FxHashSet<_>>();
+
+ if !raw_ptrs.is_empty() {
+ let tables = cx.tcx.body_tables(body.id());
+ let mut v = DerefVisitor {
+ cx,
+ ptrs: raw_ptrs,
+ tables,
+ };
+
+ intravisit::walk_expr(&mut v, expr);
+ }
+ }
+ }
+}
+
+fn check_needless_must_use(
+ cx: &LateContext<'_>,
+ decl: &hir::FnDecl<'_>,
+ item_id: hir::HirId,
+ item_span: Span,
+ fn_header_span: Span,
+ attr: &Attribute,
+) {
+ if in_external_macro(cx.sess(), item_span) {
+ return;
+ }
+ if returns_unit(decl) {
+ span_lint_and_then(
+ cx,
+ MUST_USE_UNIT,
+ fn_header_span,
+ "this unit-returning function has a `#[must_use]` attribute",
+ |diag| {
+ diag.span_suggestion(
+ attr.span,
+ "remove the attribute",
+ "".into(),
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ } else if !attr.is_value_str() && is_must_use_ty(cx, return_ty(cx, item_id)) {
+ span_lint_and_help(
+ cx,
+ DOUBLE_MUST_USE,
+ fn_header_span,
+ "this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
+ None,
+ "either add some descriptive text or remove the attribute",
+ );
+ }
+}
+
+fn check_must_use_candidate<'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<DefId>) -> bool {
+ if let hir::PatKind::Wild = pat.kind {
+ return false; // ignore `_` patterns
+ }
+ let def_id = pat.hir_id.owner.to_def_id();
+ if cx.tcx.has_typeck_tables(def_id) {
+ is_mutable_ty(
+ cx,
+ &cx.tcx.typeck_tables_of(def_id.expect_local()).pat_ty(pat),
+ pat.span,
+ tys,
+ )
+ } else {
+ false
+ }
+}
+
+static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
+
+fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut FxHashSet<DefId>) -> bool {
+ match ty.kind {
+ // primitive types are never mutable
+ ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
+ ty::Adt(ref adt, ref substs) => {
+ tys.insert(adt.did) && !ty.is_freeze(cx.tcx.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<hir::HirId> {
+ if let (&hir::PatKind::Binding(_, id, _, _), &hir::TyKind::Ptr(_)) = (&arg.pat.kind, &ty.kind) {
+ Some(id)
+ } else {
+ None
+ }
+}
+
+struct DerefVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ ptrs: FxHashSet<hir::HirId>,
+ tables: &'a ty::TypeckTables<'tcx>,
+}
+
+impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ match expr.kind {
+ hir::ExprKind::Call(ref f, args) => {
+ let ty = self.tables.expr_ty(f);
+
+ if type_is_unsafe_function(self.cx, ty) {
+ for arg in args {
+ self.check_arg(arg);
+ }
+ }
+ },
+ hir::ExprKind::MethodCall(_, _, args, _) => {
+ let def_id = self.tables.type_dependent_def_id(expr.hir_id).unwrap();
+ let base_type = self.cx.tcx.type_of(def_id);
+
+ if type_is_unsafe_function(self.cx, base_type) {
+ for arg in args {
+ self.check_arg(arg);
+ }
+ }
+ },
+ hir::ExprKind::Unary(hir::UnOp::UnDeref, ref ptr) => self.check_arg(ptr),
+ _ => (),
+ }
+
+ intravisit::walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+}
+
+impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
+ fn check_arg(&self, ptr: &hir::Expr<'_>) {
+ if let hir::ExprKind::Path(ref qpath) = ptr.kind {
+ if let Res::Local(id) = qpath_res(self.cx, qpath, ptr.hir_id) {
+ if self.ptrs.contains(&id) {
+ span_lint(
+ self.cx,
+ NOT_UNSAFE_PTR_ARG_DEREF,
+ ptr.span,
+ "this public function dereferences a raw pointer but is not marked `unsafe`",
+ );
+ }
+ }
+ }
+ }
+}
+
+struct StaticMutVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ mutates_static: bool,
+}
+
+impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
+
+ if self.mutates_static {
+ return;
+ }
+ match expr.kind {
+ Call(_, args) | MethodCall(_, _, args, _) => {
+ let mut tys = FxHashSet::default();
+ for arg in args {
+ let def_id = arg.hir_id.owner.to_def_id();
+ if self.cx.tcx.has_typeck_tables(def_id)
+ && is_mutable_ty(
+ self.cx,
+ self.cx.tcx.typeck_tables_of(def_id.expect_local()).expr_ty(arg),
+ arg.span,
+ &mut tys,
+ )
+ && is_mutated_static(self.cx, arg)
+ {
+ self.mutates_static = true;
+ return;
+ }
+ tys.clear();
+ }
+ },
+ Assign(ref target, ..) | AssignOp(_, ref target, _) | AddrOf(_, hir::Mutability::Mut, ref target) => {
+ self.mutates_static |= is_mutated_static(self.cx, target)
+ },
+ _ => {},
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+}
+
+fn is_mutated_static(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool {
+ use hir::ExprKind::{Field, Index, Path};
+
+ match e.kind {
++ Path(ref qpath) => !matches!(qpath_res(cx, qpath, e.hir_id), Res::Local(_)),
+ Field(ref inner, _) | Index(ref inner, _) => is_mutated_static(cx, 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
+}
--- /dev/null
- impl<'tcx> ArmVisitor<'_, 'tcx> {
+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<Self::Map> {
+ 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<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
- if let Some(arm_mutex) = self.found_mutex {
- SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex)
- } else {
- false
- }
++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.to_string() == "lock";
+ let ty = cx.tables().expr_ty(&args[0]);
+ if is_type_diagnostic_item(cx, ty, sym!(mutex_type));
+ then {
+ Some(&args[0])
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
- ty::Dynamic(ref tt, ..) => {
- if let Some(principal) = tt.principal() {
- cx.tcx
- .associated_items(principal.def_id())
- .in_definition_order()
- .any(|item| is_is_empty(cx, &item))
- } else {
- false
- }
- },
+use crate::utils::{get_item_name, higher, snippet_with_applicability, span_lint, span_lint_and_sugg, walk_ptrs_ty};
+use rustc_ast::ast::LitKind;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{AssocItemKind, BinOpKind, Expr, ExprKind, ImplItemRef, Item, ItemKind, TraitItemRef};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::{Span, Spanned, 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_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY]);
+
+impl<'tcx> LateLintPass<'tcx> for LenZero {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if item.span.from_expansion() {
+ return;
+ }
+
+ match item.kind {
+ ItemKind::Trait(_, _, _, _, ref trait_items) => check_trait_items(cx, item, trait_items),
+ ItemKind::Impl {
+ of_trait: None,
+ items: ref impl_items,
+ ..
+ } => check_impl_items(cx, item, impl_items),
+ _ => (),
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let ExprKind::Binary(Spanned { node: cmp, .. }, ref left, ref right) = expr.kind {
+ match cmp {
+ BinOpKind::Eq => {
+ check_cmp(cx, expr.span, left, right, "", 0); // len == 0
+ check_cmp(cx, expr.span, right, left, "", 0); // 0 == len
+ },
+ BinOpKind::Ne => {
+ check_cmp(cx, expr.span, left, right, "!", 0); // len != 0
+ check_cmp(cx, expr.span, right, left, "!", 0); // 0 != len
+ },
+ BinOpKind::Gt => {
+ check_cmp(cx, expr.span, left, right, "!", 0); // len > 0
+ check_cmp(cx, expr.span, right, left, "", 1); // 1 > len
+ },
+ BinOpKind::Lt => {
+ check_cmp(cx, expr.span, left, right, "", 1); // len < 1
+ check_cmp(cx, expr.span, right, left, "!", 0); // 0 < len
+ },
+ BinOpKind::Ge => check_cmp(cx, expr.span, left, right, "!", 1), // len >= 1
+ BinOpKind::Le => check_cmp(cx, expr.span, right, left, "!", 1), // 1 <= len
+ _ => (),
+ }
+ }
+ }
+}
+
+fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items: &[TraitItemRef]) {
+ fn is_named_self(cx: &LateContext<'_>, item: &TraitItemRef, name: &str) -> bool {
+ item.ident.name.as_str() == name
+ && if let AssocItemKind::Fn { has_self } = item.kind {
+ has_self && {
+ let did = cx.tcx.hir().local_def_id(item.id.hir_id);
+ cx.tcx.fn_sig(did).inputs().skip_binder().len() == 1
+ }
+ } else {
+ false
+ }
+ }
+
+ // fill the set with current and super traits
+ fn fill_trait_set(traitt: DefId, set: &mut FxHashSet<DefId>, cx: &LateContext<'_>) {
+ if set.insert(traitt) {
+ for supertrait in rustc_trait_selection::traits::supertrait_def_ids(cx.tcx, traitt) {
+ fill_trait_set(supertrait, set, cx);
+ }
+ }
+ }
+
+ if cx.access_levels.is_exported(visited_trait.hir_id) && trait_items.iter().any(|i| is_named_self(cx, i, "len")) {
+ let mut current_and_super_traits = FxHashSet::default();
+ let visited_trait_def_id = cx.tcx.hir().local_def_id(visited_trait.hir_id);
+ fill_trait_set(visited_trait_def_id.to_def_id(), &mut current_and_super_traits, cx);
+
+ let is_empty_method_found = current_and_super_traits
+ .iter()
+ .flat_map(|&i| cx.tcx.associated_items(i).in_definition_order())
+ .any(|i| {
+ i.kind == ty::AssocKind::Fn
+ && i.fn_has_self_parameter
+ && i.ident.name == sym!(is_empty)
+ && cx.tcx.fn_sig(i.def_id).inputs().skip_binder().len() == 1
+ });
+
+ if !is_empty_method_found {
+ span_lint(
+ cx,
+ LEN_WITHOUT_IS_EMPTY,
+ visited_trait.span,
+ &format!(
+ "trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
+ visited_trait.ident.name
+ ),
+ );
+ }
+ }
+}
+
+fn check_impl_items(cx: &LateContext<'_>, item: &Item<'_>, impl_items: &[ImplItemRef<'_>]) {
+ fn is_named_self(cx: &LateContext<'_>, item: &ImplItemRef<'_>, name: &str) -> bool {
+ item.ident.name.as_str() == name
+ && if let AssocItemKind::Fn { has_self } = item.kind {
+ has_self && {
+ let did = cx.tcx.hir().local_def_id(item.id.hir_id);
+ cx.tcx.fn_sig(did).inputs().skip_binder().len() == 1
+ }
+ } else {
+ false
+ }
+ }
+
+ let is_empty = if let Some(is_empty) = impl_items.iter().find(|i| is_named_self(cx, i, "is_empty")) {
+ if cx.access_levels.is_exported(is_empty.id.hir_id) {
+ return;
+ } else {
+ "a private"
+ }
+ } else {
+ "no corresponding"
+ };
+
+ if let Some(i) = impl_items.iter().find(|i| is_named_self(cx, i, "len")) {
+ if cx.access_levels.is_exported(i.id.hir_id) {
+ let def_id = cx.tcx.hir().local_def_id(item.hir_id);
+ let ty = cx.tcx.type_of(def_id);
+
+ span_lint(
+ cx,
+ LEN_WITHOUT_IS_EMPTY,
+ item.span,
+ &format!(
+ "item `{}` has a public `len` method but {} `is_empty` method",
+ ty, is_empty
+ ),
+ );
+ }
+ }
+}
+
+fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
+ if let (&ExprKind::MethodCall(ref method_path, _, ref args, _), &ExprKind::Lit(ref lit)) = (&method.kind, &lit.kind)
+ {
+ // check if we are in an is_empty() method
+ if let Some(name) = get_item_name(cx, method) {
+ if name.as_str() == "is_empty" {
+ return;
+ }
+ }
+
+ check_len(cx, span, method_path.ident.name, args, &lit.node, op, compare_to)
+ }
+}
+
+fn check_len(
+ cx: &LateContext<'_>,
+ span: Span,
+ method_name: 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,
+ );
+ }
+ }
+}
+
+/// Checks if this type has an `is_empty` method.
+fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ /// Special case ranges until `range_is_empty` is stabilized. See issue 3807.
+ fn should_skip_range(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ higher::range(cx, expr).map_or(false, |_| {
+ !cx.tcx
+ .features()
+ .declared_lib_features
+ .iter()
+ .any(|(name, _)| name.as_str() == "range_is_empty")
+ })
+ }
+
+ /// 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))
+ })
+ }
+
+ if should_skip_range(cx, expr) {
+ return false;
+ }
+
+ let ty = &walk_ptrs_ty(cx.tables().expr_ty(expr));
+ 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,
+ }
+}
--- /dev/null
- use rustc_hir::def_id::DefId;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
- use crate::utils::{in_macro, match_qpath, snippet_opt, span_lint_and_then};
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{Block, Expr, ExprKind, 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};
+
- impl BorrowVisitor<'_, '_> {
- fn fn_def_id(&self, expr: &Expr<'_>) -> Option<DefId> {
- match &expr.kind {
- ExprKind::MethodCall(..) => self.cx.tables().type_dependent_def_id(expr.hir_id),
- ExprKind::Call(
- Expr {
- kind: ExprKind::Path(qpath),
- ..
- },
- ..,
- ) => self.cx.qpath_res(qpath, expr.hir_id).opt_def_id(),
- _ => None,
- }
- }
- }
-
++use crate::utils::{fn_def_id, in_macro, match_qpath, snippet_opt, 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_lint_pass!(LetReturn => [LET_AND_RETURN]);
+
+impl<'tcx> LateLintPass<'tcx> for LetReturn {
+ 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 local.attrs.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(snippet) = snippet_opt(cx, initexpr.span) {
+ err.multipart_suggestion(
+ "return the expression directly",
+ vec![
+ (local.span, String::new()),
+ (retexpr.span, snippet),
+ ],
+ Applicability::MachineApplicable,
+ );
+ } else {
+ err.span_help(initexpr.span, "this expression can be directly returned");
+ }
+ },
+ );
+ }
+ }
+ }
+}
+
+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,
+}
+
- if let Some(def_id) = self.fn_def_id(expr) {
+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<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
- mod redundant_pattern_matching;
+// error-pattern:cargo-clippy
+
+#![feature(bindings_after_at)]
+#![feature(box_patterns)]
+#![feature(box_syntax)]
+#![feature(concat_idents)]
+#![feature(crate_visibility_modifier)]
+#![feature(drain_filter)]
+#![feature(or_patterns)]
+#![feature(rustc_private)]
+#![feature(stmt_expr_attributes)]
+#![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_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 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
+ }
+ };
+}
+
+mod consts;
+#[macro_use]
+mod utils;
+
+// begin lints modules, do not remove this comment, it’s used in `update_lints`
+mod approx_const;
+mod arithmetic;
+mod as_conversions;
+mod assertions_on_constants;
+mod assign_ops;
+mod atomic_ordering;
+mod attrs;
+mod await_holding_lock;
+mod bit_mask;
+mod blacklisted_name;
+mod blocks_in_if_conditions;
+mod booleans;
+mod bytecount;
+mod cargo_common_metadata;
+mod checked_conversions;
+mod cognitive_complexity;
+mod collapsible_if;
+mod comparison_chain;
+mod copies;
+mod copy_iterator;
+mod dbg_macro;
+mod default_trait_access;
+mod dereference;
+mod derive;
+mod doc;
+mod double_comparison;
+mod double_parens;
+mod drop_bounds;
+mod drop_forget_ref;
+mod duration_subsec;
+mod else_if_without_else;
+mod empty_enum;
+mod entry;
+mod enum_clike;
+mod enum_variants;
+mod eq_op;
+mod erasing_op;
+mod escape;
+mod eta_reduction;
+mod eval_order_dependence;
+mod excessive_bools;
+mod exit;
+mod explicit_write;
+mod fallible_impl_from;
+mod float_literal;
+mod floating_point_arithmetic;
+mod format;
+mod formatting;
+mod functions;
+mod future_not_send;
+mod get_last_with_len;
+mod identity_op;
+mod if_let_mutex;
+mod if_let_some_result;
+mod if_not_else;
+mod implicit_return;
+mod implicit_saturating_sub;
+mod indexing_slicing;
+mod infinite_iter;
+mod inherent_impl;
+mod inherent_to_string;
+mod inline_fn_without_body;
+mod int_plus_one;
+mod integer_division;
+mod items_after_statements;
+mod large_const_arrays;
+mod large_enum_variant;
+mod large_stack_arrays;
+mod len_zero;
+mod let_and_return;
+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_non_exhaustive;
+mod map_clone;
++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_reference;
+mod mutable_debug_assertion;
+mod mutex_atomic;
+mod needless_bool;
+mod needless_borrow;
+mod needless_borrowed_ref;
+mod needless_continue;
+mod needless_pass_by_value;
+mod needless_update;
+mod neg_cmp_op_on_partial_ord;
+mod neg_multiply;
+mod new_without_default;
+mod no_effect;
+mod non_copy_const;
+mod non_expressive_names;
+mod open_options;
+mod option_env_unwrap;
++mod option_if_let_else;
+mod overflow_check_conditional;
+mod panic_unimplemented;
+mod partialeq_ne_impl;
+mod path_buf_push_overwrite;
++mod pattern_type_mismatch;
+mod precedence;
+mod ptr;
+mod ptr_offset_with_cast;
+mod question_mark;
+mod ranges;
+mod redundant_clone;
+mod redundant_field_names;
- "associated-constants `MIN`/`MAX` of integers are prefer to `{min,max}_value()` and module constants",
+mod redundant_pub_crate;
+mod redundant_static_lifetimes;
+mod reference;
+mod regex;
++mod repeat_once;
+mod returns;
+mod serde_api;
+mod shadow;
+mod single_component_path_imports;
+mod slow_vector_initialization;
+mod strings;
+mod suspicious_trait_impl;
+mod swap;
+mod tabs_in_doc_comments;
+mod temporary_assignment;
+mod to_digit_is_some;
+mod trait_bounds;
+mod transmute;
+mod transmuting_null;
+mod trivially_copy_pass_by_ref;
+mod try_err;
+mod types;
+mod unicode;
+mod unnamed_address;
+mod unnecessary_sort_by;
+mod unnested_or_patterns;
+mod unsafe_removed_from_name;
+mod unused_io_amount;
+mod unused_self;
+mod unwrap;
+mod use_self;
+mod useless_conversion;
+mod vec;
+mod vec_resize_to_zero;
+mod verbose_file_reads;
+mod wildcard_dependencies;
+mod wildcard_imports;
+mod write;
+mod zero_div_zero;
+// end lints modules, do not remove this comment, it’s used in `update_lints`
+
+pub use crate::utils::conf::Conf;
+
+mod reexport {
+ pub use rustc_span::Symbol as Name;
+}
+
+/// Register all pre expansion lints
+///
+/// Pre-expansion lints run before any macro expansion has happened.
+///
+/// Note that due to the architecture of the compiler, currently `cfg_attr` attributes on crate
+/// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass.
+///
+/// Used in `./src/driver.rs`.
+pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) {
+ 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::ast::NestedMetaItem], sess: &Session) -> Conf {
+ use std::path::Path;
+ match utils::conf::file_from_args(args) {
+ Ok(file_name) => {
+ // if the user specified a file, it must exist, otherwise default to `clippy.toml` but
+ // do not require the file to exist
+ let file_name = match file_name {
+ Some(file_name) => file_name,
+ None => match utils::conf::lookup_conf_file() {
+ Ok(Some(path)) => path,
+ Ok(None) => return Conf::default(),
+ Err(error) => {
+ sess.struct_err(&format!("error finding Clippy's configuration file: {}", error))
+ .emit();
+ return Conf::default();
+ },
+ },
+ };
+
+ let file_name = if file_name.is_relative() {
+ sess.local_crate_source_file
+ .as_deref()
+ .and_then(Path::parent)
+ .unwrap_or_else(|| Path::new(""))
+ .join(file_name)
+ } else {
+ file_name
+ };
+
+ let (conf, errors) = utils::conf::read(&file_name);
+
+ // all conf errors are non-fatal, we just use the default conf in case of error
+ for error in errors {
+ sess.struct_err(&format!(
+ "error reading Clippy's configuration file `{}`: {}",
+ file_name.display(),
+ error
+ ))
+ .emit();
+ }
+
+ conf
+ },
+ Err((err, span)) => {
+ sess.struct_span_err(span, err)
+ .span_note(span, "Clippy will use default configuration")
+ .emit();
+ Conf::default()
+ },
+ }
+}
+
+/// Register all lints and lint groups with the rustc plugin registry
+///
+/// Used in `./src/driver.rs`.
+#[allow(clippy::too_many_lines)]
+#[rustfmt::skip]
+pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ register_removed_non_tool_lints(store);
+
+ // begin deprecated lints, do not remove this comment, it’s used in `update_lints`
+ store.register_removed(
+ "clippy::should_assert_eq",
+ "`assert!()` will be more flexible with RFC 2011",
+ );
+ store.register_removed(
+ "clippy::extend_from_slice",
+ "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice",
+ );
+ store.register_removed(
+ "clippy::range_step_by_zero",
+ "`iterator.step_by(0)` panics nowadays",
+ );
+ store.register_removed(
+ "clippy::unstable_as_slice",
+ "`Vec::as_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "clippy::unstable_as_mut_slice",
+ "`Vec::as_mut_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "clippy::str_to_string",
+ "using `str::to_string` is common even today and specialization will likely happen soon",
+ );
+ store.register_removed(
+ "clippy::string_to_string",
+ "using `string::to_string` is common even today and specialization will likely happen soon",
+ );
+ store.register_removed(
+ "clippy::misaligned_transmute",
+ "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",
+ );
+ store.register_removed(
+ "clippy::assign_ops",
+ "using compound assignment operators (e.g., `+=`) is harmless",
+ );
+ store.register_removed(
+ "clippy::if_let_redundant_pattern_matching",
+ "this lint has been changed to redundant_pattern_matching",
+ );
+ store.register_removed(
+ "clippy::unsafe_vector_initialization",
+ "the replacement suggested by this lint had substantially different behavior",
+ );
+ store.register_removed(
+ "clippy::invalid_ref",
+ "superseded by rustc lint `invalid_value`",
+ );
+ store.register_removed(
+ "clippy::unused_collect",
+ "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint",
+ );
+ store.register_removed(
+ "clippy::into_iter_on_array",
+ "this lint has been uplifted to rustc and is now called `array_into_iter`",
+ );
+ store.register_removed(
+ "clippy::unused_label",
+ "this lint has been uplifted to rustc and is now called `unused_labels`",
+ );
+ store.register_removed(
+ "clippy::replace_consts",
- &redundant_pattern_matching::REDUNDANT_PATTERN_MATCHING,
++ "associated-constants `MIN`/`MAX` of integers are prefered 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",
+ );
+ // end deprecated lints, do not remove this comment, it’s used in `update_lints`
+
+ // begin register lints, do not remove this comment, it’s used in `update_lints`
+ store.register_lints(&[
+ &approx_const::APPROX_CONSTANT,
+ &arithmetic::FLOAT_ARITHMETIC,
+ &arithmetic::INTEGER_ARITHMETIC,
+ &as_conversions::AS_CONVERSIONS,
+ &assertions_on_constants::ASSERTIONS_ON_CONSTANTS,
+ &assign_ops::ASSIGN_OP_PATTERN,
+ &assign_ops::MISREFACTORED_ASSIGN_OP,
+ &atomic_ordering::INVALID_ATOMIC_ORDERING,
++ &attrs::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::UNKNOWN_CLIPPY_LINTS,
+ &attrs::USELESS_ATTRIBUTE,
+ &await_holding_lock::AWAIT_HOLDING_LOCK,
+ &bit_mask::BAD_BIT_MASK,
+ &bit_mask::INEFFECTIVE_BIT_MASK,
+ &bit_mask::VERBOSE_BIT_MASK,
+ &blacklisted_name::BLACKLISTED_NAME,
+ &blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
+ &booleans::LOGIC_BUG,
+ &booleans::NONMINIMAL_BOOL,
+ &bytecount::NAIVE_BYTECOUNT,
+ &cargo_common_metadata::CARGO_COMMON_METADATA,
+ &checked_conversions::CHECKED_CONVERSIONS,
+ &cognitive_complexity::COGNITIVE_COMPLEXITY,
+ &collapsible_if::COLLAPSIBLE_IF,
+ &comparison_chain::COMPARISON_CHAIN,
+ &copies::IFS_SAME_COND,
+ &copies::IF_SAME_THEN_ELSE,
+ &copies::MATCH_SAME_ARMS,
+ &copies::SAME_FUNCTIONS_IN_IF_CONDITION,
+ ©_iterator::COPY_ITERATOR,
+ &dbg_macro::DBG_MACRO,
+ &default_trait_access::DEFAULT_TRAIT_ACCESS,
+ &dereference::EXPLICIT_DEREF_METHODS,
+ &derive::DERIVE_HASH_XOR_EQ,
+ &derive::EXPL_IMPL_CLONE_ON_COPY,
+ &derive::UNSAFE_DERIVE_DESERIALIZE,
+ &doc::DOC_MARKDOWN,
+ &doc::MISSING_ERRORS_DOC,
+ &doc::MISSING_SAFETY_DOC,
+ &doc::NEEDLESS_DOCTEST_MAIN,
+ &double_comparison::DOUBLE_COMPARISONS,
+ &double_parens::DOUBLE_PARENS,
+ &drop_bounds::DROP_BOUNDS,
+ &drop_forget_ref::DROP_COPY,
+ &drop_forget_ref::DROP_REF,
+ &drop_forget_ref::FORGET_COPY,
+ &drop_forget_ref::FORGET_REF,
+ &duration_subsec::DURATION_SUBSEC,
+ &else_if_without_else::ELSE_IF_WITHOUT_ELSE,
+ &empty_enum::EMPTY_ENUM,
+ &entry::MAP_ENTRY,
+ &enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT,
+ &enum_variants::ENUM_VARIANT_NAMES,
+ &enum_variants::MODULE_INCEPTION,
+ &enum_variants::MODULE_NAME_REPETITIONS,
+ &enum_variants::PUB_ENUM_VARIANT_NAMES,
+ &eq_op::EQ_OP,
+ &eq_op::OP_REF,
+ &erasing_op::ERASING_OP,
+ &escape::BOXED_LOCAL,
+ &eta_reduction::REDUNDANT_CLOSURE,
+ &eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
+ &eval_order_dependence::DIVERGING_SUB_EXPRESSION,
+ &eval_order_dependence::EVAL_ORDER_DEPENDENCE,
+ &excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS,
+ &excessive_bools::STRUCT_EXCESSIVE_BOOLS,
+ &exit::EXIT,
+ &explicit_write::EXPLICIT_WRITE,
+ &fallible_impl_from::FALLIBLE_IMPL_FROM,
+ &float_literal::EXCESSIVE_PRECISION,
+ &float_literal::LOSSY_FLOAT_LITERAL,
+ &floating_point_arithmetic::IMPRECISE_FLOPS,
+ &floating_point_arithmetic::SUBOPTIMAL_FLOPS,
+ &format::USELESS_FORMAT,
+ &formatting::POSSIBLE_MISSING_COMMA,
+ &formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
+ &formatting::SUSPICIOUS_ELSE_FORMATTING,
+ &formatting::SUSPICIOUS_UNARY_OP_FORMATTING,
+ &functions::DOUBLE_MUST_USE,
+ &functions::MUST_USE_CANDIDATE,
+ &functions::MUST_USE_UNIT,
+ &functions::NOT_UNSAFE_PTR_ARG_DEREF,
+ &functions::TOO_MANY_ARGUMENTS,
+ &functions::TOO_MANY_LINES,
+ &future_not_send::FUTURE_NOT_SEND,
+ &get_last_with_len::GET_LAST_WITH_LEN,
+ &identity_op::IDENTITY_OP,
+ &if_let_mutex::IF_LET_MUTEX,
+ &if_let_some_result::IF_LET_SOME_RESULT,
+ &if_not_else::IF_NOT_ELSE,
+ &implicit_return::IMPLICIT_RETURN,
+ &implicit_saturating_sub::IMPLICIT_SATURATING_SUB,
+ &indexing_slicing::INDEXING_SLICING,
+ &indexing_slicing::OUT_OF_BOUNDS_INDEXING,
+ &infinite_iter::INFINITE_ITER,
+ &infinite_iter::MAYBE_INFINITE_ITER,
+ &inherent_impl::MULTIPLE_INHERENT_IMPL,
+ &inherent_to_string::INHERENT_TO_STRING,
+ &inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY,
+ &inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
+ &int_plus_one::INT_PLUS_ONE,
+ &integer_division::INTEGER_DIVISION,
+ &items_after_statements::ITEMS_AFTER_STATEMENTS,
+ &large_const_arrays::LARGE_CONST_ARRAYS,
+ &large_enum_variant::LARGE_ENUM_VARIANT,
+ &large_stack_arrays::LARGE_STACK_ARRAYS,
+ &len_zero::LEN_WITHOUT_IS_EMPTY,
+ &len_zero::LEN_ZERO,
+ &let_and_return::LET_AND_RETURN,
+ &let_if_seq::USELESS_LET_IF_SEQ,
+ &let_underscore::LET_UNDERSCORE_LOCK,
+ &let_underscore::LET_UNDERSCORE_MUST_USE,
+ &lifetimes::EXTRA_UNUSED_LIFETIMES,
+ &lifetimes::NEEDLESS_LIFETIMES,
+ &literal_representation::DECIMAL_LITERAL_REPRESENTATION,
+ &literal_representation::INCONSISTENT_DIGIT_GROUPING,
+ &literal_representation::LARGE_DIGIT_GROUPS,
+ &literal_representation::MISTYPED_LITERAL_SUFFIXES,
+ &literal_representation::UNREADABLE_LITERAL,
+ &loops::EMPTY_LOOP,
+ &loops::EXPLICIT_COUNTER_LOOP,
+ &loops::EXPLICIT_INTO_ITER_LOOP,
+ &loops::EXPLICIT_ITER_LOOP,
+ &loops::FOR_KV_MAP,
+ &loops::FOR_LOOPS_OVER_FALLIBLES,
+ &loops::ITER_NEXT_LOOP,
+ &loops::MANUAL_MEMCPY,
+ &loops::MUT_RANGE_BOUND,
+ &loops::NEEDLESS_COLLECT,
+ &loops::NEEDLESS_RANGE_LOOP,
+ &loops::NEVER_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_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
+ &map_clone::MAP_CLONE,
++ &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_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::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_NEXT,
+ &methods::FILTER_NEXT,
+ &methods::FIND_MAP,
+ &methods::FLAT_MAP_IDENTITY,
+ &methods::GET_UNWRAP,
+ &methods::INEFFICIENT_TO_STRING,
+ &methods::INTO_ITER_ON_REF,
+ &methods::ITERATOR_STEP_BY_ZERO,
+ &methods::ITER_CLONED_COLLECT,
+ &methods::ITER_NEXT_SLICE,
+ &methods::ITER_NTH,
+ &methods::ITER_NTH_ZERO,
+ &methods::ITER_SKIP_NEXT,
+ &methods::MANUAL_SATURATING_ARITHMETIC,
+ &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_PATTERN,
+ &methods::SKIP_WHILE_NEXT,
+ &methods::STRING_EXTEND_CHARS,
+ &methods::SUSPICIOUS_MAP,
+ &methods::TEMPORARY_CSTRING_AS_PTR,
+ &methods::UNINIT_ASSUMED_INIT,
+ &methods::UNNECESSARY_FILTER_MAP,
+ &methods::UNNECESSARY_FOLD,
+ &methods::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_CLOSURE_CALL,
+ &misc_early::REDUNDANT_PATTERN,
+ &misc_early::UNNEEDED_FIELD_PATTERN,
+ &misc_early::UNNEEDED_WILDCARD_PATTERN,
+ &misc_early::UNSEPARATED_LITERAL_SUFFIX,
+ &misc_early::ZERO_PREFIXED_LITERAL,
+ &missing_const_for_fn::MISSING_CONST_FOR_FN,
+ &missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS,
+ &missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
+ &modulo_arithmetic::MODULO_ARITHMETIC,
+ &multiple_crate_versions::MULTIPLE_CRATE_VERSIONS,
+ &mut_key::MUTABLE_KEY_TYPE,
+ &mut_mut::MUT_MUT,
+ &mut_reference::UNNECESSARY_MUT_PASSED,
+ &mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
+ &mutex_atomic::MUTEX_ATOMIC,
+ &mutex_atomic::MUTEX_INTEGER,
+ &needless_bool::BOOL_COMPARISON,
+ &needless_bool::NEEDLESS_BOOL,
+ &needless_borrow::NEEDLESS_BORROW,
+ &needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
+ &needless_continue::NEEDLESS_CONTINUE,
+ &needless_pass_by_value::NEEDLESS_PASS_BY_VALUE,
+ &needless_update::NEEDLESS_UPDATE,
+ &neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD,
+ &neg_multiply::NEG_MULTIPLY,
+ &new_without_default::NEW_WITHOUT_DEFAULT,
+ &no_effect::NO_EFFECT,
+ &no_effect::UNNECESSARY_OPERATION,
+ &non_copy_const::BORROW_INTERIOR_MUTABLE_CONST,
+ &non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST,
+ &non_expressive_names::JUST_UNDERSCORES_AND_DIGITS,
+ &non_expressive_names::MANY_SINGLE_CHAR_NAMES,
+ &non_expressive_names::SIMILAR_NAMES,
+ &open_options::NONSENSICAL_OPEN_OPTIONS,
+ &option_env_unwrap::OPTION_ENV_UNWRAP,
++ &option_if_let_else::OPTION_IF_LET_ELSE,
+ &overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
+ &panic_unimplemented::PANIC,
+ &panic_unimplemented::PANIC_PARAMS,
+ &panic_unimplemented::TODO,
+ &panic_unimplemented::UNIMPLEMENTED,
+ &panic_unimplemented::UNREACHABLE,
+ &partialeq_ne_impl::PARTIALEQ_NE_IMPL,
+ &path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE,
++ &pattern_type_mismatch::PATTERN_TYPE_MISMATCH,
+ &precedence::PRECEDENCE,
+ &ptr::CMP_NULL,
+ &ptr::MUT_FROM_REF,
+ &ptr::PTR_ARG,
+ &ptr_offset_with_cast::PTR_OFFSET_WITH_CAST,
+ &question_mark::QUESTION_MARK,
+ &ranges::RANGE_MINUS_ONE,
+ &ranges::RANGE_PLUS_ONE,
+ &ranges::RANGE_ZIP_WITH_LEN,
+ &ranges::REVERSED_EMPTY_RANGES,
+ &redundant_clone::REDUNDANT_CLONE,
+ &redundant_field_names::REDUNDANT_FIELD_NAMES,
- ®ex::REGEX_MACRO,
+ &redundant_pub_crate::REDUNDANT_PUB_CRATE,
+ &redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES,
+ &reference::DEREF_ADDROF,
+ &reference::REF_IN_DEREF,
+ ®ex::INVALID_REGEX,
- store.register_late_pass(|| box redundant_pattern_matching::RedundantPatternMatching);
+ ®ex::TRIVIAL_REGEX,
++ &repeat_once::REPEAT_ONCE,
+ &returns::NEEDLESS_RETURN,
+ &returns::UNUSED_UNIT,
+ &serde_api::SERDE_API_MISUSE,
+ &shadow::SHADOW_REUSE,
+ &shadow::SHADOW_SAME,
+ &shadow::SHADOW_UNRELATED,
+ &single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS,
+ &slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
+ &strings::STRING_ADD,
+ &strings::STRING_ADD_ASSIGN,
+ &strings::STRING_LIT_AS_BYTES,
+ &suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL,
+ &suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL,
+ &swap::ALMOST_SWAPPED,
+ &swap::MANUAL_SWAP,
+ &tabs_in_doc_comments::TABS_IN_DOC_COMMENTS,
+ &temporary_assignment::TEMPORARY_ASSIGNMENT,
+ &to_digit_is_some::TO_DIGIT_IS_SOME,
+ &trait_bounds::TYPE_REPETITION_IN_BOUNDS,
+ &transmute::CROSSPOINTER_TRANSMUTE,
+ &transmute::TRANSMUTE_BYTES_TO_STR,
+ &transmute::TRANSMUTE_FLOAT_TO_INT,
+ &transmute::TRANSMUTE_INT_TO_BOOL,
+ &transmute::TRANSMUTE_INT_TO_CHAR,
+ &transmute::TRANSMUTE_INT_TO_FLOAT,
+ &transmute::TRANSMUTE_PTR_TO_PTR,
+ &transmute::TRANSMUTE_PTR_TO_REF,
+ &transmute::UNSOUND_COLLECTION_TRANSMUTE,
+ &transmute::USELESS_TRANSMUTE,
+ &transmute::WRONG_TRANSMUTE,
+ &transmuting_null::TRANSMUTING_NULL,
+ &trivially_copy_pass_by_ref::TRIVIALLY_COPY_PASS_BY_REF,
+ &try_err::TRY_ERR,
+ &types::ABSURD_EXTREME_COMPARISONS,
+ &types::BORROWED_BOX,
+ &types::BOX_VEC,
+ &types::CAST_LOSSLESS,
+ &types::CAST_POSSIBLE_TRUNCATION,
+ &types::CAST_POSSIBLE_WRAP,
+ &types::CAST_PRECISION_LOSS,
+ &types::CAST_PTR_ALIGNMENT,
+ &types::CAST_REF_TO_MUT,
+ &types::CAST_SIGN_LOSS,
+ &types::CHAR_LIT_AS_U8,
+ &types::FN_TO_NUMERIC_CAST,
+ &types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ &types::IMPLICIT_HASHER,
+ &types::INVALID_UPCAST_COMPARISONS,
+ &types::LET_UNIT_VALUE,
+ &types::LINKEDLIST,
+ &types::OPTION_OPTION,
+ &types::REDUNDANT_ALLOCATION,
+ &types::TYPE_COMPLEXITY,
+ &types::UNIT_ARG,
+ &types::UNIT_CMP,
+ &types::UNNECESSARY_CAST,
+ &types::VEC_BOX,
+ &unicode::NON_ASCII_LITERAL,
+ &unicode::UNICODE_NOT_NFC,
+ &unicode::ZERO_WIDTH_SPACE,
+ &unnamed_address::FN_ADDRESS_COMPARISONS,
+ &unnamed_address::VTABLE_ADDRESS_COMPARISONS,
+ &unnecessary_sort_by::UNNECESSARY_SORT_BY,
+ &unnested_or_patterns::UNNESTED_OR_PATTERNS,
+ &unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
+ &unused_io_amount::UNUSED_IO_AMOUNT,
+ &unused_self::UNUSED_SELF,
+ &unwrap::PANICKING_UNWRAP,
+ &unwrap::UNNECESSARY_UNWRAP,
+ &use_self::USE_SELF,
+ &useless_conversion::USELESS_CONVERSION,
+ &utils::internal_lints::CLIPPY_LINTS_INTERNAL,
+ &utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
+ &utils::internal_lints::COMPILER_LINT_FUNCTIONS,
+ &utils::internal_lints::DEFAULT_LINT,
+ &utils::internal_lints::LINT_WITHOUT_LINT_PASS,
+ &utils::internal_lints::OUTER_EXPN_EXPN_DATA,
+ &utils::internal_lints::PRODUCE_ICE,
+ &vec::USELESS_VEC,
+ &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_STDOUT,
+ &write::PRINT_WITH_NEWLINE,
+ &write::USE_DEBUG,
+ &write::WRITELN_EMPTY_STRING,
+ &write::WRITE_LITERAL,
+ &write::WRITE_WITH_NEWLINE,
+ &zero_div_zero::ZERO_DIVIDED_BY_ZERO,
+ ]);
+ // end register lints, do not remove this comment, it’s used in `update_lints`
+
+ store.register_late_pass(|| box await_holding_lock::AwaitHoldingLock);
+ store.register_late_pass(|| box serde_api::SerdeAPI);
+ store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new());
+ store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default());
+ store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass);
+ store.register_late_pass(|| box utils::inspector::DeepCodeInspector);
+ store.register_late_pass(|| box utils::author::Author);
+ let vec_box_size_threshold = conf.vec_box_size_threshold;
+ store.register_late_pass(move || box types::Types::new(vec_box_size_threshold));
+ store.register_late_pass(|| box booleans::NonminimalBool);
+ store.register_late_pass(|| box eq_op::EqOp);
+ store.register_late_pass(|| box enum_clike::UnportableVariant);
+ store.register_late_pass(|| box float_literal::FloatLiteral);
+ let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
+ store.register_late_pass(move || box bit_mask::BitMask::new(verbose_bit_mask_threshold));
+ store.register_late_pass(|| box ptr::Ptr);
+ store.register_late_pass(|| box needless_bool::NeedlessBool);
+ store.register_late_pass(|| box needless_bool::BoolComparison);
+ store.register_late_pass(|| box approx_const::ApproxConstant);
+ store.register_late_pass(|| box misc::MiscLints);
+ store.register_late_pass(|| box eta_reduction::EtaReduction);
+ store.register_late_pass(|| box identity_op::IdentityOp);
+ store.register_late_pass(|| box erasing_op::ErasingOp);
+ store.register_late_pass(|| box mut_mut::MutMut);
+ store.register_late_pass(|| box mut_reference::UnnecessaryMutPassed);
+ store.register_late_pass(|| box len_zero::LenZero);
+ store.register_late_pass(|| box attrs::Attributes);
+ store.register_late_pass(|| box blocks_in_if_conditions::BlocksInIfConditions);
+ store.register_late_pass(|| box unicode::Unicode);
+ store.register_late_pass(|| box strings::StringAdd);
+ store.register_late_pass(|| box implicit_return::ImplicitReturn);
+ store.register_late_pass(|| box implicit_saturating_sub::ImplicitSaturatingSub);
+ store.register_late_pass(|| box methods::Methods);
+ store.register_late_pass(|| box map_clone::MapClone);
+ store.register_late_pass(|| box shadow::Shadow);
+ store.register_late_pass(|| box types::LetUnitValue);
+ store.register_late_pass(|| box types::UnitCmp);
+ store.register_late_pass(|| box loops::Loops);
+ store.register_late_pass(|| box main_recursion::MainRecursion::default());
+ store.register_late_pass(|| box lifetimes::Lifetimes);
+ store.register_late_pass(|| box entry::HashMapPass);
+ store.register_late_pass(|| box ranges::Ranges);
+ store.register_late_pass(|| box types::Casts);
+ let type_complexity_threshold = conf.type_complexity_threshold;
+ store.register_late_pass(move || box types::TypeComplexity::new(type_complexity_threshold));
+ store.register_late_pass(|| box matches::Matches::default());
+ store.register_late_pass(|| box minmax::MinMaxPass);
+ store.register_late_pass(|| box open_options::OpenOptions);
+ store.register_late_pass(|| box zero_div_zero::ZeroDiv);
+ store.register_late_pass(|| box mutex_atomic::Mutex);
+ store.register_late_pass(|| box needless_update::NeedlessUpdate);
+ store.register_late_pass(|| box needless_borrow::NeedlessBorrow::default());
+ store.register_late_pass(|| box needless_borrowed_ref::NeedlessBorrowedRef);
+ store.register_late_pass(|| box no_effect::NoEffect);
+ store.register_late_pass(|| box temporary_assignment::TemporaryAssignment);
+ store.register_late_pass(|| box transmute::Transmute);
+ let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
+ store.register_late_pass(move || box cognitive_complexity::CognitiveComplexity::new(cognitive_complexity_threshold));
+ let too_large_for_stack = conf.too_large_for_stack;
+ store.register_late_pass(move || box escape::BoxedLocal{too_large_for_stack});
+ store.register_late_pass(|| box panic_unimplemented::PanicUnimplemented);
+ store.register_late_pass(|| box strings::StringLitAsBytes);
+ store.register_late_pass(|| box derive::Derive);
+ store.register_late_pass(|| box types::CharLitAsU8);
+ store.register_late_pass(|| box vec::UselessVec);
+ store.register_late_pass(|| box drop_bounds::DropBounds);
+ store.register_late_pass(|| box get_last_with_len::GetLastWithLen);
+ store.register_late_pass(|| box drop_forget_ref::DropForgetRef);
+ store.register_late_pass(|| box empty_enum::EmptyEnum);
+ store.register_late_pass(|| box types::AbsurdExtremeComparisons);
+ store.register_late_pass(|| box types::InvalidUpcastComparisons);
+ store.register_late_pass(|| box regex::Regex::default());
+ store.register_late_pass(|| box copies::CopyAndPaste);
+ store.register_late_pass(|| box copy_iterator::CopyIterator);
+ store.register_late_pass(|| box format::UselessFormat);
+ store.register_late_pass(|| box swap::Swap);
+ store.register_late_pass(|| box overflow_check_conditional::OverflowCheckConditional);
+ store.register_late_pass(|| box new_without_default::NewWithoutDefault::default());
+ let blacklisted_names = conf.blacklisted_names.iter().cloned().collect::<FxHashSet<_>>();
+ 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::<FxHashSet<_>>();
+ store.register_late_pass(move || box doc::DocMarkdown::new(doc_valid_idents.clone()));
+ store.register_late_pass(|| box neg_multiply::NegMultiply);
+ store.register_late_pass(|| box mem_discriminant::MemDiscriminant);
+ store.register_late_pass(|| box mem_forget::MemForget);
+ store.register_late_pass(|| box mem_replace::MemReplace);
+ store.register_late_pass(|| box arithmetic::Arithmetic::default());
+ store.register_late_pass(|| box assign_ops::AssignOps);
+ store.register_late_pass(|| box let_if_seq::LetIfSeq);
+ store.register_late_pass(|| box eval_order_dependence::EvalOrderDependence);
+ store.register_late_pass(|| box missing_doc::MissingDoc::new());
+ store.register_late_pass(|| box missing_inline::MissingInline);
+ store.register_late_pass(|| box if_let_some_result::OkIfLet);
- store.register_late_pass(|| box trait_bounds::TraitBounds);
+ store.register_late_pass(|| box partialeq_ne_impl::PartialEqNeImpl);
+ store.register_late_pass(|| box unused_io_amount::UnusedIoAmount);
+ let enum_variant_size_threshold = conf.enum_variant_size_threshold;
+ store.register_late_pass(move || box large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold));
+ store.register_late_pass(|| box explicit_write::ExplicitWrite);
+ store.register_late_pass(|| box needless_pass_by_value::NeedlessPassByValue);
+ let trivially_copy_pass_by_ref = trivially_copy_pass_by_ref::TriviallyCopyPassByRef::new(
+ conf.trivial_copy_size_limit,
+ &sess.target,
+ );
+ store.register_late_pass(move || box trivially_copy_pass_by_ref);
+ store.register_late_pass(|| box try_err::TryErr);
+ store.register_late_pass(|| box use_self::UseSelf);
+ store.register_late_pass(|| box bytecount::ByteCount);
+ store.register_late_pass(|| box infinite_iter::InfiniteIter);
+ store.register_late_pass(|| box inline_fn_without_body::InlineFnWithoutBody);
+ store.register_late_pass(|| box 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_late_pass(|| box suspicious_trait_impl::SuspiciousImpl);
+ store.register_late_pass(|| box map_unit_fn::MapUnit);
+ store.register_late_pass(|| box inherent_impl::MultipleInherentImpl::default());
+ store.register_late_pass(|| box neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd);
+ store.register_late_pass(|| box unwrap::Unwrap);
+ store.register_late_pass(|| box duration_subsec::DurationSubsec);
+ store.register_late_pass(|| box default_trait_access::DefaultTraitAccess);
+ store.register_late_pass(|| box indexing_slicing::IndexingSlicing);
+ store.register_late_pass(|| box non_copy_const::NonCopyConst);
+ store.register_late_pass(|| box ptr_offset_with_cast::PtrOffsetWithCast);
+ store.register_late_pass(|| box redundant_clone::RedundantClone);
+ store.register_late_pass(|| box slow_vector_initialization::SlowVectorInit);
+ store.register_late_pass(|| box unnecessary_sort_by::UnnecessarySortBy);
+ store.register_late_pass(|| box types::RefToMut);
+ store.register_late_pass(|| box assertions_on_constants::AssertionsOnConstants);
+ store.register_late_pass(|| box missing_const_for_fn::MissingConstForFn);
+ store.register_late_pass(|| box transmuting_null::TransmutingNull);
+ store.register_late_pass(|| box path_buf_push_overwrite::PathBufPushOverwrite);
+ store.register_late_pass(|| box checked_conversions::CheckedConversions);
+ store.register_late_pass(|| box integer_division::IntegerDivision);
+ store.register_late_pass(|| box inherent_to_string::InherentToString);
- store.register_late_pass(move || box floating_point_arithmetic::FloatingPointArithmetic);
++ 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_early_pass(|| box unsafe_removed_from_name::UnsafeNameRemoval);
+ store.register_early_pass(|| box if_not_else::IfNotElse);
+ store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse);
+ store.register_early_pass(|| box int_plus_one::IntPlusOne);
+ store.register_early_pass(|| box formatting::Formatting);
+ store.register_early_pass(|| box misc_early::MiscEarlyLints);
+ store.register_early_pass(|| box returns::Return);
+ store.register_late_pass(|| box let_and_return::LetReturn);
+ store.register_early_pass(|| box collapsible_if::CollapsibleIf);
+ store.register_early_pass(|| box items_after_statements::ItemsAfterStatements);
+ store.register_early_pass(|| box precedence::Precedence);
+ store.register_early_pass(|| box needless_continue::NeedlessContinue);
+ store.register_early_pass(|| box redundant_static_lifetimes::RedundantStaticLifetimes);
+ store.register_late_pass(|| box cargo_common_metadata::CargoCommonMetadata);
+ store.register_late_pass(|| box multiple_crate_versions::MultipleCrateVersions);
+ store.register_late_pass(|| box wildcard_dependencies::WildcardDependencies);
+ store.register_early_pass(|| box literal_representation::LiteralDigitGrouping);
+ let literal_representation_threshold = conf.literal_representation_threshold;
+ store.register_early_pass(move || box literal_representation::DecimalLiteralRepresentation::new(literal_representation_threshold));
+ store.register_early_pass(|| box utils::internal_lints::ClippyLintsInternal);
+ let enum_variant_name_threshold = conf.enum_variant_name_threshold;
+ store.register_early_pass(move || box enum_variants::EnumVariantNames::new(enum_variant_name_threshold));
+ store.register_early_pass(|| box tabs_in_doc_comments::TabsInDocComments);
+ store.register_late_pass(|| box unused_self::UnusedSelf);
+ store.register_late_pass(|| box mutable_debug_assertion::DebugAssertWithMutCall);
+ store.register_late_pass(|| box exit::Exit);
+ store.register_late_pass(|| box to_digit_is_some::ToDigitIsSome);
+ let array_size_threshold = conf.array_size_threshold;
+ store.register_late_pass(move || box large_stack_arrays::LargeStackArrays::new(array_size_threshold));
+ store.register_late_pass(move || box large_const_arrays::LargeConstArrays::new(array_size_threshold));
- LintId::of(&ranges::RANGE_MINUS_ONE),
++ store.register_late_pass(|| box floating_point_arithmetic::FloatingPointArithmetic);
+ store.register_early_pass(|| box as_conversions::AsConversions);
+ store.register_early_pass(|| box utils::internal_lints::ProduceIce);
+ store.register_late_pass(|| box let_underscore::LetUnderscore);
+ store.register_late_pass(|| box atomic_ordering::AtomicOrdering);
+ store.register_early_pass(|| box single_component_path_imports::SingleComponentPathImports);
+ let max_fn_params_bools = conf.max_fn_params_bools;
+ let max_struct_bools = conf.max_struct_bools;
+ store.register_early_pass(move || box excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools));
+ store.register_early_pass(|| box option_env_unwrap::OptionEnvUnwrap);
+ 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 utils::internal_lints::CollapsibleCalls);
+ store.register_late_pass(|| box if_let_mutex::IfLetMutex);
+ store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems);
+ store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive);
+ store.register_late_pass(|| box manual_async_fn::ManualAsyncFn);
+ store.register_early_pass(|| box redundant_field_names::RedundantFieldNames);
+ store.register_late_pass(|| box vec_resize_to_zero::VecResizeToZero);
+ 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 repeat_once::RepeatOnce);
+
+ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
+ LintId::of(&arithmetic::FLOAT_ARITHMETIC),
+ LintId::of(&arithmetic::INTEGER_ARITHMETIC),
+ LintId::of(&as_conversions::AS_CONVERSIONS),
+ LintId::of(&dbg_macro::DBG_MACRO),
+ LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE),
+ LintId::of(&exit::EXIT),
+ LintId::of(&float_literal::LOSSY_FLOAT_LITERAL),
+ LintId::of(&implicit_return::IMPLICIT_RETURN),
+ LintId::of(&indexing_slicing::INDEXING_SLICING),
+ LintId::of(&inherent_impl::MULTIPLE_INHERENT_IMPL),
+ LintId::of(&integer_division::INTEGER_DIVISION),
+ LintId::of(&let_underscore::LET_UNDERSCORE_MUST_USE),
+ LintId::of(&literal_representation::DECIMAL_LITERAL_REPRESENTATION),
+ LintId::of(&matches::REST_PAT_IN_FULLY_BOUND_STRUCTS),
+ LintId::of(&matches::WILDCARD_ENUM_MATCH_ARM),
+ LintId::of(&mem_forget::MEM_FORGET),
+ LintId::of(&methods::CLONE_ON_REF_PTR),
+ LintId::of(&methods::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_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(&shadow::SHADOW_REUSE),
+ LintId::of(&shadow::SHADOW_SAME),
+ LintId::of(&strings::STRING_ADD),
+ LintId::of(&verbose_file_reads::VERBOSE_FILE_READS),
+ LintId::of(&write::PRINT_STDOUT),
+ LintId::of(&write::USE_DEBUG),
+ ]);
+
+ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
+ LintId::of(&attrs::INLINE_ALWAYS),
+ LintId::of(&await_holding_lock::AWAIT_HOLDING_LOCK),
+ LintId::of(&checked_conversions::CHECKED_CONVERSIONS),
+ LintId::of(&copies::MATCH_SAME_ARMS),
+ LintId::of(&copies::SAME_FUNCTIONS_IN_IF_CONDITION),
+ LintId::of(©_iterator::COPY_ITERATOR),
+ LintId::of(&default_trait_access::DEFAULT_TRAIT_ACCESS),
+ LintId::of(&dereference::EXPLICIT_DEREF_METHODS),
+ LintId::of(&derive::EXPL_IMPL_CLONE_ON_COPY),
+ LintId::of(&derive::UNSAFE_DERIVE_DESERIALIZE),
+ LintId::of(&doc::DOC_MARKDOWN),
+ LintId::of(&doc::MISSING_ERRORS_DOC),
+ LintId::of(&empty_enum::EMPTY_ENUM),
+ LintId::of(&enum_variants::MODULE_NAME_REPETITIONS),
+ LintId::of(&enum_variants::PUB_ENUM_VARIANT_NAMES),
+ LintId::of(&eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS),
+ LintId::of(&excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS),
+ LintId::of(&excessive_bools::STRUCT_EXCESSIVE_BOOLS),
+ LintId::of(&functions::MUST_USE_CANDIDATE),
+ LintId::of(&functions::TOO_MANY_LINES),
+ LintId::of(&if_not_else::IF_NOT_ELSE),
+ LintId::of(&implicit_saturating_sub::IMPLICIT_SATURATING_SUB),
+ LintId::of(&infinite_iter::MAYBE_INFINITE_ITER),
+ LintId::of(&items_after_statements::ITEMS_AFTER_STATEMENTS),
+ LintId::of(&large_stack_arrays::LARGE_STACK_ARRAYS),
+ LintId::of(&literal_representation::LARGE_DIGIT_GROUPS),
+ LintId::of(&literal_representation::UNREADABLE_LITERAL),
+ LintId::of(&loops::EXPLICIT_INTO_ITER_LOOP),
+ LintId::of(&loops::EXPLICIT_ITER_LOOP),
+ LintId::of(¯o_use::MACRO_USE_IMPORTS),
+ LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS),
+ LintId::of(&matches::MATCH_BOOL),
+ 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::FIND_MAP),
+ 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(&ranges::RANGE_MINUS_ONE),
+ LintId::of(&ranges::RANGE_PLUS_ONE),
+ LintId::of(&shadow::SHADOW_UNRELATED),
+ LintId::of(&strings::STRING_ADD_ASSIGN),
+ LintId::of(&trait_bounds::TYPE_REPETITION_IN_BOUNDS),
+ LintId::of(&trivially_copy_pass_by_ref::TRIVIALLY_COPY_PASS_BY_REF),
+ LintId::of(&types::CAST_LOSSLESS),
+ LintId::of(&types::CAST_POSSIBLE_TRUNCATION),
+ LintId::of(&types::CAST_POSSIBLE_WRAP),
+ LintId::of(&types::CAST_PRECISION_LOSS),
+ LintId::of(&types::CAST_PTR_ALIGNMENT),
+ LintId::of(&types::CAST_SIGN_LOSS),
+ LintId::of(&types::IMPLICIT_HASHER),
+ LintId::of(&types::INVALID_UPCAST_COMPARISONS),
+ LintId::of(&types::LET_UNIT_VALUE),
+ LintId::of(&types::LINKEDLIST),
+ LintId::of(&types::OPTION_OPTION),
+ LintId::of(&unicode::NON_ASCII_LITERAL),
+ LintId::of(&unicode::UNICODE_NOT_NFC),
+ LintId::of(&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),
+ ]);
+
+ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
+ LintId::of(&utils::internal_lints::CLIPPY_LINTS_INTERNAL),
+ LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
+ LintId::of(&utils::internal_lints::COMPILER_LINT_FUNCTIONS),
+ LintId::of(&utils::internal_lints::DEFAULT_LINT),
+ LintId::of(&utils::internal_lints::LINT_WITHOUT_LINT_PASS),
+ LintId::of(&utils::internal_lints::OUTER_EXPN_EXPN_DATA),
+ LintId::of(&utils::internal_lints::PRODUCE_ICE),
+ ]);
+
+ store.register_group(true, "clippy::all", Some("clippy"), vec![
+ LintId::of(&approx_const::APPROX_CONSTANT),
+ LintId::of(&assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
+ LintId::of(&assign_ops::ASSIGN_OP_PATTERN),
+ LintId::of(&assign_ops::MISREFACTORED_ASSIGN_OP),
+ LintId::of(&atomic_ordering::INVALID_ATOMIC_ORDERING),
++ LintId::of(&attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
+ LintId::of(&attrs::DEPRECATED_CFG_ATTR),
+ LintId::of(&attrs::DEPRECATED_SEMVER),
+ LintId::of(&attrs::MISMATCHED_TARGET_OS),
+ LintId::of(&attrs::UNKNOWN_CLIPPY_LINTS),
+ LintId::of(&attrs::USELESS_ATTRIBUTE),
+ LintId::of(&bit_mask::BAD_BIT_MASK),
+ LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK),
+ LintId::of(&bit_mask::VERBOSE_BIT_MASK),
+ LintId::of(&blacklisted_name::BLACKLISTED_NAME),
+ LintId::of(&blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
+ LintId::of(&booleans::LOGIC_BUG),
+ LintId::of(&booleans::NONMINIMAL_BOOL),
+ LintId::of(&bytecount::NAIVE_BYTECOUNT),
+ LintId::of(&collapsible_if::COLLAPSIBLE_IF),
+ LintId::of(&comparison_chain::COMPARISON_CHAIN),
+ LintId::of(&copies::IFS_SAME_COND),
+ LintId::of(&copies::IF_SAME_THEN_ELSE),
+ LintId::of(&derive::DERIVE_HASH_XOR_EQ),
+ LintId::of(&doc::MISSING_SAFETY_DOC),
+ LintId::of(&doc::NEEDLESS_DOCTEST_MAIN),
+ LintId::of(&double_comparison::DOUBLE_COMPARISONS),
+ LintId::of(&double_parens::DOUBLE_PARENS),
+ LintId::of(&drop_bounds::DROP_BOUNDS),
+ LintId::of(&drop_forget_ref::DROP_COPY),
+ LintId::of(&drop_forget_ref::DROP_REF),
+ LintId::of(&drop_forget_ref::FORGET_COPY),
+ LintId::of(&drop_forget_ref::FORGET_REF),
+ LintId::of(&duration_subsec::DURATION_SUBSEC),
+ LintId::of(&entry::MAP_ENTRY),
+ LintId::of(&enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
+ LintId::of(&enum_variants::ENUM_VARIANT_NAMES),
+ LintId::of(&enum_variants::MODULE_INCEPTION),
+ LintId::of(&eq_op::EQ_OP),
+ LintId::of(&eq_op::OP_REF),
+ LintId::of(&erasing_op::ERASING_OP),
+ LintId::of(&escape::BOXED_LOCAL),
+ LintId::of(&eta_reduction::REDUNDANT_CLOSURE),
+ LintId::of(&eval_order_dependence::DIVERGING_SUB_EXPRESSION),
+ LintId::of(&eval_order_dependence::EVAL_ORDER_DEPENDENCE),
+ LintId::of(&explicit_write::EXPLICIT_WRITE),
+ LintId::of(&float_literal::EXCESSIVE_PRECISION),
+ LintId::of(&format::USELESS_FORMAT),
+ LintId::of(&formatting::POSSIBLE_MISSING_COMMA),
+ LintId::of(&formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
+ LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING),
+ LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
+ LintId::of(&functions::DOUBLE_MUST_USE),
+ LintId::of(&functions::MUST_USE_UNIT),
+ LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF),
+ LintId::of(&functions::TOO_MANY_ARGUMENTS),
+ LintId::of(&get_last_with_len::GET_LAST_WITH_LEN),
+ LintId::of(&identity_op::IDENTITY_OP),
+ LintId::of(&if_let_mutex::IF_LET_MUTEX),
+ LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
+ LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING),
+ LintId::of(&infinite_iter::INFINITE_ITER),
+ LintId::of(&inherent_to_string::INHERENT_TO_STRING),
+ LintId::of(&inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),
+ LintId::of(&inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
+ LintId::of(&int_plus_one::INT_PLUS_ONE),
+ LintId::of(&large_const_arrays::LARGE_CONST_ARRAYS),
+ LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT),
+ LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
+ LintId::of(&len_zero::LEN_ZERO),
+ LintId::of(&let_and_return::LET_AND_RETURN),
+ LintId::of(&let_underscore::LET_UNDERSCORE_LOCK),
+ LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES),
+ LintId::of(&lifetimes::NEEDLESS_LIFETIMES),
+ LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING),
+ LintId::of(&literal_representation::MISTYPED_LITERAL_SUFFIXES),
+ LintId::of(&loops::EMPTY_LOOP),
+ LintId::of(&loops::EXPLICIT_COUNTER_LOOP),
+ LintId::of(&loops::FOR_KV_MAP),
+ LintId::of(&loops::FOR_LOOPS_OVER_FALLIBLES),
+ LintId::of(&loops::ITER_NEXT_LOOP),
+ LintId::of(&loops::MANUAL_MEMCPY),
+ LintId::of(&loops::MUT_RANGE_BOUND),
+ LintId::of(&loops::NEEDLESS_COLLECT),
+ LintId::of(&loops::NEEDLESS_RANGE_LOOP),
+ LintId::of(&loops::NEVER_LOOP),
+ LintId::of(&loops::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_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
+ 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::CHARS_LAST_CMP),
+ LintId::of(&methods::CHARS_NEXT_CMP),
+ LintId::of(&methods::CLONE_DOUBLE_REF),
+ LintId::of(&methods::CLONE_ON_COPY),
+ LintId::of(&methods::EXPECT_FUN_CALL),
+ LintId::of(&methods::FILTER_NEXT),
+ LintId::of(&methods::FLAT_MAP_IDENTITY),
+ LintId::of(&methods::INTO_ITER_ON_REF),
+ LintId::of(&methods::ITERATOR_STEP_BY_ZERO),
+ LintId::of(&methods::ITER_CLONED_COLLECT),
+ LintId::of(&methods::ITER_NEXT_SLICE),
+ LintId::of(&methods::ITER_NTH),
+ LintId::of(&methods::ITER_NTH_ZERO),
+ LintId::of(&methods::ITER_SKIP_NEXT),
+ LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC),
+ LintId::of(&methods::NEW_RET_NO_SELF),
+ LintId::of(&methods::OK_EXPECT),
+ LintId::of(&methods::OPTION_AS_REF_DEREF),
+ LintId::of(&methods::OPTION_MAP_OR_NONE),
+ LintId::of(&methods::OR_FUN_CALL),
+ LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION),
+ LintId::of(&methods::SEARCH_IS_SOME),
+ LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(&methods::SINGLE_CHAR_PATTERN),
+ LintId::of(&methods::SKIP_WHILE_NEXT),
+ LintId::of(&methods::STRING_EXTEND_CHARS),
+ LintId::of(&methods::SUSPICIOUS_MAP),
+ LintId::of(&methods::TEMPORARY_CSTRING_AS_PTR),
+ LintId::of(&methods::UNINIT_ASSUMED_INIT),
+ LintId::of(&methods::UNNECESSARY_FILTER_MAP),
+ LintId::of(&methods::UNNECESSARY_FOLD),
+ LintId::of(&methods::USELESS_ASREF),
+ LintId::of(&methods::WRONG_SELF_CONVENTION),
+ LintId::of(&methods::ZST_OFFSET),
+ LintId::of(&minmax::MIN_MAX),
+ LintId::of(&misc::CMP_NAN),
+ LintId::of(&misc::CMP_OWNED),
+ LintId::of(&misc::FLOAT_CMP),
+ LintId::of(&misc::MODULO_ONE),
+ LintId::of(&misc::SHORT_CIRCUIT_STATEMENT),
+ LintId::of(&misc::TOPLEVEL_REF_ARG),
+ LintId::of(&misc::ZERO_PTR),
+ LintId::of(&misc_early::BUILTIN_TYPE_SHADOW),
+ LintId::of(&misc_early::DOUBLE_NEG),
+ LintId::of(&misc_early::DUPLICATE_UNDERSCORE_ARGUMENT),
+ LintId::of(&misc_early::MIXED_CASE_HEX_LITERALS),
+ LintId::of(&misc_early::REDUNDANT_CLOSURE_CALL),
+ LintId::of(&misc_early::REDUNDANT_PATTERN),
+ LintId::of(&misc_early::UNNEEDED_WILDCARD_PATTERN),
+ LintId::of(&misc_early::ZERO_PREFIXED_LITERAL),
+ LintId::of(&mut_key::MUTABLE_KEY_TYPE),
+ LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED),
+ LintId::of(&mutex_atomic::MUTEX_ATOMIC),
+ LintId::of(&needless_bool::BOOL_COMPARISON),
+ LintId::of(&needless_bool::NEEDLESS_BOOL),
+ LintId::of(&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
+ LintId::of(&needless_update::NEEDLESS_UPDATE),
+ LintId::of(&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
+ LintId::of(&neg_multiply::NEG_MULTIPLY),
+ LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT),
+ LintId::of(&no_effect::NO_EFFECT),
+ LintId::of(&no_effect::UNNECESSARY_OPERATION),
+ LintId::of(&non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
+ LintId::of(&non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
+ LintId::of(&non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
+ LintId::of(&non_expressive_names::MANY_SINGLE_CHAR_NAMES),
+ LintId::of(&open_options::NONSENSICAL_OPEN_OPTIONS),
+ LintId::of(&option_env_unwrap::OPTION_ENV_UNWRAP),
+ LintId::of(&overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
+ LintId::of(&panic_unimplemented::PANIC_PARAMS),
+ LintId::of(&partialeq_ne_impl::PARTIALEQ_NE_IMPL),
+ LintId::of(&precedence::PRECEDENCE),
+ LintId::of(&ptr::CMP_NULL),
+ LintId::of(&ptr::MUT_FROM_REF),
+ LintId::of(&ptr::PTR_ARG),
+ LintId::of(&ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
+ LintId::of(&question_mark::QUESTION_MARK),
- LintId::of(&redundant_pattern_matching::REDUNDANT_PATTERN_MATCHING),
+ LintId::of(&ranges::RANGE_ZIP_WITH_LEN),
+ LintId::of(&ranges::REVERSED_EMPTY_RANGES),
+ LintId::of(&redundant_clone::REDUNDANT_CLONE),
+ LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES),
- LintId::of(®ex::REGEX_MACRO),
+ 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(&redundant_pattern_matching::REDUNDANT_PATTERN_MATCHING),
+ LintId::of(®ex::TRIVIAL_REGEX),
++ LintId::of(&repeat_once::REPEAT_ONCE),
+ LintId::of(&returns::NEEDLESS_RETURN),
+ LintId::of(&returns::UNUSED_UNIT),
+ LintId::of(&serde_api::SERDE_API_MISUSE),
+ LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
+ LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
+ LintId::of(&strings::STRING_LIT_AS_BYTES),
+ LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
+ LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
+ LintId::of(&swap::ALMOST_SWAPPED),
+ LintId::of(&swap::MANUAL_SWAP),
+ LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+ LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT),
+ LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(&transmute::CROSSPOINTER_TRANSMUTE),
+ LintId::of(&transmute::TRANSMUTE_BYTES_TO_STR),
+ LintId::of(&transmute::TRANSMUTE_FLOAT_TO_INT),
+ LintId::of(&transmute::TRANSMUTE_INT_TO_BOOL),
+ LintId::of(&transmute::TRANSMUTE_INT_TO_CHAR),
+ LintId::of(&transmute::TRANSMUTE_INT_TO_FLOAT),
+ LintId::of(&transmute::TRANSMUTE_PTR_TO_PTR),
+ LintId::of(&transmute::TRANSMUTE_PTR_TO_REF),
+ LintId::of(&transmute::UNSOUND_COLLECTION_TRANSMUTE),
+ LintId::of(&transmute::WRONG_TRANSMUTE),
+ LintId::of(&transmuting_null::TRANSMUTING_NULL),
+ LintId::of(&try_err::TRY_ERR),
+ LintId::of(&types::ABSURD_EXTREME_COMPARISONS),
+ LintId::of(&types::BORROWED_BOX),
+ LintId::of(&types::BOX_VEC),
+ LintId::of(&types::CAST_REF_TO_MUT),
+ LintId::of(&types::CHAR_LIT_AS_U8),
+ LintId::of(&types::FN_TO_NUMERIC_CAST),
+ LintId::of(&types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
+ LintId::of(&types::REDUNDANT_ALLOCATION),
+ LintId::of(&types::TYPE_COMPLEXITY),
+ LintId::of(&types::UNIT_ARG),
+ LintId::of(&types::UNIT_CMP),
+ LintId::of(&types::UNNECESSARY_CAST),
+ LintId::of(&types::VEC_BOX),
+ LintId::of(&unicode::ZERO_WIDTH_SPACE),
+ LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS),
+ LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS),
+ LintId::of(&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(&unwrap::PANICKING_UNWRAP),
+ LintId::of(&unwrap::UNNECESSARY_UNWRAP),
+ LintId::of(&useless_conversion::USELESS_CONVERSION),
+ LintId::of(&vec::USELESS_VEC),
+ 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(&attrs::UNKNOWN_CLIPPY_LINTS),
+ LintId::of(&bit_mask::VERBOSE_BIT_MASK),
+ LintId::of(&blacklisted_name::BLACKLISTED_NAME),
+ LintId::of(&blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
+ LintId::of(&collapsible_if::COLLAPSIBLE_IF),
+ LintId::of(&comparison_chain::COMPARISON_CHAIN),
+ LintId::of(&doc::MISSING_SAFETY_DOC),
+ LintId::of(&doc::NEEDLESS_DOCTEST_MAIN),
+ LintId::of(&enum_variants::ENUM_VARIANT_NAMES),
+ LintId::of(&enum_variants::MODULE_INCEPTION),
+ LintId::of(&eq_op::OP_REF),
+ LintId::of(&eta_reduction::REDUNDANT_CLOSURE),
+ LintId::of(&float_literal::EXCESSIVE_PRECISION),
+ LintId::of(&formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
+ LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING),
+ LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
+ LintId::of(&functions::DOUBLE_MUST_USE),
+ LintId::of(&functions::MUST_USE_UNIT),
+ LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
+ LintId::of(&inherent_to_string::INHERENT_TO_STRING),
+ LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
+ LintId::of(&len_zero::LEN_ZERO),
+ LintId::of(&let_and_return::LET_AND_RETURN),
+ LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING),
+ LintId::of(&loops::EMPTY_LOOP),
+ LintId::of(&loops::FOR_KV_MAP),
+ LintId::of(&loops::NEEDLESS_RANGE_LOOP),
+ LintId::of(&loops::WHILE_LET_ON_ITERATOR),
+ LintId::of(&main_recursion::MAIN_RECURSION),
+ LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
+ 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::CHARS_LAST_CMP),
+ LintId::of(&methods::CHARS_NEXT_CMP),
+ LintId::of(&methods::INTO_ITER_ON_REF),
+ LintId::of(&methods::ITER_CLONED_COLLECT),
+ LintId::of(&methods::ITER_NEXT_SLICE),
+ LintId::of(&methods::ITER_NTH_ZERO),
+ LintId::of(&methods::ITER_SKIP_NEXT),
+ LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC),
+ LintId::of(&methods::NEW_RET_NO_SELF),
+ LintId::of(&methods::OK_EXPECT),
+ LintId::of(&methods::OPTION_MAP_OR_NONE),
+ LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION),
+ LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(&methods::STRING_EXTEND_CHARS),
+ LintId::of(&methods::UNNECESSARY_FOLD),
+ LintId::of(&methods::WRONG_SELF_CONVENTION),
+ LintId::of(&misc::TOPLEVEL_REF_ARG),
+ LintId::of(&misc::ZERO_PTR),
+ LintId::of(&misc_early::BUILTIN_TYPE_SHADOW),
+ LintId::of(&misc_early::DOUBLE_NEG),
+ LintId::of(&misc_early::DUPLICATE_UNDERSCORE_ARGUMENT),
+ LintId::of(&misc_early::MIXED_CASE_HEX_LITERALS),
+ LintId::of(&misc_early::REDUNDANT_PATTERN),
+ LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED),
+ LintId::of(&neg_multiply::NEG_MULTIPLY),
+ LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT),
+ LintId::of(&non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
+ LintId::of(&non_expressive_names::MANY_SINGLE_CHAR_NAMES),
+ LintId::of(&panic_unimplemented::PANIC_PARAMS),
+ LintId::of(&ptr::CMP_NULL),
+ LintId::of(&ptr::PTR_ARG),
+ LintId::of(&question_mark::QUESTION_MARK),
+ LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES),
- LintId::of(®ex::REGEX_MACRO),
+ LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
- LintId::of(&ranges::RANGE_MINUS_ONE),
+ LintId::of(®ex::TRIVIAL_REGEX),
+ LintId::of(&returns::NEEDLESS_RETURN),
+ LintId::of(&returns::UNUSED_UNIT),
+ LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
+ LintId::of(&strings::STRING_LIT_AS_BYTES),
+ LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+ LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(&try_err::TRY_ERR),
+ LintId::of(&types::FN_TO_NUMERIC_CAST),
+ LintId::of(&types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
+ LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
+ LintId::of(&write::PRINTLN_EMPTY_STRING),
+ LintId::of(&write::PRINT_LITERAL),
+ LintId::of(&write::PRINT_WITH_NEWLINE),
+ LintId::of(&write::WRITELN_EMPTY_STRING),
+ LintId::of(&write::WRITE_LITERAL),
+ LintId::of(&write::WRITE_WITH_NEWLINE),
+ ]);
+
+ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec![
+ LintId::of(&assign_ops::MISREFACTORED_ASSIGN_OP),
+ LintId::of(&attrs::DEPRECATED_CFG_ATTR),
+ LintId::of(&booleans::NONMINIMAL_BOOL),
+ LintId::of(&double_comparison::DOUBLE_COMPARISONS),
+ LintId::of(&double_parens::DOUBLE_PARENS),
+ LintId::of(&duration_subsec::DURATION_SUBSEC),
+ LintId::of(&eval_order_dependence::DIVERGING_SUB_EXPRESSION),
+ LintId::of(&eval_order_dependence::EVAL_ORDER_DEPENDENCE),
+ LintId::of(&explicit_write::EXPLICIT_WRITE),
+ LintId::of(&format::USELESS_FORMAT),
+ LintId::of(&functions::TOO_MANY_ARGUMENTS),
+ LintId::of(&get_last_with_len::GET_LAST_WITH_LEN),
+ LintId::of(&identity_op::IDENTITY_OP),
+ LintId::of(&int_plus_one::INT_PLUS_ONE),
+ LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES),
+ LintId::of(&lifetimes::NEEDLESS_LIFETIMES),
+ LintId::of(&loops::EXPLICIT_COUNTER_LOOP),
+ LintId::of(&loops::MUT_RANGE_BOUND),
+ LintId::of(&loops::WHILE_LET_LOOP),
++ LintId::of(&map_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_NEXT),
+ LintId::of(&methods::FLAT_MAP_IDENTITY),
+ LintId::of(&methods::OPTION_AS_REF_DEREF),
+ LintId::of(&methods::SEARCH_IS_SOME),
+ LintId::of(&methods::SKIP_WHILE_NEXT),
+ LintId::of(&methods::SUSPICIOUS_MAP),
+ LintId::of(&methods::UNNECESSARY_FILTER_MAP),
+ LintId::of(&methods::USELESS_ASREF),
+ LintId::of(&misc::SHORT_CIRCUIT_STATEMENT),
+ LintId::of(&misc_early::REDUNDANT_CLOSURE_CALL),
+ LintId::of(&misc_early::UNNEEDED_WILDCARD_PATTERN),
+ LintId::of(&misc_early::ZERO_PREFIXED_LITERAL),
+ LintId::of(&needless_bool::BOOL_COMPARISON),
+ LintId::of(&needless_bool::NEEDLESS_BOOL),
+ LintId::of(&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
+ LintId::of(&needless_update::NEEDLESS_UPDATE),
+ LintId::of(&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
+ LintId::of(&no_effect::NO_EFFECT),
+ LintId::of(&no_effect::UNNECESSARY_OPERATION),
+ LintId::of(&overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
+ LintId::of(&partialeq_ne_impl::PARTIALEQ_NE_IMPL),
+ LintId::of(&precedence::PRECEDENCE),
+ LintId::of(&ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
+ LintId::of(&ranges::RANGE_ZIP_WITH_LEN),
+ LintId::of(&reference::DEREF_ADDROF),
+ LintId::of(&reference::REF_IN_DEREF),
++ LintId::of(&repeat_once::REPEAT_ONCE),
+ LintId::of(&swap::MANUAL_SWAP),
+ LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT),
+ LintId::of(&transmute::CROSSPOINTER_TRANSMUTE),
+ LintId::of(&transmute::TRANSMUTE_BYTES_TO_STR),
+ LintId::of(&transmute::TRANSMUTE_FLOAT_TO_INT),
+ LintId::of(&transmute::TRANSMUTE_INT_TO_BOOL),
+ LintId::of(&transmute::TRANSMUTE_INT_TO_CHAR),
+ LintId::of(&transmute::TRANSMUTE_INT_TO_FLOAT),
+ LintId::of(&transmute::TRANSMUTE_PTR_TO_PTR),
+ LintId::of(&transmute::TRANSMUTE_PTR_TO_REF),
+ LintId::of(&types::BORROWED_BOX),
+ LintId::of(&types::CHAR_LIT_AS_U8),
+ LintId::of(&types::TYPE_COMPLEXITY),
+ LintId::of(&types::UNIT_ARG),
+ LintId::of(&types::UNNECESSARY_CAST),
+ LintId::of(&types::VEC_BOX),
+ LintId::of(&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(&atomic_ordering::INVALID_ATOMIC_ORDERING),
+ LintId::of(&attrs::DEPRECATED_SEMVER),
+ LintId::of(&attrs::MISMATCHED_TARGET_OS),
+ LintId::of(&attrs::USELESS_ATTRIBUTE),
+ LintId::of(&bit_mask::BAD_BIT_MASK),
+ LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK),
+ LintId::of(&booleans::LOGIC_BUG),
+ LintId::of(&copies::IFS_SAME_COND),
+ LintId::of(&copies::IF_SAME_THEN_ELSE),
+ LintId::of(&derive::DERIVE_HASH_XOR_EQ),
+ LintId::of(&drop_bounds::DROP_BOUNDS),
+ LintId::of(&drop_forget_ref::DROP_COPY),
+ LintId::of(&drop_forget_ref::DROP_REF),
+ LintId::of(&drop_forget_ref::FORGET_COPY),
+ LintId::of(&drop_forget_ref::FORGET_REF),
+ LintId::of(&enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
+ LintId::of(&eq_op::EQ_OP),
+ LintId::of(&erasing_op::ERASING_OP),
+ LintId::of(&formatting::POSSIBLE_MISSING_COMMA),
+ LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF),
+ LintId::of(&if_let_mutex::IF_LET_MUTEX),
+ LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING),
+ LintId::of(&infinite_iter::INFINITE_ITER),
+ LintId::of(&inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),
+ LintId::of(&inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
+ LintId::of(&let_underscore::LET_UNDERSCORE_LOCK),
+ LintId::of(&literal_representation::MISTYPED_LITERAL_SUFFIXES),
+ LintId::of(&loops::FOR_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::TEMPORARY_CSTRING_AS_PTR),
+ LintId::of(&methods::UNINIT_ASSUMED_INIT),
+ LintId::of(&methods::ZST_OFFSET),
+ LintId::of(&minmax::MIN_MAX),
+ LintId::of(&misc::CMP_NAN),
+ LintId::of(&misc::FLOAT_CMP),
+ LintId::of(&misc::MODULO_ONE),
+ LintId::of(&mut_key::MUTABLE_KEY_TYPE),
+ LintId::of(&non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
+ LintId::of(&non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
+ LintId::of(&open_options::NONSENSICAL_OPEN_OPTIONS),
+ LintId::of(&option_env_unwrap::OPTION_ENV_UNWRAP),
+ LintId::of(&ptr::MUT_FROM_REF),
+ LintId::of(&ranges::REVERSED_EMPTY_RANGES),
+ LintId::of(®ex::INVALID_REGEX),
+ LintId::of(&serde_api::SERDE_API_MISUSE),
+ LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
+ LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
+ LintId::of(&swap::ALMOST_SWAPPED),
+ LintId::of(&transmute::UNSOUND_COLLECTION_TRANSMUTE),
+ LintId::of(&transmute::WRONG_TRANSMUTE),
+ LintId::of(&transmuting_null::TRANSMUTING_NULL),
+ LintId::of(&types::ABSURD_EXTREME_COMPARISONS),
+ LintId::of(&types::CAST_REF_TO_MUT),
+ LintId::of(&types::UNIT_CMP),
+ LintId::of(&unicode::ZERO_WIDTH_SPACE),
+ LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS),
+ LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS),
+ LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT),
+ LintId::of(&unwrap::PANICKING_UNWRAP),
+ LintId::of(&vec_resize_to_zero::VEC_RESIZE_TO_ZERO),
+ ]);
+
+ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
+ LintId::of(&bytecount::NAIVE_BYTECOUNT),
+ LintId::of(&entry::MAP_ENTRY),
+ LintId::of(&escape::BOXED_LOCAL),
+ LintId::of(&large_const_arrays::LARGE_CONST_ARRAYS),
+ LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT),
+ LintId::of(&loops::MANUAL_MEMCPY),
+ LintId::of(&loops::NEEDLESS_COLLECT),
+ LintId::of(&methods::EXPECT_FUN_CALL),
+ LintId::of(&methods::ITER_NTH),
+ LintId::of(&methods::OR_FUN_CALL),
+ LintId::of(&methods::SINGLE_CHAR_PATTERN),
+ LintId::of(&misc::CMP_OWNED),
+ LintId::of(&mutex_atomic::MUTEX_ATOMIC),
+ LintId::of(&redundant_clone::REDUNDANT_CLONE),
+ LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
+ LintId::of(&types::BOX_VEC),
+ LintId::of(&types::REDUNDANT_ALLOCATION),
+ LintId::of(&vec::USELESS_VEC),
+ ]);
+
+ store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![
+ LintId::of(&cargo_common_metadata::CARGO_COMMON_METADATA),
+ LintId::of(&multiple_crate_versions::MULTIPLE_CRATE_VERSIONS),
+ LintId::of(&wildcard_dependencies::WILDCARD_DEPENDENCIES),
+ ]);
+
+ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
+ LintId::of(&attrs::EMPTY_LINE_AFTER_OUTER_ATTR),
+ LintId::of(&cognitive_complexity::COGNITIVE_COMPLEXITY),
+ LintId::of(&fallible_impl_from::FALLIBLE_IMPL_FROM),
+ LintId::of(&floating_point_arithmetic::IMPRECISE_FLOPS),
+ LintId::of(&floating_point_arithmetic::SUBOPTIMAL_FLOPS),
+ LintId::of(&future_not_send::FUTURE_NOT_SEND),
+ LintId::of(&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(&transmute::USELESS_TRANSMUTE),
+ LintId::of(&use_self::USE_SELF),
+ ]);
+}
+
+#[rustfmt::skip]
+fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) {
+ store.register_removed(
+ "should_assert_eq",
+ "`assert!()` will be more flexible with RFC 2011",
+ );
+ store.register_removed(
+ "extend_from_slice",
+ "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice",
+ );
+ store.register_removed(
+ "range_step_by_zero",
+ "`iterator.step_by(0)` panics nowadays",
+ );
+ store.register_removed(
+ "unstable_as_slice",
+ "`Vec::as_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "unstable_as_mut_slice",
+ "`Vec::as_mut_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "str_to_string",
+ "using `str::to_string` is common even today and specialization will likely happen soon",
+ );
+ store.register_removed(
+ "string_to_string",
+ "using `string::to_string` is common even today and specialization will likely happen soon",
+ );
+ store.register_removed(
+ "misaligned_transmute",
+ "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",
+ );
+ store.register_removed(
+ "assign_ops",
+ "using compound assignment operators (e.g., `+=`) is harmless",
+ );
+ store.register_removed(
+ "if_let_redundant_pattern_matching",
+ "this lint has been changed to redundant_pattern_matching",
+ );
+ store.register_removed(
+ "unsafe_vector_initialization",
+ "the replacement suggested by this lint had substantially different behavior",
+ );
+ 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");
+}
+
+// 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");
+}
--- /dev/null
- let types = generics.params.iter().filter(|param| match param.kind {
- GenericParamKind::Type { .. } => true,
- _ => false,
- });
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{
+ walk_fn_decl, walk_generic_param, walk_generics, walk_param_bound, walk_ty, NestedVisitorMap, Visitor,
+};
+use rustc_hir::FnRetTy::Return;
+use rustc_hir::{
+ BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item,
+ ItemKind, Lifetime, LifetimeName, ParamName, QPath, TraitBoundModifier, TraitFn, TraitItem, TraitItemKind, Ty,
+ TyKind, WhereClause, WherePredicate,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::kw;
+
+use crate::reexport::Name;
+use crate::utils::{in_macro, last_path_segment, span_lint, trait_ref_of_method};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for lifetime annotations which can be removed by
+ /// relying on lifetime elision.
+ ///
+ /// **Why is this bad?** The additional lifetimes make the code look more
+ /// complicated, while there is nothing out of the ordinary going on. Removing
+ /// them leads to more readable code.
+ ///
+ /// **Known problems:** Potential false negatives: we bail out if the function
+ /// has a `where` clause where lifetimes are mentioned.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// // Bad: unnecessary lifetime annotations
+ /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 {
+ /// x
+ /// }
+ ///
+ /// // Good
+ /// fn elided(x: &u8, y: u8) -> &u8 {
+ /// x
+ /// }
+ /// ```
+ pub NEEDLESS_LIFETIMES,
+ complexity,
+ "using explicit lifetimes for references in function arguments when elision rules \
+ would allow omitting them"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for lifetimes in generics that are never used
+ /// anywhere else.
+ ///
+ /// **Why is this bad?** The additional lifetimes make the code look more
+ /// complicated, while there is nothing out of the ordinary going on. Removing
+ /// them leads to more readable code.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// // Bad: unnecessary lifetimes
+ /// fn unused_lifetime<'a>(x: u8) {
+ /// // ..
+ /// }
+ ///
+ /// // Good
+ /// fn no_lifetime(x: u8) {
+ /// // ...
+ /// }
+ /// ```
+ pub EXTRA_UNUSED_LIFETIMES,
+ complexity,
+ "unused lifetimes in function definitions"
+}
+
+declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]);
+
+impl<'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)]
+enum RefLt {
+ Unnamed,
+ Static,
+ Named(Name),
+}
+
+fn check_fn_inner<'tcx>(
+ cx: &LateContext<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: Option<BodyId>,
+ generics: &'tcx Generics<'_>,
+ span: Span,
+ report_extra_lifetimes: bool,
+) {
+ if in_macro(span) || has_where_lifetimes(cx, &generics.where_clause) {
+ return;
+ }
+
+ let mut bounds_lts = Vec::new();
- && !last_path_segment.args.iter().any(|arg| match arg {
- GenericArg::Lifetime(_) => true,
- _ => false,
- })
++ 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;
+ }
+ bounds_lts.push(bound);
+ }
+ }
+ }
+ }
+ }
+ if could_use_elision(cx, decl, body, &generics.params, bounds_lts) {
+ span_lint(
+ cx,
+ NEEDLESS_LIFETIMES,
+ span.with_hi(decl.output.span().hi()),
+ "explicit lifetimes given in parameter types where they could be elided \
+ (or replaced with `'_` if needed by type declaration)",
+ );
+ }
+ if report_extra_lifetimes {
+ self::report_extra_lifetimes(cx, decl, generics);
+ }
+}
+
+fn could_use_elision<'tcx>(
+ cx: &LateContext<'tcx>,
+ func: &'tcx FnDecl<'_>,
+ body: Option<BodyId>,
+ named_generics: &'tcx [GenericParam<'_>],
+ bounds_lts: Vec<&'tcx Lifetime>,
+) -> bool {
+ // There are two scenarios where elision works:
+ // * no output references, all input references have different LT
+ // * output references, exactly one input reference with same LT
+ // All lifetimes must be unnamed, 'static or defined without bounds on the
+ // level of the current item.
+
+ // check named LTs
+ let allowed_lts = allowed_lts_from(named_generics);
+
+ // these will collect all the lifetimes for references in arg/return types
+ let mut input_visitor = RefVisitor::new(cx);
+ let mut output_visitor = RefVisitor::new(cx);
+
+ // extract lifetimes in input argument types
+ for arg in func.inputs {
+ input_visitor.visit_ty(arg);
+ }
+ // extract lifetimes in output type
+ if let Return(ref ty) = func.output {
+ output_visitor.visit_ty(ty);
+ }
+
+ let input_lts = match input_visitor.into_vec() {
+ Some(lts) => lts_from_bounds(lts, bounds_lts.into_iter()),
+ None => return false,
+ };
+ let output_lts = match output_visitor.into_vec() {
+ Some(val) => val,
+ None => return false,
+ };
+
+ if let Some(body_id) = body {
+ let mut checker = BodyLifetimeChecker {
+ lifetimes_used_in_body: false,
+ };
+ checker.visit_expr(&cx.tcx.hir().body(body_id).value);
+ if checker.lifetimes_used_in_body {
+ return false;
+ }
+ }
+
+ // check for lifetimes from higher scopes
+ for lt in input_lts.iter().chain(output_lts.iter()) {
+ if !allowed_lts.contains(lt) {
+ return false;
+ }
+ }
+
+ // no input lifetimes? easy case!
+ if input_lts.is_empty() {
+ false
+ } else if output_lts.is_empty() {
+ // no output lifetimes, check distinctness of input lifetimes
+
+ // only unnamed and static, ok
+ let unnamed_and_static = input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static);
+ if unnamed_and_static {
+ return false;
+ }
+ // we have no output reference, so we only need all distinct lifetimes
+ input_lts.len() == unique_lifetimes(&input_lts)
+ } else {
+ // we have output references, so we need one input reference,
+ // and all output lifetimes must be the same
+ if unique_lifetimes(&output_lts) > 1 {
+ return false;
+ }
+ if input_lts.len() == 1 {
+ match (&input_lts[0], &output_lts[0]) {
+ (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
+ (&RefLt::Named(_), &RefLt::Unnamed) => true,
+ _ => false, /* already elided, different named lifetimes
+ * or something static going on */
+ }
+ } else {
+ false
+ }
+ }
+}
+
+fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet<RefLt> {
+ let mut allowed_lts = FxHashSet::default();
+ for par in named_generics.iter() {
+ if let GenericParamKind::Lifetime { .. } = par.kind {
+ if par.bounds.is_empty() {
+ allowed_lts.insert(RefLt::Named(par.name.ident().name));
+ }
+ }
+ }
+ allowed_lts.insert(RefLt::Unnamed);
+ allowed_lts.insert(RefLt::Static);
+ allowed_lts
+}
+
+fn lts_from_bounds<'a, T: Iterator<Item = &'a Lifetime>>(mut vec: Vec<RefLt>, bounds_lts: T) -> Vec<RefLt> {
+ for lt in bounds_lts {
+ if lt.name != LifetimeName::Static {
+ vec.push(RefLt::Named(lt.name.ident().name));
+ }
+ }
+
+ vec
+}
+
+/// Number of unique lifetimes in the given vector.
+#[must_use]
+fn unique_lifetimes(lts: &[RefLt]) -> usize {
+ lts.iter().collect::<FxHashSet<_>>().len()
+}
+
+/// A visitor usable for `rustc_front::visit::walk_ty()`.
+struct RefVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ lts: Vec<RefLt>,
+ abort: bool,
+}
+
+impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ lts: Vec::new(),
+ abort: false,
+ }
+ }
+
+ fn record(&mut self, lifetime: &Option<Lifetime>) {
+ if let Some(ref lt) = *lifetime {
+ if lt.name == LifetimeName::Static {
+ self.lts.push(RefLt::Static);
+ } else if let LifetimeName::Param(ParamName::Fresh(_)) = lt.name {
+ // Fresh lifetimes generated should be ignored.
+ } else if lt.is_elided() {
+ self.lts.push(RefLt::Unnamed);
+ } else {
+ self.lts.push(RefLt::Named(lt.name.ident().name));
+ }
+ } else {
+ self.lts.push(RefLt::Unnamed);
+ }
+ }
+
+ fn into_vec(self) -> Option<Vec<RefLt>> {
+ if self.abort {
+ None
+ } else {
+ Some(self.lts)
+ }
+ }
+
+ fn collect_anonymous_lifetimes(&mut self, qpath: &QPath<'_>, ty: &Ty<'_>) {
+ if let Some(ref last_path_segment) = last_path_segment(qpath).args {
+ if !last_path_segment.parenthesized
++ && !last_path_segment
++ .args
++ .iter()
++ .any(|arg| matches!(arg, GenericArg::Lifetime(_)))
+ {
+ let hir_id = ty.hir_id;
+ match self.cx.qpath_res(qpath, hir_id) {
+ Res::Def(DefKind::TyAlias | DefKind::Struct, def_id) => {
+ let generics = self.cx.tcx.generics_of(def_id);
+ for _ in generics.params.as_slice() {
+ self.record(&None);
+ }
+ },
+ Res::Def(DefKind::Trait, def_id) => {
+ let trait_def = self.cx.tcx.trait_def(def_id);
+ for _ in &self.cx.tcx.generics_of(trait_def.def_id).params {
+ self.record(&None);
+ }
+ },
+ _ => (),
+ }
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ // for lifetimes as parameters of generics
+ fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
+ self.record(&Some(*lifetime));
+ }
+
+ fn visit_ty(&mut self, ty: &'tcx Ty<'_>) {
+ match ty.kind {
+ TyKind::Rptr(ref lt, _) if lt.is_elided() => {
+ self.record(&None);
+ },
+ TyKind::Path(ref path) => {
+ self.collect_anonymous_lifetimes(path, ty);
+ },
+ TyKind::OpaqueDef(item, _) => {
+ let map = self.cx.tcx.hir();
+ if let ItemKind::OpaqueTy(ref exist_ty) = map.expect_item(item.id).kind {
+ for bound in exist_ty.bounds {
+ if let GenericBound::Outlives(_) = *bound {
+ self.record(&None);
+ }
+ }
+ } else {
+ unreachable!()
+ }
+ walk_ty(self, ty);
+ },
+ TyKind::TraitObject(bounds, ref lt) => {
+ if !lt.is_elided() {
+ self.abort = true;
+ }
+ for bound in bounds {
+ self.visit_poly_trait_ref(bound, TraitBoundModifier::None);
+ }
+ return;
+ },
+ _ => (),
+ }
+ walk_ty(self, ty);
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ 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.lts.is_empty() {
+ return true;
+ }
+ // if the bounds define new lifetimes, they are fine to occur
+ let allowed_lts = allowed_lts_from(&pred.bound_generic_params);
+ // now walk the bounds
+ for bound in pred.bounds.iter() {
+ walk_param_bound(&mut visitor, bound);
+ }
+ // and check that all lifetimes are allowed
+ match visitor.into_vec() {
+ None => return false,
+ Some(lts) => {
+ for lt in lts {
+ if !allowed_lts.contains(<) {
+ return true;
+ }
+ }
+ },
+ }
+ },
+ WherePredicate::EqPredicate(ref pred) => {
+ let mut visitor = RefVisitor::new(cx);
+ walk_ty(&mut visitor, &pred.lhs_ty);
+ walk_ty(&mut visitor, &pred.rhs_ty);
+ if !visitor.lts.is_empty() {
+ return true;
+ }
+ },
+ }
+ }
+ false
+}
+
+struct LifetimeChecker {
+ map: FxHashMap<Name, Span>,
+}
+
+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<Self::Map> {
+ 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::Invalid && lifetime.name.ident().name != kw::StaticLifetime {
+ self.lifetimes_used_in_body = true;
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
- } else if let Some(fraction) = &mut num_lit.fraction {
- (fraction, &["32", "64"][..], 'f')
+//! Lints concerned with the grouping of digits with underscores in integral or
+//! floating-point literal expressions.
+
+use crate::utils::{
+ in_macro,
+ numeric_literal::{NumericLiteral, Radix},
+ snippet_opt, span_lint_and_sugg,
+};
+use if_chain::if_chain;
+use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// **What it does:** Warns if a long integral or floating-point constant does
+ /// not contain underscores.
+ ///
+ /// **Why is this bad?** Reading long numbers is difficult without separators.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // Bad
+ /// let x: u64 = 61864918973511;
+ ///
+ /// // Good
+ /// let x: u64 = 61_864_918_973_511;
+ /// ```
+ pub UNREADABLE_LITERAL,
+ pedantic,
+ "long integer literal without underscores"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Warns for mistyped suffix in literals
+ ///
+ /// **Why is this bad?** This is most probably a typo
+ ///
+ /// **Known problems:**
+ /// - Recommends a signed suffix, even though the number might be too big and an unsigned
+ /// suffix is required
+ /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // 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 the digits of an integral or floating-point
+ /// constant are grouped into groups that
+ /// are too large.
+ ///
+ /// **Why is this bad?** Negatively impacts readability.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// let x: u64 = 6186491_8973511;
+ /// ```
+ pub LARGE_DIGIT_GROUPS,
+ pedantic,
+ "grouping digits into groups that are too large"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Warns if there is a better representation for a numeric literal.
+ ///
+ /// **Why is this bad?** Especially for big powers of 2 a hexadecimal representation is more
+ /// readable than a decimal representation.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// `255` => `0xFF`
+ /// `65_535` => `0xFFFF`
+ /// `4_042_322_160` => `0xF0F0_F0F0`
+ pub DECIMAL_LITERAL_REPRESENTATION,
+ restriction,
+ "using decimal representation when hexadecimal would be better"
+}
+
+enum WarningType {
+ UnreadableLiteral,
+ InconsistentDigitGrouping,
+ LargeDigitGroups,
+ DecimalRepresentation,
+ MistypedLiteralSuffix,
+}
+
+impl WarningType {
+ fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: rustc_span::Span) {
+ match self {
+ Self::MistypedLiteralSuffix => span_lint_and_sugg(
+ cx,
+ MISTYPED_LITERAL_SUFFIXES,
+ span,
+ "mistyped literal suffix",
+ "did you mean to write",
+ suggested_format,
+ Applicability::MaybeIncorrect,
+ ),
+ Self::UnreadableLiteral => span_lint_and_sugg(
+ cx,
+ UNREADABLE_LITERAL,
+ span,
+ "long literal lacking separators",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::LargeDigitGroups => span_lint_and_sugg(
+ cx,
+ LARGE_DIGIT_GROUPS,
+ span,
+ "digit groups should be smaller",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::InconsistentDigitGrouping => span_lint_and_sugg(
+ cx,
+ INCONSISTENT_DIGIT_GROUPING,
+ span,
+ "digits grouped inconsistently by underscores",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::DecimalRepresentation => span_lint_and_sugg(
+ cx,
+ DECIMAL_LITERAL_REPRESENTATION,
+ span,
+ "integer literal has a better hexadecimal representation",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ };
+ }
+}
+
+declare_lint_pass!(LiteralDigitGrouping => [
+ UNREADABLE_LITERAL,
+ INCONSISTENT_DIGIT_GROUPING,
+ LARGE_DIGIT_GROUPS,
+ MISTYPED_LITERAL_SUFFIXES,
+]);
+
+impl EarlyLintPass for LiteralDigitGrouping {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ Self::check_lit(cx, lit)
+ }
+ }
+}
+
+// Length of each UUID hyphenated group in hex digits.
+const UUID_GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12];
+
+impl LiteralDigitGrouping {
+ fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
+ if_chain! {
+ if let Some(src) = snippet_opt(cx, lit.span);
+ if let Some(mut num_lit) = NumericLiteral::from_lit(&src, &lit);
+ then {
+ if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) {
+ return;
+ }
+
+ if Self::is_literal_uuid_formatted(&mut num_lit) {
+ return;
+ }
+
+ let result = (|| {
+
+ let integral_group_size = Self::get_group_size(num_lit.integer.split('_'))?;
+ if let Some(fraction) = num_lit.fraction {
+ let fractional_group_size = Self::get_group_size(fraction.rsplit('_'))?;
+
+ let consistent = Self::parts_consistent(integral_group_size,
+ fractional_group_size,
+ num_lit.integer.len(),
+ fraction.len());
+ if !consistent {
+ return Err(WarningType::InconsistentDigitGrouping);
+ };
+ }
+ Ok(())
+ })();
+
+
+ if let Err(warning_type) = result {
+ let should_warn = match warning_type {
+ | WarningType::UnreadableLiteral
+ | WarningType::InconsistentDigitGrouping
+ | WarningType::LargeDigitGroups => {
+ !in_macro(lit.span)
+ }
+ WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
+ true
+ }
+ };
+ if should_warn {
+ warning_type.display(num_lit.format(), cx, lit.span)
+ }
+ }
+ }
+ }
+ }
+
+ // Returns `false` if the check fails
+ fn check_for_mistyped_suffix(
+ cx: &EarlyContext<'_>,
+ span: rustc_span::Span,
+ num_lit: &mut NumericLiteral<'_>,
+ ) -> bool {
+ if num_lit.suffix.is_some() {
+ return true;
+ }
+
+ let (part, mistyped_suffixes, missing_char) = if let Some((_, exponent)) = &mut num_lit.exponent {
+ (exponent, &["32", "64"][..], 'f')
- (&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i')
+ } else {
++ num_lit
++ .fraction
++ .as_mut()
++ .map_or((&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i'), |fraction| {
++ (fraction, &["32", "64"][..], 'f')
++ })
+ };
+
+ 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<usize> = 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<usize>,
+ frac_group_size: Option<usize>,
+ 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<Item = &'a str>) -> Result<Option<usize>, WarningType> {
+ let mut groups = groups.map(str::len);
+
+ let first = groups.next().expect("At least one group");
+
+ if let Some(second) = groups.next() {
+ if !groups.all(|x| x == second) || first > second {
+ Err(WarningType::InconsistentDigitGrouping)
+ } else if second > 4 {
+ Err(WarningType::LargeDigitGroups)
+ } else {
+ Ok(Some(second))
+ }
+ } else if first > 5 {
+ Err(WarningType::UnreadableLiteral)
+ } else {
+ Ok(None)
+ }
+ }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Copy, Clone)]
+pub struct DecimalLiteralRepresentation {
+ threshold: u64,
+}
+
+impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]);
+
+impl EarlyLintPass for DecimalLiteralRepresentation {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ self.check_lit(cx, lit)
+ }
+ }
+}
+
+impl DecimalLiteralRepresentation {
+ #[must_use]
+ pub fn new(threshold: u64) -> Self {
+ Self { threshold }
+ }
+ fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
+ // Lint integral literals.
+ if_chain! {
+ if let LitKind::Int(val, _) = lit.kind;
+ if let Some(src) = snippet_opt(cx, lit.span);
+ if let Some(num_lit) = NumericLiteral::from_lit(&src, &lit);
+ if num_lit.radix == Radix::Decimal;
+ if val >= u128::from(self.threshold);
+ then {
+ let hex = format!("{:#X}", val);
+ let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
+ let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| {
+ warning_type.display(num_lit.format(), cx, lit.span)
+ });
+ }
+ }
+ }
+
+ fn do_lint(digits: &str) -> Result<(), WarningType> {
+ if digits.len() == 1 {
+ // Lint for 1 digit literals, if someone really sets the threshold that low
+ if digits == "1"
+ || digits == "2"
+ || digits == "4"
+ || digits == "8"
+ || digits == "3"
+ || digits == "7"
+ || digits == "F"
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ } else if digits.len() < 4 {
+ // Lint for Literals with a hex-representation of 2 or 3 digits
+ let f = &digits[0..1]; // first digit
+ let s = &digits[1..]; // suffix
+
+ // Powers of 2
+ if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0'))
+ // Powers of 2 minus 1
+ || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F'))
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ } else {
+ // Lint for Literals with a hex-representation of 4 digits or more
+ let f = &digits[0..1]; // first digit
+ let m = &digits[1..digits.len() - 1]; // middle digits, except last
+ let s = &digits[1..]; // suffix
+
+ // Powers of 2 with a margin of +15/-16
+ if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0'))
+ || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F'))
+ // Lint for representations with only 0s and Fs, while allowing 7 as the first
+ // digit
+ || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ }
+
+ Ok(())
+ }
+}
--- /dev/null
- ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => {
- if let Some(ref e) = *e {
- combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
- } else {
- NeverLoopResult::AlwaysBreak
- }
- },
+use crate::consts::constant;
+use crate::reexport::Name;
+use crate::utils::paths;
+use crate::utils::usage::{is_unused, mutated_variables};
+use crate::utils::{
+ get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait,
+ is_integer_const, is_no_std_crate, is_refutable, last_path_segment, match_trait_method, match_type, match_var,
+ multispan_sugg, snippet, snippet_opt, snippet_with_applicability, span_lint, span_lint_and_help,
+ span_lint_and_sugg, span_lint_and_then, SpanlessEq,
+};
+use crate::utils::{is_type_diagnostic_item, qpath_res, sugg};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor};
+use rustc_hir::{
+ def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, InlineAsmOperand,
+ LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind,
+};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::middle::region;
+use rustc_middle::ty::{self, Ty, TyS};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::Symbol;
+use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+use std::iter::{once, Iterator};
+use std::mem;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for for-loops that manually copy items between
+ /// slices that could be optimized by having a memcpy.
+ ///
+ /// **Why is this bad?** It is not as fast as a memcpy.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let src = vec![1];
+ /// # let mut dst = vec![0; 65];
+ /// for i in 0..src.len() {
+ /// dst[i + 64] = src[i];
+ /// }
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// # let src = vec![1];
+ /// # let mut dst = vec![0; 65];
+ /// dst[64..(src.len() + 64)].clone_from_slice(&src[..]);
+ /// ```
+ pub MANUAL_MEMCPY,
+ perf,
+ "manually copying items between slices"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for looping over the range of `0..len` of some
+ /// collection just to get the values by index.
+ ///
+ /// **Why is this bad?** Just iterating the collection itself makes the intent
+ /// more clear and is probably faster.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let vec = vec!['a', 'b', 'c'];
+ /// for i in 0..vec.len() {
+ /// println!("{}", vec[i]);
+ /// }
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// let vec = vec!['a', 'b', 'c'];
+ /// for i in vec {
+ /// println!("{}", i);
+ /// }
+ /// ```
+ pub NEEDLESS_RANGE_LOOP,
+ style,
+ "for-looping over a range of indices where an iterator over items would do"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for loops on `x.iter()` where `&x` will do, and
+ /// suggests the latter.
+ ///
+ /// **Why is this bad?** Readability.
+ ///
+ /// **Known problems:** False negatives. We currently only warn on some known
+ /// types.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// // with `y` a `Vec` or slice:
+ /// # let y = vec![1];
+ /// for x in y.iter() {
+ /// // ..
+ /// }
+ /// ```
+ /// can be rewritten to
+ /// ```rust
+ /// # let y = vec![1];
+ /// for x in &y {
+ /// // ..
+ /// }
+ /// ```
+ pub EXPLICIT_ITER_LOOP,
+ pedantic,
+ "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for loops on `y.into_iter()` where `y` will do, and
+ /// suggests the latter.
+ ///
+ /// **Why is this bad?** Readability.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let y = vec![1];
+ /// // with `y` a `Vec` or slice:
+ /// for x in y.into_iter() {
+ /// // ..
+ /// }
+ /// ```
+ /// can be rewritten to
+ /// ```rust
+ /// # let y = vec![1];
+ /// for x in y {
+ /// // ..
+ /// }
+ /// ```
+ pub EXPLICIT_INTO_ITER_LOOP,
+ pedantic,
+ "for-looping over `_.into_iter()` when `_` would do"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for loops on `x.next()`.
+ ///
+ /// **Why is this bad?** `next()` returns either `Some(value)` if there was a
+ /// value, or `None` otherwise. The insidious thing is that `Option<_>`
+ /// implements `IntoIterator`, so that possibly one value will be iterated,
+ /// leading to some hard to find bugs. No one will want to write such code
+ /// [except to win an Underhanded Rust
+ /// Contest](https://www.reddit.com/r/rust/comments/3hb0wm/underhanded_rust_contest/cu5yuhr).
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```ignore
+ /// for x in y.next() {
+ /// ..
+ /// }
+ /// ```
+ pub ITER_NEXT_LOOP,
+ correctness,
+ "for-looping over `_.next()` which is probably not intended"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `for` loops over `Option` 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<i32, std::io::Error> = 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).
+ ///
+ /// **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::<Vec<_>>().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?** Those busy loops burn CPU cycles without doing
+ /// anything. Think of the environment and either block on something or at least
+ /// make the thread sleep for some microseconds.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```no_run
+ /// loop {}
+ /// ```
+ pub EMPTY_LOOP,
+ style,
+ "empty `loop {}`, which should block or sleep"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `while let` expressions on iterators.
+ ///
+ /// **Why is this bad?** Readability. A simple `for` loop is shorter and conveys
+ /// the intent better.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```ignore
+ /// while let Some(val) = iter() {
+ /// ..
+ /// }
+ /// ```
+ pub WHILE_LET_ON_ITERATOR,
+ style,
+ "using a while-let loop instead of a for loop on an iterator"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for iterating a map (`HashMap` or `BTreeMap`) and
+ /// ignoring either the keys or values.
+ ///
+ /// **Why is this bad?** Readability. There are `keys` and `values` methods that
+ /// can be used to express that don't need the values or keys.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```ignore
+ /// for (k, _) in &map {
+ /// ..
+ /// }
+ /// ```
+ ///
+ /// could be replaced by
+ ///
+ /// ```ignore
+ /// for k in map.keys() {
+ /// ..
+ /// }
+ /// ```
+ pub FOR_KV_MAP,
+ style,
+ "looping on a map using `iter` when `keys` or `values` would do"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for loops that will always `break`, `return` or
+ /// `continue` an outer loop.
+ ///
+ /// **Why is this bad?** This loop never loops, all it does is obfuscating the
+ /// code.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// loop {
+ /// ..;
+ /// break;
+ /// }
+ /// ```
+ pub NEVER_LOOP,
+ correctness,
+ "any loop that will always `break` or `return`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for loops which have a range bound that is a mutable variable
+ ///
+ /// **Why is this bad?** One might think that modifying the mutable variable changes the loop bounds
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let mut foo = 42;
+ /// for i in 0..foo {
+ /// foo -= 1;
+ /// println!("{}", i); // prints numbers from 0 to 42, not 0 to 21
+ /// }
+ /// ```
+ pub MUT_RANGE_BOUND,
+ complexity,
+ "for loop over a range where one of the bounds is a mutable variable"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks whether variables used within while loop condition
+ /// can be (and are) mutated in the body.
+ ///
+ /// **Why is this bad?** If the condition is unchanged, entering the body of the loop
+ /// will lead to an infinite loop.
+ ///
+ /// **Known problems:** If the `while`-loop is in a closure, the check for mutation of the
+ /// condition variables in the body can cause false negatives. For example when only `Upvar` `a` is
+ /// in the condition and only `Upvar` `b` gets mutated in the body, the lint will not trigger.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let i = 0;
+ /// while i > 10 {
+ /// println!("let me loop forever!");
+ /// }
+ /// ```
+ pub WHILE_IMMUTABLE_CONDITION,
+ correctness,
+ "variables used within while expression are not mutated in the body"
+}
+
+declare_lint_pass!(Loops => [
+ MANUAL_MEMCPY,
+ NEEDLESS_RANGE_LOOP,
+ EXPLICIT_ITER_LOOP,
+ EXPLICIT_INTO_ITER_LOOP,
+ ITER_NEXT_LOOP,
+ FOR_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,
+]);
+
+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)) = higher::for_loop(expr) {
+ // we don't want to check expanded macros
+ // this check is not at the top of the function
+ // since higher::for_loop expressions are marked as expansions
+ if body.span.from_expansion() {
+ return;
+ }
+ check_for_loop(cx, pat, arg, body, expr);
+ }
+
+ // we don't want to check expanded macros
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ // check for never_loop
+ if let ExprKind::Loop(ref block, _, _) = expr.kind {
+ match never_loop_block(block, expr.hir_id) {
+ NeverLoopResult::AlwaysBreak => span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops"),
+ NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
+ }
+ }
+
+ // check for `loop { if let {} else break }` that could be `while let`
+ // (also matches an explicit "match" instead of "if let")
+ // (even if the "match" or "if let" is used for declaration)
+ if let ExprKind::Loop(ref block, _, LoopSource::Loop) = expr.kind {
+ // also check for empty `loop {}` statements
+ if block.stmts.is_empty() && block.expr.is_none() && !is_no_std_crate(cx.tcx.hir().krate()) {
+ span_lint(
+ cx,
+ EMPTY_LOOP,
+ expr.span,
+ "empty `loop {}` detected. You may want to either use `panic!()` or add \
+ `std::thread::sleep(..);` to the loop body.",
+ );
+ }
+
+ // extract the expression from the first statement (if any) in a block
+ let inner_stmt_expr = extract_expr_from_first_stmt(block);
+ // or extract the first expression (if any) from the block
+ if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(block)) {
+ if let ExprKind::Match(ref matchexpr, ref arms, ref source) = inner.kind {
+ // ensure "if let" compatible match structure
+ match *source {
+ MatchSource::Normal | MatchSource::IfLetDesugar { .. } => {
+ if arms.len() == 2
+ && arms[0].guard.is_none()
+ && arms[1].guard.is_none()
+ && is_simple_break_expr(&arms[1].body)
+ {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ // NOTE: we used to build a body here instead of using
+ // ellipsis, this was removed because:
+ // 1) it was ugly with big bodies;
+ // 2) it was not indented properly;
+ // 3) it wasn’t very smart (see #675).
+ let mut applicability = Applicability::HasPlaceholders;
+ span_lint_and_sugg(
+ cx,
+ WHILE_LET_LOOP,
+ expr.span,
+ "this loop could be written as a `while let` loop",
+ "try",
+ format!(
+ "while let {} = {} {{ .. }}",
+ snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability),
+ snippet_with_applicability(cx, matchexpr.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+ }
+ }
+ if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind {
+ let pat = &arms[0].pat.kind;
+ if let (
+ &PatKind::TupleStruct(ref qpath, ref pat_args, _),
+ &ExprKind::MethodCall(ref method_path, _, ref method_args, _),
+ ) = (pat, &match_expr.kind)
+ {
+ let iter_expr = &method_args[0];
+
+ // Don't lint when the iterator is recreated on every iteration
+ if_chain! {
+ if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind;
+ if let Some(iter_def_id) = get_trait_def_id(cx, &paths::ITERATOR);
+ if implements_trait(cx, cx.tables().expr_ty(iter_expr), iter_def_id, &[]);
+ then {
+ return;
+ }
+ }
+
+ let lhs_constructor = last_path_segment(qpath);
+ if method_path.ident.name == sym!(next)
+ && match_trait_method(cx, match_expr, &paths::ITERATOR)
+ && lhs_constructor.ident.name == sym!(Some)
+ && (pat_args.is_empty()
+ || !is_refutable(cx, &pat_args[0])
+ && !is_used_inside(cx, iter_expr, &arms[0].body)
+ && !is_iterator_used_after_while_let(cx, iter_expr)
+ && !is_nested(cx, expr, &method_args[0]))
+ {
+ let mut applicability = Applicability::MachineApplicable;
+ let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability);
+ let loop_var = if pat_args.is_empty() {
+ "_".to_string()
+ } else {
+ snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned()
+ };
+ span_lint_and_sugg(
+ cx,
+ WHILE_LET_ON_ITERATOR,
+ expr.span.with_hi(match_expr.span.hi()),
+ "this loop could be written as a `for` loop",
+ "try",
+ format!("for {} in {}", loop_var, iterator),
+ applicability,
+ );
+ }
+ }
+ }
+
+ if let Some((cond, body)) = higher::while_loop(&expr) {
+ check_infinite_loop(cx, cond, body);
+ }
+
+ check_needless_collect(expr, cx);
+ }
+}
+
+enum NeverLoopResult {
+ // A break/return always get triggered but not necessarily for the main loop.
+ AlwaysBreak,
+ // A continue may occur for the main loop.
+ MayContinueMainLoop,
+ Otherwise,
+}
+
+#[must_use]
+fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult {
+ match *arg {
+ NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise,
+ NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
+ }
+}
+
+// Combine two results for parts that are called in order.
+#[must_use]
+fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult {
+ match first {
+ NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first,
+ NeverLoopResult::Otherwise => second,
+ }
+}
+
+// Combine two results where both parts are called but not necessarily in order.
+#[must_use]
+fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult {
+ match (left, right) {
+ (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
+ NeverLoopResult::MayContinueMainLoop
+ },
+ (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
+ (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
+ }
+}
+
+// Combine two results where only one of the part may have been executed.
+#[must_use]
+fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
+ match (b1, b2) {
+ (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
+ (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
+ NeverLoopResult::MayContinueMainLoop
+ },
+ (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
+ }
+}
+
+fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult {
+ let stmts = block.stmts.iter().map(stmt_to_expr);
+ let expr = once(block.expr.as_deref());
+ let mut iter = stmts.chain(expr).filter_map(|e| e);
+ never_loop_expr_seq(&mut iter, main_loop_id)
+}
+
+fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ match stmt.kind {
+ StmtKind::Semi(ref e, ..) | StmtKind::Expr(ref e, ..) => Some(e),
+ StmtKind::Local(ref local) => local.init.as_deref(),
+ _ => None,
+ }
+}
+
+fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
+ match expr.kind {
+ ExprKind::Box(ref e)
+ | ExprKind::Unary(_, ref e)
+ | ExprKind::Cast(ref e, _)
+ | ExprKind::Type(ref e, _)
+ | ExprKind::Field(ref e, _)
+ | ExprKind::AddrOf(_, _, ref e)
+ | ExprKind::Struct(_, _, Some(ref e))
+ | ExprKind::Repeat(ref e, _)
+ | ExprKind::DropTemps(ref e) => never_loop_expr(e, main_loop_id),
+ ExprKind::Array(ref es) | ExprKind::MethodCall(_, _, ref es, _) | ExprKind::Tup(ref es) => {
+ never_loop_expr_all(&mut es.iter(), main_loop_id)
+ },
+ ExprKind::Call(ref e, ref es) => never_loop_expr_all(&mut once(&**e).chain(es.iter()), main_loop_id),
+ ExprKind::Binary(_, ref e1, ref e2)
+ | ExprKind::Assign(ref e1, ref e2, _)
+ | ExprKind::AssignOp(_, ref e1, ref e2)
+ | ExprKind::Index(ref e1, ref e2) => never_loop_expr_all(&mut [&**e1, &**e2].iter().cloned(), main_loop_id),
+ ExprKind::Loop(ref b, _, _) => {
+ // Break can come from the inner loop so remove them.
+ absorb_break(&never_loop_block(b, main_loop_id))
+ },
+ ExprKind::Match(ref e, ref arms, _) => {
+ let e = never_loop_expr(e, main_loop_id);
+ if arms.is_empty() {
+ e
+ } else {
+ let arms = never_loop_expr_branch(&mut arms.iter().map(|a| &*a.body), main_loop_id);
+ combine_seq(e, arms)
+ }
+ },
+ ExprKind::Block(ref b, _) => never_loop_block(b, main_loop_id),
+ ExprKind::Continue(d) => {
+ let id = d
+ .target_id
+ .expect("target ID can only be missing in the presence of compilation errors");
+ if id == main_loop_id {
+ NeverLoopResult::MayContinueMainLoop
+ } else {
+ NeverLoopResult::AlwaysBreak
+ }
+ },
- ty::Array(_, n) => {
- if let Some(val) = n.try_eval_usize(cx.tcx, cx.param_env) {
- (0..=32).contains(&val)
- } else {
- false
- }
- },
++ 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::Lit(_)
+ | ExprKind::Err => NeverLoopResult::Otherwise,
+ }
+}
+
+fn never_loop_expr_seq<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult {
+ es.map(|e| never_loop_expr(e, main_loop_id))
+ .fold(NeverLoopResult::Otherwise, combine_seq)
+}
+
+fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(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<Item = &'a Expr<'a>>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult {
+ e.map(|e| never_loop_expr(e, main_loop_id))
+ .fold(NeverLoopResult::AlwaysBreak, combine_branches)
+}
+
+fn check_for_loop<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) {
+ check_for_loop_range(cx, pat, arg, body, expr);
+ check_for_loop_arg(cx, pat, arg, expr);
+ check_for_loop_explicit_counter(cx, pat, arg, body, expr);
+ check_for_loop_over_map_kv(cx, pat, arg, body, expr);
+ check_for_mut_range_bound(cx, arg, body);
+ detect_manual_memcpy(cx, pat, arg, body, expr);
+}
+
+fn same_var<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, var: HirId) -> bool {
+ if_chain! {
+ if let ExprKind::Path(qpath) = &expr.kind;
+ if let QPath::Resolved(None, path) = qpath;
+ if path.segments.len() == 1;
+ if let Res::Local(local_id) = qpath_res(cx, qpath, expr.hir_id);
+ then {
+ // our variable!
+ local_id == var
+ } else {
+ false
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum OffsetSign {
+ Positive,
+ Negative,
+}
+
+struct Offset {
+ value: String,
+ sign: OffsetSign,
+}
+
+impl Offset {
+ fn negative(value: String) -> Self {
+ Self {
+ value,
+ sign: OffsetSign::Negative,
+ }
+ }
+
+ fn positive(value: String) -> Self {
+ Self {
+ value,
+ sign: OffsetSign::Positive,
+ }
+ }
+}
+
+struct FixedOffsetVar<'hir> {
+ var: &'hir Expr<'hir>,
+ offset: Offset,
+}
+
+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_offset<'tcx>(cx: &LateContext<'tcx>, idx: &Expr<'_>, var: HirId) -> Option<Offset> {
+ fn extract_offset<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>, var: HirId) -> Option<String> {
+ match &e.kind {
+ ExprKind::Lit(l) => match l.node {
+ ast::LitKind::Int(x, _ty) => Some(x.to_string()),
+ _ => None,
+ },
+ ExprKind::Path(..) if !same_var(cx, e, var) => Some(snippet_opt(cx, e.span).unwrap_or_else(|| "??".into())),
+ _ => None,
+ }
+ }
+
+ match idx.kind {
+ ExprKind::Binary(op, lhs, rhs) => match op.node {
+ BinOpKind::Add => {
+ let offset_opt = if same_var(cx, lhs, var) {
+ extract_offset(cx, rhs, var)
+ } else if same_var(cx, rhs, var) {
+ extract_offset(cx, lhs, var)
+ } else {
+ None
+ };
+
+ offset_opt.map(Offset::positive)
+ },
+ BinOpKind::Sub if same_var(cx, lhs, var) => extract_offset(cx, rhs, var).map(Offset::negative),
+ _ => None,
+ },
+ ExprKind::Path(..) if same_var(cx, idx, var) => Some(Offset::positive("0".into())),
+ _ => None,
+ }
+}
+
+fn get_assignments<'tcx>(body: &'tcx Expr<'tcx>) -> impl Iterator<Item = Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>> {
+ 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
+ }
+ }
+
+ // 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(b, _) = body.kind {
+ let Block { stmts, expr, .. } = *b;
+
+ iter_a = stmts
+ .iter()
+ .filter_map(|stmt| match stmt.kind {
+ StmtKind::Local(..) | StmtKind::Item(..) => None,
+ StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e),
+ })
+ .chain(expr.into_iter())
+ .map(get_assignment)
+ .into()
+ } else {
+ iter_b = Some(get_assignment(body))
+ }
+
+ iter_a.into_iter().flatten().chain(iter_b.into_iter())
+}
+
+fn build_manual_memcpy_suggestion<'tcx>(
+ cx: &LateContext<'tcx>,
+ start: &Expr<'_>,
+ end: &Expr<'_>,
+ limits: ast::RangeLimits,
+ dst_var: FixedOffsetVar<'_>,
+ src_var: FixedOffsetVar<'_>,
+) -> String {
+ fn print_sum(arg1: &str, arg2: &Offset) -> String {
+ match (arg1, &arg2.value[..], arg2.sign) {
+ ("0", "0", _) => "0".into(),
+ ("0", x, OffsetSign::Positive) | (x, "0", _) => x.into(),
+ ("0", x, OffsetSign::Negative) => format!("-{}", x),
+ (x, y, OffsetSign::Positive) => format!("({} + {})", x, y),
+ (x, y, OffsetSign::Negative) => {
+ if x == y {
+ "0".into()
+ } else {
+ format!("({} - {})", x, y)
+ }
+ },
+ }
+ }
+
+ fn print_offset(start_str: &str, inline_offset: &Offset) -> String {
+ let offset = print_sum(start_str, inline_offset);
+ if offset.as_str() == "0" {
+ "".into()
+ } else {
+ offset
+ }
+ }
+
+ let print_limit = |end: &Expr<'_>, offset: Offset, var: &Expr<'_>| {
+ 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 var_def_id(cx, arg) == var_def_id(cx, var);
+ then {
+ match offset.sign {
+ OffsetSign::Negative => format!("({} - {})", snippet(cx, end.span, "<src>.len()"), offset.value),
+ OffsetSign::Positive => "".into(),
+ }
+ } else {
+ let end_str = match limits {
+ ast::RangeLimits::Closed => {
+ let end = sugg::Sugg::hir(cx, end, "<count>");
+ format!("{}", end + sugg::ONE)
+ },
+ ast::RangeLimits::HalfOpen => format!("{}", snippet(cx, end.span, "..")),
+ };
+
+ print_sum(&end_str, &offset)
+ }
+ }
+ };
+
+ let start_str = snippet(cx, start.span, "").to_string();
+ let dst_offset = print_offset(&start_str, &dst_var.offset);
+ let dst_limit = print_limit(end, dst_var.offset, dst_var.var);
+ let src_offset = print_offset(&start_str, &src_var.offset);
+ let src_limit = print_limit(end, src_var.offset, src_var.var);
+
+ let dst_var_name = snippet_opt(cx, dst_var.var.span).unwrap_or_else(|| "???".into());
+ let src_var_name = snippet_opt(cx, src_var.var.span).unwrap_or_else(|| "???".into());
+
+ let dst = if dst_offset == "" && dst_limit == "" {
+ dst_var_name
+ } else {
+ format!("{}[{}..{}]", dst_var_name, dst_offset, dst_limit)
+ };
+
+ format!(
+ "{}.clone_from_slice(&{}[{}..{}])",
+ dst, src_var_name, src_offset, src_limit
+ )
+}
+/// Checks for for loops that sequentially copy items from one slice-like
+/// object to another.
+fn detect_manual_memcpy<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) {
+ if let Some(higher::Range {
+ start: Some(start),
+ end: Some(end),
+ limits,
+ }) = higher::range(cx, arg)
+ {
+ // the var must be a single name
+ if let PatKind::Binding(_, canonical_id, _, _) = pat.kind {
+ // The only statements in the for loops can be indexed assignments from
+ // indexed retrievals.
+ let big_sugg = get_assignments(body)
+ .map(|o| {
+ o.and_then(|(lhs, rhs)| {
+ let rhs = fetch_cloned_expr(rhs);
+ if_chain! {
+ if let ExprKind::Index(seqexpr_left, idx_left) = lhs.kind;
+ if let ExprKind::Index(seqexpr_right, idx_right) = rhs.kind;
+ if is_slice_like(cx, cx.tables().expr_ty(seqexpr_left))
+ && is_slice_like(cx, cx.tables().expr_ty(seqexpr_right));
+ if let Some(offset_left) = get_offset(cx, &idx_left, canonical_id);
+ if let Some(offset_right) = get_offset(cx, &idx_right, canonical_id);
+
+ // Source and destination must be different
+ if var_def_id(cx, seqexpr_left) != var_def_id(cx, seqexpr_right);
+ then {
+ Some((FixedOffsetVar { var: seqexpr_left, offset: offset_left },
+ FixedOffsetVar { var: seqexpr_right, offset: offset_right }))
+ } else {
+ None
+ }
+ }
+ })
+ })
+ .map(|o| o.map(|(dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, dst, src)))
+ .collect::<Option<Vec<_>>>()
+ .filter(|v| !v.is_empty())
+ .map(|v| v.join("\n "));
+
+ if let Some(big_sugg) = big_sugg {
+ span_lint_and_sugg(
+ cx,
+ MANUAL_MEMCPY,
+ expr.span,
+ "it looks like you're manually copying between slices",
+ "try replacing the loop by",
+ big_sugg,
+ Applicability::Unspecified,
+ );
+ }
+ }
+ }
+}
+
+/// Checks for looping over a range and then indexing a sequence with it.
+/// The iteratee must be a range literal.
+#[allow(clippy::too_many_lines)]
+fn check_for_loop_range<'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(cx, arg)
+ {
+ // the var must be a single name
+ if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind {
+ let mut visitor = VarVisitor {
+ cx,
+ var: canonical_id,
+ indexed_mut: FxHashSet::default(),
+ indexed_indirectly: FxHashMap::default(),
+ indexed_directly: FxHashMap::default(),
+ referenced: FxHashSet::default(),
+ nonindex: false,
+ prefer_mutable: false,
+ };
+ walk_expr(&mut visitor, body);
+
+ // linting condition: we only indexed one variable, and indexed it directly
+ if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
+ let (indexed, (indexed_extent, indexed_ty)) = visitor
+ .indexed_directly
+ .into_iter()
+ .next()
+ .expect("already checked that we have exactly 1 element");
+
+ // ensure that the indexed variable was declared before the loop, see #601
+ if let Some(indexed_extent) = indexed_extent {
+ let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id);
+ let parent_def_id = cx.tcx.hir().local_def_id(parent_id);
+ let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id);
+ let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id);
+ if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) {
+ return;
+ }
+ }
+
+ // don't lint if the container that is indexed does not have .iter() method
+ let has_iter = has_iter_method(cx, indexed_ty);
+ if has_iter.is_none() {
+ return;
+ }
+
+ // don't lint if the container that is indexed into is also used without
+ // indexing
+ if visitor.referenced.contains(&indexed) {
+ return;
+ }
+
+ let starts_at_zero = is_integer_const(cx, start, 0);
+
+ let skip = if starts_at_zero {
+ String::new()
+ } else {
+ format!(".skip({})", snippet(cx, start.span, ".."))
+ };
+
+ let mut end_is_start_plus_val = false;
+
+ let take = if let Some(end) = *end {
+ let mut take_expr = end;
+
+ if let ExprKind::Binary(ref op, ref left, ref right) = end.kind {
+ if let BinOpKind::Add = op.node {
+ let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
+ let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
+
+ if start_equal_left {
+ take_expr = right;
+ } else if start_equal_right {
+ take_expr = left;
+ }
+
+ end_is_start_plus_val = start_equal_left | start_equal_right;
+ }
+ }
+
+ if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
+ String::new()
+ } else {
+ match limits {
+ ast::RangeLimits::Closed => {
+ let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
+ 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!("({}, <item>)", 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, "<item>".to_string()), (arg.span, repl)],
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+fn is_len_call(expr: &Expr<'_>, var: Name) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(ref method, _, ref len_args, _) = expr.kind;
+ if len_args.len() == 1;
+ if method.ident.name == sym!(len);
+ if let ExprKind::Path(QPath::Resolved(_, ref path)) = len_args[0].kind;
+ if path.segments.len() == 1;
+ if path.segments[0].ident.name == var;
+ then {
+ return true;
+ }
+ }
+
+ false
+}
+
+fn is_end_eq_array_len<'tcx>(
+ cx: &LateContext<'tcx>,
+ end: &Expr<'_>,
+ limits: ast::RangeLimits,
+ indexed_ty: Ty<'tcx>,
+) -> bool {
+ if_chain! {
+ if let ExprKind::Lit(ref lit) = end.kind;
+ if let ast::LitKind::Int(end_int, _) = lit.node;
+ if let ty::Array(_, arr_len_const) = indexed_ty.kind;
+ if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env);
+ then {
+ return match limits {
+ ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
+ ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
+ };
+ }
+ }
+
+ false
+}
+
+fn lint_iter_method(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) {
+ let mut applicability = Applicability::MachineApplicable;
+ let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
+ let muta = if method_name == "iter_mut" { "mut " } else { "" };
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_ITER_LOOP,
+ arg.span,
+ "it is more concise to loop over references to containers instead of using explicit \
+ iteration methods",
+ "to write this more concisely, try",
+ format!("&{}{}", muta, object),
+ applicability,
+ )
+}
+
+fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) {
+ let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used
+ if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind {
+ // just the receiver, no arguments
+ if args.len() == 1 {
+ let method_name = &*method.ident.as_str();
+ // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
+ if method_name == "iter" || method_name == "iter_mut" {
+ if is_ref_iterable_type(cx, &args[0]) {
+ lint_iter_method(cx, args, arg, method_name);
+ }
+ } else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) {
+ let receiver_ty = cx.tables().expr_ty(&args[0]);
+ let receiver_ty_adjusted = cx.tables().expr_ty_adjusted(&args[0]);
+ if TyS::same_type(receiver_ty, receiver_ty_adjusted) {
+ let mut applicability = Applicability::MachineApplicable;
+ let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_INTO_ITER_LOOP,
+ arg.span,
+ "it is more concise to loop over containers instead of using explicit \
+ iteration methods",
+ "to write this more concisely, try",
+ object.to_string(),
+ applicability,
+ );
+ } else {
+ let ref_receiver_ty = cx.tcx.mk_ref(
+ cx.tcx.lifetimes.re_erased,
+ ty::TypeAndMut {
+ ty: receiver_ty,
+ mutbl: Mutability::Not,
+ },
+ );
+ if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) {
+ lint_iter_method(cx, args, arg, method_name)
+ }
+ }
+ } else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) {
+ span_lint(
+ cx,
+ ITER_NEXT_LOOP,
+ expr.span,
+ "you are iterating over `Iterator::next()` which is an Option; this will compile but is \
+ probably not what you want",
+ );
+ next_loop_linted = true;
+ }
+ }
+ }
+ if !next_loop_linted {
+ check_arg_type(cx, pat, arg);
+ }
+}
+
+/// Checks for `for` loops over `Option`s and `Result`s.
+fn check_arg_type(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) {
+ let ty = cx.tables().expr_ty(arg);
+ if is_type_diagnostic_item(cx, ty, sym!(option_type)) {
+ span_lint_and_help(
+ cx,
+ FOR_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, "_")
+ ),
+ );
+ }
+}
+
+fn check_for_loop_explicit_counter<'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 visitor = IncrementVisitor {
+ cx,
+ states: FxHashMap::default(),
+ depth: 0,
+ done: false,
+ };
+ walk_expr(&mut visitor, body);
+
+ // For each candidate, check the parent block to see if
+ // it's initialized to zero at the start of the loop.
+ if let Some(block) = get_enclosing_block(&cx, expr.hir_id) {
+ for (id, _) in visitor.states.iter().filter(|&(_, v)| *v == VarState::IncrOnce) {
+ let mut visitor2 = InitializeVisitor {
+ cx,
+ end_expr: expr,
+ var_id: *id,
+ state: VarState::IncrOnce,
+ name: None,
+ depth: 0,
+ past_loop: false,
+ };
+ walk_block(&mut visitor2, block);
+
+ if visitor2.state == VarState::Warn {
+ if let Some(name) = visitor2.name {
+ let mut applicability = Applicability::MachineApplicable;
+
+ // for some reason this is the only way to get the `Span`
+ // of the entire `for` loop
+ let for_span = if let ExprKind::Match(_, arms, _) = &expr.kind {
+ arms[0].body.span
+ } else {
+ unreachable!()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_COUNTER_LOOP,
+ for_span.with_hi(arg.span.hi()),
+ &format!("the variable `{}` is used as a loop counter.", name),
+ "consider using",
+ format!(
+ "for ({}, {}) in {}.enumerate()",
+ name,
+ snippet_with_applicability(cx, pat.span, "item", &mut applicability),
+ make_iterator_snippet(cx, arg, &mut applicability),
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+ }
+}
+
+/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the
+/// actual `Iterator` that the loop uses.
+fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String {
+ let impls_iterator = get_trait_def_id(cx, &paths::ITERATOR)
+ .map_or(false, |id| implements_trait(cx, cx.tables().expr_ty(arg), id, &[]));
+ if impls_iterator {
+ format!(
+ "{}",
+ sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
+ )
+ } else {
+ // (&x).into_iter() ==> x.iter()
+ // (&mut x).into_iter() ==> x.iter_mut()
+ match &arg.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner)
+ if has_iter_method(cx, cx.tables().expr_ty(&arg_inner)).is_some() =>
+ {
+ let meth_name = match mutability {
+ Mutability::Mut => "iter_mut",
+ Mutability::Not => "iter",
+ };
+ format!(
+ "{}.{}()",
+ sugg::Sugg::hir_with_applicability(cx, &arg_inner, "_", applic_ref).maybe_par(),
+ meth_name,
+ )
+ }
+ _ => format!(
+ "{}.into_iter()",
+ sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
+ ),
+ }
+ }
+}
+
+/// Checks for the `FOR_KV_MAP` lint.
+fn check_for_loop_over_map_kv<'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.tables().expr_ty(arg).kind {
+ ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) {
+ (key, _) if pat_is_wild(key, body) => (pat[1].span, "value", ty, mutbl),
+ (_, value) if pat_is_wild(value, body) => (pat[0].span, "key", ty, Mutability::Not),
+ _ => return,
+ },
+ _ => return,
+ };
+ let mutbl = match mutbl {
+ Mutability::Not => "",
+ Mutability::Mut => "_mut",
+ };
+ let arg = match arg.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) => &**expr,
+ _ => arg,
+ };
+
+ if is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || match_type(cx, ty, &paths::BTREEMAP) {
+ span_lint_and_then(
+ cx,
+ FOR_KV_MAP,
+ expr.span,
+ &format!("you seem to want to iterate on a map's {}s", kind),
+ |diag| {
+ let map = sugg::Sugg::hir(cx, arg, "map");
+ multispan_sugg(
+ diag,
+ "use the corresponding method",
+ vec![
+ (pat_span, snippet(cx, new_pat_span, kind).into_owned()),
+ (arg_span, format!("{}.{}s{}()", map.maybe_par(), kind, mutbl)),
+ ],
+ );
+ },
+ );
+ }
+ }
+ }
+}
+
+struct MutatePairDelegate<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ hir_id_low: Option<HirId>,
+ hir_id_high: Option<HirId>,
+ span_low: Option<Span>,
+ span_high: Option<Span>,
+}
+
+impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
+ fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: ConsumeMode) {}
+
+ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, 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(cmt.hir_id))
+ }
+ if Some(id) == self.hir_id_high {
+ self.span_high = Some(self.cx.tcx.hir().span(cmt.hir_id))
+ }
+ }
+ }
+ }
+
+ fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>) {
+ if let PlaceBase::Local(id) = cmt.place.base {
+ if Some(id) == self.hir_id_low {
+ self.span_low = Some(self.cx.tcx.hir().span(cmt.hir_id))
+ }
+ if Some(id) == self.hir_id_high {
+ self.span_high = Some(self.cx.tcx.hir().span(cmt.hir_id))
+ }
+ }
+ }
+}
+
+impl MutatePairDelegate<'_, '_> {
+ fn mutation_span(&self) -> (Option<Span>, Option<Span>) {
+ (self.span_low, self.span_high)
+ }
+}
+
+fn check_for_mut_range_bound(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
+ if let Some(higher::Range {
+ start: Some(start),
+ end: Some(end),
+ ..
+ }) = higher::range(cx, arg)
+ {
+ let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)];
+ if mut_ids[0].is_some() || mut_ids[1].is_some() {
+ let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids);
+ mut_warn_with_span(cx, span_low);
+ mut_warn_with_span(cx, span_high);
+ }
+ }
+}
+
+fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
+ 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<HirId> {
+ if_chain! {
+ if let ExprKind::Path(ref qpath) = bound.kind;
+ if let QPath::Resolved(None, _) = *qpath;
+ then {
+ let res = qpath_res(cx, qpath, bound.hir_id);
+ if let Res::Local(hir_id) = res {
+ let node_str = cx.tcx.hir().get(hir_id);
+ if_chain! {
+ if let Node::Binding(pat) = node_str;
+ if let PatKind::Binding(bind_ann, ..) = pat.kind;
+ if let BindingAnnotation::Mutable = bind_ann;
+ then {
+ return Some(hir_id);
+ }
+ }
+ }
+ }
+ }
+ None
+}
+
+fn check_for_mutation<'tcx>(
+ cx: &LateContext<'tcx>,
+ body: &Expr<'_>,
+ bound_ids: &[Option<HirId>],
+) -> (Option<Span>, Option<Span>) {
+ let mut delegate = MutatePairDelegate {
+ cx,
+ hir_id_low: bound_ids[0],
+ hir_id_high: bound_ids[1],
+ span_low: None,
+ span_high: None,
+ };
+ let def_id = body.hir_id.owner.to_def_id();
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ ExprUseVisitor::new(&mut delegate, &infcx, def_id.expect_local(), cx.param_env, cx.tables()).walk_expr(body);
+ });
+ delegate.mutation_span()
+}
+
+/// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`.
+fn pat_is_wild<'tcx>(pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
+ match *pat {
+ PatKind::Wild => true,
+ PatKind::Binding(.., ident, None) if ident.as_str().starts_with('_') => is_unused(&ident, body),
+ _ => false,
+ }
+}
+
+struct LocalUsedVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ local: HirId,
+ used: bool,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for LocalUsedVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if same_var(self.cx, expr, self.local) {
+ self.used = true;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+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<Name>,
+ /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global
+ indexed_indirectly: FxHashMap<Name, Option<region::Scope>>,
+ /// 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<Name, (Option<region::Scope>, 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<Name>,
+ /// has the loop variable been used in expressions other than the index of
+ /// an index op?
+ nonindex: bool,
+ /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar
+ /// takes `&mut self`
+ prefer_mutable: bool,
+}
+
+impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
+ fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
+ if_chain! {
+ // the indexed container is referenced by a name
+ if let ExprKind::Path(ref seqpath) = seqexpr.kind;
+ if let QPath::Resolved(None, ref seqvar) = *seqpath;
+ if seqvar.segments.len() == 1;
+ then {
+ let index_used_directly = same_var(self.cx, idx, self.var);
+ let indexed_indirectly = {
+ let mut used_visitor = LocalUsedVisitor {
+ cx: self.cx,
+ local: self.var,
+ used: false,
+ };
+ walk_expr(&mut used_visitor, idx);
+ used_visitor.used
+ };
+
+ if indexed_indirectly || index_used_directly {
+ if self.prefer_mutable {
+ self.indexed_mut.insert(seqvar.segments[0].ident.name);
+ }
+ let res = qpath_res(self.cx, seqpath, seqexpr.hir_id);
+ match res {
+ Res::Local(hir_id) => {
+ let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
+ let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id);
+ let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id);
+ if indexed_indirectly {
+ self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
+ }
+ if index_used_directly {
+ self.indexed_directly.insert(
+ seqvar.segments[0].ident.name,
+ (Some(extent), self.cx.tables().node_type(seqexpr.hir_id)),
+ );
+ }
+ return false; // no need to walk further *on the variable*
+ }
+ Res::Def(DefKind::Static | DefKind::Const, ..) => {
+ if indexed_indirectly {
+ self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
+ }
+ if index_used_directly {
+ self.indexed_directly.insert(
+ seqvar.segments[0].ident.name,
+ (None, self.cx.tables().node_type(seqexpr.hir_id)),
+ );
+ }
+ return false; // no need to walk further *on the variable*
+ }
+ _ => (),
+ }
+ }
+ }
+ }
+ true
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ // a range index op
+ if let ExprKind::MethodCall(ref meth, _, ref args, _) = expr.kind;
+ if (meth.ident.name == sym!(index) && match_trait_method(self.cx, expr, &paths::INDEX))
+ || (meth.ident.name == sym!(index_mut) && match_trait_method(self.cx, expr, &paths::INDEX_MUT));
+ if !self.check(&args[1], &args[0], expr);
+ then { return }
+ }
+
+ if_chain! {
+ // an index op
+ if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind;
+ if !self.check(idx, seqexpr, expr);
+ then { return }
+ }
+
+ if_chain! {
+ // directly using a variable
+ if let ExprKind::Path(ref qpath) = expr.kind;
+ if let QPath::Resolved(None, ref path) = *qpath;
+ if path.segments.len() == 1;
+ then {
+ if let Res::Local(local_id) = qpath_res(self.cx, qpath, expr.hir_id) {
+ if local_id == self.var {
+ self.nonindex = true;
+ } else {
+ // not the correct variable, but still a variable
+ self.referenced.insert(path.segments[0].ident.name);
+ }
+ }
+ }
+ }
+
+ let old = self.prefer_mutable;
+ match expr.kind {
+ ExprKind::AssignOp(_, ref lhs, ref rhs) | ExprKind::Assign(ref lhs, ref rhs, _) => {
+ self.prefer_mutable = true;
+ self.visit_expr(lhs);
+ self.prefer_mutable = false;
+ self.visit_expr(rhs);
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutbl, ref expr) => {
+ if mutbl == Mutability::Mut {
+ self.prefer_mutable = true;
+ }
+ self.visit_expr(expr);
+ },
+ ExprKind::Call(ref f, args) => {
+ self.visit_expr(f);
+ for expr in args {
+ let ty = self.cx.tables().expr_ty_adjusted(expr);
+ self.prefer_mutable = false;
+ if let ty::Ref(_, _, mutbl) = ty.kind {
+ if mutbl == Mutability::Mut {
+ self.prefer_mutable = true;
+ }
+ }
+ self.visit_expr(expr);
+ }
+ },
+ ExprKind::MethodCall(_, _, args, _) => {
+ let def_id = self.cx.tables().type_dependent_def_id(expr.hir_id).unwrap();
+ for (ty, expr) in self.cx.tcx.fn_sig(def_id).inputs().skip_binder().iter().zip(args) {
+ self.prefer_mutable = false;
+ if let ty::Ref(_, _, mutbl) = ty.kind {
+ if mutbl == Mutability::Mut {
+ self.prefer_mutable = true;
+ }
+ }
+ self.visit_expr(expr);
+ }
+ },
+ ExprKind::Closure(_, _, body_id, ..) => {
+ let body = self.cx.tcx.hir().body(body_id);
+ self.visit_expr(&body.value);
+ },
+ _ => walk_expr(self, expr),
+ }
+ self.prefer_mutable = old;
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool {
+ let def_id = match var_def_id(cx, expr) {
+ Some(id) => id,
+ None => return false,
+ };
+ if let Some(used_mutably) = mutated_variables(container, cx) {
+ if used_mutably.contains(&def_id) {
+ return true;
+ }
+ }
+ false
+}
+
+fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool {
+ let def_id = match var_def_id(cx, iter_expr) {
+ Some(id) => id,
+ None => return false,
+ };
+ let mut visitor = VarUsedAfterLoopVisitor {
+ cx,
+ def_id,
+ iter_expr_id: iter_expr.hir_id,
+ past_while_let: false,
+ var_used_after_while_let: false,
+ };
+ if let Some(enclosing_block) = get_enclosing_block(cx, def_id) {
+ walk_block(&mut visitor, enclosing_block);
+ }
+ visitor.var_used_after_while_let
+}
+
+struct VarUsedAfterLoopVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ def_id: HirId,
+ iter_expr_id: HirId,
+ past_while_let: bool,
+ var_used_after_while_let: bool,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.past_while_let {
+ if Some(self.def_id) == var_def_id(self.cx, expr) {
+ self.var_used_after_while_let = true;
+ }
+ } else if self.iter_expr_id == expr.hir_id {
+ self.past_while_let = true;
+ }
+ walk_expr(self, expr);
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Returns `true` if the type of expr is one that provides `IntoIterator` impls
+/// for `&T` and `&mut T`, such as `Vec`.
+#[rustfmt::skip]
+fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ // no walk_ptrs_ty: calling iter() on a reference can make sense because it
+ // will allow further borrows afterwards
+ let ty = cx.tables().expr_ty(e);
+ is_iterable_array(ty, cx) ||
+ is_type_diagnostic_item(cx, ty, sym!(vec_type)) ||
+ match_type(cx, ty, &paths::LINKED_LIST) ||
+ is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) ||
+ is_type_diagnostic_item(cx, ty, sym!(hashset_type)) ||
+ is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) ||
+ match_type(cx, ty, &paths::BINARY_HEAP) ||
+ match_type(cx, ty, &paths::BTREEMAP) ||
+ match_type(cx, ty, &paths::BTREESET)
+}
+
+fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
+ // IntoIterator is currently only implemented for array sizes <= 32 in rustc
+ match ty.kind {
- if let Some(expr) = local.init {
- Some(expr)
- } else {
- None
- }
++ ty::Array(_, n) => n
++ .try_eval_usize(cx.tcx, cx.param_env)
++ .map_or(false, |val| (0..=32).contains(&val)),
+ _ => false,
+ }
+}
+
+/// If a block begins with a statement (possibly a `let` binding) and has an
+/// expression, return it.
+fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ if block.stmts.is_empty() {
+ return None;
+ }
+ if let StmtKind::Local(ref local) = block.stmts[0].kind {
- self.state = if let Some(ref init) = local.init {
++ 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,
+ }
+}
+
+// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be
+// incremented exactly once in the loop body, and initialized to zero
+// at the start of the loop.
+#[derive(Debug, PartialEq)]
+enum VarState {
+ Initial, // Not examined yet
+ IncrOnce, // Incremented exactly once, may be a loop counter
+ Declared, // Declared but not (yet) initialized to zero
+ Warn,
+ DontWarn,
+}
+
+/// Scan a for loop for variables that are incremented exactly once.
+struct IncrementVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>, // context reference
+ states: FxHashMap<HirId, VarState>, // incremented variables
+ depth: u32, // depth of conditional expressions
+ done: bool,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.done {
+ return;
+ }
+
+ // If node is a variable
+ if let Some(def_id) = var_def_id(self.cx, expr) {
+ if let Some(parent) = get_parent_expr(self.cx, expr) {
+ let state = self.states.entry(def_id).or_insert(VarState::Initial);
+
+ match parent.kind {
+ ExprKind::AssignOp(op, ref lhs, ref rhs) => {
+ if lhs.hir_id == expr.hir_id {
+ if op.node == BinOpKind::Add && is_integer_const(self.cx, rhs, 1) {
+ *state = match *state {
+ VarState::Initial if self.depth == 0 => VarState::IncrOnce,
+ _ => VarState::DontWarn,
+ };
+ } else {
+ // Assigned some other value
+ *state = VarState::DontWarn;
+ }
+ }
+ },
+ ExprKind::Assign(ref lhs, _, _) if lhs.hir_id == expr.hir_id => *state = VarState::DontWarn,
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
+ *state = VarState::DontWarn
+ },
+ _ => (),
+ }
+ }
+ } else if is_loop(expr) || is_conditional(expr) {
+ self.depth += 1;
+ walk_expr(self, expr);
+ self.depth -= 1;
+ return;
+ } else if let ExprKind::Continue(_) = expr.kind {
+ self.done = true;
+ return;
+ }
+ walk_expr(self, expr);
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Checks whether a variable is initialized to zero at the start of a loop.
+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: VarState,
+ name: Option<Name>,
+ depth: u32, // depth of conditional expressions
+ past_loop: bool,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ // Look for declarations of the variable
+ if let StmtKind::Local(ref local) = stmt.kind {
+ if local.pat.hir_id == self.var_id {
+ if let PatKind::Binding(.., ident, _) = local.pat.kind {
+ self.name = Some(ident.name);
+
- } else {
- VarState::Declared
- }
++ self.state = local.init.as_ref().map_or(VarState::Declared, |init| {
+ if is_integer_const(&self.cx, init, 0) {
+ VarState::Warn
+ } else {
+ VarState::Declared
+ }
- match expr.kind {
- ExprKind::Loop(..) => true,
- _ => false,
- }
++ })
+ }
+ }
+ }
+ walk_stmt(self, stmt);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.state == VarState::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 self.state == VarState::IncrOnce {
+ return;
+ }
+
+ // If node is the desired variable, see how it's used
+ if var_def_id(self.cx, expr) == Some(self.var_id) {
+ if let Some(parent) = get_parent_expr(self.cx, expr) {
+ match parent.kind {
+ ExprKind::AssignOp(_, ref lhs, _) if lhs.hir_id == expr.hir_id => {
+ self.state = VarState::DontWarn;
+ },
+ ExprKind::Assign(ref lhs, ref rhs, _) if lhs.hir_id == expr.hir_id => {
+ self.state = if is_integer_const(&self.cx, rhs, 0) && self.depth == 0 {
+ VarState::Warn
+ } else {
+ VarState::DontWarn
+ }
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
+ self.state = VarState::DontWarn
+ },
+ _ => (),
+ }
+ }
+
+ if self.past_loop {
+ self.state = VarState::DontWarn;
+ return;
+ }
+ } else if !self.past_loop && is_loop(expr) {
+ self.state = VarState::DontWarn;
+ return;
+ } else if is_conditional(expr) {
+ self.depth += 1;
+ walk_expr(self, expr);
+ self.depth -= 1;
+ return;
+ }
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
+
+fn var_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<HirId> {
+ if let ExprKind::Path(ref qpath) = expr.kind {
+ let path_res = qpath_res(cx, qpath, expr.hir_id);
+ if let Res::Local(hir_id) = path_res {
+ return Some(hir_id);
+ }
+ }
+ None
+}
+
+fn is_loop(expr: &Expr<'_>) -> bool {
- match expr.kind {
- ExprKind::Match(..) => true,
- _ => false,
- }
++ matches!(expr.kind, ExprKind::Loop(..))
+}
+
+fn is_conditional(expr: &Expr<'_>) -> bool {
++ matches!(expr.kind, ExprKind::Match(..))
+}
+
+fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
+ if_chain! {
+ if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id);
+ let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id);
+ if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node);
+ then {
+ return is_loop_nested(cx, loop_expr, iter_expr)
+ }
+ }
+ false
+}
+
+fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
+ let mut id = loop_expr.hir_id;
+ let iter_name = if let Some(name) = path_name(iter_expr) {
+ name
+ } else {
+ return true;
+ };
+ loop {
+ let parent = cx.tcx.hir().get_parent_node(id);
+ if parent == id {
+ return false;
+ }
+ match cx.tcx.hir().find(parent) {
+ Some(Node::Expr(expr)) => {
+ if let ExprKind::Loop(..) = expr.kind {
+ return true;
+ };
+ },
+ Some(Node::Block(block)) => {
+ let mut block_visitor = LoopNestVisitor {
+ hir_id: id,
+ iterator: iter_name,
+ nesting: Unknown,
+ };
+ walk_block(&mut block_visitor, block);
+ if block_visitor.nesting == RuledOut {
+ return false;
+ }
+ },
+ Some(Node::Stmt(_)) => (),
+ _ => {
+ return false;
+ },
+ }
+ id = parent;
+ }
+}
+
+#[derive(PartialEq, Eq)]
+enum Nesting {
+ Unknown, // no nesting detected yet
+ RuledOut, // the iterator is initialized or assigned within scope
+ LookFurther, // no nesting detected, no further walk required
+}
+
+use self::Nesting::{LookFurther, RuledOut, Unknown};
+
+struct LoopNestVisitor {
+ hir_id: HirId,
+ iterator: Name,
+ nesting: Nesting,
+}
+
+impl<'tcx> Visitor<'tcx> for LoopNestVisitor {
+ type Map = Map<'tcx>;
+
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ if stmt.hir_id == self.hir_id {
+ self.nesting = LookFurther;
+ } else if self.nesting == Unknown {
+ walk_stmt(self, stmt);
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.nesting != Unknown {
+ return;
+ }
+ if expr.hir_id == self.hir_id {
+ self.nesting = LookFurther;
+ return;
+ }
+ match expr.kind {
+ ExprKind::Assign(ref path, _, _) | ExprKind::AssignOp(_, ref path, _) => {
+ if match_var(path, self.iterator) {
+ self.nesting = RuledOut;
+ }
+ },
+ _ => walk_expr(self, expr),
+ }
+ }
+
+ fn visit_pat(&mut self, pat: &'tcx Pat<'_>) {
+ if self.nesting != Unknown {
+ return;
+ }
+ if let PatKind::Binding(.., span_name, _) = pat.kind {
+ if self.iterator == span_name.name {
+ self.nesting = RuledOut;
+ return;
+ }
+ }
+ walk_pat(self, pat)
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+fn path_name(e: &Expr<'_>) -> Option<Name> {
+ if let ExprKind::Path(QPath::Resolved(_, ref path)) = e.kind {
+ let segments = &path.segments;
+ if segments.len() == 1 {
+ return Some(segments[0].ident.name);
+ }
+ };
+ None
+}
+
+fn check_infinite_loop<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) {
+ if constant(cx, cx.tables(), cond).is_some() {
+ // A pure constant condition (e.g., `while false`) is not linted.
+ return;
+ }
+
+ let mut var_visitor = VarCollectorVisitor {
+ cx,
+ ids: FxHashSet::default(),
+ def_ids: FxHashMap::default(),
+ skip: false,
+ };
+ var_visitor.visit_expr(cond);
+ if var_visitor.skip {
+ return;
+ }
+ let used_in_condition = &var_visitor.ids;
+ let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) {
+ used_in_condition.is_disjoint(&used_mutably)
+ } else {
+ return;
+ };
+ let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v);
+
+ let mut has_break_or_return_visitor = HasBreakOrReturnVisitor {
+ has_break_or_return: false,
+ };
+ has_break_or_return_visitor.visit_expr(expr);
+ let has_break_or_return = has_break_or_return_visitor.has_break_or_return;
+
+ if no_cond_variable_mutated && !mutable_static_in_cond {
+ span_lint_and_then(
+ cx,
+ WHILE_IMMUTABLE_CONDITION,
+ cond.span,
+ "variables in the condition are not mutated in the loop body",
+ |diag| {
+ diag.note("this may lead to an infinite or to a never running loop");
+
+ if has_break_or_return {
+ diag.note("this loop contains `return`s or `break`s");
+ diag.help("rewrite it as `if cond { loop { } }`");
+ }
+ },
+ );
+ }
+}
+
+struct HasBreakOrReturnVisitor {
+ has_break_or_return: bool,
+}
+
+impl<'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<Self::Map> {
+ 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<HirId>,
+ def_ids: FxHashMap<def_id::DefId, bool>,
+ skip: bool,
+}
+
+impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
+ fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Path(ref qpath) = ex.kind;
+ if let QPath::Resolved(None, _) = *qpath;
+ let res = qpath_res(self.cx, qpath, ex.hir_id);
+ then {
+ match res {
+ Res::Local(hir_id) => {
+ self.ids.insert(hir_id);
+ },
+ Res::Def(DefKind::Static, def_id) => {
+ let mutable = self.cx.tcx.is_mutable_static(def_id);
+ self.def_ids.insert(def_id, mutable);
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
+ match ex.kind {
+ ExprKind::Path(_) => self.insert_def_id(ex),
+ // If there is any function/method call… we just stop analysis
+ ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true,
+
+ _ => walk_expr(self, ex),
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
+
+fn check_needless_collect<'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.tables().node_type(ty.hir_id);
+ if is_type_diagnostic_item(cx, ty, sym!(vec_type)) ||
+ is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) ||
+ match_type(cx, ty, &paths::BTREEMAP) ||
+ is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) {
+ if method.ident.name == sym!(len) {
+ let span = shorten_span(expr, sym!(collect));
+ 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_span(expr, sym!(iter));
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_COLLECT,
+ span,
+ NEEDLESS_COLLECT_MSG,
+ "replace with",
+ "get(0).is_none()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ if method.ident.name == sym!(contains) {
+ let contains_arg = snippet(cx, args[1].span, "??");
+ let span = shorten_span(expr, sym!(collect));
+ span_lint_and_then(
+ cx,
+ NEEDLESS_COLLECT,
+ span,
+ NEEDLESS_COLLECT_MSG,
+ |diag| {
+ let (arg, pred) = if contains_arg.starts_with('&') {
+ ("x", &contains_arg[1..])
+ } else {
+ ("&x", &*contains_arg)
+ };
+ diag.span_suggestion(
+ span,
+ "replace with",
+ format!(
+ "any(|{}| x == {})",
+ arg, pred
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ );
+ }
+ }
+ }
+ }
+}
+
+fn shorten_span(expr: &Expr<'_>, target_fn_name: Symbol) -> Span {
+ let mut current_expr = expr;
+ while let ExprKind::MethodCall(ref path, ref span, ref args, _) = current_expr.kind {
+ if path.ident.name == target_fn_name {
+ return expr.span.with_lo(span.lo());
+ }
+ current_expr = &args[0];
+ }
+ unreachable!()
+}
--- /dev/null
--- /dev/null
++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};
++
++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.as_str() == "map";
++ let caller_ty = cx.tables().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.tables().hir_owner == expr.hir_id.owner && is_adjusted(cx, expr))
++ } else {
++ false
++ }
++}
--- /dev/null
- Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, PatKind,
- QPath, RangeEnd,
+use crate::consts::{constant, miri_to_const, Constant};
+use crate::utils::paths;
+use crate::utils::sugg::Sugg;
+use crate::utils::usage::is_unused;
+use crate::utils::{
+ expr_block, get_arg_name, get_parent_expr, in_macro, indent_of, is_allowed, is_expn_of, is_refutable,
+ is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, multispan_sugg, remove_blocks, snippet,
+ snippet_block, snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg,
+ span_lint_and_then, walk_ptrs_ty,
+};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::def::CtorKind;
+use rustc_hir::{
- use rustc_span::source_map::Span;
++ Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, Local, MatchSource, Mutability, Node, Pat,
++ PatKind, QPath, RangeEnd,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
- REST_PAT_IN_FULLY_BOUND_STRUCTS
++use rustc_span::source_map::{Span, Spanned};
+use std::cmp::Ordering;
+use std::collections::Bound;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for matches with a single arm where an `if let`
+ /// will usually suffice.
+ ///
+ /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # fn bar(stool: &str) {}
+ /// # let x = Some("abc");
+ ///
+ /// // 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<i32, &str> = 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` or
++ /// `Option`
++ ///
++ /// **Why is this bad?** It's more concise and clear to just use the proper
++ /// utility function
++ ///
++ /// **Known problems:** None.
++ ///
++ /// **Example:**
++ ///
++ /// ```rust
++ /// if let Ok(_) = Ok::<i32, i32>(42) {}
++ /// if let Err(_) = Err::<i32, i32>(42) {}
++ /// if let None = None::<()> {}
++ /// if let Some(_) = Some(42) {}
++ /// match Ok::<i32, i32>(42) {
++ /// Ok(_) => true,
++ /// Err(_) => false,
++ /// };
++ /// ```
++ ///
++ /// The more idiomatic use would be:
++ ///
++ /// ```rust
++ /// if Ok::<i32, i32>(42).is_ok() {}
++ /// if Err::<i32, i32>(42).is_err() {}
++ /// if None::<()>.is_none() {}
++ /// if Some(42).is_some() {}
++ /// Ok::<i32, i32>(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:** None
++ ///
++ /// **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"
++}
++
+#[derive(Default)]
+pub struct Matches {
+ infallible_destructuring_match_linted: bool,
+}
+
+impl_lint_pass!(Matches => [
+ SINGLE_MATCH,
+ MATCH_REF_PATS,
+ MATCH_BOOL,
+ SINGLE_MATCH_ELSE,
+ MATCH_OVERLAPPING_ARM,
+ MATCH_WILD_ERR_ARM,
+ MATCH_AS_REF,
+ WILDCARD_ENUM_MATCH_ARM,
+ MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ WILDCARD_IN_OR_PATTERNS,
+ MATCH_SINGLE_BINDING,
+ INFALLIBLE_DESTRUCTURING_MATCH,
- let els = remove_blocks(&arms[1].body);
- let els = if is_unit_expr(els) {
++ REST_PAT_IN_FULLY_BOUND_STRUCTS,
++ REDUNDANT_PATTERN_MATCHING,
++ MATCH_LIKE_MATCHES_MACRO
+]);
+
+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) {
+ return;
+ }
++
++ redundant_pattern_match::check(cx, expr);
++ check_match_like_matches(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 Some(arg) = get_arg_name(&args[0]);
+ let body = remove_blocks(&arms[0].body);
+ if match_var(body, arg);
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ self.infallible_destructuring_match_linted = true;
+ span_lint_and_sugg(
+ cx,
+ INFALLIBLE_DESTRUCTURING_MATCH,
+ local.span,
+ "you seem to be trying to use `match` to destructure a single infallible pattern. \
+ Consider using `let`",
+ "try this",
+ format!(
+ "let {}({}) = {};",
+ snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
+ snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
+ snippet_with_applicability(cx, target.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
+ if_chain! {
+ if !in_external_macro(cx.sess(), pat.span);
+ if !in_macro(pat.span);
+ if let PatKind::Struct(ref qpath, fields, true) = pat.kind;
+ if let QPath::Resolved(_, ref path) = qpath;
+ if let Some(def_id) = path.res.opt_def_id();
+ let ty = cx.tcx.type_of(def_id);
+ if let ty::Adt(def, _) = ty.kind;
+ if def.is_struct() || def.is_union();
+ if fields.len() == def.non_enum_variant().fields.len();
+
+ then {
+ span_lint_and_help(
+ cx,
+ REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ pat.span,
+ "unnecessary use of `..` pattern in struct binding. All fields were already bound",
+ None,
+ "consider removing `..` from this binding",
+ );
+ }
+ }
+ }
+}
+
+#[rustfmt::skip]
+fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
+ if in_macro(expr.span) {
+ // Don't lint match expressions present in
+ // macro_rules! block
+ return;
+ }
+ if let PatKind::Or(..) = arms[0].pat.kind {
+ // don't lint for or patterns for now, this makes
+ // the lint noisy in unnecessary situations
+ return;
+ }
- } else if let ExprKind::Block(_, _) = els.kind {
- // matches with blocks that contain statements are prettier as `if let + else`
- Some(els)
++ let els = arms[1].body;
++ let els = if is_unit_expr(remove_blocks(els)) {
+ None
- // allow match arms with just expressions
- return;
++ } 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;
++ } else {
++ // block with 2+ statements or 1 expr and 1+ statement
++ Some(els)
++ }
+ } else {
- let is_pattern_exhaustive = |pat: &&Pat<'_>| {
- if let PatKind::Wild | PatKind::Binding(.., None) = pat.kind {
- true
- } else {
- false
- }
- };
++ // not a block, don't lint
++ return;
+ };
++
+ let ty = cx.tables().expr_ty(ex);
+ if ty.kind != ty::Bool || is_allowed(cx, MATCH_BOOL, ex.hir_id) {
+ check_single_match_single_pattern(cx, ex, arms, expr, els);
+ check_single_match_opt_like(cx, ex, arms, expr, ty, els);
+ }
+ }
+}
+
+fn check_single_match_single_pattern(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ if is_wild(&arms[1].pat) {
+ report_single_match_single_pattern(cx, ex, arms, expr, els);
+ }
+}
+
+fn report_single_match_single_pattern(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH };
+ let els_str = els.map_or(String::new(), |els| {
+ format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
+ });
+ span_lint_and_sugg(
+ cx,
+ lint,
+ expr.span,
+ "you seem to be trying to use match for destructuring a single pattern. Consider using `if \
+ let`",
+ "try this",
+ format!(
+ "if let {} = {} {}{}",
+ snippet(cx, arms[0].pat.span, ".."),
+ snippet(cx, ex.span, ".."),
+ expr_block(cx, &arms[0].body, None, "..", Some(expr.span)),
+ els_str,
+ ),
+ Applicability::HasPlaceholders,
+ );
+}
+
+fn check_single_match_opt_like(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ ty: Ty<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ // list of candidate `Enum`s we know will never get any more members
+ let candidates = &[
+ (&paths::COW, "Borrowed"),
+ (&paths::COW, "Cow::Borrowed"),
+ (&paths::COW, "Cow::Owned"),
+ (&paths::COW, "Owned"),
+ (&paths::OPTION, "None"),
+ (&paths::RESULT, "Err"),
+ (&paths::RESULT, "Ok"),
+ ];
+
+ let path = match arms[1].pat.kind {
+ PatKind::TupleStruct(ref path, ref inner, _) => {
+ // Contains any non wildcard patterns (e.g., `Err(err)`)?
+ if !inner.iter().all(is_wild) {
+ return;
+ }
+ rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
+ },
+ PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => ident.to_string(),
+ PatKind::Path(ref path) => {
+ rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
+ },
+ _ => return,
+ };
+
+ for &(ty_path, pat_path) in candidates {
+ if path == *pat_path && match_type(cx, ty, ty_path) {
+ report_single_match_single_pattern(cx, ex, arms, expr, els);
+ }
+ }
+}
+
+fn check_match_bool(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ // Type of expression is `bool`.
+ if cx.tables().expr_ty(ex).kind == ty::Bool {
+ span_lint_and_then(
+ cx,
+ MATCH_BOOL,
+ expr.span,
+ "you seem to be trying to match on a boolean expression",
+ move |diag| {
+ if arms.len() == 2 {
+ // no guards
+ let exprs = if let PatKind::Lit(ref arm_bool) = arms[0].pat.kind {
+ if let ExprKind::Lit(ref lit) = arm_bool.kind {
+ match lit.node {
+ LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
+ LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ if let Some((true_expr, false_expr)) = exprs {
+ let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) {
+ (false, false) => Some(format!(
+ "if {} {} else {}",
+ snippet(cx, ex.span, "b"),
+ expr_block(cx, true_expr, None, "..", Some(expr.span)),
+ expr_block(cx, false_expr, None, "..", Some(expr.span))
+ )),
+ (false, true) => Some(format!(
+ "if {} {}",
+ snippet(cx, ex.span, "b"),
+ expr_block(cx, true_expr, None, "..", Some(expr.span))
+ )),
+ (true, false) => {
+ let test = Sugg::hir(cx, ex, "..");
+ Some(format!(
+ "if {} {}",
+ !test,
+ expr_block(cx, false_expr, None, "..", Some(expr.span))
+ ))
+ },
+ (true, true) => None,
+ };
+
+ if let Some(sugg) = sugg {
+ diag.span_suggestion(
+ expr.span,
+ "consider using an `if`/`else` expression",
+ sugg,
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+ }
+ },
+ );
+ }
+}
+
+fn check_overlapping_arms<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
+ if arms.len() >= 2 && cx.tables().expr_ty(ex).is_integral() {
+ let ranges = all_ranges(cx, arms, cx.tables().expr_ty(ex));
+ let type_ranges = type_ranges(&ranges);
+ if !type_ranges.is_empty() {
+ if let Some((start, end)) = overlapping(&type_ranges) {
+ span_lint_and_note(
+ cx,
+ MATCH_OVERLAPPING_ARM,
+ start.span,
+ "some ranges overlap",
+ Some(end.span),
+ "overlaps with this",
+ );
+ }
+ }
+ }
+}
+
+fn check_wild_err_arm(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
+ let ex_ty = walk_ptrs_ty(cx.tables().expr_ty(ex));
+ if is_type_diagnostic_item(cx, ex_ty, sym!(result_type)) {
+ for arm in arms {
+ if let PatKind::TupleStruct(ref path, ref inner, _) = arm.pat.kind {
+ let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false));
+ if path_str == "Err" {
+ let mut matching_wild = inner.iter().any(is_wild);
+ let mut ident_bind_name = String::from("_");
+ if !matching_wild {
+ // Looking for unused bindings (i.e.: `_e`)
+ inner.iter().for_each(|pat| {
+ if let PatKind::Binding(.., ident, None) = &pat.kind {
+ if ident.as_str().starts_with('_') && is_unused(ident, arm.body) {
+ ident_bind_name = (&ident.name.as_str()).to_string();
+ matching_wild = true;
+ }
+ }
+ });
+ }
+ if_chain! {
+ if matching_wild;
+ if let ExprKind::Block(ref block, _) = arm.body.kind;
+ if is_panic_block(block);
+ then {
+ // `Err(_)` or `Err(_e)` arm with `panic!` found
+ span_lint_and_note(cx,
+ MATCH_WILD_ERR_ARM,
+ arm.pat.span,
+ &format!("`Err({})` matches all errors", &ident_bind_name),
+ None,
+ "match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable",
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
+ let ty = cx.tables().expr_ty(ex);
+ if !ty.is_enum() {
+ // If there isn't a nice closed set of possible values that can be conveniently enumerated,
+ // don't complain about not enumerating the mall.
+ return;
+ }
+
+ // First pass - check for violation, but don't do much book-keeping because this is hopefully
+ // the uncommon case, and the book-keeping is slightly expensive.
+ let mut wildcard_span = None;
+ let mut wildcard_ident = None;
+ for arm in arms {
+ if let PatKind::Wild = arm.pat.kind {
+ wildcard_span = Some(arm.pat.span);
+ } else if let PatKind::Binding(_, _, ident, None) = arm.pat.kind {
+ wildcard_span = Some(arm.pat.span);
+ wildcard_ident = Some(ident);
+ }
+ }
+
+ if let Some(wildcard_span) = wildcard_span {
+ // Accumulate the variants which should be put in place of the wildcard because they're not
+ // already covered.
+
+ let mut missing_variants = vec![];
+ if let ty::Adt(def, _) = ty.kind {
+ for variant in &def.variants {
+ missing_variants.push(variant);
+ }
+ }
+
+ for arm in arms {
+ if arm.guard.is_some() {
+ // Guards mean that this case probably isn't exhaustively covered. Technically
+ // this is incorrect, as we should really check whether each variant is exhaustively
+ // covered by the set of guards that cover it, but that's really hard to do.
+ continue;
+ }
+ if let PatKind::Path(ref path) = arm.pat.kind {
+ if let QPath::Resolved(_, p) = path {
+ missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
+ }
+ } else if let PatKind::TupleStruct(ref path, ref patterns, ..) = arm.pat.kind {
+ if let QPath::Resolved(_, p) = path {
+ // 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.
- match arm.pat.kind {
- PatKind::Path(ref path) if match_qpath(path, &paths::OPTION_NONE) => true,
- _ => false,
- }
++ 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<String> = 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<BindingAnnotation> = if is_none_arm(&arms[0]) {
+ is_ref_some_arm(&arms[1])
+ } else if is_none_arm(&arms[1]) {
+ is_ref_some_arm(&arms[0])
+ } else {
+ None
+ };
+ if let Some(rb) = arm_ref {
+ let suggestion = if rb == BindingAnnotation::Ref {
+ "as_ref"
+ } else {
+ "as_mut"
+ };
+
+ let output_ty = cx.tables().expr_ty(expr);
+ let input_ty = cx.tables().expr_ty(ex);
+
+ let cast = if_chain! {
+ if let ty::Adt(_, substs) = input_ty.kind;
+ let input_ty = substs.type_at(0);
+ if let ty::Adt(_, substs) = output_ty.kind;
+ let output_ty = substs.type_at(0);
+ if let ty::Ref(_, output_ty, _) = output_ty.kind;
+ if input_ty != output_ty;
+ then {
+ ".map(|x| x as _)"
+ } else {
+ ""
+ }
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MATCH_AS_REF,
+ expr.span,
+ &format!("use `{}()` instead", suggestion),
+ "try this",
+ format!(
+ "{}.{}(){}",
+ snippet_with_applicability(cx, ex.span, "_", &mut applicability),
+ suggestion,
+ cast,
+ ),
+ applicability,
+ )
+ }
+ }
+}
+
+fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) {
+ for arm in arms {
+ if let PatKind::Or(ref fields) = arm.pat.kind {
+ // look for multiple fields in this arm that contains at least one Wild pattern
+ if fields.len() > 1 && fields.iter().any(is_wild) {
+ span_lint_and_help(
+ cx,
+ WILDCARD_IN_OR_PATTERNS,
+ arm.pat.span,
+ "wildcard pattern covers any other pattern as it will match anyway.",
+ None,
+ "Consider handling `_` separately.",
+ );
+ }
+ }
+ }
+}
+
++/// 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<'_>) {
++ 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),
++ _ => return,
++ }
++ }
++}
++
++/// 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) {
++ if_chain! {
++ if arms.len() == 2;
++ if cx.tables().expr_ty(expr).is_bool();
++ if is_wild(&arms[1].pat);
++ if let Some(first) = find_bool_lit(&arms[0].body.kind, desugared);
++ if let Some(second) = find_bool_lit(&arms[1].body.kind, desugared);
++ if first != second;
++ then {
++ let mut applicability = Applicability::MachineApplicable;
++
++ let pat_and_guard = if let Some(Guard::If(g)) = arms[0].guard {
++ format!("{} if {}", snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability), snippet_with_applicability(cx, g.span, "..", &mut applicability))
++ } else {
++ format!("{}", snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability))
++ };
++ 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 first { "" } else { "!" },
++ snippet_with_applicability(cx, ex.span, "..", &mut applicability),
++ pat_and_guard,
++ ),
++ applicability,
++ )
++ }
++ }
++}
++
++/// Extract a `bool` or `{ bool }`
++fn find_bool_lit(ex: &ExprKind<'_>, desugared: bool) -> Option<bool> {
++ 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;
+ }
+ let matched_vars = ex.span;
+ let bind_names = arms[0].pat.span;
+ let match_body = remove_blocks(&arms[0].body);
+ let mut snippet_body = if match_body.span.from_expansion() {
+ Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
+ } else {
+ snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
+ };
+
+ // Do we need to add ';' to suggestion ?
+ match match_body.kind {
+ ExprKind::Block(block, _) => {
+ // macro + expr_ty(body) == ()
+ if block.span.from_expansion() && cx.tables().expr_ty(&match_body).is_unit() {
+ snippet_body.push(';');
+ }
+ },
+ _ => {
+ // expr_ty(body) == ()
+ if cx.tables().expr_ty(&match_body).is_unit() {
+ snippet_body.push(';');
+ }
+ },
+ }
+
+ let mut applicability = Applicability::MaybeIncorrect;
+ match arms[0].pat.kind {
+ PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
+ // If this match is in a local (`let`) stmt
+ let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) {
+ (
+ parent_let_node.span,
+ format!(
+ "let {} = {};\n{}let {} = {};",
+ snippet_with_applicability(cx, bind_names, "..", &mut applicability),
+ snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
+ " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
+ snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability),
+ snippet_body
+ ),
+ )
+ } else {
+ // If we are in closure, we need curly braces around suggestion
+ let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
+ let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string());
+ if let Some(parent_expr) = get_parent_expr(cx, expr) {
+ if let ExprKind::Closure(..) = parent_expr.kind {
+ cbrace_end = format!("\n{}}}", indent);
+ // Fix body indent due to the closure
+ indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
+ cbrace_start = format!("{{\n{}", indent);
+ }
+ };
+ (
+ expr.span,
+ format!(
+ "{}let {} = {};\n{}{}{}",
+ cbrace_start,
+ snippet_with_applicability(cx, bind_names, "..", &mut applicability),
+ snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
+ indent,
+ snippet_body,
+ cbrace_end
+ ),
+ )
+ };
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ target_span,
+ "this match could be written as a `let` statement",
+ "consider using `let` statement",
+ sugg,
+ applicability,
+ );
+ },
+ PatKind::Wild => {
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ expr.span,
+ "this match could be replaced by its body itself",
+ "consider using the match body instead",
+ snippet_body,
+ Applicability::MachineApplicable,
+ );
+ },
+ _ => (),
+ }
+}
+
+/// Returns true if the `ex` match expression is in a local (`let`) statement
+fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> {
+ if_chain! {
+ let map = &cx.tcx.hir();
+ if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id));
+ if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id));
+ then {
+ return Some(parent_let_expr);
+ }
+ }
+ None
+}
+
+/// Gets all arms that are unbounded `PatRange`s.
+fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec<SpannedRange<Constant>> {
+ arms.iter()
+ .flat_map(|arm| {
+ if let Arm {
+ ref pat, guard: None, ..
+ } = *arm
+ {
+ if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
+ let lhs = match lhs {
+ Some(lhs) => constant(cx, cx.tables(), lhs)?.0,
+ None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?,
+ };
+ let rhs = match rhs {
+ Some(rhs) => constant(cx, cx.tables(), rhs)?.0,
+ None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?,
+ };
+ let rhs = match range_end {
+ RangeEnd::Included => Bound::Included(rhs),
+ RangeEnd::Excluded => Bound::Excluded(rhs),
+ };
+ return Some(SpannedRange {
+ span: pat.span,
+ node: (lhs, rhs),
+ });
+ }
+
+ if let PatKind::Lit(ref value) = pat.kind {
+ let value = constant(cx, cx.tables(), value)?.0;
+ return Some(SpannedRange {
+ span: pat.span,
+ node: (value.clone(), Bound::Included(value)),
+ });
+ }
+ }
+ None
+ })
+ .collect()
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub struct SpannedRange<T> {
+ pub span: Span,
+ pub node: (T, Bound<T>),
+}
+
+type TypedRanges = Vec<SpannedRange<u128>>;
+
+/// 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<Constant>]) -> 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<BindingAnnotation> {
+ if_chain! {
+ if let PatKind::TupleStruct(ref path, ref pats, _) = arm.pat.kind;
+ if pats.len() == 1 && match_qpath(path, &paths::OPTION_SOME);
+ if let PatKind::Binding(rb, .., ident, _) = pats[0].kind;
+ if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
+ if let ExprKind::Call(ref e, ref args) = remove_blocks(&arm.body).kind;
+ if let ExprKind::Path(ref some_path) = e.kind;
+ if match_qpath(some_path, &paths::OPTION_SOME) && args.len() == 1;
+ if let ExprKind::Path(ref qpath) = args[0].kind;
+ if let &QPath::Resolved(_, ref path2) = qpath;
+ if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
+ then {
+ return Some(rb)
+ }
+ }
+ None
+}
+
+fn has_only_ref_pats(arms: &[Arm<'_>]) -> bool {
+ let mapped = arms
+ .iter()
+ .map(|a| {
+ match a.pat.kind {
+ PatKind::Ref(..) => Some(true), // &-patterns
+ PatKind::Wild => Some(false), // an "anything" wildcard is also fine
+ _ => None, // any other pattern is not fine
+ }
+ })
+ .collect::<Option<Vec<bool>>>();
+ // 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<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
+where
+ T: Copy + Ord,
+{
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ enum Kind<'a, T> {
+ Start(T, &'a SpannedRange<T>),
+ End(Bound<T>, &'a SpannedRange<T>),
+ }
+
+ impl<'a, T: Copy> Kind<'a, T> {
+ fn range(&self) -> &'a SpannedRange<T> {
+ match *self {
+ Kind::Start(_, r) | Kind::End(_, r) => r,
+ }
+ }
+
+ fn value(self) -> Bound<T> {
+ 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<Ordering> {
+ Some(self.cmp(other))
+ }
+ }
+
+ impl<'a, T: Copy + Ord> Ord for Kind<'a, T> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ match (self.value(), other.value()) {
+ (Bound::Included(a), Bound::Included(b)) | (Bound::Excluded(a), Bound::Excluded(b)) => a.cmp(&b),
+ // Range patterns cannot be unbounded (yet)
+ (Bound::Unbounded, _) | (_, Bound::Unbounded) => unimplemented!(),
+ (Bound::Included(a), Bound::Excluded(b)) => match a.cmp(&b) {
+ Ordering::Equal => Ordering::Greater,
+ other => other,
+ },
+ (Bound::Excluded(a), Bound::Included(b)) => match a.cmp(&b) {
+ Ordering::Equal => Ordering::Less,
+ other => other,
+ },
+ }
+ }
+ }
+
+ let mut values = Vec::with_capacity(2 * ranges.len());
+
+ for r in ranges {
+ values.push(Kind::Start(r.node.0, r));
+ values.push(Kind::End(r.node.1, r));
+ }
+
+ values.sort();
+
+ for (a, b) in values.iter().zip(values.iter().skip(1)) {
+ match (a, b) {
+ (&Kind::Start(_, ra), &Kind::End(_, rb)) => {
+ if ra.node != rb.node {
+ return Some((ra, rb));
+ }
+ },
+ (&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (),
+ _ => return Some((a.range(), b.range())),
+ }
+ }
+
+ None
+}
+
++mod redundant_pattern_match {
++ use super::REDUNDANT_PATTERN_MATCHING;
++ use crate::utils::{in_constant, 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, HirId, MatchSource, PatKind, QPath};
++ use rustc_lint::LateContext;
++ use rustc_middle::ty;
++ use rustc_mir::const_eval::is_const_fn;
++ use rustc_span::source_map::Symbol;
++
++ 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,
++ ) {
++ fn find_suggestion(cx: &LateContext<'_>, hir_id: HirId, path: &QPath<'_>) -> Option<&'static str> {
++ if match_qpath(path, &paths::RESULT_OK) && can_suggest(cx, hir_id, sym!(result_type), "is_ok") {
++ return Some("is_ok()");
++ }
++ if match_qpath(path, &paths::RESULT_ERR) && can_suggest(cx, hir_id, sym!(result_type), "is_err") {
++ return Some("is_err()");
++ }
++ if match_qpath(path, &paths::OPTION_SOME) && can_suggest(cx, hir_id, sym!(option_type), "is_some") {
++ return Some("is_some()");
++ }
++ if match_qpath(path, &paths::OPTION_NONE) && can_suggest(cx, hir_id, sym!(option_type), "is_none") {
++ return Some("is_none()");
++ }
++ None
++ }
++
++ let hir_id = expr.hir_id;
++ 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 {
++ find_suggestion(cx, hir_id, path)
++ } else {
++ None
++ }
++ },
++ PatKind::Path(ref path) => find_suggestion(cx, hir_id, path),
++ _ => None,
++ };
++ let good_method = match good_method {
++ Some(method) => method,
++ None => return,
++ };
++
++ // check that `while_let_on_iterator` lint does not trigger
++ if_chain! {
++ if keyword == "while";
++ if let ExprKind::MethodCall(method_path, _, _, _) = op.kind;
++ if method_path.ident.name == sym!(next);
++ if match_trait_method(cx, op, &paths::ITERATOR);
++ then {
++ return;
++ }
++ }
++
++ span_lint_and_then(
++ cx,
++ REDUNDANT_PATTERN_MATCHING,
++ arms[0].pat.span,
++ &format!("redundant pattern matching, consider using `{}`", good_method),
++ |diag| {
++ // while let ... = ... { ... }
++ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ let expr_span = expr.span;
++
++ // while let ... = ... { ... }
++ // ^^^
++ let op_span = op.span.source_callsite();
++
++ // while let ... = ... { ... }
++ // ^^^^^^^^^^^^^^^^^^^
++ let span = expr_span.until(op_span.shrink_to_hi());
++ diag.span_suggestion(
++ span,
++ "try this",
++ format!("{} {}.{}", keyword, snippet(cx, op_span, "_"), good_method),
++ Applicability::MachineApplicable, // snippet
++ );
++ },
++ );
++ }
++
++ fn find_sugg_for_match<'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 hir_id = expr.hir_id;
++ 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()",
++ || can_suggest(cx, hir_id, sym!(result_type), "is_ok"),
++ || can_suggest(cx, hir_id, sym!(result_type), "is_err"),
++ )
++ } else {
++ None
++ }
++ },
++ (PatKind::TupleStruct(ref path_left, ref patterns, _), PatKind::Path(ref path_right))
++ | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, ref patterns, _))
++ if patterns.len() == 1 =>
++ {
++ if let PatKind::Wild = patterns[0].kind {
++ find_good_method_for_match(
++ arms,
++ path_left,
++ path_right,
++ &paths::OPTION_SOME,
++ &paths::OPTION_NONE,
++ "is_some()",
++ "is_none()",
++ || can_suggest(cx, hir_id, sym!(option_type), "is_some"),
++ || can_suggest(cx, hir_id, sym!(option_type), "is_none"),
++ )
++ } else {
++ None
++ }
++ },
++ _ => None,
++ };
++
++ if let Some(good_method) = found_good_method {
++ span_lint_and_then(
++ cx,
++ REDUNDANT_PATTERN_MATCHING,
++ expr.span,
++ &format!("redundant pattern matching, consider using `{}`", good_method),
++ |diag| {
++ let span = expr.span.to(op.span);
++ diag.span_suggestion(
++ span,
++ "try this",
++ format!("{}.{}", snippet(cx, op.span, "_"), good_method),
++ Applicability::MaybeIncorrect, // snippet
++ );
++ },
++ );
++ }
++ }
++ }
++
++ #[allow(clippy::too_many_arguments)]
++ 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,
++ can_suggest_left: impl Fn() -> bool,
++ can_suggest_right: impl Fn() -> bool,
++ ) -> 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)) if can_suggest_left() => Some(should_be_left),
++ (LitKind::Bool(false), LitKind::Bool(true)) if can_suggest_right() => Some(should_be_right),
++ _ => None,
++ },
++ _ => None,
++ }
++ }
++
++ fn can_suggest(cx: &LateContext<'_>, hir_id: HirId, diag_item: Symbol, name: &str) -> bool {
++ if !in_constant(cx, hir_id) {
++ return true;
++ }
++
++ // Avoid suggesting calls to non-`const fn`s in const contexts, see #5697.
++ cx.tcx
++ .get_diagnostic_item(diag_item)
++ .and_then(|def_id| {
++ cx.tcx.inherent_impls(def_id).iter().find_map(|imp| {
++ cx.tcx
++ .associated_items(*imp)
++ .in_definition_order()
++ .find_map(|item| match item.kind {
++ ty::AssocKind::Fn if item.ident.name.as_str() == name => Some(item.def_id),
++ _ => None,
++ })
++ })
++ })
++ .map_or(false, |def_id| is_const_fn(cx.tcx, def_id))
++ }
++}
++
+#[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::<u8>(&[]));
+ 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))
+ ],)
+ );
+}
--- /dev/null
- hir::ExprKind::Path(ref p) => match cx.qpath_res(p, arg.hir_id) {
- hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _) => true,
- _ => false,
- },
+mod bind_instead_of_map;
+mod inefficient_to_string;
+mod manual_saturating_arithmetic;
+mod option_map_unwrap_or;
+mod unnecessary_filter_map;
+
+use std::borrow::Cow;
+use std::fmt;
+use std::iter;
+
+use 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::intravisit::{self, Visitor};
+use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::{self, Ty, TyS};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::{sym, SymbolStr};
+
+use crate::consts::{constant, Constant};
+use crate::utils::usage::mutated_variables;
+use crate::utils::{
+ get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro, is_copy,
+ is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment,
+ match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls, method_chain_args, paths,
+ remove_blocks, return_ty, single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite,
+ span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, sugg, walk_ptrs_ty,
+ walk_ptrs_ty_depth, SpanlessEq,
+};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `.unwrap()` calls on `Option`s 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<usize, ()> = 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<usize, ()> = 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<usize, ()> = 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<u32, &str> = Ok(1);
+ /// assert_eq!(Some(1), r.map_or(None, Some));
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// # let r: Result<u32, &str> = 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(_)`,
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as a
+ /// single method call using `_.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 a
+ /// single method call.
+ ///
+ /// **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(_).next()`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as a
+ /// single method call.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next();
+ /// ```
+ /// Can be written as
+ ///
+ /// ```rust
+ /// (0..3).find_map(|x| if x == 2 { Some(x) } else { None });
+ /// ```
+ pub FILTER_MAP_NEXT,
+ pedantic,
+ "using combination of `filter_map` and `next` which can usually be written as a single method call"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `flat_map(|x| x)`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely by using `flatten`.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let iter = vec![vec![0]].into_iter();
+ /// iter.flat_map(|x| x);
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// # let iter = vec![vec![0]].into_iter();
+ /// iter.flatten();
+ /// ```
+ pub FLAT_MAP_IDENTITY,
+ complexity,
+ "call to `flat_map` where `flatten` is sufficient"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.find(_).map(_)`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as a
+ /// single method call.
+ ///
+ /// **Known problems:** Often requires a condition + Option/Iterator creation
+ /// inside the closure.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// (0..3).find(|x| *x == 2).map(|x| x * 2);
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// (0..3).find_map(|x| if x == 2 { Some(x * 2) } else { None });
+ /// ```
+ pub FIND_MAP,
+ pedantic,
+ "using a combination of `find` and `map` can usually be written as a single method call"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for an iterator search (such as `find()`,
+ /// `position()`, or `rposition()`) followed by a call to `is_some()`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.any(_)`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().find(|x| **x == 0).is_some();
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().any(|x| *x == 0);
+ /// ```
+ pub SEARCH_IS_SOME,
+ complexity,
+ "using an iterator search followed by `is_some()`, which is more succinctly expressed as a call to `any()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `.chars().next()` on a `str` to check
+ /// if it starts with a given char.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.starts_with(_)`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let name = "foo";
+ /// if name.chars().next() == Some('_') {};
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// let name = "foo";
+ /// if name.starts_with('_') {};
+ /// ```
+ pub CHARS_NEXT_CMP,
+ style,
+ "using `.chars().next()` to check if a string starts with a char"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`,
+ /// etc., and suggests to use `or_else`, `unwrap_or_else`, etc., or
+ /// `unwrap_or_default` instead.
+ ///
+ /// **Why is this bad?** The function will always be called and potentially
+ /// allocate an object acting as the default.
+ ///
+ /// **Known problems:** If the function has side-effects, not calling it will
+ /// change the semantic of the program, but you shouldn't rely on that anyway.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or(String::new());
+ /// ```
+ /// this can instead be written:
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or_else(String::new);
+ /// ```
+ /// or
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or_default();
+ /// ```
+ pub OR_FUN_CALL,
+ perf,
+ "using any `*or` method with a function call, which suggests `*or_else`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`,
+ /// etc., and suggests to use `unwrap_or_else` instead
+ ///
+ /// **Why is this bad?** The function will always be called.
+ ///
+ /// **Known problems:** If the function has side-effects, not calling it will
+ /// change the semantics of the program, but you shouldn't rely on that anyway.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.expect(&format!("Err {}: {}", err_code, err_msg));
+ /// ```
+ /// or
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str());
+ /// ```
+ /// this can instead be written:
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg));
+ /// ```
+ pub EXPECT_FUN_CALL,
+ perf,
+ "using any `expect` method with a function call"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `.clone()` on a `Copy` type.
+ ///
+ /// **Why is this bad?** The only reason `Copy` types implement `Clone` is for
+ /// generics, not for using the `clone` method on a concrete type.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// 42u64.clone();
+ /// ```
+ pub CLONE_ON_COPY,
+ complexity,
+ "using `clone` on a `Copy` type"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `.clone()` on a ref-counted pointer,
+ /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified
+ /// function syntax instead (e.g., `Rc::clone(foo)`).
+ ///
+ /// **Why is this bad?** Calling '.clone()' on an Rc, Arc, or Weak
+ /// can obscure the fact that only the pointer is being cloned, not the underlying
+ /// data.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// let x = Rc::new(1);
+ ///
+ /// // 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:**
+ /// ```rust
+ /// # struct Foo;
+ /// # struct NotAFoo;
+ /// impl Foo {
+ /// fn new() -> NotAFoo {
+ /// # NotAFoo
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// # struct FooError;
+ /// impl Foo {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Result<Foo, FooError> {
+ /// # Ok(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// struct Bar(Foo);
+ /// impl Foo {
+ /// // Bad. The type name must contain `Self`.
+ /// fn new() -> Bar {
+ /// # Bar(Foo)
+ /// }
+ /// }
+ /// ```
+ pub NEW_RET_NO_SELF,
+ style,
+ "not returning type containing `Self` in a `new` method"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for string methods that receive a single-character
+ /// `str` as an argument, e.g., `_.split("x")`.
+ ///
+ /// **Why is this bad?** Performing these methods using a `char` is faster than
+ /// using a `str`.
+ ///
+ /// **Known problems:** Does not catch multi-byte unicode characters.
+ ///
+ /// **Example:**
+ /// ```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 getting the inner pointer of a temporary
+ /// `CString`.
+ ///
+ /// **Why is this bad?** The inner pointer of a `CString` is only valid as long
+ /// as the `CString` is alive.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # use std::ffi::CString;
+ /// # fn call_some_ffi_func(_: *const i8) {}
+ /// #
+ /// let c_str = CString::new("foo").unwrap().as_ptr();
+ /// unsafe {
+ /// call_some_ffi_func(c_str);
+ /// }
+ /// ```
+ /// Here `c_str` point to a freed address. The correct use would be:
+ /// ```rust
+ /// # use std::ffi::CString;
+ /// # fn call_some_ffi_func(_: *const i8) {}
+ /// #
+ /// let c_str = CString::new("foo").unwrap();
+ /// unsafe {
+ /// call_some_ffi_func(c_str.as_ptr());
+ /// }
+ /// ```
+ pub TEMPORARY_CSTRING_AS_PTR,
+ correctness,
+ "getting the inner pointer of a temporary `CString`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calling `.step_by(0)` on iterators which panics.
+ ///
+ /// **Why is this bad?** This very much looks like an oversight. Use `panic!()` instead if you
+ /// actually intend to panic.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,should_panic
+ /// for x in (0..100).step_by(0) {
+ /// //..
+ /// }
+ /// ```
+ pub ITERATOR_STEP_BY_ZERO,
+ correctness,
+ "using `Iterator::step_by(0)`, which will panic at runtime"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the use of `iter.nth(0)`.
+ ///
+ /// **Why is this bad?** `iter.next()` is equivalent to
+ /// `iter.nth(0)`, as they both consume the next element,
+ /// but is more readable.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// // Bad
+ /// # let mut s = HashSet::new();
+ /// # s.insert(1);
+ /// let x = s.iter().nth(0);
+ ///
+ /// // Good
+ /// # let mut s = HashSet::new();
+ /// # s.insert(1);
+ /// let x = s.iter().next();
+ /// ```
+ pub ITER_NTH_ZERO,
+ style,
+ "replace `iter.nth(0)` with `iter.next()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for use of `.iter().nth()` (and the related
+ /// `.iter_mut().nth()`) on standard library types with O(1) element access.
+ ///
+ /// **Why is this bad?** `.get()` and `.get_mut()` are more efficient and more
+ /// readable.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().nth(3);
+ /// let bad_slice = &some_vec[..].iter().nth(3);
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.get(3);
+ /// let bad_slice = &some_vec[..].get(3);
+ /// ```
+ pub ITER_NTH,
+ perf,
+ "using `.iter().nth()` on a standard library type with O(1) element access"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for use of `.skip(x).next()` on iterators.
+ ///
+ /// **Why is this bad?** `.nth(x)` is cleaner
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().skip(3).next();
+ /// let bad_slice = &some_vec[..].iter().skip(3).next();
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().nth(3);
+ /// let bad_slice = &some_vec[..].iter().nth(3);
+ /// ```
+ pub ITER_SKIP_NEXT,
+ style,
+ "using `.skip(x).next()` on an iterator"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for use of `.get().unwrap()` (or
+ /// `.get_mut().unwrap`) on a standard library type which implements `Index`
+ ///
+ /// **Why is this bad?** Using the Index trait (`[]`) is more clear and more
+ /// concise.
+ ///
+ /// **Known problems:** Not a replacement for error handling: Using either
+ /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic`
+ /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a
+ /// temporary placeholder for dealing with the `Option` type, then this does
+ /// not mitigate the need for error handling. If there is a chance that `.get()`
+ /// will be `None` in your program, then it is advisable that the `None` case
+ /// is handled in a future refactor instead of using `.unwrap()` or the Index
+ /// trait.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let mut some_vec = vec![0, 1, 2, 3];
+ /// let last = some_vec.get(3).unwrap();
+ /// *some_vec.get_mut(0).unwrap() = 1;
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let mut some_vec = vec![0, 1, 2, 3];
+ /// let last = some_vec[3];
+ /// some_vec[0] = 1;
+ /// ```
+ pub GET_UNWRAP,
+ restriction,
+ "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the use of `.extend(s.chars())` where s is a
+ /// `&str` or `String`.
+ ///
+ /// **Why is this bad?** `.push_str(s)` is clearer
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let abc = "abc";
+ /// let def = String::from("def");
+ /// let mut s = String::new();
+ /// s.extend(abc.chars());
+ /// s.extend(def.chars());
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let abc = "abc";
+ /// let def = String::from("def");
+ /// let mut s = String::new();
+ /// s.push_str(abc);
+ /// s.push_str(&def);
+ /// ```
+ pub STRING_EXTEND_CHARS,
+ style,
+ "using `x.extend(s.chars())` where s is a `&str` or `String`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the use of `.cloned().collect()` on slice to
+ /// create a `Vec`.
+ ///
+ /// **Why is this bad?** `.to_vec()` is clearer
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let s = [1, 2, 3, 4, 5];
+ /// let s2: Vec<isize> = s[..].iter().cloned().collect();
+ /// ```
+ /// The better use would be:
+ /// ```rust
+ /// let s = [1, 2, 3, 4, 5];
+ /// let s2: Vec<isize> = 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:** False positive in pattern guards. Will be resolved once
+ /// non-lexical lifetimes are stable.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let _ = (0..3).fold(false, |acc, x| acc || x > 2);
+ /// ```
+ /// This could be written as:
+ /// ```rust
+ /// let _ = (0..3).any(|x| x > 2);
+ /// ```
+ pub UNNECESSARY_FOLD,
+ style,
+ "using `fold` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `filter_map` calls which could be replaced by `filter` or `map`.
+ /// More specifically it checks if the closure provided is only performing one of the
+ /// filter or map operations and suggests the appropriate option.
+ ///
+ /// **Why is this bad?** Complexity. The intent is also clearer if only a single
+ /// operation is being performed.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None });
+ ///
+ /// // As there is no transformation of the argument this could be written as:
+ /// 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<bool>; 5] = unsafe {
+ /// MaybeUninit::uninit().assume_init()
+ /// };
+ /// ```
+ pub UNINIT_ASSUMED_INIT,
+ correctness,
+ "`MaybeUninit::uninit().assume_init()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`.
+ ///
+ /// **Why is this bad?** These can be written simply with `saturating_add/sub` methods.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # let y: u32 = 0;
+ /// # let x: u32 = 100;
+ /// let add = x.checked_add(y).unwrap_or(u32::MAX);
+ /// let sub = x.checked_sub(y).unwrap_or(u32::MIN);
+ /// ```
+ ///
+ /// can be written using dedicated methods for saturating addition/subtraction as:
+ ///
+ /// ```rust
+ /// # let y: u32 = 0;
+ /// # let x: u32 = 100;
+ /// let add = x.saturating_add(y);
+ /// let sub = x.saturating_sub(y);
+ /// ```
+ pub MANUAL_SATURATING_ARITHMETIC,
+ style,
+ "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to
+ /// zero-sized types
+ ///
+ /// **Why is this bad?** This is a no-op, and likely unintended
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// unsafe { (&() as *const ()).offset(1) };
+ /// ```
+ pub ZST_OFFSET,
+ correctness,
+ "Check for offset calculations on raw pointers to zero-sized types"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `FileType::is_file()`.
+ ///
+ /// **Why is this bad?** When people testing a file type with `FileType::is_file`
+ /// they are testing whether a path is something they can get bytes from. But
+ /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover
+ /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # || {
+ /// let metadata = std::fs::metadata("foo.txt")?;
+ /// let filetype = metadata.file_type();
+ ///
+ /// if filetype.is_file() {
+ /// // read file
+ /// }
+ /// # Ok::<_, std::io::Error>(())
+ /// # };
+ /// ```
+ ///
+ /// should be written as:
+ ///
+ /// ```rust
+ /// # || {
+ /// let metadata = std::fs::metadata("foo.txt")?;
+ /// let filetype = metadata.file_type();
+ ///
+ /// if !filetype.is_dir() {
+ /// // read file
+ /// }
+ /// # Ok::<_, std::io::Error>(())
+ /// # };
+ /// ```
+ pub FILETYPE_IS_FILE,
+ restriction,
+ "`FileType::is_file` is not recommended to test for readable file type"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str).
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as a
+ /// single method call.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let opt = Some("".to_string());
+ /// opt.as_ref().map(String::as_str)
+ /// # ;
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// # let opt = Some("".to_string());
+ /// opt.as_deref()
+ /// # ;
+ /// ```
+ pub OPTION_AS_REF_DEREF,
+ complexity,
+ "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
+}
+
+declare_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_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,
+ SEARCH_IS_SOME,
+ TEMPORARY_CSTRING_AS_PTR,
+ FILTER_NEXT,
+ SKIP_WHILE_NEXT,
+ FILTER_MAP,
+ FILTER_MAP_NEXT,
+ FLAT_MAP_IDENTITY,
+ FIND_MAP,
+ MAP_FLATTEN,
+ ITERATOR_STEP_BY_ZERO,
+ ITER_NEXT_SLICE,
+ ITER_NTH,
+ ITER_NTH_ZERO,
+ ITER_SKIP_NEXT,
+ GET_UNWRAP,
+ STRING_EXTEND_CHARS,
+ ITER_CLONED_COLLECT,
+ USELESS_ASREF,
+ UNNECESSARY_FOLD,
+ UNNECESSARY_FILTER_MAP,
+ INTO_ITER_ON_REF,
+ SUSPICIOUS_MAP,
+ UNINIT_ASSUMED_INIT,
+ MANUAL_SATURATING_ARITHMETIC,
+ ZST_OFFSET,
+ FILETYPE_IS_FILE,
+ OPTION_AS_REF_DEREF,
+]);
+
+impl<'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<SymbolStr> = method_names.iter().map(|s| s.as_str()).collect();
+ let method_names: Vec<&str> = method_names.iter().map(|s| &**s).collect();
+
+ match method_names.as_slice() {
+ ["unwrap", "get"] => lint_get_unwrap(cx, expr, arg_lists[1], false),
+ ["unwrap", "get_mut"] => lint_get_unwrap(cx, expr, arg_lists[1], true),
+ ["unwrap", ..] => lint_unwrap(cx, expr, arg_lists[0]),
+ ["expect", "ok"] => lint_ok_expect(cx, expr, arg_lists[1]),
+ ["expect", ..] => lint_expect(cx, expr, arg_lists[0]),
+ ["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]),
+ ["unwrap_or_else", "map"] => lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]),
+ ["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]),
+ ["and_then", ..] => {
+ bind_instead_of_map::OptionAndThenSome::lint(cx, expr, arg_lists[0]);
+ bind_instead_of_map::ResultAndThenOk::lint(cx, expr, arg_lists[0]);
+ },
+ ["or_else", ..] => {
+ bind_instead_of_map::ResultOrElseErrInfo::lint(cx, expr, arg_lists[0]);
+ },
+ ["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
+ ["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]),
+ ["next", "iter"] => lint_iter_next(cx, expr, arg_lists[1]),
+ ["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]),
+ ["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]),
+ ["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1]),
+ ["map", "find"] => lint_find_map(cx, expr, arg_lists[1], arg_lists[0]),
+ ["flat_map", "filter"] => lint_filter_flat_map(cx, expr, arg_lists[1], arg_lists[0]),
+ ["flat_map", "filter_map"] => lint_filter_map_flat_map(cx, expr, arg_lists[1], arg_lists[0]),
+ ["flat_map", ..] => lint_flat_map_identity(cx, expr, arg_lists[0], method_spans[0]),
+ ["flatten", "map"] => lint_map_flatten(cx, expr, arg_lists[1]),
+ ["is_some", "find"] => lint_search_is_some(cx, expr, "find", arg_lists[1], arg_lists[0], method_spans[1]),
+ ["is_some", "position"] => {
+ lint_search_is_some(cx, expr, "position", arg_lists[1], arg_lists[0], method_spans[1])
+ },
+ ["is_some", "rposition"] => {
+ lint_search_is_some(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1])
+ },
+ ["extend", ..] => lint_extend(cx, expr, arg_lists[0]),
+ ["as_ptr", "unwrap" | "expect"] => lint_cstring_as_ptr(cx, expr, &arg_lists[1][0], &arg_lists[0][0]),
+ ["nth", "iter"] => lint_iter_nth(cx, expr, &arg_lists, false),
+ ["nth", "iter_mut"] => lint_iter_nth(cx, expr, &arg_lists, true),
+ ["nth", ..] => lint_iter_nth_zero(cx, expr, arg_lists[0]),
+ ["step_by", ..] => lint_step_by(cx, expr, arg_lists[0]),
+ ["next", "skip"] => lint_iter_skip_next(cx, expr),
+ ["collect", "cloned"] => lint_iter_cloned_collect(cx, expr, arg_lists[1]),
+ ["as_ref"] => lint_asref(cx, expr, "as_ref", arg_lists[0]),
+ ["as_mut"] => lint_asref(cx, expr, "as_mut", arg_lists[0]),
+ ["fold", ..] => lint_unnecessary_fold(cx, expr, arg_lists[0], method_spans[0]),
+ ["filter_map", ..] => unnecessary_filter_map::lint(cx, expr, arg_lists[0]),
+ ["count", "map"] => lint_suspicious_map(cx, expr),
+ ["assume_init"] => lint_maybe_uninit(cx, &arg_lists[0][0], expr),
+ ["unwrap_or", arith @ ("checked_add" | "checked_sub" | "checked_mul")] => {
+ manual_saturating_arithmetic::lint(cx, expr, &arg_lists, &arith["checked_".len()..])
+ },
+ ["add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub"] => {
+ check_pointer_offset(cx, expr, arg_lists[0])
+ },
+ ["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]),
+ ["map", "as_ref"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false),
+ ["map", "as_mut"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true),
+ _ => {},
+ }
+
+ match expr.kind {
+ hir::ExprKind::MethodCall(ref method_call, ref method_span, ref args, _) => {
+ lint_or_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args);
+ lint_expect_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args);
+
+ let self_ty = cx.tables().expr_ty_adjusted(&args[0]);
+ if args.len() == 1 && method_call.ident.name == sym!(clone) {
+ lint_clone_on_copy(cx, expr, &args[0], self_ty);
+ lint_clone_on_ref_ptr(cx, expr, &args[0]);
+ }
+ if args.len() == 1 && method_call.ident.name == sym!(to_string) {
+ inefficient_to_string::lint(cx, expr, &args[0], self_ty);
+ }
+
+ match self_ty.kind {
+ ty::Ref(_, ty, _) if ty.kind == ty::Str => {
+ for &(method, pos) in &PATTERN_METHODS {
+ if method_call.ident.name.as_str() == method && args.len() > pos {
+ lint_single_char_pattern(cx, expr, &args[pos]);
+ }
+ }
+ },
+ ty::Ref(..) if method_call.ident.name == sym!(into_iter) => {
+ lint_into_iter(cx, expr, self_ty, *method_span);
+ },
+ _ => (),
+ }
+ },
+ hir::ExprKind::Binary(op, ref lhs, ref rhs)
+ if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne =>
+ {
+ let mut info = BinaryExprInfo {
+ expr,
+ chain: lhs,
+ other: rhs,
+ eq: op.node == hir::BinOpKind::Eq,
+ };
+ lint_binary_expr_with_method_call(cx, &mut info);
+ }
+ _ => (),
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ if in_external_macro(cx.sess(), impl_item.span) {
+ return;
+ }
+ let name = impl_item.ident.name.as_str();
+ let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id);
+ let item = cx.tcx.hir().expect_item(parent);
+ let def_id = cx.tcx.hir().local_def_id(item.hir_id);
+ let self_ty = cx.tcx.type_of(def_id);
+ if_chain! {
+ if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind;
+ if let Some(first_arg) = iter_input_pats(&sig.decl, cx.tcx.hir().body(id)).next();
+ if let hir::ItemKind::Impl{ of_trait: None, .. } = item.kind;
+
+ let method_def_id = cx.tcx.hir().local_def_id(impl_item.hir_id);
+ let method_sig = cx.tcx.fn_sig(method_def_id);
+ let method_sig = cx.tcx.erase_late_bound_regions(&method_sig);
+
+ let first_arg_ty = &method_sig.inputs().iter().next();
+
+ // check conventions w.r.t. conversion method names and predicates
+ if let Some(first_arg_ty) = first_arg_ty;
+
+ then {
+ if cx.access_levels.is_exported(impl_item.hir_id) {
+ // check missing trait implementations
+ for &(method_name, n_args, fn_header, self_kind, out_type, trait_name) in &TRAIT_METHODS {
+ if name == method_name &&
+ sig.decl.inputs.len() == n_args &&
+ out_type.matches(cx, &sig.decl.output) &&
+ self_kind.matches(cx, self_ty, first_arg_ty) &&
+ fn_header_equals(*fn_header, sig.header) {
+ span_lint(cx, SHOULD_IMPLEMENT_TRAIT, impl_item.span, &format!(
+ "defining a method called `{}` on this type; consider implementing \
+ the `{}` trait or choosing a less ambiguous name", name, trait_name));
+ }
+ }
+ }
+
+ if let Some((ref conv, self_kinds)) = &CONVENTIONS
+ .iter()
+ .find(|(ref conv, _)| conv.check(&name))
+ {
+ if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) {
+ let lint = if item.vis.node.is_pub() {
+ WRONG_PUB_SELF_CONVENTION
+ } else {
+ WRONG_SELF_CONVENTION
+ };
+
+ span_lint(
+ cx,
+ lint,
+ first_arg.pat.span,
+ &format!("methods called `{}` usually take {}; consider choosing a less ambiguous name",
+ conv,
+ &self_kinds
+ .iter()
+ .map(|k| k.description())
+ .collect::<Vec<_>>()
+ .join(" or ")
+ ),
+ );
+ }
+ }
+ }
+ }
+
+ if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
+ let ret_ty = return_ty(cx, impl_item.hir_id);
+
+ let contains_self_ty = |ty: Ty<'tcx>| {
+ ty.walk().any(|inner| match inner.unpack() {
+ GenericArgKind::Type(inner_ty) => TyS::same_type(self_ty, inner_ty),
+
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
+ })
+ };
+
+ // walk the return type and check for Self (this does not check associated types)
+ if contains_self_ty(ret_ty) {
+ return;
+ }
+
+ // if return type is impl trait, check the associated types
+ if let ty::Opaque(def_id, _) = ret_ty.kind {
+ // one of the associated types must be Self
+ for predicate in cx.tcx.predicates_of(def_id).predicates {
+ if let ty::PredicateKind::Projection(poly_projection_predicate) = predicate.0.kind() {
+ let binder = poly_projection_predicate.ty();
+ let associated_type = binder.skip_binder();
+
+ // walk the associated type and check for Self
+ if contains_self_ty(associated_type) {
+ return;
+ }
+ }
+ }
+ }
+
+ if name == "new" && !TyS::same_type(ret_ty, self_ty) {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ impl_item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+}
+
+/// Checks for the `OR_FUN_CALL` lint.
+#[allow(clippy::too_many_lines)]
+fn lint_or_fun_call<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ method_span: Span,
+ name: &str,
+ args: &'tcx [hir::Expr<'_>],
+) {
+ // Searches an expression for method calls or function calls that aren't ctors
+ struct FunCallFinder<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ found: bool,
+ }
+
+ impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ let call_found = match &expr.kind {
+ // ignore enum and struct constructors
+ hir::ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr),
+ hir::ExprKind::MethodCall(..) => true,
+ _ => false,
+ };
+
+ if call_found {
+ self.found |= true;
+ }
+
+ if !self.found {
+ intravisit::walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+ }
+
+ /// Checks for `unwrap_or(T::new())` or `unwrap_or(T::default())`.
+ fn check_unwrap_or_default(
+ cx: &LateContext<'_>,
+ name: &str,
+ fun: &hir::Expr<'_>,
+ self_expr: &hir::Expr<'_>,
+ arg: &hir::Expr<'_>,
+ or_has_args: bool,
+ span: Span,
+ ) -> bool {
+ if_chain! {
+ if !or_has_args;
+ if name == "unwrap_or";
+ if let hir::ExprKind::Path(ref qpath) = fun.kind;
+ let path = &*last_path_segment(qpath).ident.as_str();
+ if ["default", "new"].contains(&path);
+ let arg_ty = cx.tables().expr_ty(arg);
+ if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT);
+ if implements_trait(cx, arg_ty, default_trait_id, &[]);
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ OR_FUN_CALL,
+ span,
+ &format!("use of `{}` followed by a call to `{}`", name, path),
+ "try this",
+ format!(
+ "{}.unwrap_or_default()",
+ snippet_with_applicability(cx, self_expr.span, "_", &mut applicability)
+ ),
+ applicability,
+ );
+
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ /// Checks for `*or(foo())`.
+ #[allow(clippy::too_many_arguments)]
+ fn check_general_case<'tcx>(
+ cx: &LateContext<'tcx>,
+ name: &str,
+ method_span: Span,
+ fun_span: Span,
+ self_expr: &hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
+ or_has_args: bool,
+ span: Span,
+ ) {
+ if let hir::ExprKind::MethodCall(ref path, _, ref args, _) = &arg.kind {
+ if path.ident.as_str() == "len" {
+ let ty = walk_ptrs_ty(cx.tables().expr_ty(&args[0]));
+
+ match ty.kind {
+ ty::Slice(_) | ty::Array(_, _) => return,
+ _ => (),
+ }
+
+ if match_type(cx, ty, &paths::VEC) {
+ return;
+ }
+ }
+ }
+
+ // (path, fn_has_argument, methods, suffix)
+ let know_types: &[(&[_], _, &[_], _)] = &[
+ (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"),
+ (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"),
+ (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
+ (&paths::RESULT, true, &["or", "unwrap_or"], "else"),
+ ];
+
+ if_chain! {
+ if know_types.iter().any(|k| k.2.contains(&name));
+
+ let mut finder = FunCallFinder { cx: &cx, found: false };
+ if { finder.visit_expr(&arg); finder.found };
+ if !contains_return(&arg);
+
+ let self_ty = cx.tables().expr_ty(self_expr);
+
+ if let Some(&(_, fn_has_arguments, poss, suffix)) =
+ know_types.iter().find(|&&i| match_type(cx, self_ty, i.0));
+
+ if poss.contains(&name);
+
+ then {
+ let sugg: Cow<'_, _> = match (fn_has_arguments, !or_has_args) {
+ (true, _) => format!("|_| {}", snippet_with_macro_callsite(cx, arg.span, "..")).into(),
+ (false, false) => format!("|| {}", snippet_with_macro_callsite(cx, arg.span, "..")).into(),
+ (false, true) => snippet_with_macro_callsite(cx, fun_span, ".."),
+ };
+ let span_replace_word = method_span.with_hi(span.hi());
+ span_lint_and_sugg(
+ cx,
+ OR_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{}` followed by a function call", name),
+ "try this",
+ format!("{}_{}({})", name, suffix, sugg),
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+ }
+
+ if args.len() == 2 {
+ match args[1].kind {
+ hir::ExprKind::Call(ref fun, ref or_args) => {
+ let or_has_args = !or_args.is_empty();
+ if !check_unwrap_or_default(cx, name, fun, &args[0], &args[1], or_has_args, expr.span) {
+ check_general_case(
+ cx,
+ name,
+ method_span,
+ fun.span,
+ &args[0],
+ &args[1],
+ or_has_args,
+ expr.span,
+ );
+ }
+ },
+ hir::ExprKind::MethodCall(_, span, ref or_args, _) => check_general_case(
+ cx,
+ name,
+ method_span,
+ span,
+ &args[0],
+ &args[1],
+ !or_args.is_empty(),
+ expr.span,
+ ),
+ _ => {},
+ }
+ }
+}
+
+/// Checks for the `EXPECT_FUN_CALL` lint.
+#[allow(clippy::too_many_lines)]
+fn lint_expect_fun_call(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ method_span: Span,
+ name: &str,
+ args: &[hir::Expr<'_>],
+) {
+ // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
+ // `&str`
+ fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
+ let mut arg_root = arg;
+ loop {
+ arg_root = match &arg_root.kind {
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
+ hir::ExprKind::MethodCall(method_name, _, call_args, _) => {
+ if call_args.len() == 1
+ && (method_name.ident.name == sym!(as_str) || method_name.ident.name == sym!(as_ref))
+ && {
+ let arg_type = cx.tables().expr_ty(&call_args[0]);
+ let base_type = walk_ptrs_ty(arg_type);
+ base_type.kind == ty::Str || is_type_diagnostic_item(cx, base_type, sym!(string_type))
+ }
+ {
+ &call_args[0]
+ } else {
+ break;
+ }
+ },
+ _ => break,
+ };
+ }
+ arg_root
+ }
+
+ // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be
+ // converted to string.
+ fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
+ let arg_ty = cx.tables().expr_ty(arg);
+ if is_type_diagnostic_item(cx, arg_ty, sym!(string_type)) {
+ return false;
+ }
+ if let ty::Ref(_, ty, ..) = arg_ty.kind {
+ if ty.kind == ty::Str && can_be_static_str(cx, arg) {
+ return false;
+ }
+ };
+ true
+ }
+
+ // Check if an expression could have type `&'static str`, knowing that it
+ // has type `&str` for some lifetime.
+ fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
+ match arg.kind {
+ hir::ExprKind::Lit(_) => true,
+ hir::ExprKind::Call(fun, _) => {
+ if let hir::ExprKind::Path(ref p) = fun.kind {
+ match cx.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
+ .tables()
+ .type_dependent_def_id(arg.hir_id)
+ .map_or(false, |method_id| {
+ matches!(
+ cx.tcx.fn_sig(method_id).output().skip_binder().kind,
+ ty::Ref(ty::ReStatic, ..)
+ )
+ }),
- .filter(|adj| {
- if let ty::adjustment::Adjust::Deref(_) = adj.kind {
- true
- } else {
- false
- }
- })
++ 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<String> {
+ if_chain! {
+ if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind;
+ if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind;
+ if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind;
+
+ then {
+ format_arg_expr_tup
+ .iter()
+ .map(|a| snippet_with_applicability(cx, a.span, "..", applicability).into_owned())
+ .collect()
+ } else {
+ unreachable!()
+ }
+ }
+ }
+
+ fn is_call(node: &hir::ExprKind<'_>) -> bool {
+ match node {
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
+ is_call(&expr.kind)
+ },
+ hir::ExprKind::Call(..)
+ | hir::ExprKind::MethodCall(..)
+ // These variants are debatable or require further examination
+ | hir::ExprKind::Match(..)
+ | hir::ExprKind::Block{ .. } => true,
+ _ => false,
+ }
+ }
+
+ if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) {
+ return;
+ }
+
+ let receiver_type = cx.tables().expr_ty_adjusted(&args[0]);
+ let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym!(option_type)) {
+ "||"
+ } else if is_type_diagnostic_item(cx, receiver_type, sym!(result_type)) {
+ "|_|"
+ } else {
+ return;
+ };
+
+ let arg_root = get_arg_root(cx, &args[1]);
+
+ let span_replace_word = method_span.with_hi(expr.span.hi());
+
+ let mut applicability = Applicability::MachineApplicable;
+
+ //Special handling for `format!` as arg_root
+ if_chain! {
+ if let hir::ExprKind::Block(block, None) = &arg_root.kind;
+ if block.stmts.len() == 1;
+ if let hir::StmtKind::Local(local) = &block.stmts[0].kind;
+ if let Some(arg_root) = &local.init;
+ if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind;
+ if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1;
+ if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind;
+ then {
+ let fmt_spec = &format_args[0];
+ let fmt_args = &format_args[1];
+
+ let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()];
+
+ args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability));
+
+ let sugg = args.join(", ");
+
+ span_lint_and_sugg(
+ cx,
+ EXPECT_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{}` followed by a function call", name),
+ "try this",
+ format!("unwrap_or_else({} panic!({}))", closure_args, sugg),
+ applicability,
+ );
+
+ return;
+ }
+ }
+
+ let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
+ if requires_to_string(cx, arg_root) {
+ arg_root_snippet.to_mut().push_str(".to_string()");
+ }
+
+ span_lint_and_sugg(
+ cx,
+ EXPECT_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{}` followed by a function call", name),
+ "try this",
+ format!("unwrap_or_else({} {{ panic!({}) }})", closure_args, arg_root_snippet),
+ applicability,
+ );
+}
+
+/// Checks for the `CLONE_ON_COPY` lint.
+fn lint_clone_on_copy(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, arg_ty: Ty<'_>) {
+ let ty = cx.tables().expr_ty(expr);
+ if let ty::Ref(_, inner, _) = arg_ty.kind {
+ if let ty::Ref(_, innermost, _) = inner.kind {
+ span_lint_and_then(
+ cx,
+ CLONE_DOUBLE_REF,
+ expr.span,
+ "using `clone` on a double-reference; \
+ this will copy the reference instead of cloning the inner type",
+ |diag| {
+ if let Some(snip) = sugg::Sugg::hir_opt(cx, arg) {
+ let mut ty = innermost;
+ let mut n = 0;
+ while let ty::Ref(_, inner, _) = ty.kind {
+ ty = inner;
+ n += 1;
+ }
+ let refs: String = iter::repeat('&').take(n + 1).collect();
+ let derefs: String = iter::repeat('*').take(n).collect();
+ let explicit = format!("<{}{}>::clone({})", refs, ty, snip);
+ diag.span_suggestion(
+ expr.span,
+ "try dereferencing it",
+ format!("{}({}{}).clone()", refs, derefs, snip.deref()),
+ Applicability::MaybeIncorrect,
+ );
+ diag.span_suggestion(
+ expr.span,
+ "or try being explicit if you are sure, that you want to clone a reference",
+ explicit,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ },
+ );
+ return; // don't report clone_on_copy
+ }
+ }
+
+ if is_copy(cx, ty) {
+ let snip;
+ if let Some(snippet) = sugg::Sugg::hir_opt(cx, arg) {
+ let parent = cx.tcx.hir().get_parent_node(expr.hir_id);
+ match &cx.tcx.hir().get(parent) {
+ hir::Node::Expr(parent) => match parent.kind {
+ // &*x is a nop, &x.clone() is not
+ hir::ExprKind::AddrOf(..) => return,
+ // (*x).func() is useless, x.clone().func() can work in case func borrows mutably
+ hir::ExprKind::MethodCall(_, _, parent_args, _) if expr.hir_id == parent_args[0].hir_id => return,
+
+ _ => {},
+ },
+ hir::Node::Stmt(stmt) => {
+ if let hir::StmtKind::Local(ref loc) = stmt.kind {
+ if let hir::PatKind::Ref(..) = loc.pat.kind {
+ // let ref y = *x borrows x, let ref y = x.clone() does not
+ return;
+ }
+ }
+ },
+ _ => {},
+ }
+
+ // x.clone() might have dereferenced x, possibly through Deref impls
+ if cx.tables().expr_ty(arg) == ty {
+ snip = Some(("try removing the `clone` call", format!("{}", snippet)));
+ } else {
+ let deref_count = cx
+ .tables()
+ .expr_adjustments(arg)
+ .iter()
- diag.span_suggestion(expr.span, text, snip, Applicability::Unspecified);
++ .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, "using `clone` on a `Copy` type", |diag| {
+ if let Some((text, snip)) = snip {
- ty::Array(_, size) => {
- if let Some(size) = size.try_eval_usize(cx.tcx, cx.param_env) {
- size < 32
- } else {
- false
- }
- },
++ diag.span_suggestion(expr.span, text, snip, Applicability::MachineApplicable);
+ }
+ });
+ }
+}
+
+fn lint_clone_on_ref_ptr(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+ let obj_ty = walk_ptrs_ty(cx.tables().expr_ty(arg));
+
+ if let ty::Adt(_, subst) = obj_ty.kind {
+ let caller_type = if is_type_diagnostic_item(cx, obj_ty, sym::Rc) {
+ "Rc"
+ } else if is_type_diagnostic_item(cx, obj_ty, sym::Arc) {
+ "Arc"
+ } else if match_type(cx, obj_ty, &paths::WEAK_RC) || match_type(cx, obj_ty, &paths::WEAK_ARC) {
+ "Weak"
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ CLONE_ON_REF_PTR,
+ expr.span,
+ "using `.clone()` on a ref-counted pointer",
+ "try this",
+ format!(
+ "{}::<{}>::clone(&{})",
+ caller_type,
+ subst.type_at(0),
+ snippet(cx, arg.span, "_")
+ ),
+ Applicability::Unspecified, // Sometimes unnecessary ::<_> after Rc/Arc/Weak
+ );
+ }
+}
+
+fn lint_string_extend(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ let arg = &args[1];
+ if let Some(arglists) = method_chain_args(arg, &["chars"]) {
+ let target = &arglists[0][0];
+ let self_ty = walk_ptrs_ty(cx.tables().expr_ty(target));
+ let ref_str = if self_ty.kind == ty::Str {
+ ""
+ } else if is_type_diagnostic_item(cx, self_ty, sym!(string_type)) {
+ "&"
+ } else {
+ return;
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ STRING_EXTEND_CHARS,
+ expr.span,
+ "calling `.extend(_.chars())`",
+ "try this",
+ format!(
+ "{}.push_str({}{})",
+ snippet_with_applicability(cx, args[0].span, "_", &mut applicability),
+ ref_str,
+ snippet_with_applicability(cx, target.span, "_", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
+
+fn lint_extend(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ let obj_ty = walk_ptrs_ty(cx.tables().expr_ty(&args[0]));
+ if is_type_diagnostic_item(cx, obj_ty, sym!(string_type)) {
+ lint_string_extend(cx, expr, args);
+ }
+}
+
+fn lint_cstring_as_ptr(cx: &LateContext<'_>, expr: &hir::Expr<'_>, source: &hir::Expr<'_>, unwrap: &hir::Expr<'_>) {
+ if_chain! {
+ let source_type = cx.tables().expr_ty(source);
+ if let ty::Adt(def, substs) = source_type.kind;
+ if cx.tcx.is_diagnostic_item(sym!(result_type), def.did);
+ if match_type(cx, substs.type_at(0), &paths::CSTRING);
+ then {
+ span_lint_and_then(
+ cx,
+ TEMPORARY_CSTRING_AS_PTR,
+ expr.span,
+ "you are getting the inner pointer of a temporary `CString`",
+ |diag| {
+ diag.note("that pointer will be invalid outside this expression");
+ diag.span_help(unwrap.span, "assign the `CString` to a variable to extend its lifetime");
+ });
+ }
+ }
+}
+
+fn lint_iter_cloned_collect<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) {
+ if_chain! {
+ if is_type_diagnostic_item(cx, cx.tables().expr_ty(expr), sym!(vec_type));
+ if let Some(slice) = derefs_to_slice(cx, &iter_args[0], cx.tables().expr_ty(&iter_args[0]));
+ if let Some(to_replace) = expr.span.trim_start(slice.span.source_callsite());
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ ITER_CLONED_COLLECT,
+ to_replace,
+ "called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \
+ more readable",
+ "try",
+ ".to_vec()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn lint_unnecessary_fold(cx: &LateContext<'_>, expr: &hir::Expr<'_>, fold_args: &[hir::Expr<'_>], fold_span: Span) {
+ fn check_fold_with_op(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ fold_args: &[hir::Expr<'_>],
+ fold_span: Span,
+ op: hir::BinOpKind,
+ replacement_method_name: &str,
+ replacement_has_args: bool,
+ ) {
+ if_chain! {
+ // Extract the body of the closure passed to fold
+ if let hir::ExprKind::Closure(_, _, body_id, _, _) = fold_args[2].kind;
+ let closure_body = cx.tcx.hir().body(body_id);
+ let closure_expr = remove_blocks(&closure_body.value);
+
+ // Check if the closure body is of the form `acc <op> some_expr(x)`
+ if let hir::ExprKind::Binary(ref bin_op, ref left_expr, ref right_expr) = closure_expr.kind;
+ if bin_op.node == op;
+
+ // Extract the names of the two arguments to the closure
+ if let Some(first_arg_ident) = get_arg_name(&closure_body.params[0].pat);
+ if let Some(second_arg_ident) = get_arg_name(&closure_body.params[1].pat);
+
+ if match_var(&*left_expr, first_arg_ident);
+ if replacement_has_args || match_var(&*right_expr, second_arg_ident);
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let sugg = if replacement_has_args {
+ format!(
+ "{replacement}(|{s}| {r})",
+ replacement = replacement_method_name,
+ s = second_arg_ident,
+ r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
+ )
+ } else {
+ format!(
+ "{replacement}()",
+ replacement = replacement_method_name,
+ )
+ };
+
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_FOLD,
+ fold_span.with_hi(expr.span.hi()),
+ // TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
+ "this `.fold` can be written more succinctly using another method",
+ "try",
+ sugg,
+ applicability,
+ );
+ }
+ }
+ }
+
+ // Check that this is a call to Iterator::fold rather than just some function called fold
+ if !match_trait_method(cx, expr, &paths::ITERATOR) {
+ return;
+ }
+
+ assert!(
+ fold_args.len() == 3,
+ "Expected fold_args to have three entries - the receiver, the initial value and the closure"
+ );
+
+ // Check if the first argument to .fold is a suitable literal
+ if let hir::ExprKind::Lit(ref lit) = fold_args[1].kind {
+ match lit.node {
+ ast::LitKind::Bool(false) => {
+ check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Or, "any", true)
+ },
+ ast::LitKind::Bool(true) => {
+ check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::And, "all", true)
+ },
+ ast::LitKind::Int(0, _) => {
+ check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Add, "sum", false)
+ },
+ ast::LitKind::Int(1, _) => {
+ check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Mul, "product", false)
+ },
+ _ => (),
+ }
+ }
+}
+
+fn lint_step_by<'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.tables(), &args[1]) {
+ span_lint(
+ cx,
+ ITERATOR_STEP_BY_ZERO,
+ expr.span,
+ "Iterator::step_by(0) will panic at runtime",
+ );
+ }
+ }
+}
+
+fn lint_iter_next<'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.tables().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(cx, 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.tables().expr_ty(caller_expr), sym!(vec_type))
+ || matches!(&walk_ptrs_ty(cx.tables().expr_ty(caller_expr)).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,
+ );
+ }
+}
+
+fn lint_iter_nth<'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.tables().expr_ty(&iter_args[0])).is_some() {
+ "slice"
+ } else if is_type_diagnostic_item(cx, cx.tables().expr_ty(&iter_args[0]), sym!(vec_type)) {
+ "Vec"
+ } else if is_type_diagnostic_item(cx, cx.tables().expr_ty(&iter_args[0]), sym!(vecdeque_type)) {
+ "VecDeque"
+ } else {
+ let nth_args = nth_and_iter_args[0];
+ lint_iter_nth_zero(cx, expr, &nth_args);
+ return; // caller is not a type that we want to lint
+ };
+
+ span_lint_and_help(
+ cx,
+ ITER_NTH,
+ expr.span,
+ &format!("called `.iter{0}().nth()` on a {1}", mut_str, caller_type),
+ None,
+ &format!("calling `.get{}()` is both faster and more readable", mut_str),
+ );
+}
+
+fn lint_iter_nth_zero<'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.tables(), &nth_args[1]);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ ITER_NTH_ZERO,
+ expr.span,
+ "called `.nth(0)` on a `std::iter::Iterator`",
+ "try calling",
+ format!("{}.next()", snippet_with_applicability(cx, nth_args[0].span, "..", &mut applicability)),
+ applicability,
+ );
+ }
+ }
+}
+
+fn lint_get_unwrap<'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.tables().expr_ty(&get_args[0]);
+ let get_args_str = if get_args.len() > 1 {
+ snippet_with_applicability(cx, get_args[1].span, "_", &mut applicability)
+ } else {
+ return; // not linting on a .get().unwrap() chain or variant
+ };
+ let mut needs_ref;
+ let caller_type = if derefs_to_slice(cx, &get_args[0], expr_ty).is_some() {
+ needs_ref = get_args_str.parse::<usize>().is_ok();
+ "slice"
+ } else if is_type_diagnostic_item(cx, expr_ty, sym!(vec_type)) {
+ needs_ref = get_args_str.parse::<usize>().is_ok();
+ "Vec"
+ } else if is_type_diagnostic_item(cx, expr_ty, sym!(vecdeque_type)) {
+ needs_ref = get_args_str.parse::<usize>().is_ok();
+ "VecDeque"
+ } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym!(hashmap_type)) {
+ needs_ref = true;
+ "HashMap"
+ } else if !is_mut && match_type(cx, expr_ty, &paths::BTREEMAP) {
+ needs_ref = true;
+ "BTreeMap"
+ } else {
+ return; // caller is not a type that we want to lint
+ };
+
+ let mut span = expr.span;
+
+ // Handle the case where the result is immediately dereferenced
+ // by not requiring ref and pulling the dereference into the
+ // suggestion.
+ if_chain! {
+ if needs_ref;
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let hir::ExprKind::Unary(hir::UnOp::UnDeref, _) = parent.kind;
+ then {
+ needs_ref = false;
+ span = parent.span;
+ }
+ }
+
+ let mut_str = if is_mut { "_mut" } else { "" };
+ let borrow_str = if !needs_ref {
+ ""
+ } else if is_mut {
+ "&mut "
+ } else {
+ "&"
+ };
+
+ span_lint_and_sugg(
+ cx,
+ GET_UNWRAP,
+ span,
+ &format!(
+ "called `.get{0}().unwrap()` on a {1}. Using `[]` is more clear and more concise",
+ mut_str, caller_type
+ ),
+ "try this",
+ format!(
+ "{}{}[{}]",
+ borrow_str,
+ snippet_with_applicability(cx, get_args[0].span, "_", &mut applicability),
+ get_args_str
+ ),
+ applicability,
+ );
+}
+
+fn lint_iter_skip_next(cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+ // lint if caller of skip is an Iterator
+ if match_trait_method(cx, expr, &paths::ITERATOR) {
+ span_lint_and_help(
+ cx,
+ ITER_SKIP_NEXT,
+ expr.span,
+ "called `skip(x).next()` on an iterator",
+ None,
+ "this is more succinctly expressed by calling `nth(x)`",
+ );
+ }
+}
+
+fn derefs_to_slice<'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.tables().expr_ty(&args[0])) {
+ Some(&args[0])
+ } else {
+ None
+ }
+ } else {
+ match ty.kind {
+ ty::Slice(_) => Some(expr),
+ ty::Adt(def, _) if def.is_box() && may_slice(cx, ty.boxed_ty()) => Some(expr),
+ ty::Ref(_, inner, _) => {
+ if may_slice(cx, inner) {
+ Some(expr)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+ }
+}
+
+/// lint use of `unwrap()` for `Option`s and `Result`s
+fn lint_unwrap(cx: &LateContext<'_>, expr: &hir::Expr<'_>, unwrap_args: &[hir::Expr<'_>]) {
+ let obj_ty = walk_ptrs_ty(cx.tables().expr_ty(&unwrap_args[0]));
+
+ let mess = if is_type_diagnostic_item(cx, obj_ty, sym!(option_type)) {
+ Some((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,
+ ),
+ );
+ }
+}
+
+/// lint use of `expect()` for `Option`s and `Result`s
+fn lint_expect(cx: &LateContext<'_>, expr: &hir::Expr<'_>, expect_args: &[hir::Expr<'_>]) {
+ let obj_ty = walk_ptrs_ty(cx.tables().expr_ty(&expect_args[0]));
+
+ let mess = if is_type_diagnostic_item(cx, obj_ty, sym!(option_type)) {
+ Some((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,),
+ );
+ }
+}
+
+/// lint use of `ok().expect()` for `Result`s
+fn lint_ok_expect(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ok_args: &[hir::Expr<'_>]) {
+ if_chain! {
+ // lint if the caller of `ok()` is a `Result`
+ if is_type_diagnostic_item(cx, cx.tables().expr_ty(&ok_args[0]), sym!(result_type));
+ let result_type = cx.tables().expr_ty(&ok_args[0]);
+ if let Some(error_type) = get_error_type(cx, result_type);
+ if has_debug_impl(error_type, cx);
+
+ then {
+ span_lint_and_help(
+ cx,
+ OK_EXPECT,
+ expr.span,
+ "called `ok().expect()` on a `Result` value",
+ None,
+ "you can call `expect()` directly on the `Result`",
+ );
+ }
+ }
+}
+
+/// lint use of `map().flatten()` for `Iterators` and 'Options'
+fn lint_map_flatten<'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 msg = "called `map(..).flatten()` on an `Iterator`. \
+ This is more succinctly expressed by calling `.flat_map(..)`";
+ let self_snippet = snippet(cx, map_args[0].span, "..");
+ let func_snippet = snippet(cx, map_args[1].span, "..");
+ let hint = format!("{0}.flat_map({1})", self_snippet, func_snippet);
+ span_lint_and_sugg(
+ cx,
+ MAP_FLATTEN,
+ expr.span,
+ msg,
+ "try using `flat_map` instead",
+ hint,
+ Applicability::MachineApplicable,
+ );
+ }
+
+ // lint if caller of `.map().flatten()` is an Option
+ if is_type_diagnostic_item(cx, cx.tables().expr_ty(&map_args[0]), sym!(option_type)) {
+ let msg = "called `map(..).flatten()` on an `Option`. \
+ This is more succinctly expressed by calling `.and_then(..)`";
+ let self_snippet = snippet(cx, map_args[0].span, "..");
+ let func_snippet = snippet(cx, map_args[1].span, "..");
+ let hint = format!("{0}.and_then({1})", self_snippet, func_snippet);
+ span_lint_and_sugg(
+ cx,
+ MAP_FLATTEN,
+ expr.span,
+ msg,
+ "try using `and_then` instead",
+ hint,
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
+fn lint_map_unwrap_or_else<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ map_args: &'tcx [hir::Expr<'_>],
+ unwrap_args: &'tcx [hir::Expr<'_>],
+) {
+ // lint if the caller of `map()` is an `Option`
+ let is_option = is_type_diagnostic_item(cx, cx.tables().expr_ty(&map_args[0]), sym!(option_type));
+ let is_result = is_type_diagnostic_item(cx, cx.tables().expr_ty(&map_args[0]), sym!(result_type));
+
+ if is_option || is_result {
+ // Don't make a suggestion that may fail to compile due to mutably borrowing
+ // the same variable twice.
+ let map_mutated_vars = mutated_variables(&map_args[0], cx);
+ let unwrap_mutated_vars = mutated_variables(&unwrap_args[1], cx);
+ if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
+ if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
+ return;
+ }
+ } else {
+ return;
+ }
+
+ // lint message
+ let msg = if is_option {
+ "called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling \
+ `map_or_else(g, f)` instead"
+ } else {
+ "called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling \
+ `.map_or_else(g, f)` instead"
+ };
+ // get snippets for args to map() and unwrap_or_else()
+ let map_snippet = snippet(cx, map_args[1].span, "..");
+ let unwrap_snippet = snippet(cx, unwrap_args[1].span, "..");
+ // lint, with note if neither arg is > 1 line and both map() and
+ // unwrap_or_else() have the same span
+ let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
+ let same_span = map_args[1].span.ctxt() == unwrap_args[1].span.ctxt();
+ if same_span && !multiline {
+ span_lint_and_note(
+ cx,
+ MAP_UNWRAP_OR,
+ expr.span,
+ msg,
+ None,
+ &format!(
+ "replace `map({0}).unwrap_or_else({1})` with `map_or_else({1}, {0})`",
+ map_snippet, unwrap_snippet,
+ ),
+ );
+ } else if same_span && multiline {
+ span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
+ };
+ }
+}
+
+/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
+fn lint_map_or_none<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_or_args: &'tcx [hir::Expr<'_>]) {
+ let is_option = is_type_diagnostic_item(cx, cx.tables().expr_ty(&map_or_args[0]), sym!(option_type));
+ let is_result = is_type_diagnostic_item(cx, cx.tables().expr_ty(&map_or_args[0]), sym!(result_type));
+
+ // There are two variants of this `map_or` lint:
+ // (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>`
+ // (2) using `map_or` as a combinator instead of `and_then`
+ //
+ // (For this lint) we don't care if any other type calls `map_or`
+ if !is_option && !is_result {
+ return;
+ }
+
+ let (lint_name, msg, instead, hint) = {
+ let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = map_or_args[1].kind {
+ match_qpath(qpath, &paths::OPTION_NONE)
+ } else {
+ return;
+ };
+
+ if !default_arg_is_none {
+ // nothing to lint!
+ return;
+ }
+
+ let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_or_args[2].kind {
+ match_qpath(qpath, &paths::OPTION_SOME)
+ } else {
+ false
+ };
+
+ if is_option {
+ let self_snippet = snippet(cx, map_or_args[0].span, "..");
+ let func_snippet = snippet(cx, map_or_args[2].span, "..");
+ let msg = "called `map_or(None, f)` on an `Option` value. This can be done more directly by calling \
+ `and_then(f)` instead";
+ (
+ OPTION_MAP_OR_NONE,
+ msg,
+ "try using `and_then` instead",
+ format!("{0}.and_then({1})", self_snippet, func_snippet),
+ )
+ } else if f_arg_is_some {
+ let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \
+ `ok()` instead";
+ let self_snippet = snippet(cx, map_or_args[0].span, "..");
+ (
+ RESULT_MAP_OR_INTO_OPTION,
+ msg,
+ "try using `ok` instead",
+ format!("{0}.ok()", self_snippet),
+ )
+ } else {
+ // nothing to lint!
+ return;
+ }
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint_name,
+ expr.span,
+ msg,
+ instead,
+ hint,
+ Applicability::MachineApplicable,
+ );
+}
+
+/// lint use of `filter().next()` for `Iterators`
+fn lint_filter_next<'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(p).next()` on an `Iterator`. This is more succinctly expressed by calling \
+ `.find(p)` instead.";
+ let filter_snippet = snippet(cx, filter_args[1].span, "..");
+ if filter_snippet.lines().count() <= 1 {
+ // add note if not multi-line
+ span_lint_and_note(
+ cx,
+ FILTER_NEXT,
+ expr.span,
+ msg,
+ None,
+ &format!("replace `filter({0}).next()` with `find({0})`", filter_snippet),
+ );
+ } else {
+ span_lint(cx, FILTER_NEXT, expr.span, msg);
+ }
+ }
+}
+
+/// lint use of `skip_while().next()` for `Iterators`
+fn lint_skip_while_next<'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(p).next()` on an `Iterator`",
+ None,
+ "this is more succinctly expressed by calling `.find(!p)` instead",
+ );
+ }
+}
+
+/// lint use of `filter().map()` for `Iterators`
+fn lint_filter_map<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ _filter_args: &'tcx [hir::Expr<'_>],
+ _map_args: &'tcx [hir::Expr<'_>],
+) {
+ // lint if caller of `.filter().map()` is an Iterator
+ if match_trait_method(cx, expr, &paths::ITERATOR) {
+ let msg = "called `filter(p).map(q)` on an `Iterator`";
+ let hint = "this is more succinctly expressed by calling `.filter_map(..)` instead";
+ span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
+ }
+}
+
+/// lint use of `filter_map().next()` for `Iterators`
+fn lint_filter_map_next<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, filter_args: &'tcx [hir::Expr<'_>]) {
+ if match_trait_method(cx, expr, &paths::ITERATOR) {
+ let msg = "called `filter_map(p).next()` on an `Iterator`. This is more succinctly expressed by calling \
+ `.find_map(p)` instead.";
+ let filter_snippet = snippet(cx, filter_args[1].span, "..");
+ if filter_snippet.lines().count() <= 1 {
+ span_lint_and_note(
+ cx,
+ FILTER_MAP_NEXT,
+ expr.span,
+ msg,
+ None,
+ &format!("replace `filter_map({0}).next()` with `find_map({0})`", filter_snippet),
+ );
+ } else {
+ span_lint(cx, FILTER_MAP_NEXT, expr.span, msg);
+ }
+ }
+}
+
+/// lint use of `find().map()` for `Iterators`
+fn lint_find_map<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ _find_args: &'tcx [hir::Expr<'_>],
+ map_args: &'tcx [hir::Expr<'_>],
+) {
+ // lint if caller of `.filter().map()` is an Iterator
+ if match_trait_method(cx, &map_args[0], &paths::ITERATOR) {
+ let msg = "called `find(p).map(q)` on an `Iterator`";
+ let hint = "this is more succinctly expressed by calling `.find_map(..)` instead";
+ span_lint_and_help(cx, FIND_MAP, expr.span, msg, None, hint);
+ }
+}
+
+/// lint use of `filter_map().map()` for `Iterators`
+fn lint_filter_map_map<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ _filter_args: &'tcx [hir::Expr<'_>],
+ _map_args: &'tcx [hir::Expr<'_>],
+) {
+ // lint if caller of `.filter().map()` is an Iterator
+ if match_trait_method(cx, expr, &paths::ITERATOR) {
+ let msg = "called `filter_map(p).map(q)` on an `Iterator`";
+ let hint = "this is more succinctly expressed by only calling `.filter_map(..)` instead";
+ span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
+ }
+}
+
+/// lint use of `filter().flat_map()` for `Iterators`
+fn lint_filter_flat_map<'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(p).flat_map(q)` on an `Iterator`";
+ let hint = "this is more succinctly expressed by calling `.flat_map(..)` \
+ and filtering by returning `iter::empty()`";
+ span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
+ }
+}
+
+/// lint use of `filter_map().flat_map()` for `Iterators`
+fn lint_filter_map_flat_map<'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(p).flat_map(q)` on an `Iterator`";
+ let hint = "this is more succinctly expressed by calling `.flat_map(..)` \
+ and filtering by returning `iter::empty()`";
+ span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
+ }
+}
+
+/// lint use of `flat_map` for `Iterators` where `flatten` would be sufficient
+fn lint_flat_map_identity<'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.as_str() == binding_ident.as_str();
+
+ then {
+ apply_lint("called `flat_map(|x| x)` on an `Iterator`");
+ }
+ }
+
+ if_chain! {
+ if let hir::ExprKind::Path(ref qpath) = arg_node;
+
+ if match_qpath(qpath, &paths::STD_CONVERT_IDENTITY);
+
+ then {
+ apply_lint("called `flat_map(std::convert::identity)` on an `Iterator`");
+ }
+ }
+ }
+}
+
+/// lint searching an Iterator followed by `is_some()`
+fn lint_search_is_some<'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 {}. This is more succinctly \
+ expressed by calling `any()`.",
+ search_method
+ );
+ let search_snippet = snippet(cx, search_args[1].span, "..");
+ if search_snippet.lines().count() <= 1 {
+ // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
+ // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()`
+ let any_search_snippet = if_chain! {
+ if search_method == "find";
+ if let hir::ExprKind::Closure(_, _, body_id, ..) = search_args[1].kind;
+ let closure_body = cx.tcx.hir().body(body_id);
+ if let Some(closure_arg) = closure_body.params.get(0);
+ then {
+ if let hir::PatKind::Ref(..) = closure_arg.pat.kind {
+ Some(search_snippet.replacen('&', "", 1))
+ } else if let Some(name) = get_arg_name(&closure_arg.pat) {
+ Some(search_snippet.replace(&format!("*{}", name), &name.as_str()))
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ };
+ // add note if not multi-line
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ method_span.with_hi(expr.span.hi()),
+ &msg,
+ "try this",
+ format!(
+ "any({})",
+ any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
+ ),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint(cx, SEARCH_IS_SOME, expr.span, &msg);
+ }
+ }
+}
+
+/// Used for `lint_binary_expr_with_method_call`.
+#[derive(Copy, Clone)]
+struct BinaryExprInfo<'a> {
+ expr: &'a hir::Expr<'a>,
+ chain: &'a hir::Expr<'a>,
+ other: &'a hir::Expr<'a>,
+ eq: bool,
+}
+
+/// Checks for the `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
+fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExprInfo<'_>) {
+ macro_rules! lint_with_both_lhs_and_rhs {
+ ($func:ident, $cx:expr, $info:ident) => {
+ if !$func($cx, $info) {
+ ::std::mem::swap(&mut $info.chain, &mut $info.other);
+ if $func($cx, $info) {
+ return;
+ }
+ }
+ };
+ }
+
+ lint_with_both_lhs_and_rhs!(lint_chars_next_cmp, cx, info);
+ lint_with_both_lhs_and_rhs!(lint_chars_last_cmp, cx, info);
+ lint_with_both_lhs_and_rhs!(lint_chars_next_cmp_with_unwrap, cx, info);
+ lint_with_both_lhs_and_rhs!(lint_chars_last_cmp_with_unwrap, cx, info);
+}
+
+/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
+fn lint_chars_cmp(
+ cx: &LateContext<'_>,
+ info: &BinaryExprInfo<'_>,
+ chain_methods: &[&str],
+ lint: &'static Lint,
+ suggest: &str,
+) -> bool {
+ if_chain! {
+ if let Some(args) = method_chain_args(info.chain, chain_methods);
+ if let hir::ExprKind::Call(ref fun, ref arg_char) = info.other.kind;
+ if arg_char.len() == 1;
+ if let hir::ExprKind::Path(ref qpath) = fun.kind;
+ if let Some(segment) = single_segment_path(qpath);
+ if segment.ident.name == sym!(Some);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let self_ty = walk_ptrs_ty(cx.tables().expr_ty_adjusted(&args[0][0]));
+
+ if self_ty.kind != ty::Str {
+ return false;
+ }
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ info.expr.span,
+ &format!("you should use the `{}` method", suggest),
+ "like this",
+ format!("{}{}.{}({})",
+ if info.eq { "" } else { "!" },
+ snippet_with_applicability(cx, args[0][0].span, "_", &mut applicability),
+ suggest,
+ snippet_with_applicability(cx, arg_char[0].span, "_", &mut applicability)),
+ applicability,
+ );
+
+ return true;
+ }
+ }
+
+ false
+}
+
+/// Checks for the `CHARS_NEXT_CMP` lint.
+fn lint_chars_next_cmp<'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")
+ }
+}
+
+/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
+fn lint_single_char_pattern<'tcx>(cx: &LateContext<'tcx>, _expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if let hir::ExprKind::Lit(lit) = &arg.kind;
+ if let ast::LitKind::Str(r, style) = lit.node;
+ if r.as_str().len() == 1;
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let snip = snippet_with_applicability(cx, arg.span, "..", &mut applicability);
+ let ch = if let ast::StrStyle::Raw(nhash) = style {
+ let nhash = nhash as usize;
+ // for raw string: r##"a"##
+ &snip[(nhash + 2)..(snip.len() - 1 - nhash)]
+ } else {
+ // for regular string: "a"
+ &snip[1..(snip.len() - 1)]
+ };
+ let hint = format!("'{}'", if ch == "'" { "\\'" } else { ch });
+ span_lint_and_sugg(
+ cx,
+ SINGLE_CHAR_PATTERN,
+ arg.span,
+ "single-character string constant used as pattern",
+ "try using a `char` instead",
+ hint,
+ applicability,
+ );
+ }
+ }
+}
+
+/// Checks for the `USELESS_ASREF` lint.
+fn lint_asref(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, as_ref_args: &[hir::Expr<'_>]) {
+ // when we get here, we've already checked that the call name is "as_ref" or "as_mut"
+ // check if the call is to the actual `AsRef` or `AsMut` trait
+ if match_trait_method(cx, expr, &paths::ASREF_TRAIT) || match_trait_method(cx, expr, &paths::ASMUT_TRAIT) {
+ // check if the type after `as_ref` or `as_mut` is the same as before
+ let recvr = &as_ref_args[0];
+ let rcv_ty = cx.tables().expr_ty(recvr);
+ let res_ty = cx.tables().expr_ty(expr);
+ let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty);
+ let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty);
+ if base_rcv_ty == base_res_ty && rcv_depth >= res_depth {
+ // allow the `as_ref` or `as_mut` if it is followed by another method call
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let hir::ExprKind::MethodCall(_, ref span, _, _) = parent.kind;
+ if span != &expr.span;
+ then {
+ return;
+ }
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ USELESS_ASREF,
+ expr.span,
+ &format!("this call to `{}` does nothing", call_name),
+ "try this",
+ snippet_with_applicability(cx, recvr.span, "_", &mut applicability).to_string(),
+ applicability,
+ );
+ }
+ }
+}
+
+fn ty_has_iter_method(cx: &LateContext<'_>, self_ref_ty: Ty<'_>) -> Option<(&'static str, &'static str)> {
+ has_iter_method(cx, self_ref_ty).map(|ty_name| {
+ let mutbl = match self_ref_ty.kind {
+ ty::Ref(_, _, mutbl) => mutbl,
+ _ => unreachable!(),
+ };
+ let method_name = match mutbl {
+ hir::Mutability::Not => "iter",
+ hir::Mutability::Mut => "iter_mut",
+ };
+ (ty_name, method_name)
+ })
+}
+
+fn lint_into_iter(cx: &LateContext<'_>, expr: &hir::Expr<'_>, self_ref_ty: Ty<'_>, method_span: Span) {
+ if !match_trait_method(cx, expr, &paths::INTO_ITERATOR) {
+ return;
+ }
+ if let Some((kind, method_name)) = ty_has_iter_method(cx, self_ref_ty) {
+ span_lint_and_sugg(
+ cx,
+ INTO_ITER_ON_REF,
+ method_span,
+ &format!(
+ "this `.into_iter()` call is equivalent to `.{}()` and will not move the `{}`",
+ method_name, kind,
+ ),
+ "call directly",
+ method_name.to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+/// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter)
+fn lint_maybe_uninit(cx: &LateContext<'_>, expr: &hir::Expr<'_>, outer: &hir::Expr<'_>) {
+ if_chain! {
+ if let hir::ExprKind::Call(ref callee, ref args) = expr.kind;
+ if args.is_empty();
+ if let hir::ExprKind::Path(ref path) = callee.kind;
+ if match_qpath(path, &paths::MEM_MAYBEUNINIT_UNINIT);
+ if !is_maybe_uninit_ty_valid(cx, cx.tables().expr_ty_adjusted(outer));
+ then {
+ span_lint(
+ cx,
+ UNINIT_ASSUMED_INIT,
+ outer.span,
+ "this call for this type may be undefined behavior"
+ );
+ }
+ }
+}
+
+fn is_maybe_uninit_ty_valid(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+ match ty.kind {
+ ty::Array(ref component, _) => is_maybe_uninit_ty_valid(cx, component),
+ ty::Tuple(ref types) => types.types().all(|ty| is_maybe_uninit_ty_valid(cx, ty)),
+ ty::Adt(ref adt, _) => match_def_path(cx, adt.did, &paths::MEM_MAYBEUNINIT),
+ _ => false,
+ }
+}
+
+fn lint_suspicious_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+ span_lint_and_help(
+ cx,
+ SUSPICIOUS_MAP,
+ expr.span,
+ "this call to `map()` won't have an effect on the call to `count()`",
+ None,
+ "make sure you did not confuse `map` with `filter` or `for_each`",
+ );
+}
+
+/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s
+fn lint_option_as_ref_deref<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ as_ref_args: &[hir::Expr<'_>],
+ map_args: &[hir::Expr<'_>],
+ is_mut: bool,
+) {
+ let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not);
+
+ let option_ty = cx.tables().expr_ty(&as_ref_args[0]);
+ if !is_type_diagnostic_item(cx, option_ty, sym!(option_type)) {
+ return;
+ }
+
+ let deref_aliases: [&[&str]; 9] = [
+ &paths::DEREF_TRAIT_METHOD,
+ &paths::DEREF_MUT_TRAIT_METHOD,
+ &paths::CSTRING_AS_C_STR,
+ &paths::OS_STRING_AS_OS_STR,
+ &paths::PATH_BUF_AS_PATH,
+ &paths::STRING_AS_STR,
+ &paths::STRING_AS_MUT_STR,
+ &paths::VEC_AS_SLICE,
+ &paths::VEC_AS_MUT_SLICE,
+ ];
+
+ let is_deref = match map_args[1].kind {
+ hir::ExprKind::Path(ref expr_qpath) => deref_aliases.iter().any(|path| match_qpath(expr_qpath, path)),
+ hir::ExprKind::Closure(_, _, body_id, _, _) => {
+ let closure_body = cx.tcx.hir().body(body_id);
+ let closure_expr = remove_blocks(&closure_body.value);
+
+ match &closure_expr.kind {
+ hir::ExprKind::MethodCall(_, _, args, _) => {
+ if_chain! {
+ if args.len() == 1;
+ if let hir::ExprKind::Path(qpath) = &args[0].kind;
+ if let hir::def::Res::Local(local_id) = cx.qpath_res(qpath, args[0].hir_id);
+ if closure_body.params[0].pat.hir_id == local_id;
+ let adj = cx.tables().expr_adjustments(&args[0]).iter().map(|x| &x.kind).collect::<Box<[_]>>();
+ if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj;
+ then {
+ let method_did = cx.tables().type_dependent_def_id(closure_expr.hir_id).unwrap();
+ deref_aliases.iter().any(|path| match_def_path(cx, method_did, path))
+ } else {
+ false
+ }
+ }
+ },
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, ref inner) if same_mutability(m) => {
+ if_chain! {
+ if let hir::ExprKind::Unary(hir::UnOp::UnDeref, ref inner1) = inner.kind;
+ if let hir::ExprKind::Unary(hir::UnOp::UnDeref, ref inner2) = inner1.kind;
+ if let hir::ExprKind::Path(ref qpath) = inner2.kind;
+ if let hir::def::Res::Local(local_id) = cx.qpath_res(qpath, inner2.hir_id);
+ then {
+ closure_body.params[0].pat.hir_id == local_id
+ } else {
+ false
+ }
+ }
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ };
+
+ if is_deref {
+ let current_method = if is_mut {
+ format!(".as_mut().map({})", snippet(cx, map_args[1].span, ".."))
+ } else {
+ format!(".as_ref().map({})", snippet(cx, map_args[1].span, ".."))
+ };
+ let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" };
+ let hint = format!("{}.{}()", snippet(cx, as_ref_args[0].span, ".."), method_hint);
+ let suggestion = format!("try using {} instead", method_hint);
+
+ let msg = format!(
+ "called `{0}` on an Option value. This can be done more directly \
+ by calling `{1}` instead",
+ current_method, hint
+ );
+ span_lint_and_sugg(
+ cx,
+ OPTION_AS_REF_DEREF,
+ expr.span,
+ &msg,
+ &suggestion,
+ hint,
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+/// Given a `Result<T, E>` type, return its error type (`E`).
+fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> {
+ 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, &[]))
+}
+
+enum Convention {
+ Eq(&'static str),
+ StartsWith(&'static str),
+}
+
+#[rustfmt::skip]
+const CONVENTIONS: [(Convention, &[SelfKind]); 7] = [
+ (Convention::Eq("new"), &[SelfKind::No]),
+ (Convention::StartsWith("as_"), &[SelfKind::Ref, SelfKind::RefMut]),
+ (Convention::StartsWith("from_"), &[SelfKind::No]),
+ (Convention::StartsWith("into_"), &[SelfKind::Value]),
+ (Convention::StartsWith("is_"), &[SelfKind::Ref, SelfKind::No]),
+ (Convention::Eq("to_mut"), &[SelfKind::RefMut]),
+ (Convention::StartsWith("to_"), &[SelfKind::Ref]),
+];
+
+const FN_HEADER: hir::FnHeader = hir::FnHeader {
+ unsafety: hir::Unsafety::Normal,
+ constness: hir::Constness::NotConst,
+ asyncness: hir::IsAsync::NotAsync,
+ abi: rustc_target::spec::abi::Abi::Rust,
+};
+
+#[rustfmt::skip]
+const TRAIT_METHODS: [(&str, usize, &hir::FnHeader, SelfKind, OutType, &str); 30] = [
+ ("add", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Add"),
+ ("as_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::convert::AsMut"),
+ ("as_ref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::convert::AsRef"),
+ ("bitand", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitAnd"),
+ ("bitor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitOr"),
+ ("bitxor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitXor"),
+ ("borrow", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::borrow::Borrow"),
+ ("borrow_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::borrow::BorrowMut"),
+ ("clone", 1, &FN_HEADER, SelfKind::Ref, OutType::Any, "std::clone::Clone"),
+ ("cmp", 2, &FN_HEADER, SelfKind::Ref, OutType::Any, "std::cmp::Ord"),
+ ("default", 0, &FN_HEADER, SelfKind::No, OutType::Any, "std::default::Default"),
+ ("deref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::ops::Deref"),
+ ("deref_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::ops::DerefMut"),
+ ("div", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Div"),
+ ("drop", 1, &FN_HEADER, SelfKind::RefMut, OutType::Unit, "std::ops::Drop"),
+ ("eq", 2, &FN_HEADER, SelfKind::Ref, OutType::Bool, "std::cmp::PartialEq"),
+ ("from_iter", 1, &FN_HEADER, SelfKind::No, OutType::Any, "std::iter::FromIterator"),
+ ("from_str", 1, &FN_HEADER, SelfKind::No, OutType::Any, "std::str::FromStr"),
+ ("hash", 2, &FN_HEADER, SelfKind::Ref, OutType::Unit, "std::hash::Hash"),
+ ("index", 2, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::ops::Index"),
+ ("index_mut", 2, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::ops::IndexMut"),
+ ("into_iter", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::iter::IntoIterator"),
+ ("mul", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Mul"),
+ ("neg", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Neg"),
+ ("next", 1, &FN_HEADER, SelfKind::RefMut, OutType::Any, "std::iter::Iterator"),
+ ("not", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Not"),
+ ("rem", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Rem"),
+ ("shl", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Shl"),
+ ("shr", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Shr"),
+ ("sub", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Sub"),
+];
+
+#[rustfmt::skip]
+const PATTERN_METHODS: [(&str, usize); 17] = [
+ ("contains", 1),
+ ("starts_with", 1),
+ ("ends_with", 1),
+ ("find", 1),
+ ("rfind", 1),
+ ("split", 1),
+ ("rsplit", 1),
+ ("split_terminator", 1),
+ ("rsplit_terminator", 1),
+ ("splitn", 2),
+ ("rsplitn", 2),
+ ("matches", 1),
+ ("rmatches", 1),
+ ("match_indices", 1),
+ ("rmatch_indices", 1),
+ ("trim_start_matches", 1),
+ ("trim_end_matches", 1),
+];
+
+#[derive(Clone, Copy, PartialEq, Debug)]
+enum SelfKind {
+ Value,
+ Ref,
+ RefMut,
+ No,
+}
+
+impl SelfKind {
+ fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'_>, ty: Ty<'_>) -> bool {
+ if ty == parent_ty {
+ true
+ } else if ty.is_box() {
+ ty.boxed_ty() == parent_ty
+ } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) {
+ if let ty::Adt(_, substs) = ty.kind {
+ substs.types().next().map_or(false, |t| t == parent_ty)
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ if let ty::Ref(_, t, m) = ty.kind {
+ return m == mutability && t == parent_ty;
+ }
+
+ let trait_path = match mutability {
+ hir::Mutability::Not => &paths::ASREF_TRAIT,
+ hir::Mutability::Mut => &paths::ASMUT_TRAIT,
+ };
+
+ let trait_def_id = match get_trait_def_id(cx, trait_path) {
+ Some(did) => did,
+ None => return false,
+ };
+ implements_trait(cx, ty, trait_def_id, &[parent_ty.into()])
+ }
+
+ match self {
+ Self::Value => matches_value(cx, parent_ty, ty),
+ Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty),
+ Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty),
+ Self::No => ty != parent_ty,
+ }
+ }
+
+ #[must_use]
+ fn description(self) -> &'static str {
+ match self {
+ Self::Value => "self by value",
+ Self::Ref => "self by reference",
+ Self::RefMut => "self by mutable reference",
+ Self::No => "no self",
+ }
+ }
+}
+
+impl Convention {
+ #[must_use]
+ fn check(&self, other: &str) -> bool {
+ match *self {
+ Self::Eq(this) => this == other,
+ Self::StartsWith(this) => other.starts_with(this) && this != other,
+ }
+ }
+}
+
+impl fmt::Display for Convention {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ match *self {
+ Self::Eq(this) => this.fmt(f),
+ Self::StartsWith(this) => this.fmt(f).and_then(|_| '*'.fmt(f)),
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum OutType {
+ Unit,
+ Bool,
+ Any,
+ Ref,
+}
+
+impl OutType {
+ fn matches(self, cx: &LateContext<'_>, ty: &hir::FnRetTy<'_>) -> bool {
+ let is_unit = |ty: &hir::Ty<'_>| SpanlessEq::new(cx).eq_ty_kind(&ty.kind, &hir::TyKind::Tup(&[]));
+ match (self, ty) {
+ (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true,
+ (Self::Unit, &hir::FnRetTy::Return(ref ty)) if is_unit(ty) => true,
+ (Self::Bool, &hir::FnRetTy::Return(ref ty)) if is_bool(ty) => true,
+ (Self::Any, &hir::FnRetTy::Return(ref ty)) if !is_unit(ty) => true,
+ (Self::Ref, &hir::FnRetTy::Return(ref ty)) => matches!(ty.kind, hir::TyKind::Rptr(_, _)),
+ _ => false,
+ }
+ }
+}
+
+fn is_bool(ty: &hir::Ty<'_>) -> bool {
+ if let hir::TyKind::Path(ref p) = ty.kind {
+ match_qpath(p, &["bool"])
+ } else {
+ false
+ }
+}
+
+// Returns `true` if `expr` contains a return expression
+fn contains_return(expr: &hir::Expr<'_>) -> bool {
+ struct RetCallFinder {
+ found: bool,
+ }
+
+ impl<'tcx> intravisit::Visitor<'tcx> for RetCallFinder {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ if self.found {
+ return;
+ }
+ if let hir::ExprKind::Ret(..) = &expr.kind {
+ self.found = true;
+ } else {
+ intravisit::walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+ }
+
+ let mut visitor = RetCallFinder { found: false };
+ visitor.visit_expr(expr);
+ visitor.found
+}
+
+fn check_pointer_offset(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ if_chain! {
+ if args.len() == 2;
+ if let ty::RawPtr(ty::TypeAndMut { ref ty, .. }) = cx.tables().expr_ty(&args[0]).kind;
+ if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty));
+ if layout.is_zst();
+ then {
+ span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value");
+ }
+ }
+}
+
+fn lint_filetype_is_file(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ let ty = cx.tables().expr_ty(&args[0]);
+
+ if !match_type(cx, ty, &paths::FILE_TYPE) {
+ return;
+ }
+
+ let span: Span;
+ let verb: &str;
+ let lint_unary: &str;
+ let help_unary: &str;
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let hir::ExprKind::Unary(op, _) = parent.kind;
+ if op == hir::UnOp::UnNot;
+ then {
+ lint_unary = "!";
+ verb = "denies";
+ help_unary = "";
+ span = parent.span;
+ } else {
+ lint_unary = "";
+ verb = "covers";
+ help_unary = "!";
+ span = expr.span;
+ }
+ }
+ let lint_msg = format!("`{}FileType::is_file()` only {} regular files", lint_unary, verb);
+ let help_msg = format!("use `{}FileType::is_dir()` instead", help_unary);
+ span_lint_and_help(cx, FILETYPE_IS_FILE, span, &lint_msg, None, &help_msg);
+}
+
+fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool {
+ expected.constness == actual.constness
+ && expected.unsafety == actual.unsafety
+ && expected.asyncness == actual.asyncness
+}
--- /dev/null
- hir::ExprKind::Block(ref block, _) => {
- if let Some(expr) = &block.expr {
- check_expression(cx, arg_id, &expr)
- } else {
- (false, false)
- }
- },
+use crate::utils::paths;
+use crate::utils::usage::mutated_variables;
+use crate::utils::{match_qpath, match_trait_method, span_lint};
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
+
+use if_chain::if_chain;
+
+use super::UNNECESSARY_FILTER_MAP;
+
+pub(super) fn lint(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ if !match_trait_method(cx, expr, &paths::ITERATOR) {
+ return;
+ }
+
+ if let hir::ExprKind::Closure(_, _, body_id, ..) = args[1].kind {
+ let body = cx.tcx.hir().body(body_id);
+ let arg_id = body.params[0].pat.hir_id;
+ let mutates_arg =
+ mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id));
+
+ let (mut found_mapping, mut found_filtering) = check_expression(&cx, arg_id, &body.value);
+
+ let mut return_visitor = ReturnVisitor::new(&cx, arg_id);
+ return_visitor.visit_expr(&body.value);
+ found_mapping |= return_visitor.found_mapping;
+ found_filtering |= return_visitor.found_filtering;
+
+ if !found_filtering {
+ span_lint(
+ cx,
+ UNNECESSARY_FILTER_MAP,
+ expr.span,
+ "this `.filter_map` can be written more simply using `.map`",
+ );
+ return;
+ }
+
+ if !found_mapping && !mutates_arg {
+ span_lint(
+ cx,
+ UNNECESSARY_FILTER_MAP,
+ expr.span,
+ "this `.filter_map` can be written more simply using `.filter`",
+ );
+ return;
+ }
+ }
+}
+
+// returns (found_mapping, found_filtering)
+fn check_expression<'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_chain! {
+ if let hir::ExprKind::Path(path) = &args[0].kind;
+ if let Res::Local(ref local) = cx.qpath_res(path, args[0].hir_id);
+ then {
+ if arg_id == *local {
+ return (false, false)
+ }
+ }
+ }
+ return (true, false);
+ } else {
+ // We don't know. It might do anything.
+ return (true, true);
+ }
+ }
+ }
+ (true, true)
+ },
++ hir::ExprKind::Block(ref block, _) => 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)
+ },
+ 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<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
- #[derive(PartialEq, Eq, Debug)]
+use crate::consts::{constant_simple, Constant};
+use crate::utils::{match_def_path, paths, span_lint};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use std::cmp::Ordering;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for expressions where `std::cmp::min` and `max` are
+ /// used to clamp values, but switched so that the result is constant.
+ ///
+ /// **Why is this bad?** This is in all probability not the intended outcome. At
+ /// the least it hurts readability of the code.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```ignore
+ /// min(0, max(100, x))
+ /// ```
+ /// It will always be equal to `0`. Probably the author meant to clamp the value
+ /// between 0 and 100, but has erroneously swapped `min` and `max`.
+ pub MIN_MAX,
+ correctness,
+ "`min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant"
+}
+
+declare_lint_pass!(MinMaxPass => [MIN_MAX]);
+
+impl<'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.tables().expr_ty(ie), &outer_c, &inner_c),
+ ) {
+ (_, None) | (MinMax::Max, Some(Ordering::Less)) | (MinMax::Min, Some(Ordering::Greater)) => (),
+ _ => {
+ span_lint(
+ cx,
+ MIN_MAX,
+ expr.span,
+ "this `min`/`max` combination leads to constant result",
+ );
+ },
+ }
+ }
+ }
+ }
+}
+
- if let Some(c) = constant_simple(cx, cx.tables(), &args[0]) {
- if constant_simple(cx, cx.tables(), &args[1]).is_none() {
- // otherwise ignore
- Some((m, c, &args[1]))
- } else {
- None
- }
- } else if let Some(c) = constant_simple(cx, cx.tables(), &args[1]) {
- Some((m, c, &args[0]))
- } else {
- None
- }
++#[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>)> {
+ if let ExprKind::Call(ref path, ref args) = expr.kind {
+ if let ExprKind::Path(ref qpath) = path.kind {
+ cx.tables()
+ .qpath_res(qpath, path.hir_id)
+ .opt_def_id()
+ .and_then(|def_id| {
+ if match_def_path(cx, def_id, &paths::CMP_MIN) {
+ fetch_const(cx, args, MinMax::Min)
+ } else if match_def_path(cx, def_id, &paths::CMP_MAX) {
+ fetch_const(cx, args, MinMax::Max)
+ } else {
+ None
+ }
+ })
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+}
+
+fn fetch_const<'a>(cx: &LateContext<'_>, args: &'a [Expr<'a>], m: MinMax) -> Option<(MinMax, Constant, &'a Expr<'a>)> {
+ if args.len() != 2 {
+ return None;
+ }
++ constant_simple(cx, cx.tables(), &args[0]).map_or_else(
++ || constant_simple(cx, cx.tables(), &args[1]).map(|c| (m, c, &args[0])),
++ |c| {
++ if constant_simple(cx, cx.tables(), &args[1]).is_none() {
++ // otherwise ignore
++ Some((m, c, &args[1]))
++ } else {
++ None
++ }
++ },
++ )
+}
--- /dev/null
- def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt, StmtKind, Ty,
- TyKind, UnOp,
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
- use rustc_middle::ty;
++ self as hir, def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt,
++ StmtKind, TyKind, UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass};
- check_to_owned(cx, left, right);
- check_to_owned(cx, right, left);
++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 crate::consts::{constant, Constant};
+use crate::utils::sugg::Sugg;
+use crate::utils::{
+ get_item_name, get_parent_expr, higher, implements_trait, in_constant, is_integer_const, iter_input_pats,
+ last_path_segment, match_qpath, match_trait_method, paths, snippet, snippet_opt, span_lint, span_lint_and_sugg,
+ span_lint_and_then, span_lint_hir_and_then, walk_ptrs_ty, SpanlessEq,
+};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for function arguments and let bindings denoted as
+ /// `ref`.
+ ///
+ /// **Why is this bad?** The `ref` declaration makes the function take an owned
+ /// value, but turns the argument into a reference (which means that the value
+ /// is destroyed when exiting the function). This adds not much value: either
+ /// take a reference type, or take an owned value and create references in the
+ /// body.
+ ///
+ /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The
+ /// type of `x` is more obvious with the former.
+ ///
+ /// **Known problems:** If the argument is dereferenced within the function,
+ /// removing the `ref` will lead to errors. This can be fixed by removing the
+ /// dereferences, e.g., changing `*x` to `x` within the function.
+ ///
+ /// **Example:**
+ /// ```rust,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 = 0.01f64; // Use an epsilon for comparison
+ /// if (y - 1.23f64).abs() < error { }
+ /// if (y - x).abs() > error { }
+ /// ```
+ pub FLOAT_CMP,
+ correctness,
+ "using `==` or `!=` on float values instead of comparing difference with an epsilon"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for conversions to owned values just for the sake
+ /// of a comparison.
+ ///
+ /// **Why is this bad?** The comparison can operate on a reference, so creating
+ /// an owned value effectively throws it away directly afterwards, which is
+ /// needlessly consuming code and heap space.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let x = "foo";
+ /// # let y = String::from("foo");
+ /// if x.to_owned() == y {}
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// # let x = "foo";
+ /// # let y = String::from("foo");
+ /// if x == y {}
+ /// ```
+ pub CMP_OWNED,
+ perf,
+ "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for getting the remainder of a division by one.
+ ///
+ /// **Why is this bad?** The result can only ever be zero. No one will write
+ /// such code deliberately, unless trying to win an Underhanded Rust
+ /// Contest. Even for that contest, it's probably a bad idea. Use something more
+ /// underhanded.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let x = 1;
+ /// let a = x % 1;
+ /// ```
+ pub MODULO_ONE,
+ correctness,
+ "taking a number modulo 1, which always returns 0"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the use of bindings with a single leading
+ /// underscore.
+ ///
+ /// **Why is this bad?** A single leading underscore is usually used to indicate
+ /// that a binding will not be used. Using such a binding breaks this
+ /// expectation.
+ ///
+ /// **Known problems:** The lint does not work properly with desugaring and
+ /// macro, it has been allowed in the mean time.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let _x = 0;
+ /// let y = _x + 1; // Here we are using `_x`, even though it has a leading
+ /// // underscore. We should rename `_x` to `x`
+ /// ```
+ pub USED_UNDERSCORE_BINDING,
+ pedantic,
+ "using a binding which is prefixed with an underscore"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the use of short circuit boolean conditions as
+ /// a
+ /// statement.
+ ///
+ /// **Why is this bad?** Using a short circuit boolean condition as a statement
+ /// may hide the fact that the second part is executed or not depending on the
+ /// outcome of the first part.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// f() && g(); // We should write `if f() { g(); }`.
+ /// ```
+ pub SHORT_CIRCUIT_STATEMENT,
+ complexity,
+ "using a short circuit boolean condition as a statement"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Catch casts from `0` to some pointer type
+ ///
+ /// **Why is this bad?** This generally means `null` and is better expressed as
+ /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // Bad
+ /// let a = 0 as *const u32;
+ ///
+ /// // Good
+ /// let a = std::ptr::null::<u32>();
+ /// ```
+ pub ZERO_PTR,
+ style,
+ "using `0 as *{const, mut} T`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for (in-)equality comparisons on floating-point
+ /// value and constant, except in functions called `*eq*` (which probably
+ /// implement equality for a type involving floats).
+ ///
+ /// **Why is this bad?** Floating point calculations are usually imprecise, so
+ /// asking if two values are *exactly* equal is asking for trouble. For a good
+ /// guide on what to do, see [the floating point
+ /// guide](http://www.floating-point-gui.de/errors/comparison).
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let x: f64 = 1.0;
+ /// const ONE: f64 = 1.00;
+ ///
+ /// // Bad
+ /// if x == ONE { } // where both are floats
+ ///
+ /// // Good
+ /// let error = 0.1f64; // Use an epsilon for comparison
+ /// if (x - ONE).abs() < error { }
+ /// ```
+ 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,
+ _: HirId,
+ ) {
+ if let FnKind::Closure(_) = k {
+ // Does not apply to closures
+ 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 let StmtKind::Local(ref local) = stmt.kind;
+ if let PatKind::Binding(an, .., name, None) = local.pat.kind;
+ if let Some(ref init) = local.init;
+ if !higher::is_from_for_desugar(local);
+ then {
+ if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut {
+ let sugg_init = if init.span.from_expansion() {
+ Sugg::hir_with_macro_callsite(cx, init, "..")
+ } else {
+ Sugg::hir(cx, init, "..")
+ };
+ let (mutopt, initref) = if an == BindingAnnotation::RefMut {
+ ("mut ", sugg_init.mut_addr())
+ } else {
+ ("", sugg_init.addr())
+ };
+ let tyopt = if let Some(ref ty) = local.ty {
+ format!(": &{mutopt}{ty}", mutopt=mutopt, ty=snippet(cx, ty.span, "_"))
+ } else {
+ String::new()
+ };
+ span_lint_hir_and_then(
+ cx,
+ TOPLEVEL_REF_ARG,
+ init.hir_id,
+ local.pat.span,
+ "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead",
+ |diag| {
+ diag.span_suggestion(
+ stmt.span,
+ "try",
+ format!(
+ "let {name}{tyopt} = {initref};",
+ name=snippet(cx, name.span, "_"),
+ tyopt=tyopt,
+ initref=initref,
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ );
+ }
+ }
+ };
+ if_chain! {
+ if let StmtKind::Semi(ref expr) = stmt.kind;
+ if let ExprKind::Binary(ref binop, ref a, ref b) = expr.kind;
+ if binop.node == BinOpKind::And || binop.node == BinOpKind::Or;
+ if let Some(sugg) = Sugg::hir_opt(cx, a);
+ then {
+ span_lint_and_then(cx,
+ SHORT_CIRCUIT_STATEMENT,
+ stmt.span,
+ "boolean short circuit operator in statement may be clearer using an explicit test",
+ |diag| {
+ let sugg = if binop.node == BinOpKind::Or { !sugg } else { sugg };
+ diag.span_suggestion(
+ stmt.span,
+ "replace it with",
+ format!(
+ "if {} {{ {}; }}",
+ sugg,
+ &snippet(cx, b.span, ".."),
+ ),
+ Applicability::MachineApplicable, // snippet
+ );
+ });
+ }
+ };
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ match expr.kind {
+ ExprKind::Cast(ref e, ref ty) => {
+ check_cast(cx, expr.span, e, ty);
+ return;
+ },
+ ExprKind::Binary(ref cmp, ref left, ref right) => {
+ let op = cmp.node;
+ if op.is_comparison() {
+ check_nan(cx, left, expr);
+ check_nan(cx, right, expr);
- fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &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 error",
+ format!(
+ "({}).abs() {} error",
+ lhs - rhs,
+ if op == BinOpKind::Eq { '<' } else { '>' }
+ ),
+ Applicability::HasPlaceholders, // snippet
+ );
+ }
+ diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error`");
+ });
+ } else if op == BinOpKind::Rem && is_integer_const(cx, right, 1) {
+ span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
+ }
+ },
+ _ => {},
+ }
+ if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
+ // Don't lint things expanded by #[derive(...)], etc or `await` desugaring
+ return;
+ }
+ let binding = match expr.kind {
+ ExprKind::Path(ref qpath) => {
+ let binding = last_path_segment(qpath).ident.as_str();
+ if binding.starts_with('_') &&
+ !binding.starts_with("__") &&
+ binding != "_result" && // FIXME: #944
+ is_used(cx, expr) &&
+ // don't lint if the declaration is in a macro
+ non_macro_local(cx, cx.qpath_res(qpath, expr.hir_id))
+ {
+ Some(binding)
+ } else {
+ None
+ }
+ },
+ ExprKind::Field(_, ident) => {
+ let name = ident.as_str();
+ if name.starts_with('_') && !name.starts_with("__") {
+ Some(name)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ };
+ if let Some(binding) = binding {
+ span_lint(
+ cx,
+ USED_UNDERSCORE_BINDING,
+ expr.span,
+ &format!(
+ "used binding `{}` which is prefixed with an underscore. A leading \
+ underscore signals that a binding will not be used.",
+ binding
+ ),
+ );
+ }
+ }
+}
+
+fn get_lint_and_message(
+ is_comparing_constants: bool,
+ is_comparing_arrays: bool,
+) -> (&'static rustc_lint::Lint, &'static str) {
+ if is_comparing_constants {
+ (
+ FLOAT_CMP_CONST,
+ if is_comparing_arrays {
+ "strict comparison of `f32` or `f64` constant arrays"
+ } else {
+ "strict comparison of `f32` or `f64` constant"
+ },
+ )
+ } else {
+ (
+ FLOAT_CMP,
+ if is_comparing_arrays {
+ "strict comparison of `f32` or `f64` arrays"
+ } else {
+ "strict comparison of `f32` or `f64`"
+ },
+ )
+ }
+}
+
+fn check_nan(cx: &LateContext<'_>, expr: &Expr<'_>, cmp_expr: &Expr<'_>) {
+ if_chain! {
+ if !in_constant(cx, cmp_expr.hir_id);
+ if let Some((value, _)) = constant(cx, cx.tables(), expr);
+ then {
+ let needs_lint = match value {
+ Constant::F32(num) => num.is_nan(),
+ Constant::F64(num) => num.is_nan(),
+ _ => false,
+ };
+
+ if needs_lint {
+ span_lint(
+ cx,
+ CMP_NAN,
+ cmp_expr.span,
+ "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead",
+ );
+ }
+ }
+ }
+}
+
+fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ if let Some((_, res)) = constant(cx, cx.tables(), expr) {
+ res
+ } else {
+ false
+ }
+}
+
+fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ match constant(cx, cx.tables(), expr) {
+ Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(),
+ Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(),
+ Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f {
+ Constant::F32(f) => *f == 0.0 || (*f).is_infinite(),
+ Constant::F64(f) => *f == 0.0 || (*f).is_infinite(),
+ _ => false,
+ }),
+ _ => false,
+ }
+}
+
+// Return true if `expr` is the result of `signum()` invoked on a float value.
+fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ // The negation of a signum is still a signum
+ if let ExprKind::Unary(UnOp::UnNeg, ref child_expr) = expr.kind {
+ return is_signum(cx, &child_expr);
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(ref method_name, _, ref expressions, _) = expr.kind;
+ if sym!(signum) == method_name.ident.name;
+ // Check that the receiver of the signum() is a float (expressions[0] is the receiver of
+ // the method call)
+ then {
+ return is_float(cx, &expressions[0]);
+ }
+ }
+ false
+}
+
+fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let value = &walk_ptrs_ty(cx.tables().expr_ty(expr)).kind;
+
+ if let ty::Array(arr_ty, _) = value {
+ return matches!(arr_ty.kind, ty::Float(_));
+ };
+
+ matches!(value, ty::Float(_))
+}
+
+fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ matches!(&walk_ptrs_ty(cx.tables().expr_ty(expr)).kind, ty::Array(_, _))
+}
+
- (cx.tables().expr_ty_adjusted(&args[0]), snippet(cx, args[0].span, ".."))
++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<EqImpl> {
++ 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 match_trait_method(cx, expr, &paths::TO_STRING) || match_trait_method(cx, expr, &paths::TO_OWNED) {
- (cx.tables().expr_ty_adjusted(&v[0]), snippet(cx, v[0].span, ".."))
++ (cx.tables().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"]) {
- let other_ty = cx.tables().expr_ty_adjusted(other);
- let partial_eq_trait_id = match cx.tcx.lang_items().eq_trait() {
- Some(id) => id,
- None => return,
- };
++ (cx.tables().expr_ty(&v[0]), snippet(cx, v[0].span, ".."))
+ } else {
+ return;
+ }
+ } else {
+ return;
+ }
+ },
+ _ => return,
+ };
+
- let deref_arg_impl_partial_eq_other = arg_ty.builtin_deref(true).map_or(false, |tam| {
- implements_trait(cx, tam.ty, partial_eq_trait_id, &[other_ty.into()])
- });
- let arg_impl_partial_eq_deref_other = other_ty.builtin_deref(true).map_or(false, |tam| {
- implements_trait(cx, arg_ty, partial_eq_trait_id, &[tam.ty.into()])
- });
- let arg_impl_partial_eq_other = implements_trait(cx, arg_ty, partial_eq_trait_id, &[other_ty.into()]);
++ let other_ty = cx.tables().expr_ty(other);
+
- if !deref_arg_impl_partial_eq_other && !arg_impl_partial_eq_deref_other && !arg_impl_partial_eq_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();
+
- let other_gets_derefed = match other.kind {
- ExprKind::Unary(UnOp::UnDeref, _) => true,
- _ => false,
- };
++ if !with_deref.is_implemented() && !without_deref.is_implemented() {
+ return;
+ }
+
- let try_hint = if deref_arg_impl_partial_eq_other {
- // suggest deref on the left
- format!("*{}", snip)
++ let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::UnDeref, _));
+
+ 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;
+ }
+
- // suggest dropping the to_owned on the left
- snip.to_string()
++ let expr_snip;
++ let eq_impl;
++ if with_deref.is_implemented() {
++ expr_snip = format!("*{}", snip);
++ eq_impl = with_deref;
+ } else {
- lint_span,
++ 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(
- try_hint,
++ span,
+ "try",
- if let Some(parent) = get_parent_expr(cx, expr) {
- match parent.kind {
- ExprKind::Assign(_, ref rhs, _) | ExprKind::AssignOp(_, _, ref rhs) => {
- SpanlessEq::new(cx).eq_expr(rhs, expr)
- },
- _ => is_used(cx, parent),
- }
- } else {
- true
- }
++ hint,
+ Applicability::MachineApplicable, // snippet
+ );
+ },
+ );
+}
+
+/// Heuristic to see if an expression is used. Should be compatible with
+/// `unused_variables`'s idea
+/// of what it means for an expression to be "used".
+fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
-
- if let ExpnKind::Macro(MacroKind::Attr, _) = data.kind {
- true
- } else {
- false
- }
++ 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();
- fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &Ty<'_>) {
++ 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);
+ }
+ }
+}
--- /dev/null
- #[allow(clippy::trivially_copy_pass_by_ref)]
- fn is_wild<P: std::ops::Deref<Target = Pat>>(pat: &&P) -> bool {
- if let PatKind::Wild = pat.kind {
- true
- } else {
- false
- }
- }
-
+use crate::utils::{
+ constants, snippet_opt, snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg,
+ span_lint_and_then,
+};
+use if_chain::if_chain;
+use rustc_ast::ast::{
+ BindingMode, Block, Expr, ExprKind, GenericParamKind, Generics, Lit, LitFloatType, LitIntType, LitKind, Mutability,
+ NodeId, Pat, PatKind, StmtKind, UnOp,
+};
+use rustc_ast::visit::{walk_expr, FnKind, Visitor};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for structure field patterns bound to wildcards.
+ ///
+ /// **Why is this bad?** Using `..` instead is shorter and leaves the focus on
+ /// the fields that are actually bound.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```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 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_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 <stdio.h>
+ ///
+ /// 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<u32> Foo<u32> {
+ /// 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,
+ REDUNDANT_CLOSURE_CALL,
+ DOUBLE_NEG,
+ MIXED_CASE_HEX_LITERALS,
+ UNSEPARATED_LITERAL_SUFFIX,
+ ZERO_PREFIXED_LITERAL,
+ BUILTIN_TYPE_SHADOW,
+ REDUNDANT_PATTERN,
+ UNNEEDED_WILDCARD_PATTERN,
+]);
+
+// Used to find `return` statements or equivalents e.g., `?`
+struct ReturnVisitor {
+ found_return: bool,
+}
+
+impl ReturnVisitor {
+ #[must_use]
+ fn new() -> Self {
+ Self { found_return: false }
+ }
+}
+
+impl<'ast> Visitor<'ast> for ReturnVisitor {
+ fn visit_expr(&mut self, ex: &'ast Expr) {
+ if let ExprKind::Ret(_) = ex.kind {
+ self.found_return = true;
+ } else if let ExprKind::Try(_) = ex.kind {
+ self.found_return = true;
+ }
+
+ walk_expr(self, ex)
+ }
+}
+
+impl EarlyLintPass for MiscEarlyLints {
+ fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
+ for param in &gen.params {
+ if let GenericParamKind::Type { .. } = param.kind {
+ let name = param.ident.as_str();
+ if constants::BUILTIN_TYPES.contains(&&*name) {
+ span_lint(
+ cx,
+ BUILTIN_TYPE_SHADOW,
+ param.ident.span,
+ &format!("This generic shadows the built-in type `{}`", name),
+ );
+ }
+ }
+ }
+ }
+
+ fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
+ if let PatKind::Struct(ref npat, ref pfields, _) = pat.kind {
+ let mut wilds = 0;
+ let type_name = npat
+ .segments
+ .last()
+ .expect("A path must have at least one segment")
+ .ident
+ .name;
+
+ for field in pfields {
+ if let PatKind::Wild = field.pat.kind {
+ wilds += 1;
+ }
+ }
+ if !pfields.is_empty() && wilds == pfields.len() {
+ span_lint_and_help(
+ cx,
+ UNNEEDED_FIELD_PATTERN,
+ pat.span,
+ "All the struct fields are matched to a wildcard pattern, consider using `..`.",
+ None,
+ &format!("Try with `{} {{ .. }}` instead", type_name),
+ );
+ return;
+ }
+ if wilds > 0 {
+ for field in pfields {
+ if let PatKind::Wild = field.pat.kind {
+ wilds -= 1;
+ if wilds > 0 {
+ span_lint(
+ cx,
+ UNNEEDED_FIELD_PATTERN,
+ field.span,
+ "You matched a field with a wildcard pattern. Consider using `..` instead",
+ );
+ } else {
+ let mut normal = vec![];
+
+ for field in pfields {
+ match field.pat.kind {
+ PatKind::Wild => {},
+ _ => {
+ if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) {
+ normal.push(n);
+ }
+ },
+ }
+ }
+
+ span_lint_and_help(
+ cx,
+ UNNEEDED_FIELD_PATTERN,
+ field.span,
+ "You matched a field with a wildcard pattern. Consider using `..` \
+ instead",
+ None,
+ &format!("Try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
+ );
+ }
+ }
+ }
+ }
+ }
+
+ if let PatKind::Ident(left, ident, Some(ref right)) = pat.kind {
+ let left_binding = match left {
+ BindingMode::ByRef(Mutability::Mut) => "ref mut ",
+ BindingMode::ByRef(Mutability::Not) => "ref ",
+ 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<String, Span> = FxHashMap::default();
+
+ for arg in &fn_kind.decl().inputs {
+ if let PatKind::Ident(_, ident, None) = arg.pat.kind {
+ let arg_name = ident.to_string();
+
+ if arg_name.starts_with('_') {
+ if let Some(correspondence) = registered_names.get(&arg_name[1..]) {
+ span_lint(
+ cx,
+ DUPLICATE_UNDERSCORE_ARGUMENT,
+ *correspondence,
+ &format!(
+ "`{}` already exists, having another argument having almost the same \
+ name makes code comprehension and documentation more difficult",
+ arg_name[1..].to_owned()
+ ),
+ );
+ }
+ } else {
+ registered_names.insert(arg_name, arg.pat.span);
+ }
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+ match expr.kind {
+ ExprKind::Call(ref paren, _) => {
+ if let ExprKind::Paren(ref closure) = paren.kind {
+ if let ExprKind::Closure(_, _, _, ref decl, ref block, _) = closure.kind {
+ let mut visitor = ReturnVisitor::new();
+ visitor.visit_expr(block);
+ if !visitor.found_return {
+ span_lint_and_then(
+ cx,
+ REDUNDANT_CLOSURE_CALL,
+ expr.span,
+ "Try not to call a closure in the expression where it is declared.",
+ |diag| {
+ if decl.inputs.is_empty() {
+ let mut app = Applicability::MachineApplicable;
+ let hint =
+ snippet_with_applicability(cx, block.span, "..", &mut app).into_owned();
+ diag.span_suggestion(expr.span, "Try doing something like: ", hint, app);
+ }
+ },
+ );
+ }
+ }
+ }
+ },
+ ExprKind::Unary(UnOp::Neg, ref inner) => {
+ if let ExprKind::Unary(UnOp::Neg, _) = inner.kind {
+ span_lint(
+ cx,
+ DOUBLE_NEG,
+ expr.span,
+ "`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op",
+ );
+ }
+ },
+ ExprKind::Lit(ref lit) => Self::check_lit(cx, lit),
+ _ => (),
+ }
+ }
+
+ fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
+ for w in block.stmts.windows(2) {
+ if_chain! {
+ if let StmtKind::Local(ref local) = w[0].kind;
+ if let Option::Some(ref t) = local.init;
+ if let ExprKind::Closure(..) = t.kind;
+ if let PatKind::Ident(_, ident, _) = local.pat.kind;
+ if let StmtKind::Semi(ref second) = w[1].kind;
+ if let ExprKind::Assign(_, ref call, _) = second.kind;
+ if let ExprKind::Call(ref closure, _) = call.kind;
+ if let ExprKind::Path(_, ref path) = closure.kind;
+ then {
+ if ident == path.segments[0].ident {
+ span_lint(
+ cx,
+ REDUNDANT_CLOSURE_CALL,
+ second.span,
+ "Closure called just once immediately after it was declared",
+ );
+ }
+ }
+ }
+ }
+ }
+}
+
+impl MiscEarlyLints {
+ fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
+ // We test if first character in snippet is a number, because the snippet could be an expansion
+ // from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`.
+ // Note that this check also covers special case that `line!()` is eagerly expanded by compiler.
+ // See <https://github.com/rust-lang/rust-clippy/issues/4507> 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,
+ );
+ }
+
- .take_while(is_wild)
+ if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) {
+ if let Some((left_index, left_pat)) = patterns[..rest_index]
+ .iter()
+ .rev()
- if let Some((right_index, right_pat)) =
- patterns[rest_index + 1..].iter().take_while(is_wild).enumerate().last()
++ .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,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- cx.tcx.sess.crate_types().iter().any(|t: &CrateType| match t {
- CrateType::Executable => true,
- _ => false,
- })
+use crate::utils::span_lint;
+use rustc_ast::ast;
+use rustc_hir as hir;
+use rustc_lint::{self, LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// **What it does:** it lints if an exported function, method, trait method with default impl,
+ /// or trait method impl is not `#[inline]`.
+ ///
+ /// **Why is this bad?** In general, it is not. Functions can be inlined across
+ /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled,
+ /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates
+ /// might intend for most of the methods in their public API to be able to be inlined across
+ /// crates even when LTO is disabled. For these types of crates, enabling this lint might make
+ /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and
+ /// then opt out for specific methods where this might not make sense.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// pub fn foo() {} // missing #[inline]
+ /// fn ok() {} // ok
+ /// #[inline] pub fn bar() {} // ok
+ /// #[inline(always)] pub fn baz() {} // ok
+ ///
+ /// pub trait Bar {
+ /// fn bar(); // ok
+ /// fn def_bar() {} // missing #[inline]
+ /// }
+ ///
+ /// struct Baz;
+ /// impl Baz {
+ /// fn private() {} // ok
+ /// }
+ ///
+ /// impl Bar for Baz {
+ /// fn bar() {} // ok - Baz is not exported
+ /// }
+ ///
+ /// pub struct PubBaz;
+ /// impl PubBaz {
+ /// fn private() {} // ok
+ /// pub fn not_ptrivate() {} // missing #[inline]
+ /// }
+ ///
+ /// impl Bar for PubBaz {
+ /// fn bar() {} // missing #[inline]
+ /// fn def_bar() {} // missing #[inline]
+ /// }
+ /// ```
+ pub MISSING_INLINE_IN_PUBLIC_ITEMS,
+ restriction,
+ "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)"
+}
+
+fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[ast::Attribute], sp: Span, desc: &'static str) {
+ let has_inline = attrs.iter().any(|a| a.check_name(sym!(inline)));
+ if !has_inline {
+ span_lint(
+ cx,
+ MISSING_INLINE_IN_PUBLIC_ITEMS,
+ sp,
+ &format!("missing `#[inline]` for {}", desc),
+ );
+ }
+}
+
+fn is_executable(cx: &LateContext<'_>) -> bool {
+ use rustc_session::config::CrateType;
+
++ cx.tcx
++ .sess
++ .crate_types()
++ .iter()
++ .any(|t: &CrateType| matches!(t, CrateType::Executable))
+}
+
+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(cx) {
+ return;
+ }
+
+ if !cx.access_levels.is_exported(it.hir_id) {
+ return;
+ }
+ match it.kind {
+ hir::ItemKind::Fn(..) => {
+ let desc = "a function";
+ check_missing_inline_attrs(cx, &it.attrs, it.span, desc);
+ },
+ hir::ItemKind::Trait(ref _is_auto, ref _unsafe, ref _generics, ref _bounds, trait_items) => {
+ // note: we need to check if the trait is exported so we can't use
+ // `LateLintPass::check_trait_item` here.
+ for tit in trait_items {
+ let tit_ = cx.tcx.hir().trait_item(tit.id);
+ match tit_.kind {
+ hir::TraitItemKind::Const(..) | hir::TraitItemKind::Type(..) => {},
+ hir::TraitItemKind::Fn(..) => {
+ if tit.defaultness.has_value() {
+ // trait method with default body needs inline in case
+ // an impl is not provided
+ let desc = "a default trait method";
+ let item = cx.tcx.hir().expect_trait_item(tit.id.hir_id);
+ check_missing_inline_attrs(cx, &item.attrs, item.span, desc);
+ }
+ },
+ }
+ }
+ },
+ hir::ItemKind::Const(..)
+ | hir::ItemKind::Enum(..)
+ | hir::ItemKind::Mod(..)
+ | hir::ItemKind::Static(..)
+ | hir::ItemKind::Struct(..)
+ | hir::ItemKind::TraitAlias(..)
+ | hir::ItemKind::GlobalAsm(..)
+ | hir::ItemKind::TyAlias(..)
+ | hir::ItemKind::Union(..)
+ | hir::ItemKind::OpaqueTy(..)
+ | hir::ItemKind::ExternCrate(..)
+ | hir::ItemKind::ForeignMod(..)
+ | hir::ItemKind::Impl { .. }
+ | hir::ItemKind::Use(..) => {},
+ };
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ use rustc_middle::ty::{ImplContainer, TraitContainer};
+ if rustc_middle::lint::in_external_macro(cx.sess(), impl_item.span) || is_executable(cx) {
+ return;
+ }
+
+ // If the item being implemented is not exported, then we don't need #[inline]
+ if !cx.access_levels.is_exported(impl_item.hir_id) {
+ return;
+ }
+
+ let desc = match impl_item.kind {
+ hir::ImplItemKind::Fn(..) => "a method",
+ hir::ImplItemKind::Const(..) | hir::ImplItemKind::TyAlias(_) => return,
+ };
+
+ let def_id = cx.tcx.hir().local_def_id(impl_item.hir_id);
+ let trait_def_id = match cx.tcx.associated_item(def_id).container {
+ TraitContainer(cid) => Some(cid),
+ ImplContainer(cid) => cx.tcx.impl_trait_ref(cid).map(|t| t.def_id),
+ };
+
+ if let Some(trait_def_id) = trait_def_id {
+ if trait_def_id.is_local() && !cx.access_levels.is_exported(impl_item.hir_id) {
+ // If a trait is being implemented for an item, and the
+ // trait is not exported, we don't need #[inline]
+ return;
+ }
+ }
+
+ check_missing_inline_attrs(cx, &impl_item.attrs, impl_item.span, desc);
+ }
+}
--- /dev/null
- if impl_item.generics.params.iter().any(|gen| match gen.kind {
- hir::GenericParamKind::Type { .. } => true,
- _ => false,
- }) {
+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};
+
+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<HirIdSet>,
+}
+
+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 {
+ 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().as_local_hir_id(local_def_id));
+ }
+ }
+ });
+ self.impling_types = Some(impls);
+ }
+
+ // Check if a Default implementation exists for the Self type, regardless of
+ // generics
+ if_chain! {
+ if let Some(ref impling_types) = self.impling_types;
+ if let Some(self_def) = cx.tcx.type_of(self_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)
+}
--- /dev/null
- if let Some(i) = adjustments.iter().position(|adj| match adj.kind {
- Adjust::Borrow(_) | Adjust::Deref(_) => true,
- _ => false,
- }) {
+//! Checks for uses of const which the type is not `Freeze` (`Cell`-free).
+//!
+//! This lint is **deny** by default.
+
+use std::ptr;
+
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass, Lint};
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_middle::ty::{Ty, TypeFlags};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{InnerSpan, Span, DUMMY_SP};
+use rustc_typeck::hir_ty_to_ty;
+
+use crate::utils::{in_constant, is_copy, qpath_res, span_lint_and_then};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for declaration of `const` items which is interior
+ /// mutable (e.g., contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.).
+ ///
+ /// **Why is this bad?** Consts are copied everywhere they are referenced, i.e.,
+ /// every time you refer to the const a fresh instance of the `Cell` or `Mutex`
+ /// or `AtomicXxxx` will be created, which defeats the whole purpose of using
+ /// these types in the first place.
+ ///
+ /// The `const` should better be replaced by a `static` item if a global
+ /// variable is wanted, or replaced by a `const fn` if a constructor is wanted.
+ ///
+ /// **Known problems:** A "non-constant" const item is a legacy way to supply an
+ /// initialized value to downstream `static` items (e.g., the
+ /// `std::sync::ONCE_INIT` constant). In this case the use of `const` is legit,
+ /// and this lint should be suppressed.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+ ///
+ /// // Bad.
+ /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
+ /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged
+ /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct
+ ///
+ /// // Good.
+ /// static STATIC_ATOM: AtomicUsize = AtomicUsize::new(15);
+ /// STATIC_ATOM.store(9, SeqCst);
+ /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance
+ /// ```
+ pub DECLARE_INTERIOR_MUTABLE_CONST,
+ correctness,
+ "declaring `const` with interior mutability"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks if `const` items which is interior mutable (e.g.,
+ /// contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.) has been borrowed directly.
+ ///
+ /// **Why is this bad?** Consts are copied everywhere they are referenced, i.e.,
+ /// every time you refer to the const a fresh instance of the `Cell` or `Mutex`
+ /// or `AtomicXxxx` will be created, which defeats the whole purpose of using
+ /// these types in the first place.
+ ///
+ /// The `const` value should be stored inside a `static` item.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+ /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
+ ///
+ /// // Bad.
+ /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged
+ /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct
+ ///
+ /// // Good.
+ /// static STATIC_ATOM: AtomicUsize = CONST_ATOM;
+ /// STATIC_ATOM.store(9, SeqCst);
+ /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance
+ /// ```
+ pub BORROW_INTERIOR_MUTABLE_CONST,
+ correctness,
+ "referencing `const` with interior mutability"
+}
+
+#[allow(dead_code)]
+#[derive(Copy, Clone)]
+enum Source {
+ Item { item: Span },
+ Assoc { item: Span, ty: Span },
+ Expr { expr: Span },
+}
+
+impl Source {
+ #[must_use]
+ fn lint(&self) -> (&'static Lint, &'static str, Span) {
+ match self {
+ Self::Item { item } | Self::Assoc { item, .. } => (
+ DECLARE_INTERIOR_MUTABLE_CONST,
+ "a `const` item should never be interior mutable",
+ *item,
+ ),
+ Self::Expr { expr } => (
+ BORROW_INTERIOR_MUTABLE_CONST,
+ "a `const` item with interior mutability should not be borrowed",
+ *expr,
+ ),
+ }
+ }
+}
+
+fn verify_ty_bound<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, source: Source) {
+ if ty.is_freeze(cx.tcx.at(DUMMY_SP), cx.param_env) || is_copy(cx, ty) {
+ // An `UnsafeCell` is `!Copy`, and an `UnsafeCell` is also the only type which
+ // is `!Freeze`, thus if our type is `Copy` we can be sure it must be `Freeze`
+ // as well.
+ return;
+ }
+
+ let (lint, msg, span) = source.lint();
+ span_lint_and_then(cx, lint, span, msg, |diag| {
+ if span.from_expansion() {
+ return; // Don't give suggestions into macros.
+ }
+ match source {
+ Source::Item { .. } => {
+ let const_kw_span = span.from_inner(InnerSpan::new(0, 5));
+ diag.span_label(const_kw_span, "make this a static item (maybe with lazy_static)");
+ },
+ Source::Assoc { ty: ty_span, .. } => {
+ if ty.flags.intersects(TypeFlags::HAS_FREE_LOCAL_NAMES) {
+ diag.span_label(ty_span, &format!("consider requiring `{}` to be `Copy`", ty));
+ }
+ },
+ Source::Expr { .. } => {
+ diag.help("assign this const to a local or static variable, and use the variable here");
+ },
+ }
+ });
+}
+
+declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST]);
+
+impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
+ if let ItemKind::Const(hir_ty, ..) = &it.kind {
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ verify_ty_bound(cx, ty, Source::Item { item: it.span });
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'_>) {
+ if let TraitItemKind::Const(hir_ty, ..) = &trait_item.kind {
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ verify_ty_bound(
+ cx,
+ ty,
+ Source::Assoc {
+ ty: hir_ty.span,
+ item: trait_item.span,
+ },
+ );
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
+ if let ImplItemKind::Const(hir_ty, ..) = &impl_item.kind {
+ let item_hir_id = cx.tcx.hir().get_parent_node(impl_item.hir_id);
+ let item = cx.tcx.hir().expect_item(item_hir_id);
+ // Ensure the impl is an inherent impl.
+ if let ItemKind::Impl { of_trait: None, .. } = item.kind {
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ verify_ty_bound(
+ cx,
+ ty,
+ Source::Assoc {
+ ty: hir_ty.span,
+ item: impl_item.span,
+ },
+ );
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Path(qpath) = &expr.kind {
+ // Only lint if we use the const item inside a function.
+ if in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ // Make sure it is a const item.
+ match qpath_res(cx, qpath, expr.hir_id) {
+ Res::Def(DefKind::Const | DefKind::AssocConst, _) => {},
+ _ => return,
+ };
+
+ // Climb up to resolve any field access and explicit referencing.
+ let mut cur_expr = expr;
+ let mut dereferenced_expr = expr;
+ let mut needs_check_adjustment = true;
+ loop {
+ let parent_id = cx.tcx.hir().get_parent_node(cur_expr.hir_id);
+ if parent_id == cur_expr.hir_id {
+ break;
+ }
+ if let Some(Node::Expr(parent_expr)) = cx.tcx.hir().find(parent_id) {
+ match &parent_expr.kind {
+ ExprKind::AddrOf(..) => {
+ // `&e` => `e` must be referenced.
+ needs_check_adjustment = false;
+ },
+ ExprKind::Field(..) => {
+ dereferenced_expr = parent_expr;
+ needs_check_adjustment = true;
+ },
+ ExprKind::Index(e, _) if ptr::eq(&**e, cur_expr) => {
+ // `e[i]` => desugared to `*Index::index(&e, i)`,
+ // meaning `e` must be referenced.
+ // no need to go further up since a method call is involved now.
+ needs_check_adjustment = false;
+ break;
+ },
+ ExprKind::Unary(UnOp::UnDeref, _) => {
+ // `*e` => desugared to `*Deref::deref(&e)`,
+ // meaning `e` must be referenced.
+ // no need to go further up since a method call is involved now.
+ needs_check_adjustment = false;
+ break;
+ },
+ _ => break,
+ }
+ cur_expr = parent_expr;
+ } else {
+ break;
+ }
+ }
+
+ let ty = if needs_check_adjustment {
+ let adjustments = cx.tables().expr_adjustments(dereferenced_expr);
++ if let Some(i) = adjustments
++ .iter()
++ .position(|adj| matches!(adj.kind, Adjust::Borrow(_) | Adjust::Deref(_)))
++ {
+ if i == 0 {
+ cx.tables().expr_ty(dereferenced_expr)
+ } else {
+ adjustments[i - 1].target
+ }
+ } else {
+ // No borrow adjustments means the entire const is moved.
+ return;
+ }
+ } else {
+ cx.tables().expr_ty(dereferenced_expr)
+ };
+
+ verify_ty_bound(cx, ty, Source::Expr { expr: expr.span });
+ }
+ }
+}
--- /dev/null
--- /dev/null
++use crate::utils;
++use crate::utils::sugg::Sugg;
++use crate::utils::{match_type, paths, span_lint_and_sugg};
++use if_chain::if_chain;
++
++use rustc_errors::Applicability;
++use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
++use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, PatKind, UnOp};
++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 usage of `if let Some(v) = ... { y } else { x }` which is more
++ /// idiomatically done with `Option::map_or` (if the else bit is a simple
++ /// expression) or `Option::map_or_else` (if the else bit is a longer
++ /// block).
++ ///
++ /// **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 whether the block is just an expression or if it has
++ /// more statements to decide whether to use `Option::map_or` or
++ /// `Option::map_or_else`. If you have a single expression which calls
++ /// an expensive function, then it would be more efficient to use
++ /// `Option::map_or_else`, but this lint would suggest `Option::map_or`.
++ ///
++ /// Also, 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<u32> = 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<u32> = 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.to_ident_string() == "ok" && match_type(cx, &cx.tables().expr_ty(&receiver), &paths::RESULT)
++ } else {
++ false
++ }
++}
++
++/// A struct containing information about occurences 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,
++}
++
++struct ReturnBreakContinueMacroVisitor {
++ seen_return_break_continue: bool,
++}
++impl ReturnBreakContinueMacroVisitor {
++ fn new() -> ReturnBreakContinueMacroVisitor {
++ ReturnBreakContinueMacroVisitor {
++ seen_return_break_continue: false,
++ }
++ }
++}
++impl<'tcx> Visitor<'tcx> for ReturnBreakContinueMacroVisitor {
++ type Map = Map<'tcx>;
++ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
++ NestedVisitorMap::None
++ }
++
++ fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
++ if self.seen_return_break_continue {
++ // No need to look farther if we've already seen one of them
++ return;
++ }
++ match &ex.kind {
++ ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
++ self.seen_return_break_continue = true;
++ },
++ // Something special could be done here to handle while or for loop
++ // desugaring, as this will detect a break if there's a while loop
++ // or a for loop inside the expression.
++ _ => {
++ if utils::in_macro(ex.span) {
++ self.seen_return_break_continue = true;
++ } else {
++ rustc_hir::intravisit::walk_expr(self, ex);
++ }
++ },
++ }
++ }
++}
++
++fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
++ let mut recursive_visitor = ReturnBreakContinueMacroVisitor::new();
++ recursive_visitor.visit_expr(expression);
++ recursive_visitor.seen_return_break_continue
++}
++
++/// 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 curcly 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| {
++ if let Some(Expr {
++ kind:
++ ExprKind::Match(
++ _,
++ arms,
++ MatchSource::IfDesugar {
++ contains_else_clause: true,
++ }
++ | MatchSource::IfLetDesugar {
++ contains_else_clause: true,
++ },
++ ),
++ ..
++ }) = parent.expr
++ {
++ expr.hir_id == arms[1].body.hir_id
++ } else {
++ false
++ }
++ })
++}
++
++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(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<OptionIfLetElseOccurence> {
++ 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 !contains_return_break_continue_macro(arms[0].body);
++ if !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 = match &none_body.kind {
++ ExprKind::Block(..) => "map_or_else",
++ _ => "map_or",
++ };
++ 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::UnDeref, 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<'a> LateLintPass<'a> for OptionIfLetElse {
++ fn check_expr(&mut self, cx: &LateContext<'a>, expr: &Expr<'_>) {
++ 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,
++ );
++ }
++ }
++}
--- /dev/null
--- /dev/null
++use crate::utils::{last_path_segment, span_lint_and_help};
++use rustc_hir::{
++ intravisit, Body, Expr, ExprKind, FieldPat, 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 `&<pattern>`
++ /// or `&mut <pattern>` 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 <variable>` or `ref <variable>` 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.tables().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.tables().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.tables().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<Item = Ty<'tcx>>,
++{
++ 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: &[FieldPat<'_>],
++ 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,
++ }
++}
--- /dev/null
- match op {
- BitXor | BitAnd | BitOr | Shl | Shr => true,
- _ => false,
- }
+use crate::utils::{snippet_with_applicability, span_lint_and_sugg};
+use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+
+const 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, ref rhs) = expr.kind {
+ if let ExprKind::MethodCall(ref path_segment, ref args, _) = rhs.kind {
+ let path_segment_str = path_segment.ident.name.as_str();
+ if let Some(slf) = args.first() {
+ if let ExprKind::Lit(ref lit) = slf.kind {
+ match lit.kind {
+ LitKind::Int(..) | LitKind::Float(..) => {
+ if ALLOWED_ODD_FUNCTIONS
+ .iter()
+ .any(|odd_function| **odd_function == *path_segment_str)
+ {
+ return;
+ }
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ PRECEDENCE,
+ expr.span,
+ "unary minus has lower precedence than method call",
+ "consider adding parentheses to clarify your intent",
+ format!(
+ "-({})",
+ snippet_with_applicability(cx, rhs.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ },
+ _ => (),
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fn is_arith_expr(expr: &Expr) -> bool {
+ match expr.kind {
+ ExprKind::Binary(Spanned { node: op, .. }, _, _) => is_arith_op(op),
+ _ => false,
+ }
+}
+
+#[must_use]
+fn is_bit_op(op: BinOpKind) -> bool {
+ use rustc_ast::ast::BinOpKind::{BitAnd, BitOr, BitXor, Shl, Shr};
- match op {
- Add | Sub | Mul | Div | Rem => true,
- _ => false,
- }
++ 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)
+}
--- /dev/null
- /// **Known problems:** None.
+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, QPath};
+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 std::cmp::Ordering;
+
+use crate::utils::sugg::Sugg;
+use crate::utils::{get_parent_expr, is_integer_const, snippet, snippet_opt, span_lint, 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`.
+ ///
- complexity,
++ /// **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_lint_pass!(Ranges => [
+ RANGE_ZIP_WITH_LEN,
+ RANGE_PLUS_ONE,
+ RANGE_MINUS_ONE,
+ REVERSED_EMPTY_RANGES,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Ranges {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind {
+ let name = path.ident.as_str();
+ if name == "zip" && args.len() == 2 {
+ let iter = &args[0].kind;
+ let zip_arg = &args[1];
+ if_chain! {
+ // `.iter()` call
+ if let ExprKind::MethodCall(ref iter_path, _, ref iter_args , _) = *iter;
+ if iter_path.ident.name == sym!(iter);
+ // range expression in `.zip()` call: `0..x.len()`
+ if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::range(cx, zip_arg);
+ if is_integer_const(cx, start, 0);
+ // `.len()` call
+ if let ExprKind::MethodCall(ref len_path, _, ref len_args, _) = end.kind;
+ if len_path.ident.name == sym!(len) && len_args.len() == 1;
+ // `.iter()` and `.len()` called on same `Path`
+ if let ExprKind::Path(QPath::Resolved(_, ref iter_path)) = iter_args[0].kind;
+ if let ExprKind::Path(QPath::Resolved(_, ref len_path)) = len_args[0].kind;
+ if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments);
+ then {
+ span_lint(cx,
+ RANGE_ZIP_WITH_LEN,
+ expr.span,
+ &format!("It is more idiomatic to use `{}.iter().enumerate()`",
+ snippet(cx, iter_args[0].span, "_")));
+ }
+ }
+ }
+ }
+
+ check_exclusive_range_plus_one(cx, expr);
+ check_inclusive_range_minus_one(cx, expr);
+ check_reversed_empty_range(cx, expr);
+ }
+}
+
+// exclusive range plus one: `x..(y+1)`
+fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::Range {
+ start,
+ end: Some(end),
+ limits: RangeLimits::HalfOpen
+ }) = higher::range(cx, expr);
+ if let Some(y) = y_plus_one(cx, end);
+ then {
+ let span = if expr.span.from_expansion() {
+ expr.span
+ .ctxt()
+ .outer_expn_data()
+ .call_site
+ } else {
+ expr.span
+ };
+ span_lint_and_then(
+ cx,
+ RANGE_PLUS_ONE,
+ span,
+ "an inclusive range would be more readable",
+ |diag| {
+ let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").to_string());
+ let end = Sugg::hir(cx, y, "y");
+ if let Some(is_wrapped) = &snippet_opt(cx, span) {
+ if is_wrapped.starts_with('(') && is_wrapped.ends_with(')') {
+ diag.span_suggestion(
+ span,
+ "use",
+ format!("({}..={})", start, end),
+ Applicability::MaybeIncorrect,
+ );
+ } else {
+ diag.span_suggestion(
+ span,
+ "use",
+ format!("{}..={}", start, end),
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ },
+ );
+ }
+ }
+}
+
+// inclusive range minus one: `x..=(y-1)`
+fn check_inclusive_range_minus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::Range { start, end: Some(end), limits: RangeLimits::Closed }) = higher::range(cx, expr);
+ if let Some(y) = y_minus_one(cx, end);
+ then {
+ span_lint_and_then(
+ cx,
+ RANGE_MINUS_ONE,
+ expr.span,
+ "an exclusive range would be more readable",
+ |diag| {
+ let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").to_string());
+ let end = Sugg::hir(cx, y, "y");
+ diag.span_suggestion(
+ expr.span,
+ "use",
+ format!("{}..{}", start, end),
+ Applicability::MachineApplicable, // snippet
+ );
+ },
+ );
+ }
+ }
+}
+
+fn 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(cx, expr);
+ let ty = cx.tables().expr_ty(start);
+ if let ty::Int(_) | ty::Uint(_) = ty.kind;
+ if let Some((start_idx, _)) = constant(cx, cx.tables(), start);
+ if let Some((end_idx, _)) = constant(cx, cx.tables(), 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,
+ }
+}
--- /dev/null
- use crate::utils::{is_expn_of, match_def_path, match_type, paths, span_lint, span_lint_and_help};
+use crate::consts::{constant, Constant};
- use rustc_hir::{Block, BorrowKind, Crate, Expr, ExprKind, HirId};
++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;
- declare_clippy_lint! {
- /// **What it does:** Checks for usage of `regex!(_)` which (as of now) is
- /// usually slower than `Regex::new(_)` unless called in a loop (which is a bad
- /// idea anyway).
- ///
- /// **Why is this bad?** Performance, at least for now. The macro version is
- /// likely to catch up long-term, but for now the dynamic version is faster.
- ///
- /// **Known problems:** None.
- ///
- /// **Example:**
- /// ```ignore
- /// regex!("foo|bar")
- /// ```
- pub REGEX_MACRO,
- style,
- "use of `regex!(_)` instead of `Regex::new(_)`"
- }
-
++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:** None.
+ ///
+ /// **Example:**
+ /// ```ignore
+ /// Regex::new("^foobar")
+ /// ```
+ pub TRIVIAL_REGEX,
+ style,
+ "trivial regular expressions"
+}
+
- impl_lint_pass!(Regex => [INVALID_REGEX, REGEX_MACRO, TRIVIAL_REGEX]);
+#[derive(Clone, Default)]
+pub struct Regex {
+ spans: FxHashSet<Span>,
+ last: Option<HirId>,
+}
+
- fn check_crate(&mut self, _: &LateContext<'tcx>, _: &'tcx Crate<'_>) {
- self.spans.clear();
- }
-
- fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
- if_chain! {
- if self.last.is_none();
- if let Some(ref expr) = block.expr;
- if match_type(cx, cx.tables().expr_ty(expr), &paths::REGEX);
- if let Some(span) = is_expn_of(expr.span, "regex");
- then {
- if !self.spans.contains(&span) {
- span_lint(
- cx,
- REGEX_MACRO,
- span,
- "`regex!(_)` found. \
- Please use `Regex::new(_)`, which is faster for now."
- );
- self.spans.insert(span);
- }
- self.last = Some(block.hir_id);
- }
- }
- }
-
- fn check_block_post(&mut self, _: &LateContext<'tcx>, block: &'tcx Block<'_>) {
- if self.last.map_or(false, |id| block.hir_id == id) {
- self.last = None;
- }
- }
-
++impl_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX]);
+
+impl<'tcx> LateLintPass<'tcx> for Regex {
- let is_literal = |e: &[regex_syntax::hir::Hir]| {
- e.iter().all(|e| match *e.kind() {
- Literal(_) => true,
- _ => false,
- })
- };
+ 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<String> {
+ constant(cx, cx.tables(), e).and_then(|(c, _)| match c {
+ Constant::Str(s) => Some(s),
+ _ => None,
+ })
+}
+
+fn is_trivial_regex(s: ®ex_syntax::hir::Hir) -> Option<&'static str> {
+ use regex_syntax::hir::Anchor::{EndText, StartText};
+ use regex_syntax::hir::HirKind::{Alternation, Anchor, Concat, Empty, Literal};
+
++ let is_literal = |e: &[regex_syntax::hir::Hir]| e.iter().all(|e| 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(utf8)
+ .allow_invalid_utf8(!utf8)
+ .build();
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ if let LitKind::Str(ref r, style) = lit.node {
+ let r = &r.as_str();
+ let offset = if let StrStyle::Raw(n) = style { 2 + n } else { 1 };
+ match parser.parse(r) {
+ Ok(r) => {
+ if let Some(repl) = is_trivial_regex(&r) {
+ span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl);
+ }
+ },
+ Err(regex_syntax::Error::Parse(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ str_span(expr.span, *e.span(), offset),
+ &format!("regex syntax error: {}", e.kind()),
+ );
+ },
+ Err(regex_syntax::Error::Translate(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ str_span(expr.span, *e.span(), offset),
+ &format!("regex syntax error: {}", e.kind()),
+ );
+ },
+ Err(e) => {
+ span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e));
+ },
+ }
+ }
+ } else if let Some(r) = const_str(cx, expr) {
+ match parser.parse(&r) {
+ Ok(r) => {
+ if let Some(repl) = is_trivial_regex(&r) {
+ span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl);
+ }
+ },
+ Err(regex_syntax::Error::Parse(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ expr.span,
+ &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()),
+ );
+ },
+ Err(regex_syntax::Error::Translate(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ expr.span,
+ &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()),
+ );
+ },
+ Err(e) => {
+ span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e));
+ },
+ }
+ }
+}
--- /dev/null
--- /dev/null
++use crate::consts::{constant_context, Constant};
++use crate::utils::{in_macro, is_type_diagnostic_item, snippet, span_lint_and_sugg, walk_ptrs_ty};
++use if_chain::if_chain;
++use rustc_errors::Applicability;
++use rustc_hir::{Expr, ExprKind};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_session::{declare_lint_pass, declare_tool_lint};
++
++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(ref path, _, ref args, _) = expr.kind;
++ if path.ident.name == sym!(repeat);
++ if let Some(Constant::Int(1)) = constant_context(cx, cx.tables()).expr(&args[1]);
++ if !in_macro(args[0].span);
++ then {
++ let ty = walk_ptrs_ty(cx.tables().expr_ty(&args[0]));
++ 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, args[0].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, args[0].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, args[0].span, r#""...""#)),
++ Applicability::MachineApplicable,
++ );
++ }
++ }
++ }
++ }
++}
--- /dev/null
- if let Some(rpos) = fn_source.rfind("->") {
- #[allow(clippy::cast_possible_truncation)]
- (
- ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
- Applicability::MachineApplicable,
- )
- } else {
- (ty.span, Applicability::MaybeIncorrect)
- }
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_ast::visit::FnKind;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::BytePos;
+
+use crate::utils::{snippet_opt, span_lint_and_sugg, span_lint_and_then};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for return statements at the end of a block.
+ ///
+ /// **Why is this bad?** Removing the `return` and semicolon will make the code
+ /// more rusty.
+ ///
+ /// **Known problems:** If the computation returning the value borrows a local
+ /// variable, removing the `return` may run afoul of the borrow checker.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// return x;
+ /// }
+ /// ```
+ /// simplify to
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// x
+ /// }
+ /// ```
+ pub NEEDLESS_RETURN,
+ style,
+ "using a return statement like `return expr;` where an expression would suffice"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for unit (`()`) expressions that can be removed.
+ ///
+ /// **Why is this bad?** Such expressions add no value, but can make the code
+ /// less readable. Depending on formatting they can make a `break` or `return`
+ /// statement look like a function call.
+ ///
+ /// **Known problems:** The lint currently misses unit return types in types,
+ /// e.g., the `F` in `fn generic_unit<F: Fn() -> ()>(f: F) { .. }`.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// fn return_unit() -> () {
+ /// ()
+ /// }
+ /// ```
+ pub UNUSED_UNIT,
+ style,
+ "needless unit expression"
+}
+
+#[derive(PartialEq, Eq, Copy, Clone)]
+enum RetReplacement {
+ Empty,
+ Block,
+}
+
+declare_lint_pass!(Return => [NEEDLESS_RETURN, UNUSED_UNIT]);
+
+impl Return {
+ // Check the final stmt or expr in a block for unnecessary return.
+ fn check_block_return(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
+ if let Some(stmt) = block.stmts.last() {
+ match stmt.kind {
+ ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => {
+ self.check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty);
+ },
+ _ => (),
+ }
+ }
+ }
+
+ // Check the final expression in a block if it's a return.
+ fn check_final_expr(
+ &mut self,
+ cx: &EarlyContext<'_>,
+ expr: &ast::Expr,
+ span: Option<Span>,
+ replacement: RetReplacement,
+ ) {
+ match expr.kind {
+ // simple return is always "bad"
+ ast::ExprKind::Ret(ref inner) => {
+ // allow `#[cfg(a)] return a; #[cfg(b)] return b;`
+ if !expr.attrs.iter().any(attr_is_cfg) {
+ Self::emit_return_lint(
+ cx,
+ span.expect("`else return` is not possible"),
+ inner.as_ref().map(|i| i.span),
+ replacement,
+ );
+ }
+ },
+ // a whole block? check it!
+ ast::ExprKind::Block(ref block, _) => {
+ self.check_block_return(cx, block);
+ },
+ // an if/if let expr, check both exprs
+ // note, if without else is going to be a type checking error anyways
+ // (except for unit type functions) so we don't match it
+ ast::ExprKind::If(_, ref ifblock, Some(ref elsexpr)) => {
+ self.check_block_return(cx, ifblock);
+ self.check_final_expr(cx, elsexpr, None, RetReplacement::Empty);
+ },
+ // a match expr, check all arms
+ ast::ExprKind::Match(_, ref arms) => {
+ for arm in arms {
+ self.check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block);
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn emit_return_lint(cx: &EarlyContext<'_>, ret_span: Span, inner_span: Option<Span>, replacement: RetReplacement) {
+ match inner_span {
+ Some(inner_span) => {
+ if in_external_macro(cx.sess(), inner_span) || inner_span.from_expansion() {
+ return;
+ }
+
+ span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
+ if let Some(snippet) = snippet_opt(cx, inner_span) {
+ diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable);
+ }
+ })
+ },
+ None => match replacement {
+ RetReplacement::Empty => {
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_RETURN,
+ ret_span,
+ "unneeded `return` statement",
+ "remove `return`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ },
+ RetReplacement::Block => {
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_RETURN,
+ ret_span,
+ "unneeded `return` statement",
+ "replace `return` with an empty block",
+ "{}".to_string(),
+ Applicability::MachineApplicable,
+ );
+ },
+ },
+ }
+ }
+}
+
+impl EarlyLintPass for Return {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) {
+ match kind {
+ FnKind::Fn(.., Some(block)) => self.check_block_return(cx, block),
+ FnKind::Closure(_, body) => self.check_final_expr(cx, body, Some(body.span), RetReplacement::Empty),
+ FnKind::Fn(.., None) => {},
+ }
+ if_chain! {
+ if let ast::FnRetTy::Ty(ref ty) = kind.decl().output;
+ if let ast::TyKind::Tup(ref vals) = ty.kind;
+ if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span);
+ then {
+ 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);
+ }
+ }
+ }
+}
+
+fn attr_is_cfg(attr: &ast::Attribute) -> bool {
+ attr.meta_item_list().is_some() && attr.check_name(sym!(cfg))
+}
+
+// get the def site
+#[must_use]
+fn get_def(span: Span) -> Option<Span> {
+ 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())) {
++ fn_source
++ .rfind("->")
++ .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,
+ );
+}
--- /dev/null
- if let Some(var_ty) = var_ty {
- match var_ty.kind {
- ty::Adt(..) => false,
- _ => true,
- }
- } else {
- false
- }
+use crate::reexport::Name;
+use crate::utils::{contains_name, higher, iter_input_pats, snippet, span_lint_and_then};
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
+ Block, Body, Expr, ExprKind, FnDecl, Guard, HirId, Local, MutTy, Pat, PatKind, Path, QPath, StmtKind, Ty, TyKind,
+ UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for bindings that shadow other bindings already in
+ /// scope, while just changing reference level or mutability.
+ ///
+ /// **Why is this bad?** Not much, in fact it's a very common pattern in Rust
+ /// code. Still, some may opt to avoid it in their code base, they can set this
+ /// lint to `Warn`.
+ ///
+ /// **Known problems:** This lint, as the other shadowing related lints,
+ /// currently only catches very simple patterns.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let x = 1;
+ ///
+ /// // 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.
+ ///
+ /// **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<(Name, Span)>) {
+ let len = bindings.len();
+ for stmt in block.stmts {
+ match stmt.kind {
+ StmtKind::Local(ref local) => check_local(cx, local, bindings),
+ StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => check_expr(cx, e, bindings),
+ StmtKind::Item(..) => {},
+ }
+ }
+ if let Some(ref o) = block.expr {
+ check_expr(cx, o, bindings);
+ }
+ bindings.truncate(len);
+}
+
+fn check_local<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>, bindings: &mut Vec<(Name, Span)>) {
+ if in_external_macro(cx.sess(), local.span) {
+ return;
+ }
+ if higher::is_from_for_desugar(local) {
+ return;
+ }
+ let Local {
+ ref pat,
+ ref ty,
+ ref init,
+ span,
+ ..
+ } = *local;
+ if let Some(ref t) = *ty {
+ check_ty(cx, t, bindings)
+ }
+ if let Some(ref o) = *init {
+ check_expr(cx, o, bindings);
+ check_pat(cx, pat, Some(o), span, bindings);
+ } else {
+ check_pat(cx, pat, None, span, bindings);
+ }
+}
+
+fn is_binding(cx: &LateContext<'_>, pat_id: HirId) -> bool {
+ let var_ty = cx.tables().node_type_opt(pat_id);
++ 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<(Name, Span)>,
+) {
+ // TODO: match more stuff / destructuring
+ match pat.kind {
+ PatKind::Binding(.., ident, ref inner) => {
+ let name = ident.name;
+ if is_binding(cx, pat.hir_id) {
+ let mut new_binding = true;
+ for tup in bindings.iter_mut() {
+ if tup.0 == name {
+ lint_shadow(cx, name, span, pat.span, init, tup.1);
+ tup.1 = ident.span;
+ new_binding = false;
+ break;
+ }
+ }
+ if new_binding {
+ bindings.push((name, ident.span));
+ }
+ }
+ if let Some(ref p) = *inner {
+ check_pat(cx, p, init, span, bindings);
+ }
+ },
+ PatKind::Struct(_, pfields, _) => {
+ if let Some(init_struct) = init {
+ if let ExprKind::Struct(_, ref efields, _) = init_struct.kind {
+ for field in pfields {
+ let name = field.ident.name;
+ let efield = efields
+ .iter()
+ .find_map(|f| if f.ident.name == name { Some(&*f.expr) } else { None });
+ check_pat(cx, &field.pat, efield, span, bindings);
+ }
+ } else {
+ for field in pfields {
+ check_pat(cx, &field.pat, init, span, bindings);
+ }
+ }
+ } else {
+ for field in pfields {
+ check_pat(cx, &field.pat, None, span, bindings);
+ }
+ }
+ },
+ PatKind::Tuple(inner, _) => {
+ if let Some(init_tup) = init {
+ if let ExprKind::Tup(ref tup) = init_tup.kind {
+ for (i, p) in inner.iter().enumerate() {
+ check_pat(cx, p, Some(&tup[i]), p.span, bindings);
+ }
+ } else {
+ for p in inner {
+ check_pat(cx, p, init, span, bindings);
+ }
+ }
+ } else {
+ for p in inner {
+ check_pat(cx, p, None, span, bindings);
+ }
+ }
+ },
+ PatKind::Box(ref inner) => {
+ if let Some(initp) = init {
+ if let ExprKind::Box(ref inner_init) = initp.kind {
+ check_pat(cx, inner, Some(&**inner_init), span, bindings);
+ } else {
+ check_pat(cx, inner, init, span, bindings);
+ }
+ } else {
+ check_pat(cx, inner, init, span, bindings);
+ }
+ },
+ PatKind::Ref(ref inner, _) => check_pat(cx, inner, init, span, bindings),
+ // PatVec(Vec<P<Pat>>, Option<P<Pat>>, Vec<P<Pat>>),
+ _ => (),
+ }
+}
+
+fn lint_shadow<'tcx>(
+ cx: &LateContext<'tcx>,
+ name: Name,
+ span: Span,
+ pattern_span: Span,
+ init: Option<&'tcx Expr<'_>>,
+ prev_span: Span,
+) {
+ if let Some(expr) = init {
+ if is_self_shadow(name, expr) {
+ span_lint_and_then(
+ cx,
+ SHADOW_SAME,
+ span,
+ &format!(
+ "`{}` is shadowed by itself in `{}`",
+ snippet(cx, pattern_span, "_"),
+ snippet(cx, expr.span, "..")
+ ),
+ |diag| {
+ diag.span_note(prev_span, "previous binding is here");
+ },
+ );
+ } else if contains_name(name, expr) {
+ span_lint_and_then(
+ cx,
+ SHADOW_REUSE,
+ pattern_span,
+ &format!(
+ "`{}` is shadowed by `{}` which reuses the original value",
+ snippet(cx, pattern_span, "_"),
+ snippet(cx, expr.span, "..")
+ ),
+ |diag| {
+ diag.span_note(expr.span, "initialization happens here");
+ diag.span_note(prev_span, "previous binding is here");
+ },
+ );
+ } else {
+ span_lint_and_then(
+ cx,
+ SHADOW_UNRELATED,
+ pattern_span,
+ &format!(
+ "`{}` is shadowed by `{}`",
+ snippet(cx, pattern_span, "_"),
+ snippet(cx, expr.span, "..")
+ ),
+ |diag| {
+ diag.span_note(expr.span, "initialization happens here");
+ diag.span_note(prev_span, "previous binding is here");
+ },
+ );
+ }
+ } else {
+ span_lint_and_then(
+ cx,
+ SHADOW_UNRELATED,
+ span,
+ &format!("`{}` shadows a previous declaration", snippet(cx, pattern_span, "_")),
+ |diag| {
+ diag.span_note(prev_span, "previous binding is here");
+ },
+ );
+ }
+}
+
+fn check_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, bindings: &mut Vec<(Name, Span)>) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+ match expr.kind {
+ ExprKind::Unary(_, ref e)
+ | ExprKind::Field(ref e, _)
+ | ExprKind::AddrOf(_, _, ref e)
+ | ExprKind::Box(ref e) => check_expr(cx, e, bindings),
+ ExprKind::Block(ref block, _) | ExprKind::Loop(ref block, _, _) => check_block(cx, block, bindings),
+ // ExprKind::Call
+ // ExprKind::MethodCall
+ ExprKind::Array(v) | ExprKind::Tup(v) => {
+ for e in v {
+ check_expr(cx, e, bindings)
+ }
+ },
+ ExprKind::Match(ref init, arms, _) => {
+ check_expr(cx, init, bindings);
+ let len = bindings.len();
+ for arm in arms {
+ check_pat(cx, &arm.pat, Some(&**init), arm.pat.span, bindings);
+ // This is ugly, but needed to get the right type
+ if let Some(ref guard) = arm.guard {
+ match guard {
+ Guard::If(if_expr) => check_expr(cx, if_expr, bindings),
+ }
+ }
+ check_expr(cx, &arm.body, bindings);
+ bindings.truncate(len);
+ }
+ },
+ _ => (),
+ }
+}
+
+fn check_ty<'tcx>(cx: &LateContext<'tcx>, ty: &'tcx Ty<'_>, bindings: &mut Vec<(Name, Span)>) {
+ match ty.kind {
+ TyKind::Slice(ref sty) => check_ty(cx, sty, bindings),
+ TyKind::Array(ref fty, ref anon_const) => {
+ check_ty(cx, fty, bindings);
+ check_expr(cx, &cx.tcx.hir().body(anon_const.body).value, bindings);
+ },
+ TyKind::Ptr(MutTy { ty: ref mty, .. }) | TyKind::Rptr(_, MutTy { ty: ref mty, .. }) => {
+ check_ty(cx, mty, bindings)
+ },
+ TyKind::Tup(tup) => {
+ for t in tup {
+ check_ty(cx, t, bindings)
+ }
+ },
+ TyKind::Typeof(ref anon_const) => check_expr(cx, &cx.tcx.hir().body(anon_const.body).value, bindings),
+ _ => (),
+ }
+}
+
+fn is_self_shadow(name: Name, expr: &Expr<'_>) -> bool {
+ match expr.kind {
+ ExprKind::Box(ref inner) | ExprKind::AddrOf(_, _, ref inner) => is_self_shadow(name, inner),
+ ExprKind::Block(ref block, _) => {
+ block.stmts.is_empty() && block.expr.as_ref().map_or(false, |e| is_self_shadow(name, e))
+ },
+ ExprKind::Unary(op, ref inner) => (UnOp::UnDeref == op) && is_self_shadow(name, inner),
+ ExprKind::Path(QPath::Resolved(_, ref path)) => path_eq_name(name, path),
+ _ => false,
+ }
+}
+
+fn path_eq_name(name: Name, path: &Path<'_>) -> bool {
+ !path.is_global() && path.segments.len() == 1 && path.segments[0].ident.as_str() == name.as_str()
+}
--- /dev/null
- ExprKind::Path(qpath) => {
- if let Res::Def(DefKind::Const, ..) = cx.qpath_res(qpath, expr.hir_id) {
- true
- } else {
- false
- }
- },
+use crate::utils::{is_adjusted, span_lint};
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for construction of a structure or tuple just to
+ /// assign a value in it.
+ ///
+ /// **Why is this bad?** Readability. If the structure is only created to be
+ /// updated, why not write the structure you want in the first place?
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// (0, 0).0 = 1
+ /// ```
+ pub TEMPORARY_ASSIGNMENT,
+ complexity,
+ "assignments to temporaries"
+}
+
+fn is_temporary(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ match &expr.kind {
+ ExprKind::Struct(..) | ExprKind::Tup(..) => true,
++ ExprKind::Path(qpath) => matches!(cx.qpath_res(qpath, expr.hir_id), Res::Def(DefKind::Const, ..)),
+ _ => false,
+ }
+}
+
+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(cx, base) && !is_adjusted(cx, base) {
+ span_lint(cx, TEMPORARY_ASSIGNMENT, expr.span, "assignment to temporary");
+ }
+ }
+ }
+}
--- /dev/null
- #[derive(Copy, Clone)]
- pub struct TraitBounds;
-
+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::{GenericBound, Generics, WherePredicate};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
- if let WherePredicate::BoundPredicate(ref p) = bound {
+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: T) where T: Copy, T: Clone {}
+ /// ```
+ ///
+ /// Could be written as:
+ ///
+ /// ```rust
+ /// pub fn foo<T>(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: _`"
+}
+
++#[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]);
+
+impl<'tcx> LateLintPass<'tcx> for TraitBounds {
+ fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx 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 let Some(ref v) = map.insert(h, p.bounds.iter().collect::<Vec<_>>()) {
++ 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::<Vec<_>>());
++
++ 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,
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
- if let ExprKind::Match(.., MatchSource::TryDesugar) = &arg.kind {
- false
- } else {
- true
- }
+#![allow(rustc::default_hash_types)]
+
+use std::borrow::Cow;
+use std::cmp::Ordering;
+use std::collections::BTreeMap;
+
+use if_chain::if_chain;
+use rustc_ast::ast::{FloatTy, IntTy, LitFloatType, LitIntType, LitKind, UintTy};
+use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::{
+ BinOpKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem,
+ ImplItemKind, Item, ItemKind, Lifetime, Local, MatchSource, MutTy, Mutability, QPath, Stmt, StmtKind, TraitFn,
+ TraitItem, TraitItemKind, TyKind, UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, InferTy, Ty, TyCtxt, TyS, TypeckTables};
+use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
+use rustc_span::hygiene::{ExpnKind, MacroKind};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+use rustc_target::abi::LayoutOf;
+use rustc_target::spec::abi::Abi;
+use rustc_typeck::hir_ty_to_ty;
+
+use crate::consts::{constant, Constant};
+use crate::utils::paths;
+use crate::utils::{
+ clip, comparisons, differing_macro_contexts, higher, in_constant, indent_of, int_bits, is_type_diagnostic_item,
+ last_path_segment, match_def_path, match_path, method_chain_args, multispan_sugg, numeric_literal::NumericLiteral,
+ qpath_res, sext, snippet, snippet_block_with_applicability, snippet_opt, snippet_with_applicability,
+ snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext,
+};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for use of `Box<Vec<_>>` anywhere in the code.
+ ///
+ /// **Why is this bad?** `Vec` already keeps its contents in a separate area on
+ /// the heap. So if you `Box` it, you just add another level of indirection
+ /// without any benefit whatsoever.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// struct X {
+ /// values: Box<Vec<Foo>>,
+ /// }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust,ignore
+ /// struct X {
+ /// values: Vec<Foo>,
+ /// }
+ /// ```
+ pub BOX_VEC,
+ perf,
+ "usage of `Box<Vec<T>>`, vector elements are already on the heap"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for use of `Vec<Box<T>>` where T: Sized anywhere in the code.
+ ///
+ /// **Why is this bad?** `Vec` already keeps its contents in a separate area on
+ /// the heap. So if you `Box` its contents, you just add another level of indirection.
+ ///
+ /// **Known problems:** Vec<Box<T: Sized>> makes sense if T is a large type (see #3530,
+ /// 1st comment).
+ ///
+ /// **Example:**
+ /// ```rust
+ /// struct X {
+ /// values: Vec<Box<i32>>,
+ /// }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// struct X {
+ /// values: Vec<i32>,
+ /// }
+ /// ```
+ pub VEC_BOX,
+ complexity,
+ "usage of `Vec<Box<T>>` where T: Sized, vector elements are already on the heap"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for use of `Option<Option<_>>` in function signatures and type
+ /// definitions
+ ///
+ /// **Why is this bad?** `Option<_>` represents an optional value. `Option<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<Option<u32>> {
+ /// None
+ /// }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// pub enum Contents {
+ /// Data(Vec<u8>), // Was Some(Some(Vec<u8>))
+ /// NotYetFetched, // Was Some(None)
+ /// None, // Was None
+ /// }
+ ///
+ /// fn get_data() -> Contents {
+ /// Contents::None
+ /// }
+ /// ```
+ pub OPTION_OPTION,
+ pedantic,
+ "usage of `Option<Option<T>>`"
+}
+
+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<usize> = 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<T>` anywhere in the code.
+ ///
+ /// **Why is this bad?** Any `&Box<T>` can also be a `&T`, which is more
+ /// general.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// fn foo(bar: &Box<T>) { ... }
+ /// ```
+ ///
+ /// 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<T>>`, `Rc<Box<T>>`, `Box<&T>`
+ /// add an unnecessary level of indirection.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// fn foo(bar: Rc<&usize>) {}
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// fn foo(bar: &usize) {}
+ /// ```
+ pub REDUNDANT_ALLOCATION,
+ perf,
+ "redundant allocation"
+}
+
+pub struct Types {
+ vec_box_size_threshold: u64,
+}
+
+impl_lint_pass!(Types => [BOX_VEC, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION]);
+
+impl<'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 { of_trait: Some(_), .. } = item.kind {
+ return;
+ }
+ }
+
+ self.check_fn_decl(cx, decl);
+ }
+
+ fn check_struct_field(&mut self, cx: &LateContext<'_>, field: &hir::StructField<'_>) {
+ self.check_ty(cx, &field.ty, false);
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
+ match item.kind {
+ TraitItemKind::Const(ref ty, _) | TraitItemKind::Type(_, Some(ref ty)) => self.check_ty(cx, ty, false),
+ TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, &sig.decl),
+ _ => (),
+ }
+ }
+
+ fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
+ if let Some(ref ty) = local.ty {
+ self.check_ty(cx, ty, true);
+ }
+ }
+}
+
+/// Checks if `qpath` has last segment with type parameter matching `path`
+fn match_type_parameter(cx: &LateContext<'_>, qpath: &QPath<'_>, path: &[&str]) -> Option<Span> {
+ let last = last_path_segment(qpath);
+ if_chain! {
+ if let Some(ref params) = last.args;
+ if !params.parenthesized;
+ if let Some(ty) = params.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ });
+ if let TyKind::Path(ref qpath) = ty.kind;
+ if let Some(did) = qpath_res(cx, qpath, ty.hir_id).opt_def_id();
+ if match_def_path(cx, did, path);
+ then {
+ return Some(ty.span);
+ }
+ }
+ None
+}
+
+fn match_borrows_parameter(_cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<Span> {
+ let last = last_path_segment(qpath);
+ if_chain! {
+ if let Some(ref params) = last.args;
+ if !params.parenthesized;
+ if let Some(ty) = params.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ });
+ if let TyKind::Rptr(..) = ty.kind;
+ then {
+ return Some(ty.span);
+ }
+ }
+ None
+}
+
+impl Types {
+ pub fn new(vec_box_size_threshold: u64) -> Self {
+ Self { vec_box_size_threshold }
+ }
+
+ fn check_fn_decl(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>) {
+ for input in decl.inputs {
+ self.check_ty(cx, input, false);
+ }
+
+ if let FnRetTy::Return(ref ty) = decl.output {
+ self.check_ty(cx, ty, false);
+ }
+ }
+
+ /// Recursively check for `TypePass` lints in the given type. Stop at the first
+ /// lint found.
+ ///
+ /// The parameter `is_local` distinguishes the context of the type; types from
+ /// local bindings should only be checked for the `BORROWED_BOX` lint.
+ #[allow(clippy::too_many_lines)]
+ fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, is_local: bool) {
+ if hir_ty.span.from_expansion() {
+ return;
+ }
+ match hir_ty.kind {
+ TyKind::Path(ref qpath) if !is_local => {
+ let hir_id = hir_ty.hir_id;
+ let res = qpath_res(cx, qpath, hir_id);
+ if let Some(def_id) = res.opt_def_id() {
+ if Some(def_id) == cx.tcx.lang_items().owned_box() {
+ if let Some(span) = match_borrows_parameter(cx, qpath) {
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
+ "usage of `Box<&T>`",
+ "try",
+ snippet(cx, span, "..").to_string(),
+ Applicability::MachineApplicable,
+ );
+ return; // don't recurse into the type
+ }
+ if match_type_parameter(cx, qpath, &paths::VEC).is_some() {
+ span_lint_and_help(
+ cx,
+ BOX_VEC,
+ hir_ty.span,
+ "you seem to be trying to use `Box<Vec<T>>`. Consider using just `Vec<T>`",
+ None,
+ "`Vec<T>` is already on the heap, `Box<Vec<T>>` makes an extra allocation.",
+ );
+ return; // don't recurse into the type
+ }
+ } else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) {
+ if let Some(span) = match_type_parameter(cx, qpath, &paths::RC) {
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
+ "usage of `Rc<Rc<T>>`",
+ "try",
+ snippet(cx, span, "..").to_string(),
+ Applicability::MachineApplicable,
+ );
+ return; // don't recurse into the type
+ }
+ if let Some(span) = match_type_parameter(cx, qpath, &paths::BOX) {
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
+ "usage of `Rc<Box<T>>`",
+ "try",
+ snippet(cx, span, "..").to_string(),
+ Applicability::MachineApplicable,
+ );
+ return; // don't recurse into the type
+ }
+ if let Some(span) = match_borrows_parameter(cx, qpath) {
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
+ "usage of `Rc<&T>`",
+ "try",
+ snippet(cx, span, "..").to_string(),
+ Applicability::MachineApplicable,
+ );
+ return; // don't recurse into the type
+ }
+ } else if cx.tcx.is_diagnostic_item(sym!(vec_type), def_id) {
+ if_chain! {
+ // Get the _ part of Vec<_>
+ if let Some(ref last) = last_path_segment(qpath).args;
+ if let Some(ty) = last.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ });
+ // ty is now _ at this point
+ if let TyKind::Path(ref ty_qpath) = ty.kind;
+ let res = qpath_res(cx, ty_qpath, ty.hir_id);
+ if let Some(def_id) = res.opt_def_id();
+ if Some(def_id) == cx.tcx.lang_items().owned_box();
+ // At this point, we know ty is Box<T>, now get T
+ if let Some(ref last) = last_path_segment(ty_qpath).args;
+ if let Some(boxed_ty) = last.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ });
+ let ty_ty = hir_ty_to_ty(cx.tcx, boxed_ty);
+ if ty_ty.is_sized(cx.tcx.at(ty.span), cx.param_env);
+ if let Ok(ty_ty_size) = cx.layout_of(ty_ty).map(|l| l.size.bytes());
+ if ty_ty_size <= self.vec_box_size_threshold;
+ then {
+ span_lint_and_sugg(
+ cx,
+ VEC_BOX,
+ hir_ty.span,
+ "`Vec<T>` is already on the heap, the boxing is unnecessary.",
+ "try",
+ format!("Vec<{}>", ty_ty),
+ Applicability::MachineApplicable,
+ );
+ return; // don't recurse into the type
+ }
+ }
+ } else if cx.tcx.is_diagnostic_item(sym!(option_type), def_id) {
+ if match_type_parameter(cx, qpath, &paths::OPTION).is_some() {
+ span_lint(
+ cx,
+ OPTION_OPTION,
+ hir_ty.span,
+ "consider using `Option<T>` instead of `Option<Option<T>>` or a custom \
+ enum if you need to distinguish all 3 cases",
+ );
+ return; // don't recurse into the type
+ }
+ } else if match_def_path(cx, def_id, &paths::LINKED_LIST) {
+ span_lint_and_help(
+ cx,
+ LINKEDLIST,
+ hir_ty.span,
+ "I see you're using a LinkedList! Perhaps you meant some other data structure?",
+ None,
+ "a `VecDeque` might work",
+ );
+ return; // don't recurse into the type
+ }
+ }
+ match *qpath {
+ QPath::Resolved(Some(ref ty), ref p) => {
+ self.check_ty(cx, ty, is_local);
+ for ty in p.segments.iter().flat_map(|seg| {
+ seg.args
+ .as_ref()
+ .map_or_else(|| [].iter(), |params| params.args.iter())
+ .filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ })
+ }) {
+ self.check_ty(cx, ty, is_local);
+ }
+ },
+ QPath::Resolved(None, ref p) => {
+ for ty in p.segments.iter().flat_map(|seg| {
+ seg.args
+ .as_ref()
+ .map_or_else(|| [].iter(), |params| params.args.iter())
+ .filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ })
+ }) {
+ self.check_ty(cx, ty, is_local);
+ }
+ },
+ QPath::TypeRelative(ref ty, ref seg) => {
+ self.check_ty(cx, ty, is_local);
+ if let Some(ref params) = seg.args {
+ for ty in params.args.iter().filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ }) {
+ self.check_ty(cx, ty, is_local);
+ }
+ }
+ },
+ }
+ },
+ TyKind::Rptr(ref lt, ref mut_ty) => self.check_ty_rptr(cx, hir_ty, is_local, lt, mut_ty),
+ // recurse
+ TyKind::Slice(ref ty) | TyKind::Array(ref ty, _) | TyKind::Ptr(MutTy { ref ty, .. }) => {
+ self.check_ty(cx, ty, is_local)
+ },
+ TyKind::Tup(tys) => {
+ for ty in tys {
+ self.check_ty(cx, ty, is_local);
+ }
+ },
+ _ => {},
+ }
+ }
+
+ fn check_ty_rptr(
+ &mut self,
+ cx: &LateContext<'_>,
+ hir_ty: &hir::Ty<'_>,
+ is_local: bool,
+ lt: &Lifetime,
+ mut_ty: &MutTy<'_>,
+ ) {
+ match mut_ty.ty.kind {
+ TyKind::Path(ref qpath) => {
+ let hir_id = mut_ty.ty.hir_id;
+ let def = qpath_res(cx, qpath, hir_id);
+ if_chain! {
+ if let Some(def_id) = def.opt_def_id();
+ if Some(def_id) == cx.tcx.lang_items().owned_box();
+ if let QPath::Resolved(None, ref path) = *qpath;
+ if let [ref bx] = *path.segments;
+ if let Some(ref params) = bx.args;
+ if !params.parenthesized;
+ if let Some(inner) = params.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ });
+ then {
+ if is_any_trait(inner) {
+ // Ignore `Box<Any>` types; see issue #1884 for details.
+ return;
+ }
+
+ let ltopt = if lt.is_elided() {
+ String::new()
+ } else {
+ format!("{} ", lt.name.ident().as_str())
+ };
+
+ if mut_ty.mutbl == Mutability::Mut {
+ // Ignore `&mut Box<T>` types; see issue #2907 for
+ // details.
+ return;
+ }
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ BORROWED_BOX,
+ hir_ty.span,
+ "you seem to be trying to use `&Box<T>`. Consider using just `&T`",
+ "try",
+ format!(
+ "&{}{}",
+ ltopt,
+ &snippet_with_applicability(cx, inner.span, "..", &mut applicability)
+ ),
+ Applicability::Unspecified,
+ );
+ return; // don't recurse into the type
+ }
+ };
+ self.check_ty(cx, &mut_ty.ty, is_local);
+ },
+ _ => self.check_ty(cx, &mut_ty.ty, is_local),
+ }
+ }
+}
+
+// Returns true if given type is `Any` trait.
+fn is_any_trait(t: &hir::Ty<'_>) -> bool {
+ if_chain! {
+ if let TyKind::TraitObject(ref traits, _) = t.kind;
+ if !traits.is_empty();
+ // Only Send/Sync can be used as additional traits, so it is enough to
+ // check only the first trait.
+ if match_path(&traits[0].trait_ref.path, &paths::ANY_TRAIT);
+ then {
+ return true;
+ }
+ }
+
+ false
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for binding a unit value.
+ ///
+ /// **Why is this bad?** A unit value cannot usefully be used anywhere. So
+ /// binding one is kind of pointless.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let x = {
+ /// 1;
+ /// };
+ /// ```
+ pub LET_UNIT_VALUE,
+ pedantic,
+ "creating a `let` binding to a value of unit type, which usually can't be used afterwards"
+}
+
+declare_lint_pass!(LetUnitValue => [LET_UNIT_VALUE]);
+
+impl<'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.tables().pat_ty(&local.pat)) {
+ if in_external_macro(cx.sess(), stmt.span) || local.pat.span.from_expansion() {
+ return;
+ }
+ if higher::is_from_for_desugar(local) {
+ return;
+ }
+ span_lint_and_then(
+ cx,
+ LET_UNIT_VALUE,
+ stmt.span,
+ "this let-binding has unit value",
+ |diag| {
+ if let Some(expr) = &local.init {
+ let snip = snippet_with_macro_callsite(cx, expr.span, "()");
+ diag.span_suggestion(
+ stmt.span,
+ "omit the `let` binding",
+ format!("{};", snip),
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ },
+ );
+ }
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for comparisons to unit. This includes all binary
+ /// comparisons (like `==` and `<`) and asserts.
+ ///
+ /// **Why is this bad?** Unit is always equal to itself, and thus is just a
+ /// clumsily written constant. Mostly this happens when someone accidentally
+ /// adds semicolons at the end of the operands.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # fn foo() {};
+ /// # fn bar() {};
+ /// # fn baz() {};
+ /// if {
+ /// foo();
+ /// } == {
+ /// bar();
+ /// } {
+ /// baz();
+ /// }
+ /// ```
+ /// is equal to
+ /// ```rust
+ /// # fn foo() {};
+ /// # fn bar() {};
+ /// # fn baz() {};
+ /// {
+ /// foo();
+ /// bar();
+ /// baz();
+ /// }
+ /// ```
+ ///
+ /// For asserts:
+ /// ```rust
+ /// # fn foo() {};
+ /// # fn bar() {};
+ /// assert_eq!({ foo(); }, { bar(); });
+ /// ```
+ /// will always succeed
+ pub UNIT_CMP,
+ correctness,
+ "comparing unit values"
+}
+
+declare_lint_pass!(UnitCmp => [UNIT_CMP]);
+
+impl<'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.tables().expr_ty(left)) {
+ let result = match &*symbol.as_str() {
+ "assert_eq" | "debug_assert_eq" => "succeed",
+ "assert_ne" | "debug_assert_ne" => "fail",
+ _ => return,
+ };
+ span_lint(
+ cx,
+ UNIT_CMP,
+ expr.span,
+ &format!(
+ "`{}` of unit values detected. This will always {}",
+ symbol.as_str(),
+ result
+ ),
+ );
+ }
+ }
+ }
+ }
+ return;
+ }
+ if let ExprKind::Binary(ref cmp, ref left, _) = expr.kind {
+ let op = cmp.node;
+ if op.is_comparison() && is_unit(cx.tables().expr_ty(left)) {
+ let result = match op {
+ BinOpKind::Eq | BinOpKind::Le | BinOpKind::Ge => "true",
+ _ => "false",
+ };
+ span_lint(
+ cx,
+ UNIT_CMP,
+ expr.span,
+ &format!(
+ "{}-comparison of unit values detected. This will always be {}",
+ op.as_str(),
+ result
+ ),
+ );
+ }
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for passing a unit value as an argument to a function without using a
+ /// unit literal (`()`).
+ ///
+ /// **Why is this bad?** This is likely the result of an accidental semicolon.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// foo({
+ /// let a = bar();
+ /// baz(a);
+ /// })
+ /// ```
+ pub UNIT_ARG,
+ complexity,
+ "passing unit to a function"
+}
+
+declare_lint_pass!(UnitArg => [UNIT_ARG]);
+
+impl<'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.tables().expr_ty(arg)) && !is_unit_literal(arg) {
- match ty.kind {
- ty::Tuple(slice) if slice.is_empty() => true,
- _ => false,
- }
++ !matches!(&arg.kind, ExprKind::Match(.., MatchSource::TryDesugar))
+ } else {
+ false
+ }
+ })
+ .collect::<Vec<_>>();
+ if !args_to_recover.is_empty() {
+ lint_unit_args(cx, expr, &args_to_recover);
+ }
+ },
+ _ => (),
+ }
+ }
+}
+
+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 ";
+ });
+ let sugg = args_to_recover
+ .iter()
+ .filter(|arg| !is_empty_block(arg))
+ .enumerate()
+ .map(|(i, arg)| {
+ let indent = if i == 0 {
+ 0
+ } else {
+ indent_of(cx, expr.span).unwrap_or(0)
+ };
+ format!(
+ "{}{};",
+ " ".repeat(indent),
+ snippet_block_with_applicability(cx, arg.span, "..", Some(expr.span), &mut applicability)
+ )
+ })
+ .collect::<Vec<String>>();
+ let mut and = "";
+ if !sugg.is_empty() {
+ let plural = if sugg.len() > 1 { "s" } else { "" };
+ db.span_suggestion(
+ expr.span.with_hi(expr.span.lo()),
+ &format!("{}move the expression{} in front of the call...", or, plural),
+ format!("{}\n", sugg.join("\n")),
+ applicability,
+ );
+ and = "...and "
+ }
+ db.multipart_suggestion(
+ &format!("{}use {}unit literal{} instead", and, singular, plural),
+ args_to_recover
+ .iter()
+ .map(|arg| (arg.span, "()".to_string()))
+ .collect::<Vec<_>>(),
+ 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 {
- match expr.kind {
- ExprKind::Tup(ref slice) if slice.is_empty() => true,
- _ => false,
- }
++ matches!(ty.kind, ty::Tuple(slice) if slice.is_empty())
+}
+
+fn is_unit_literal(expr: &Expr<'_>) -> bool {
- match typ.kind {
- ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => true,
- _ => false,
- }
++ matches!(expr.kind, ExprKind::Tup(ref slice) if slice.is_empty())
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for casts from any numerical to a float type where
+ /// the receiving type cannot store all values from the original type without
+ /// rounding errors. This possible rounding is to be expected, so this lint is
+ /// `Allow` by default.
+ ///
+ /// Basically, this warns on casting any integer with 32 or more bits to `f32`
+ /// or any 64-bit integer to `f64`.
+ ///
+ /// **Why is this bad?** It's not bad at all. But in some applications it can be
+ /// helpful to know where precision loss can take place. This lint can help find
+ /// those places in the code.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let x = u64::MAX;
+ /// x as f64;
+ /// ```
+ pub CAST_PRECISION_LOSS,
+ pedantic,
+ "casts that cause loss of precision, e.g., `x as f32` where `x: u64`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for casts from a signed to an unsigned numerical
+ /// type. In this case, negative values wrap around to large positive values,
+ /// which can be quite surprising in practice. However, as the cast works as
+ /// defined, this lint is `Allow` by default.
+ ///
+ /// **Why is this bad?** Possibly surprising results. You can activate this lint
+ /// as a one-time check to see where numerical wrapping can arise.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let y: i8 = -1;
+ /// y as u128; // will return 18446744073709551615
+ /// ```
+ pub CAST_SIGN_LOSS,
+ pedantic,
+ "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for casts between numerical types that may
+ /// truncate large values. This is expected behavior, so the cast is `Allow` by
+ /// default.
+ ///
+ /// **Why is this bad?** In some problem domains, it is good practice to avoid
+ /// truncation. This lint can be activated to help assess where additional
+ /// checks could be beneficial.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// fn as_u8(x: u64) -> u8 {
+ /// x as u8
+ /// }
+ /// ```
+ pub CAST_POSSIBLE_TRUNCATION,
+ pedantic,
+ "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for casts from an unsigned type to a signed type of
+ /// the same size. Performing such a cast is a 'no-op' for the compiler,
+ /// i.e., nothing is changed at the bit level, and the binary representation of
+ /// the value is reinterpreted. This can cause wrapping if the value is too big
+ /// for the target signed type. However, the cast works as defined, so this lint
+ /// is `Allow` by default.
+ ///
+ /// **Why is this bad?** While such a cast is not bad in itself, the results can
+ /// be surprising when this is not the intended behavior, as demonstrated by the
+ /// example below.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// u32::MAX as i32; // will yield a value of `-1`
+ /// ```
+ pub CAST_POSSIBLE_WRAP,
+ pedantic,
+ "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for casts between numerical types that may
+ /// be replaced by safe conversion functions.
+ ///
+ /// **Why is this bad?** Rust's `as` keyword will perform many kinds of
+ /// conversions, including silently lossy conversions. Conversion functions such
+ /// as `i32::from` will only perform lossless conversions. Using the conversion
+ /// functions prevents conversions from turning into silent lossy conversions if
+ /// the types of the input expressions ever change, and make it easier for
+ /// people reading the code to know that the conversion is lossless.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// fn as_u64(x: u8) -> u64 {
+ /// x as u64
+ /// }
+ /// ```
+ ///
+ /// Using `::from` would look like this:
+ ///
+ /// ```rust
+ /// fn as_u64(x: u8) -> u64 {
+ /// u64::from(x)
+ /// }
+ /// ```
+ pub CAST_LOSSLESS,
+ pedantic,
+ "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for casts to the same type.
+ ///
+ /// **Why is this bad?** It's just unnecessary.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let _ = 2i32 as i32;
+ /// ```
+ pub UNNECESSARY_CAST,
+ complexity,
+ "cast to the same type, e.g., `x as i32` where `x: i32`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for casts from a less-strictly-aligned pointer to a
+ /// more-strictly-aligned pointer
+ ///
+ /// **Why is this bad?** Dereferencing the resulting pointer may be undefined
+ /// behavior.
+ ///
+ /// **Known problems:** Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar
+ /// on the resulting pointer is fine. 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;
+ /// ```
+ 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"
+}
+
+/// Returns the size in bits of an integral type.
+/// Will return 0 if the type is not an int or uint variant
+fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 {
+ match typ.kind {
+ ty::Int(i) => match i {
+ IntTy::Isize => tcx.data_layout.pointer_size.bits(),
+ IntTy::I8 => 8,
+ IntTy::I16 => 16,
+ IntTy::I32 => 32,
+ IntTy::I64 => 64,
+ IntTy::I128 => 128,
+ },
+ ty::Uint(i) => match i {
+ UintTy::Usize => tcx.data_layout.pointer_size.bits(),
+ UintTy::U8 => 8,
+ UintTy::U16 => 16,
+ UintTy::U32 => 32,
+ UintTy::U64 => 64,
+ UintTy::U128 => 128,
+ },
+ _ => 0,
+ }
+}
+
+fn is_isize_or_usize(typ: Ty<'_>) -> bool {
- let sugg = if let Some(ref snip) = opt {
- if should_strip_parens(op, snip) {
- &snip[1..snip.len() - 1]
- } else {
- snip.as_str()
- }
- } else {
- applicability = Applicability::HasPlaceholders;
- ".."
- };
++ matches!(typ.kind, ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
+}
+
+fn span_precision_loss_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to_f64: bool) {
+ let mantissa_nbits = if cast_to_f64 { 52 } else { 23 };
+ let arch_dependent = is_isize_or_usize(cast_from) && cast_to_f64;
+ let arch_dependent_str = "on targets with 64-bit wide pointers ";
+ let from_nbits_str = if arch_dependent {
+ "64".to_owned()
+ } else if is_isize_or_usize(cast_from) {
+ "32 or 64".to_owned()
+ } else {
+ int_ty_to_nbits(cast_from, cx.tcx).to_string()
+ };
+ span_lint(
+ cx,
+ CAST_PRECISION_LOSS,
+ expr.span,
+ &format!(
+ "casting `{0}` to `{1}` causes a loss of precision {2}(`{0}` is {3} bits wide, \
+ but `{1}`'s mantissa is only {4} bits wide)",
+ cast_from,
+ if cast_to_f64 { "f64" } else { "f32" },
+ if arch_dependent { arch_dependent_str } else { "" },
+ from_nbits_str,
+ mantissa_nbits
+ ),
+ );
+}
+
+fn should_strip_parens(op: &Expr<'_>, snip: &str) -> bool {
+ if let ExprKind::Binary(_, _, _) = op.kind {
+ if snip.starts_with('(') && snip.ends_with(')') {
+ return true;
+ }
+ }
+ false
+}
+
+fn span_lossless_lint(cx: &LateContext<'_>, expr: &Expr<'_>, op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+ // Do not suggest using From in consts/statics until it is valid to do so (see #2267).
+ if in_constant(cx, expr.hir_id) {
+ return;
+ }
+ // The suggestion is to use a function call, so if the original expression
+ // has parens on the outside, they are no longer needed.
+ let mut applicability = Applicability::MachineApplicable;
+ let opt = snippet_opt(cx, op.span);
- bound.bound_generic_params.iter().any(|gen| match gen.kind {
- GenericParamKind::Lifetime { .. } => true,
- _ => false,
- })
++ let sugg = opt.as_ref().map_or_else(
++ || {
++ applicability = Applicability::HasPlaceholders;
++ ".."
++ },
++ |snip| {
++ if should_strip_parens(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,
+ );
+}
+
+enum ArchSuffix {
+ _32,
+ _64,
+ None,
+}
+
+fn check_loss_of_sign(cx: &LateContext<'_>, expr: &Expr<'_>, op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+ if !cast_from.is_signed() || cast_to.is_signed() {
+ return;
+ }
+
+ // don't lint for positive constants
+ let const_val = constant(cx, &cx.tables(), op);
+ if_chain! {
+ if let Some((const_val, _)) = const_val;
+ if let Constant::Int(n) = const_val;
+ if let ty::Int(ity) = cast_from.kind;
+ if sext(cx.tcx, n, ity) >= 0;
+ then {
+ return
+ }
+ }
+
+ // don't lint for the result of methods that always return non-negative values
+ if let ExprKind::MethodCall(ref path, _, _, _) = op.kind {
+ let mut method_name = path.ident.name.as_str();
+ let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
+
+ if_chain! {
+ if method_name == "unwrap";
+ if let Some(arglist) = method_chain_args(op, &["unwrap"]);
+ if let ExprKind::MethodCall(ref inner_path, _, _, _) = &arglist[0][0].kind;
+ then {
+ method_name = inner_path.ident.name.as_str();
+ }
+ }
+
+ if allowed_methods.iter().any(|&name| method_name == name) {
+ return;
+ }
+ }
+
+ span_lint(
+ cx,
+ CAST_SIGN_LOSS,
+ expr.span,
+ &format!(
+ "casting `{}` to `{}` may lose the sign of the value",
+ cast_from, cast_to
+ ),
+ );
+}
+
+fn check_truncation_and_wrapping(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+ let arch_64_suffix = " on targets with 64-bit wide pointers";
+ let arch_32_suffix = " on targets with 32-bit wide pointers";
+ let cast_unsigned_to_signed = !cast_from.is_signed() && cast_to.is_signed();
+ let from_nbits = int_ty_to_nbits(cast_from, cx.tcx);
+ let to_nbits = int_ty_to_nbits(cast_to, cx.tcx);
+ let (span_truncation, suffix_truncation, span_wrap, suffix_wrap) =
+ match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) {
+ (true, true) | (false, false) => (
+ to_nbits < from_nbits,
+ ArchSuffix::None,
+ to_nbits == from_nbits && cast_unsigned_to_signed,
+ ArchSuffix::None,
+ ),
+ (true, false) => (
+ to_nbits <= 32,
+ if to_nbits == 32 {
+ ArchSuffix::_64
+ } else {
+ ArchSuffix::None
+ },
+ to_nbits <= 32 && cast_unsigned_to_signed,
+ ArchSuffix::_32,
+ ),
+ (false, true) => (
+ from_nbits == 64,
+ ArchSuffix::_32,
+ cast_unsigned_to_signed,
+ if from_nbits == 64 {
+ ArchSuffix::_64
+ } else {
+ ArchSuffix::_32
+ },
+ ),
+ };
+ if span_truncation {
+ span_lint(
+ cx,
+ CAST_POSSIBLE_TRUNCATION,
+ expr.span,
+ &format!(
+ "casting `{}` to `{}` may truncate the value{}",
+ cast_from,
+ cast_to,
+ match suffix_truncation {
+ ArchSuffix::_32 => arch_32_suffix,
+ ArchSuffix::_64 => arch_64_suffix,
+ ArchSuffix::None => "",
+ }
+ ),
+ );
+ }
+ if span_wrap {
+ span_lint(
+ cx,
+ CAST_POSSIBLE_WRAP,
+ expr.span,
+ &format!(
+ "casting `{}` to `{}` may wrap around the value{}",
+ cast_from,
+ cast_to,
+ match suffix_wrap {
+ ArchSuffix::_32 => arch_32_suffix,
+ ArchSuffix::_64 => arch_64_suffix,
+ ArchSuffix::None => "",
+ }
+ ),
+ );
+ }
+}
+
+fn check_lossless(cx: &LateContext<'_>, expr: &Expr<'_>, op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+ let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed();
+ let from_nbits = int_ty_to_nbits(cast_from, cx.tcx);
+ let to_nbits = int_ty_to_nbits(cast_to, cx.tcx);
+ if !is_isize_or_usize(cast_from) && !is_isize_or_usize(cast_to) && from_nbits < to_nbits && !cast_signed_to_unsigned
+ {
+ span_lossless_lint(cx, expr, op, cast_from, cast_to);
+ }
+}
+
+declare_lint_pass!(Casts => [
+ CAST_PRECISION_LOSS,
+ CAST_SIGN_LOSS,
+ CAST_POSSIBLE_TRUNCATION,
+ CAST_POSSIBLE_WRAP,
+ CAST_LOSSLESS,
+ UNNECESSARY_CAST,
+ CAST_PTR_ALIGNMENT,
+ FN_TO_NUMERIC_CAST,
+ FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+]);
+
+// Check if the given type is either `core::ffi::c_void` or
+// one of the platform specific `libc::<platform>::c_void` of libc.
+fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+ if let ty::Adt(adt, _) = ty.kind {
+ let names = cx.get_def_path(adt.did);
+
+ if names.is_empty() {
+ return false;
+ }
+ if names[0] == sym!(libc) || names[0] == sym::core && *names.last().unwrap() == sym!(c_void) {
+ return true;
+ }
+ }
+ false
+}
+
+/// Returns the mantissa bits wide of a fp type.
+/// Will return 0 if the type is not a fp
+fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 {
+ match typ.kind {
+ ty::Float(FloatTy::F32) => 23,
+ ty::Float(FloatTy::F64) | ty::Infer(InferTy::FloatVar(_)) => 52,
+ _ => 0,
+ }
+}
+
+impl<'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 ex, _) = expr.kind {
+ let (cast_from, cast_to) = (cx.tables().expr_ty(ex), cx.tables().expr_ty(expr));
+ lint_fn_to_numeric_cast(cx, expr, ex, cast_from, cast_to);
+ if let ExprKind::Lit(ref lit) = ex.kind {
+ if_chain! {
+ if let LitKind::Int(n, _) = lit.node;
+ if let Some(src) = snippet_opt(cx, lit.span);
+ if cast_to.is_floating_point();
+ if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node);
+ let from_nbits = 128 - n.leading_zeros();
+ let to_nbits = fp_ty_mantissa_nbits(cast_to);
+ if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal();
+ then {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_CAST,
+ expr.span,
+ &format!("casting integer literal to `{}` is unnecessary", cast_to),
+ "try",
+ format!("{}_{}", n, cast_to),
+ Applicability::MachineApplicable,
+ );
+ return;
+ }
+ }
+ match lit.node {
+ LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) => {},
+ _ => {
+ if cast_from.kind == cast_to.kind && !in_external_macro(cx.sess(), expr.span) {
+ span_lint(
+ cx,
+ UNNECESSARY_CAST,
+ expr.span,
+ &format!(
+ "casting to the same type is unnecessary (`{}` -> `{}`)",
+ cast_from, cast_to
+ ),
+ );
+ }
+ },
+ }
+ }
+ if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
+ lint_numeric_casts(cx, expr, ex, cast_from, cast_to);
+ }
+
+ lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
+ }
+ }
+}
+
+fn lint_numeric_casts<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &Expr<'tcx>,
+ cast_expr: &Expr<'_>,
+ cast_from: Ty<'tcx>,
+ cast_to: Ty<'tcx>,
+) {
+ match (cast_from.is_integral(), cast_to.is_integral()) {
+ (true, false) => {
+ let from_nbits = int_ty_to_nbits(cast_from, cx.tcx);
+ let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind {
+ 32
+ } else {
+ 64
+ };
+ if is_isize_or_usize(cast_from) || from_nbits >= to_nbits {
+ span_precision_loss_lint(cx, expr, cast_from, to_nbits == 64);
+ }
+ if from_nbits < to_nbits {
+ span_lossless_lint(cx, expr, cast_expr, cast_from, cast_to);
+ }
+ },
+ (false, true) => {
+ span_lint(
+ cx,
+ CAST_POSSIBLE_TRUNCATION,
+ expr.span,
+ &format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to),
+ );
+ if !cast_to.is_signed() {
+ span_lint(
+ cx,
+ CAST_SIGN_LOSS,
+ expr.span,
+ &format!(
+ "casting `{}` to `{}` may lose the sign of the value",
+ cast_from, cast_to
+ ),
+ );
+ }
+ },
+ (true, true) => {
+ check_loss_of_sign(cx, expr, cast_expr, cast_from, cast_to);
+ check_truncation_and_wrapping(cx, expr, cast_from, cast_to);
+ check_lossless(cx, expr, cast_expr, cast_from, cast_to);
+ },
+ (false, false) => {
+ if let (&ty::Float(FloatTy::F64), &ty::Float(FloatTy::F32)) = (&cast_from.kind, &cast_to.kind) {
+ span_lint(
+ cx,
+ CAST_POSSIBLE_TRUNCATION,
+ expr.span,
+ "casting `f64` to `f32` may truncate the value",
+ );
+ }
+ if let (&ty::Float(FloatTy::F32), &ty::Float(FloatTy::F64)) = (&cast_from.kind, &cast_to.kind) {
+ span_lossless_lint(cx, expr, cast_expr, cast_from, cast_to);
+ }
+ },
+ }
+}
+
+fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) {
+ if_chain! {
+ if let ty::RawPtr(from_ptr_ty) = &cast_from.kind;
+ if let ty::RawPtr(to_ptr_ty) = &cast_to.kind;
+ if let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty);
+ if let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty);
+ if from_layout.align.abi < to_layout.align.abi;
+ // with c_void, we inherently need to trust the user
+ if !is_c_void(cx, from_ptr_ty.ty);
+ // when casting from a ZST, we don't know enough to properly lint
+ if !from_layout.is_zst();
+ then {
+ span_lint(
+ cx,
+ CAST_PTR_ALIGNMENT,
+ expr.span,
+ &format!(
+ "casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)",
+ cast_from,
+ cast_to,
+ from_layout.align.abi.bytes(),
+ to_layout.align.abi.bytes(),
+ ),
+ );
+ }
+ }
+}
+
+fn lint_fn_to_numeric_cast(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ cast_expr: &Expr<'_>,
+ cast_from: Ty<'_>,
+ cast_to: Ty<'_>,
+) {
+ // We only want to check casts to `ty::Uint` or `ty::Int`
+ match cast_to.kind {
+ ty::Uint(_) | ty::Int(..) => { /* continue on */ },
+ _ => return,
+ }
+ match cast_from.kind {
+ ty::FnDef(..) | ty::FnPtr(_) => {
+ let mut applicability = Applicability::MaybeIncorrect;
+ let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
+
+ let to_nbits = int_ty_to_nbits(cast_to, cx.tcx);
+ if to_nbits < cx.tcx.data_layout.pointer_size.bits() {
+ span_lint_and_sugg(
+ cx,
+ FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ expr.span,
+ &format!(
+ "casting function pointer `{}` to `{}`, which truncates the value",
+ from_snippet, cast_to
+ ),
+ "try",
+ format!("{} as usize", from_snippet),
+ applicability,
+ );
+ } else if cast_to.kind != ty::Uint(UintTy::Usize) {
+ span_lint_and_sugg(
+ cx,
+ FN_TO_NUMERIC_CAST,
+ expr.span,
+ &format!("casting function pointer `{}` to `{}`", from_snippet, cast_to),
+ "try",
+ format!("{} as usize", from_snippet),
+ applicability,
+ );
+ }
+ },
+ _ => {},
+ }
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for types used in structs, parameters and `let`
+ /// declarations above a certain complexity threshold.
+ ///
+ /// **Why is this bad?** Too complex types make the code less readable. Consider
+ /// using a `type` definition to simplify them.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// struct Foo {
+ /// inner: Rc<Vec<Vec<Box<(u32, u32, u32, u32)>>>>,
+ /// }
+ /// ```
+ 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_struct_field(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::StructField<'_>) {
+ // enum variants are also struct fields now
+ self.check_type(cx, &field.ty);
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'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<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for expressions where a character literal is cast
+ /// to `u8` and suggests using a byte literal instead.
+ ///
+ /// **Why is this bad?** In general, casting values to smaller types is
+ /// error-prone and should be avoided where possible. In the particular case of
+ /// converting a character literal to u8, it is easy to avoid by just using a
+ /// byte literal instead. As an added bonus, `b'a'` is even slightly shorter
+ /// than `'a' as u8`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// 'x' as u8
+ /// ```
+ ///
+ /// A better version, using the byte literal:
+ ///
+ /// ```rust,ignore
+ /// b'x'
+ /// ```
+ pub CHAR_LIT_AS_U8,
+ complexity,
+ "casting a character literal to `u8` truncates"
+}
+
+declare_lint_pass!(CharLitAsU8 => [CHAR_LIT_AS_U8]);
+
+impl<'tcx> LateLintPass<'tcx> for CharLitAsU8 {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if !expr.span.from_expansion();
+ if let ExprKind::Cast(e, _) = &expr.kind;
+ if let ExprKind::Lit(l) = &e.kind;
+ if let LitKind::Char(c) = l.node;
+ if ty::Uint(UintTy::U8) == cx.tables().expr_ty(expr).kind;
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability);
+
+ span_lint_and_then(
+ cx,
+ CHAR_LIT_AS_U8,
+ expr.span,
+ "casting a character literal to `u8` truncates",
+ |diag| {
+ diag.note("`char` is four bytes wide, but `u8` is a single byte");
+
+ if c.is_ascii() {
+ diag.span_suggestion(
+ expr.span,
+ "use a byte literal instead",
+ format!("b{}", snippet),
+ applicability,
+ );
+ }
+ });
+ }
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for comparisons where one side of the relation is
+ /// either the minimum or maximum value for its type and warns if it involves a
+ /// case that is always true or always false. Only integer and boolean types are
+ /// checked.
+ ///
+ /// **Why is this bad?** An expression like `min <= x` may misleadingly imply
+ /// that it is possible for `x` to be less than the minimum. Expressions like
+ /// `max < x` are probably mistakes.
+ ///
+ /// **Known problems:** For `usize` the size of the current compile target will
+ /// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such
+ /// a comparison to detect target pointer width will trigger this lint. One can
+ /// use `mem::sizeof` and compare its value or conditional compilation
+ /// attributes
+ /// like `#[cfg(target_pointer_width = "64")] ..` instead.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// let vec: Vec<isize> = 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.tables().expr_ty(cast_exp);
+ let cast_ty = cx.tables().expr_ty(expr);
+
+ return is_isize_or_usize(precast_ty) != is_isize_or_usize(cast_ty);
+ }
+
+ false
+}
+
+fn detect_absurd_comparison<'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.tables().expr_ty(lhs) != cx.tables().expr_ty(rhs) {
+ return None;
+ }
+
+ // comparisons between fix sized types and target sized types are considered unanalyzable
+ if is_cast_between_fixed_and_target(cx, lhs) || is_cast_between_fixed_and_target(cx, rhs) {
+ return None;
+ }
+
+ let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?;
+
+ let lx = detect_extreme_expr(cx, normalized_lhs);
+ let rx = detect_extreme_expr(cx, normalized_rhs);
+
+ Some(match rel {
+ Rel::Lt => {
+ match (lx, rx) {
+ (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, AlwaysFalse), // max < x
+ (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, AlwaysFalse), // x < min
+ _ => return None,
+ }
+ },
+ Rel::Le => {
+ match (lx, rx) {
+ (Some(l @ ExtremeExpr { which: Minimum, .. }), _) => (l, AlwaysTrue), // min <= x
+ (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, InequalityImpossible), // max <= x
+ (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, InequalityImpossible), // x <= min
+ (_, Some(r @ ExtremeExpr { which: Maximum, .. })) => (r, AlwaysTrue), // x <= max
+ _ => return None,
+ }
+ },
+ Rel::Ne | Rel::Eq => return None,
+ })
+}
+
+fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<ExtremeExpr<'tcx>> {
+ use crate::types::ExtremeType::{Maximum, Minimum};
+
+ let ty = cx.tables().expr_ty(expr);
+
+ let cv = constant(cx, cx.tables(), expr)?.0;
+
+ let which = match (&ty.kind, cv) {
+ (&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => Minimum,
+ (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MIN >> (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<Ordering> {
+ Some(match (self, other) {
+ (&Self::S(s), &Self::S(o)) => s.cmp(&o),
+ (&Self::U(s), &Self::U(o)) => s.cmp(&o),
+ (&Self::S(s), &Self::U(o)) => Self::cmp_s_u(s, o),
+ (&Self::U(s), &Self::S(o)) => Self::cmp_s_u(o, s).reverse(),
+ })
+ }
+}
+impl Ord for FullInt {
+ #[must_use]
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.partial_cmp(other)
+ .expect("`partial_cmp` for FullInt can never return `None`")
+ }
+}
+
+fn numeric_cast_precast_bounds<'a>(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<(FullInt, FullInt)> {
+ if let ExprKind::Cast(ref cast_exp, _) = expr.kind {
+ let pre_cast_ty = cx.tables().expr_ty(cast_exp);
+ let cast_ty = cx.tables().expr_ty(expr);
+ // if it's a cast from i32 to u32 wrapping will invalidate all these checks
+ if cx.layout_of(pre_cast_ty).ok().map(|l| l.size) == cx.layout_of(cast_ty).ok().map(|l| l.size) {
+ return None;
+ }
+ match pre_cast_ty.kind {
+ ty::Int(int_ty) => Some(match int_ty {
+ IntTy::I8 => (FullInt::S(i128::from(i8::MIN)), 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<FullInt> {
+ let val = constant(cx, cx.tables(), expr)?.0;
+ if let Constant::Int(const_int) = val {
+ match cx.tables().expr_ty(expr).kind {
+ ty::Int(ity) => Some(FullInt::S(sext(cx.tcx, const_int, ity))),
+ ty::Uint(_) => Some(FullInt::U(const_int)),
+ _ => None,
+ }
+ } else {
+ None
+ }
+}
+
+fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) {
+ if let ExprKind::Cast(ref cast_val, _) = expr.kind {
+ span_lint(
+ cx,
+ INVALID_UPCAST_COMPARISONS,
+ span,
+ &format!(
+ "because of the numeric bounds on `{}` prior to casting, this expression is always {}",
+ snippet(cx, cast_val.span, "the expression"),
+ if always { "true" } else { "false" },
+ ),
+ );
+ }
+}
+
+fn upcast_comparison_bounds_err<'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<K: Hash + Eq, V> Serialize for HashMap<K, V> { }
+ ///
+ /// pub fn foo(map: &mut HashMap<i32, i32>) { }
+ /// ```
+ /// could be rewritten as
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # use std::hash::{Hash, BuildHasher};
+ /// # trait Serialize {};
+ /// impl<K: Hash + Eq, V, S: BuildHasher> Serialize for HashMap<K, V, S> { }
+ ///
+ /// pub fn foo<S: BuildHasher>(map: &mut HashMap<i32, i32, S>) { }
+ /// ```
+ 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 generics,
+ self_ty: ref ty,
+ ref items,
+ ..
+ } => {
+ let mut vis = ImplicitHasherTypeVisitor::new(cx);
+ vis.visit_ty(ty);
+
+ for target in &vis.found {
+ if differing_macro_contexts(item.span, target.span()) {
+ return;
+ }
+
+ let generics_suggestion_span = generics.span.substitute_dummy({
+ let pos = snippet_opt(cx, item.span.until(target.span()))
+ .and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4)));
+ if let Some(pos) = pos {
+ Span::new(pos, pos, item.span.data().ctxt)
+ } else {
+ return;
+ }
+ });
+
+ let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
+ for item in items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) {
+ ctr_vis.visit_impl_item(item);
+ }
+
+ span_lint_and_then(
+ cx,
+ IMPLICIT_HASHER,
+ target.span(),
+ &format!(
+ "impl for `{}` should be generalized over different hashers",
+ target.type_name()
+ ),
+ move |diag| {
+ suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis);
+ },
+ );
+ }
+ },
+ ItemKind::Fn(ref sig, ref generics, body_id) => {
+ let body = cx.tcx.hir().body(body_id);
+
+ for ty in sig.decl.inputs {
+ let mut vis = ImplicitHasherTypeVisitor::new(cx);
+ vis.visit_ty(ty);
+
+ for target in &vis.found {
+ if in_external_macro(cx.sess(), generics.span) {
+ continue;
+ }
+ let generics_suggestion_span = generics.span.substitute_dummy({
+ let pos = snippet_opt(cx, item.span.until(body.params[0].pat.span))
+ .and_then(|snip| {
+ let i = snip.find("fn")?;
+ Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32))
+ })
+ .expect("failed to create span for type parameters");
+ Span::new(pos, pos, item.span.data().ctxt)
+ });
+
+ let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
+ ctr_vis.visit_body(body);
+
+ span_lint_and_then(
+ cx,
+ IMPLICIT_HASHER,
+ target.span(),
+ &format!(
+ "parameter of type `{}` should be generalized over different hashers",
+ target.type_name()
+ ),
+ move |diag| {
+ suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis);
+ },
+ );
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+enum ImplicitHasherType<'tcx> {
+ HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>),
+ HashSet(Span, Ty<'tcx>, Cow<'static, str>),
+}
+
+impl<'tcx> ImplicitHasherType<'tcx> {
+ /// Checks that `ty` is a target type without a `BuildHasher`.
+ fn new(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Option<Self> {
+ 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<ImplicitHasherType<'tcx>>,
+}
+
+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<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Looks for default-hasher-dependent constructors like `HashMap::new`.
+struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_tables: Option<&'tcx TypeckTables<'tcx>>,
+ target: &'b ImplicitHasherType<'tcx>,
+ suggestions: BTreeMap<Span, String>,
+}
+
+impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_tables: cx.maybe_typeck_tables(),
+ target,
+ suggestions: BTreeMap::new(),
+ }
+ }
+}
+
+impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_body(&mut self, body: &'tcx Body<'_>) {
+ let old_maybe_typeck_tables = self.maybe_typeck_tables.replace(self.cx.tcx.body_tables(body.id()));
+ walk_body(self, body);
+ self.maybe_typeck_tables = old_maybe_typeck_tables;
+ }
+
+ 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_tables.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<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for casts of `&T` to `&mut T` anywhere in the code.
+ ///
+ /// **Why is this bad?** It’s basically guaranteed to be undefined behaviour.
+ /// `UnsafeCell` is the only way to obtain aliasable data that is considered
+ /// mutable.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// fn x(r: &i32) {
+ /// unsafe {
+ /// *(r as *const _ as *mut _) += 1;
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Instead consider using interior mutability types.
+ ///
+ /// ```rust
+ /// use std::cell::UnsafeCell;
+ ///
+ /// fn x(r: &UnsafeCell<i32>) {
+ /// unsafe {
+ /// *r.get() += 1;
+ /// }
+ /// }
+ /// ```
+ pub CAST_REF_TO_MUT,
+ correctness,
+ "a cast of reference to a mutable pointer"
+}
+
+declare_lint_pass!(RefToMut => [CAST_REF_TO_MUT]);
+
+impl<'tcx> LateLintPass<'tcx> for RefToMut {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Unary(UnOp::UnDeref, e) = &expr.kind;
+ if let ExprKind::Cast(e, t) = &e.kind;
+ if let TyKind::Ptr(MutTy { mutbl: Mutability::Mut, .. }) = t.kind;
+ if let ExprKind::Cast(e, t) = &e.kind;
+ if let TyKind::Ptr(MutTy { mutbl: Mutability::Not, .. }) = t.kind;
+ if let ty::Ref(..) = cx.tables().node_type(e.hir_id).kind;
+ then {
+ span_lint(
+ cx,
+ CAST_REF_TO_MUT,
+ expr.span,
+ "casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell`",
+ );
+ }
+ }
+ }
+}
--- /dev/null
- match binop {
- BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Ge | BinOpKind::Gt => true,
- _ => false,
- }
+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<dyn Trait> = ...
+ /// let b: Rc<dyn Trait> = ...
+ /// 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 {
- if let ty::FnDef(..) = cx.tables().expr_ty(expr).kind {
- true
- } else {
- false
- }
++ 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.tables().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.tables().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.tables().node_substs(func.hir_id).type_at(0);
+ if ty_param.is_trait();
+ then {
+ span_lint_and_help(
+ cx,
+ VTABLE_ADDRESS_COMPARISONS,
+ expr.span,
+ "comparing trait object pointers compares a non-unique vtable address",
+ None,
+ "consider extracting and comparing data pointers only",
+ );
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::Binary(binop, ref left, ref right) = expr.kind;
+ if is_comparison(binop.node);
+ if cx.tables().expr_ty_adjusted(left).is_fn_ptr() &&
+ cx.tables().expr_ty_adjusted(right).is_fn_ptr();
+ if is_fn_def(cx, left) || is_fn_def(cx, right);
+ then {
+ span_lint(
+ cx,
+ FN_ADDRESS_COMPARISONS,
+ expr.span,
+ "comparing with a non-unique address of a function item",
+ );
+ }
+ }
+ }
+}
--- /dev/null
- /// Detects when people use `Vec::sort_by` and pass in a function
+use crate::utils;
+use crate::utils::paths;
+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::symbol::Ident;
+
+declare_clippy_lint! {
+ /// **What it does:**
- /// possible) than to use `Vec::sort_by` and and a more complicated
++ /// 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
- /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't
- /// imported by a use statement in the current frame, then a `use`
- /// statement that imports it will need to be added (which this lint
- /// can't do).
++ /// possible) than to use `Vec::sort_by` and a more complicated
+ /// closure.
+ ///
+ /// **Known problems:**
- Some(LintTrigger::Sort(SortDetection { vec_name, unstable }))
- }
- else {
- Some(LintTrigger::SortByKey(SortByKeyDetection {
- vec_name,
- unstable,
- closure_arg,
- closure_body,
- reverse
- }))
++ /// 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<A> = 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<A> = 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<LintTrigger> {
+ 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::match_type(cx, &cx.tables().expr_ty(vec), &paths::VEC);
+ 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.to_ident_string() == "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_chain! {
+ if let ExprKind::Path(QPath::Resolved(_, Path {
+ segments: [PathSegment { ident: left_name, .. }], ..
+ })) = &left_expr.kind;
+ if left_name == left_ident;
+ then {
- } else {
- None
++ return Some(LintTrigger::Sort(SortDetection { vec_name, unstable }))
++ } else {
++ if !key_returns_borrow(cx, left_expr) {
++ return Some(LintTrigger::SortByKey(SortByKeyDetection {
++ vec_name,
++ unstable,
++ closure_arg,
++ closure_body,
++ reverse
++ }))
++ }
+ }
+ }
+ }
+ }
++
++ None
++}
++
++fn key_returns_borrow(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
++ if let Some(def_id) = utils::fn_def_id(cx, expr) {
++ let output = cx.tcx.fn_sig(def_id).output();
++ let ty = output.skip_binder();
++ return matches!(ty.kind, ty::Ref(..))
++ || ty.walk().any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
++ }
++
++ false
+}
+
+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 => {},
+ }
+ }
+}
--- /dev/null
- if !cx.sess.opts.unstable_features.is_nightly_build() {
- // User cannot do `#![feature(or_patterns)]`, so bail.
+#![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::ast::{self, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
+use rustc_ast::mut_visit::*;
+use rustc_ast::ptr::P;
+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) {
- ps1[idx].is_rest() == ps2[idx].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`.
- && ps1.len() == ps2.len()
++ 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<Pat>) {
+ struct Visitor;
+ impl MutVisitor for Visitor {
+ fn visit_pat(&mut self, pat: &mut P<Pat>) {
+ 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<Pat>) {
+ struct Visitor;
+ impl MutVisitor for Visitor {
+ fn visit_pat(&mut self, pat: &mut P<Pat>) {
+ 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<Pat>) -> bool {
+ struct Visitor {
+ changed: bool,
+ }
+ impl MutVisitor for Visitor {
+ fn visit_pat(&mut self, p: &mut P<Pat>) {
+ // 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<P<Pat>>, 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<ast::FieldPat>,
+ rest1: bool,
+ start: usize,
+ alternatives: &mut Vec<P<Pat>>,
+) -> 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<P<Pat>>,
+ start: usize,
+ alternatives: &mut Vec<P<Pat>>,
+ predicate: impl Fn(&PatKind, &[P<Pat>], usize) -> bool,
+ extract: impl Fn(PatKind) -> Vec<P<Pat>>,
+) -> 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,
+ };
+ 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<P<Pat>>) -> bool {
+ fn extend(target: &mut Pat, mut tail_or: Vec<P<Pat>>) {
+ 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<P<Pat>>,
+ predicate: impl Fn(&PatKind) -> bool,
+ extract: impl Fn(PatKind) -> P<Pat>,
+) -> Vec<P<Pat>> {
+ 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<P<Pat>>,
+ predicate: impl Fn(&PatKind) -> bool,
+ extract: impl Fn(PatKind) -> P<Pat>,
+) -> 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<Pat>], ps2: &[P<Pat>], 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))
+}
--- /dev/null
- let should_check = if let Some(ref params) = *parameters {
- !params.parenthesized && !params.args.iter().any(|arg| match arg {
- GenericArg::Lifetime(_) => true,
- _ => false,
- })
- } else {
- true
- };
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{walk_item, walk_path, walk_ty, NestedVisitorMap, Visitor};
+use rustc_hir::{
+ def, FnDecl, FnRetTy, FnSig, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Path, PathSegment, QPath,
+ TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_middle::ty::{DefIdTree, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+use rustc_typeck::hir_ty_to_ty;
+
+use crate::utils::{differing_macro_contexts, span_lint_and_sugg};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for unnecessary repetition of structure name when a
+ /// replacement with `Self` is applicable.
+ ///
+ /// **Why is this bad?** Unnecessary repetition. Mixed use of `Self` and struct
+ /// name
+ /// feels inconsistent.
+ ///
+ /// **Known problems:**
+ /// - False positive when using associated types (#2843)
+ /// - False positives in some situations when using generics (#3410)
+ ///
+ /// **Example:**
+ /// ```rust
+ /// struct Foo {}
+ /// impl Foo {
+ /// fn new() -> Foo {
+ /// Foo {}
+ /// }
+ /// }
+ /// ```
+ /// could be
+ /// ```rust
+ /// struct Foo {}
+ /// impl Foo {
+ /// fn new() -> Self {
+ /// Self {}
+ /// }
+ /// }
+ /// ```
+ pub USE_SELF,
+ nursery,
+ "Unnecessary structure name repetition whereas `Self` is applicable"
+}
+
+declare_lint_pass!(UseSelf => [USE_SELF]);
+
+const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element";
+
+fn span_use_self_lint(cx: &LateContext<'_>, path: &Path<'_>, last_segment: Option<&PathSegment<'_>>) {
+ let last_segment = last_segment.unwrap_or_else(|| path.segments.last().expect(SEGMENTS_MSG));
+
+ // Path segments only include actual path, no methods or fields.
+ let last_path_span = last_segment.ident.span;
+
+ if differing_macro_contexts(path.span, last_path_span) {
+ return;
+ }
+
+ // Only take path up to the end of last_path_span.
+ let span = path.span.with_hi(last_path_span.hi());
+
+ span_lint_and_sugg(
+ cx,
+ USE_SELF,
+ span,
+ "unnecessary structure name repetition",
+ "use the applicable keyword",
+ "Self".to_owned(),
+ Applicability::MachineApplicable,
+ );
+}
+
+// FIXME: always use this (more correct) visitor, not just in method signatures.
+struct SemanticUseSelfVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ self_ty: Ty<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for SemanticUseSelfVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_ty(&mut self, hir_ty: &'tcx hir::Ty<'_>) {
+ if let TyKind::Path(QPath::Resolved(_, path)) = &hir_ty.kind {
+ match path.res {
+ def::Res::SelfTy(..) => {},
+ _ => {
+ if hir_ty_to_ty(self.cx.tcx, hir_ty) == self.self_ty {
+ span_use_self_lint(self.cx, path, None);
+ }
+ },
+ }
+ }
+
+ walk_ty(self, hir_ty)
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+fn check_trait_method_impl_decl<'tcx>(
+ cx: &LateContext<'tcx>,
+ impl_item: &ImplItem<'_>,
+ impl_decl: &'tcx FnDecl<'_>,
+ impl_trait_ref: ty::TraitRef<'tcx>,
+) {
+ let trait_method = cx
+ .tcx
+ .associated_items(impl_trait_ref.def_id)
+ .find_by_name_and_kind(cx.tcx, impl_item.ident, ty::AssocKind::Fn, impl_trait_ref.def_id)
+ .expect("impl method matches a trait method");
+
+ let trait_method_sig = cx.tcx.fn_sig(trait_method.def_id);
+ let trait_method_sig = cx.tcx.erase_late_bound_regions(&trait_method_sig);
+
+ let output_hir_ty = if let FnRetTy::Return(ty) = &impl_decl.output {
+ Some(&**ty)
+ } else {
+ None
+ };
+
+ // `impl_hir_ty` (of type `hir::Ty`) represents the type written in the signature.
+ // `trait_ty` (of type `ty::Ty`) is the semantic type for the signature in the trait.
+ // We use `impl_hir_ty` to see if the type was written as `Self`,
+ // `hir_ty_to_ty(...)` to check semantic types of paths, and
+ // `trait_ty` to determine which parts of the signature in the trait, mention
+ // the type being implemented verbatim (as opposed to `Self`).
+ for (impl_hir_ty, trait_ty) in impl_decl
+ .inputs
+ .iter()
+ .chain(output_hir_ty)
+ .zip(trait_method_sig.inputs_and_output)
+ {
+ // Check if the input/output type in the trait method specifies the implemented
+ // type verbatim, and only suggest `Self` if that isn't the case.
+ // This avoids suggestions to e.g. replace `Vec<u8>` with `Vec<Self>`,
+ // in an `impl Trait for u8`, when the trait always uses `Vec<u8>`.
+ // See also https://github.com/rust-lang/rust-clippy/issues/2894.
+ let self_ty = impl_trait_ref.self_ty();
+ if !trait_ty.walk().any(|inner| inner == self_ty.into()) {
+ let mut visitor = SemanticUseSelfVisitor { cx, self_ty };
+
+ visitor.visit_ty(&impl_hir_ty);
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UseSelf {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if in_external_macro(cx.sess(), item.span) {
+ return;
+ }
+ if_chain! {
+ if let ItemKind::Impl{ self_ty: ref item_type, items: refs, .. } = item.kind;
+ if let TyKind::Path(QPath::Resolved(_, ref item_path)) = item_type.kind;
+ then {
+ let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args;
++ let should_check = parameters.as_ref().map_or(
++ true,
++ |params| !params.parenthesized
++ &&!params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
++ );
+
+ if should_check {
+ let visitor = &mut UseSelfVisitor {
+ item_path,
+ cx,
+ };
+ let impl_def_id = cx.tcx.hir().local_def_id(item.hir_id);
+ let impl_trait_ref = cx.tcx.impl_trait_ref(impl_def_id);
+
+ if let Some(impl_trait_ref) = impl_trait_ref {
+ for impl_item_ref in refs {
+ let impl_item = cx.tcx.hir().impl_item(impl_item_ref.id);
+ if let ImplItemKind::Fn(FnSig{ decl: impl_decl, .. }, impl_body_id)
+ = &impl_item.kind {
+ check_trait_method_impl_decl(cx, impl_item, impl_decl, impl_trait_ref);
+
+ let body = cx.tcx.hir().body(*impl_body_id);
+ visitor.visit_body(body);
+ } else {
+ visitor.visit_impl_item(impl_item);
+ }
+ }
+ } else {
+ for impl_item_ref in refs {
+ let impl_item = cx.tcx.hir().impl_item(impl_item_ref.id);
+ visitor.visit_impl_item(impl_item);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+struct UseSelfVisitor<'a, 'tcx> {
+ item_path: &'a Path<'a>,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for UseSelfVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
+ if !path.segments.iter().any(|p| p.ident.span.is_dummy()) {
+ if path.segments.len() >= 2 {
+ let last_but_one = &path.segments[path.segments.len() - 2];
+ if last_but_one.ident.name != kw::SelfUpper {
+ let enum_def_id = match path.res {
+ Res::Def(DefKind::Variant, variant_def_id) => self.cx.tcx.parent(variant_def_id),
+ Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), ctor_def_id) => {
+ let variant_def_id = self.cx.tcx.parent(ctor_def_id);
+ variant_def_id.and_then(|def_id| self.cx.tcx.parent(def_id))
+ },
+ _ => None,
+ };
+
+ if self.item_path.res.opt_def_id() == enum_def_id {
+ span_use_self_lint(self.cx, path, Some(last_but_one));
+ }
+ }
+ }
+
+ if path.segments.last().expect(SEGMENTS_MSG).ident.name != kw::SelfUpper {
+ if self.item_path.res == path.res {
+ span_use_self_lint(self.cx, path, None);
+ } else if let Res::Def(DefKind::Ctor(def::CtorOf::Struct, _), ctor_def_id) = path.res {
+ if self.item_path.res.opt_def_id() == self.cx.tcx.parent(ctor_def_id) {
+ span_use_self_lint(self.cx, path, None);
+ }
+ }
+ }
+ }
+
+ walk_path(self, path);
+ }
+
+ fn visit_item(&mut self, item: &'tcx Item<'_>) {
+ match item.kind {
+ ItemKind::Use(..)
+ | ItemKind::Static(..)
+ | ItemKind::Enum(..)
+ | ItemKind::Struct(..)
+ | ItemKind::Union(..)
+ | ItemKind::Impl { .. }
+ | ItemKind::Fn(..) => {
+ // Don't check statements that shadow `Self` or where `Self` can't be used
+ },
+ _ => walk_item(self, item),
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::All(self.cx.tcx.hir())
+ }
+}
--- /dev/null
- match (l, r) {
- (Defaultness::Final, Defaultness::Final) | (Defaultness::Default(_), Defaultness::Default(_)) => true,
- _ => false,
- }
+//! 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::utils::{both, over};
+use rustc_ast::ast::{self, *};
+use rustc_ast::ptr::P;
+use rustc_span::symbol::Ident;
+use std::mem;
+
+/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
+pub fn unordered_over<X>(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: &FieldPat, r: &FieldPat) -> 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<P<Expr>>, r: &Option<P<Expr>>) -> bool {
+ both(l, r, |l, r| eq_expr(l, r))
+}
+
+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(lp, lfs, lb), Struct(rp, rfs, rb)) => {
+ eq_path(lp, rp) && eq_expr_opt(lb, rb) && unordered_over(lfs, rfs, |l, r| eq_field(l, r))
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_field(l: &Field, r: &Field) -> 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<Label>, r: &Option<Label>) -> bool {
+ both(l, r, |l, r| eq_id(l.ident, r.ident))
+}
+
+pub fn eq_block(l: &Block, r: &Block) -> bool {
+ l.rules == r.rules && over(&l.stmts, &r.stmts, |l, r| eq_stmt(l, r))
+}
+
+pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
+ use StmtKind::*;
+ match (&l.kind, &r.kind) {
+ (Local(l), Local(r)) => {
+ eq_pat(&l.pat, &r.pat)
+ && both(&l.ty, &r.ty, |l, r| eq_ty(l, r))
+ && eq_expr_opt(&l.init, &r.init)
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ },
+ (Item(l), Item(r)) => eq_item(l, r, eq_item_kind),
+ (Expr(l), Expr(r)) | (Semi(l), Semi(r)) => eq_expr(l, r),
+ (Empty, Empty) => true,
+ (MacCall(l), MacCall(r)) => l.1 == r.1 && eq_mac_call(&l.0, &r.0) && over(&l.2, &r.2, |l, r| eq_attr(l, r)),
+ _ => false,
+ }
+}
+
+pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool {
+ eq_id(l.ident, r.ident)
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ && eq_vis(&l.vis, &r.vis)
+ && eq_kind(&l.kind, &r.kind)
+}
+
+pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
+ use ItemKind::*;
+ match (l, r) {
+ (ExternCrate(l), ExternCrate(r)) => l == r,
+ (Use(l), Use(r)) => eq_use_tree(l, r),
+ (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (Fn(ld, lf, lg, lb), Fn(rd, rf, rg, rb)) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
+ (Mod(l), Mod(r)) => l.inline == r.inline && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_item_kind)),
+ (ForeignMod(l), ForeignMod(r)) => {
+ both(&l.abi, &r.abi, |l, r| eq_str_lit(l, r))
+ && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
+ },
+ (TyAlias(ld, lg, lb, lt), TyAlias(rd, rg, rb, rt)) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, |l, r| eq_generic_bound(l, r))
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (Enum(le, lg), Enum(re, rg)) => {
+ over(&le.variants, &re.variants, |l, r| eq_variant(l, r)) && eq_generics(lg, rg)
+ },
+ (Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => {
+ eq_variant_data(lv, rv) && eq_generics(lg, rg)
+ },
+ (Trait(la, lu, lg, lb, li), Trait(ra, ru, rg, rb, ri)) => {
+ la == ra
+ && matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
+ && eq_generics(lg, rg)
+ && over(lb, rb, |l, r| eq_generic_bound(l, r))
+ && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
+ },
+ (TraitAlias(lg, lb), TraitAlias(rg, rb)) => eq_generics(lg, rg) && over(lb, rb, |l, r| eq_generic_bound(l, r)),
+ (
+ Impl {
+ unsafety: lu,
+ polarity: lp,
+ defaultness: ld,
+ constness: lc,
+ generics: lg,
+ of_trait: lot,
+ self_ty: lst,
+ items: li,
+ },
+ Impl {
+ unsafety: ru,
+ polarity: rp,
+ defaultness: rd,
+ constness: rc,
+ generics: rg,
+ of_trait: rot,
+ self_ty: rst,
+ items: ri,
+ },
+ ) => {
+ matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
+ && matches!(lp, ImplPolarity::Positive) == matches!(rp, ImplPolarity::Positive)
+ && eq_defaultness(*ld, *rd)
+ && matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No)
+ && eq_generics(lg, rg)
+ && both(lot, rot, |l, r| eq_path(&l.path, &r.path))
+ && eq_ty(lst, rst)
+ && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ (MacroDef(l), MacroDef(r)) => l.macro_rules == r.macro_rules && eq_mac_args(&l.body, &r.body),
+ _ => false,
+ }
+}
+
+pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
+ use ForeignItemKind::*;
+ match (l, r) {
+ (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (Fn(ld, lf, lg, lb), Fn(rd, rf, rg, rb)) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
+ (TyAlias(ld, lg, lb, lt), TyAlias(rd, rg, rb, rt)) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, |l, r| eq_generic_bound(l, r))
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
+ use AssocItemKind::*;
+ match (l, r) {
+ (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (Fn(ld, lf, lg, lb), Fn(rd, rf, rg, rb)) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
+ (TyAlias(ld, lg, lb, lt), TyAlias(rd, rg, rb, rt)) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, |l, r| eq_generic_bound(l, r))
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_variant(l: &Variant, r: &Variant) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ && eq_vis(&l.vis, &r.vis)
+ && eq_id(l.ident, r.ident)
+ && eq_variant_data(&l.data, &r.data)
+ && both(&l.disr_expr, &r.disr_expr, |l, r| eq_expr(&l.value, &r.value))
+}
+
+pub fn eq_variant_data(l: &VariantData, r: &VariantData) -> bool {
+ use VariantData::*;
+ match (l, r) {
+ (Unit(_), Unit(_)) => true,
+ (Struct(l, _), Struct(r, _)) | (Tuple(l, _), Tuple(r, _)) => over(l, r, |l, r| eq_struct_field(l, r)),
+ _ => false,
+ }
+}
+
+pub fn eq_struct_field(l: &StructField, r: &StructField) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ && eq_vis(&l.vis, &r.vis)
+ && both(&l.ident, &r.ident, |l, r| eq_id(*l, *r))
+ && eq_ty(&l.ty, &r.ty)
+}
+
+pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
+ eq_fn_decl(&l.decl, &r.decl) && eq_fn_header(&l.header, &r.header)
+}
+
+pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
+ matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No)
+ && l.asyncness.is_async() == r.asyncness.is_async()
+ && matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
+ && eq_ext(&l.ext, &r.ext)
+}
+
+pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
+ over(&l.params, &r.params, |l, r| eq_generic_param(l, r))
+ && over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
+ eq_where_predicate(l, r)
+ })
+}
+
+pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
+ use WherePredicate::*;
+ match (l, r) {
+ (BoundPredicate(l), BoundPredicate(r)) => {
+ over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
+ eq_generic_param(l, r)
+ }) && eq_ty(&l.bounded_ty, &r.bounded_ty)
+ && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
+ },
+ (RegionPredicate(l), RegionPredicate(r)) => {
+ eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
+ },
+ (EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
+ _ => false,
+ }
+}
+
+pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
+ eq_path(&l.prefix, &r.prefix) && eq_use_tree_kind(&l.kind, &r.kind)
+}
+
+pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool {
+ use UseTreeKind::*;
+ match (l, r) {
+ (Glob, Glob) => true,
+ (Simple(l, _, _), Simple(r, _, _)) => both(l, r, |l, r| eq_id(*l, *r)),
+ (Nested(l), Nested(r)) => over(l, r, |(l, _), (r, _)| eq_use_tree(l, r)),
+ _ => false,
+ }
+}
+
+pub fn eq_defaultness(l: Defaultness, r: Defaultness) -> bool {
++ matches!((l, r), (Defaultness::Final, Defaultness::Final) | (Defaultness::Default(_), Defaultness::Default(_)))
+}
+
+pub fn eq_vis(l: &Visibility, r: &Visibility) -> bool {
+ use VisibilityKind::*;
+ match (&l.node, &r.node) {
+ (Public, Public) | (Inherited, Inherited) | (Crate(_), Crate(_)) => true,
+ (Restricted { path: l, .. }, Restricted { path: r, .. }) => eq_path(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool {
+ eq_fn_ret_ty(&l.output, &r.output)
+ && over(&l.inputs, &r.inputs, |l, r| {
+ l.is_placeholder == r.is_placeholder
+ && eq_pat(&l.pat, &r.pat)
+ && eq_ty(&l.ty, &r.ty)
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ })
+}
+
+pub fn eq_fn_ret_ty(l: &FnRetTy, r: &FnRetTy) -> bool {
+ match (l, r) {
+ (FnRetTy::Default(_), FnRetTy::Default(_)) => true,
+ (FnRetTy::Ty(l), FnRetTy::Ty(r)) => eq_ty(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
+ use TyKind::*;
+ match (&l.kind, &r.kind) {
+ (Paren(l), _) => eq_ty(l, r),
+ (_, Paren(r)) => eq_ty(l, r),
+ (Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err, Err) | (CVarArgs, CVarArgs) => true,
+ (Slice(l), Slice(r)) => eq_ty(l, r),
+ (Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value),
+ (Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty),
+ (Rptr(ll, l), Rptr(rl, r)) => {
+ both(ll, rl, |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty)
+ },
+ (BareFn(l), BareFn(r)) => {
+ l.unsafety == r.unsafety
+ && eq_ext(&l.ext, &r.ext)
+ && over(&l.generic_params, &r.generic_params, |l, r| eq_generic_param(l, r))
+ && eq_fn_decl(&l.decl, &r.decl)
+ },
+ (Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
+ (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
+ (TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, |l, r| eq_generic_bound(l, r)),
+ (ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, |l, r| eq_generic_bound(l, r)),
+ (Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_ext(l: &Extern, r: &Extern) -> bool {
+ use Extern::*;
+ match (l, r) {
+ (None, None) | (Implicit, Implicit) => true,
+ (Explicit(l), Explicit(r)) => eq_str_lit(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_str_lit(l: &StrLit, r: &StrLit) -> bool {
+ l.style == r.style && l.symbol == r.symbol && l.suffix == r.suffix
+}
+
+pub fn eq_poly_ref_trait(l: &PolyTraitRef, r: &PolyTraitRef) -> bool {
+ eq_path(&l.trait_ref.path, &r.trait_ref.path)
+ && over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
+ eq_generic_param(l, r)
+ })
+}
+
+pub fn eq_generic_param(l: &GenericParam, r: &GenericParam) -> bool {
+ use GenericParamKind::*;
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
+ && match (&l.kind, &r.kind) {
+ (Lifetime, Lifetime) => true,
+ (Type { default: l }, Type { default: r }) => both(l, r, |l, r| eq_ty(l, r)),
+ (Const { ty: l, kw_span: _ }, Const { ty: r, kw_span: _ }) => eq_ty(l, r),
+ _ => false,
+ }
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+}
+
+pub fn eq_generic_bound(l: &GenericBound, r: &GenericBound) -> bool {
+ use GenericBound::*;
+ match (l, r) {
+ (Trait(ptr1, tbm1), Trait(ptr2, tbm2)) => tbm1 == tbm2 && eq_poly_ref_trait(ptr1, ptr2),
+ (Outlives(l), Outlives(r)) => eq_id(l.ident, r.ident),
+ _ => false,
+ }
+}
+
+pub fn eq_assoc_constraint(l: &AssocTyConstraint, r: &AssocTyConstraint) -> bool {
+ use AssocTyConstraintKind::*;
+ eq_id(l.ident, r.ident)
+ && match (&l.kind, &r.kind) {
+ (Equality { ty: l }, Equality { ty: r }) => eq_ty(l, r),
+ (Bound { bounds: l }, Bound { bounds: r }) => over(l, r, |l, r| eq_generic_bound(l, r)),
+ _ => false,
+ }
+}
+
+pub fn eq_mac_call(l: &MacCall, r: &MacCall) -> bool {
+ eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args)
+}
+
+pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
+ use AttrKind::*;
+ l.style == r.style
+ && match (&l.kind, &r.kind) {
+ (DocComment(l), DocComment(r)) => l == r,
+ (Normal(l), Normal(r)) => eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args),
+ _ => false,
+ }
+}
+
+pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
+ use MacArgs::*;
+ match (l, r) {
+ (Empty, Empty) => true,
+ (Delimited(_, ld, lts), Delimited(_, rd, rts)) => ld == rd && lts.eq_unspanned(rts),
+ (Eq(_, lts), Eq(_, rts)) => lts.eq_unspanned(rts),
+ _ => false,
+ }
+}
--- /dev/null
- if let Some(deprecation_status) =
- BUILTIN_ATTRIBUTES
- .iter()
- .find_map(|(builtin_name, deprecation_status)| {
- if *builtin_name == attr_segments[1].ident.to_string() {
- Some(deprecation_status)
- } else {
- None
- }
- })
- {
- let mut diag = sess.struct_span_err(attr_segments[1].ident.span, "Usage of deprecated attribute");
- match *deprecation_status {
- DeprecationStatus::Deprecated => {
- diag.emit();
- false
- },
- DeprecationStatus::Replaced(new_name) => {
- diag.span_suggestion(
- attr_segments[1].ident.span,
- "consider using",
- new_name.to_string(),
- Applicability::MachineApplicable,
- );
- diag.emit();
+use rustc_ast::ast;
+use rustc_ast::expand::is_proc_macro_attr;
+use rustc_errors::Applicability;
+use rustc_session::Session;
+use std::str::FromStr;
+
+/// Deprecation status of attributes known by Clippy.
+#[allow(dead_code)]
+pub enum DeprecationStatus {
+ /// Attribute is deprecated
+ Deprecated,
+ /// Attribute is deprecated and was replaced by the named attribute
+ Replaced(&'static str),
+ None,
+}
+
+pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
+ ("author", DeprecationStatus::None),
+ ("cognitive_complexity", DeprecationStatus::None),
+ (
+ "cyclomatic_complexity",
+ DeprecationStatus::Replaced("cognitive_complexity"),
+ ),
+ ("dump", DeprecationStatus::None),
+];
+
+pub struct LimitStack {
+ stack: Vec<u64>,
+}
+
+impl Drop for LimitStack {
+ fn drop(&mut self) {
+ assert_eq!(self.stack.len(), 1);
+ }
+}
+
+impl LimitStack {
+ #[must_use]
+ pub fn new(limit: u64) -> Self {
+ Self { stack: vec![limit] }
+ }
+ pub fn limit(&self) -> u64 {
+ *self.stack.last().expect("there should always be a value in the stack")
+ }
+ pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
+ let stack = &mut self.stack;
+ parse_attrs(sess, attrs, name, |val| stack.push(val));
+ }
+ pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
+ let stack = &mut self.stack;
+ parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
+ }
+}
+
+pub fn get_attr<'a>(
+ sess: &'a Session,
+ attrs: &'a [ast::Attribute],
+ name: &'static str,
+) -> impl Iterator<Item = &'a ast::Attribute> {
+ attrs.iter().filter(move |attr| {
+ let attr = if let ast::AttrKind::Normal(ref attr) = attr.kind {
+ attr
+ } else {
+ return false;
+ };
+ let attr_segments = &attr.path.segments;
+ if attr_segments.len() == 2 && attr_segments[0].ident.to_string() == "clippy" {
- DeprecationStatus::None => {
- diag.cancel();
- attr_segments[1].ident.to_string() == name
++ BUILTIN_ATTRIBUTES
++ .iter()
++ .find_map(|(builtin_name, deprecation_status)| {
++ if *builtin_name == attr_segments[1].ident.to_string() {
++ Some(deprecation_status)
++ } else {
++ None
++ }
++ })
++ .map_or_else(
++ || {
++ sess.span_err(attr_segments[1].ident.span, "Usage of unknown attribute");
+ false
+ },
- }
- } else {
- sess.span_err(attr_segments[1].ident.span, "Usage of unknown attribute");
- false
- }
++ |deprecation_status| {
++ let mut diag =
++ sess.struct_span_err(attr_segments[1].ident.span, "Usage of deprecated attribute");
++ match *deprecation_status {
++ DeprecationStatus::Deprecated => {
++ diag.emit();
++ false
++ },
++ DeprecationStatus::Replaced(new_name) => {
++ diag.span_suggestion(
++ attr_segments[1].ident.span,
++ "consider using",
++ new_name.to_string(),
++ Applicability::MachineApplicable,
++ );
++ diag.emit();
++ false
++ },
++ DeprecationStatus::None => {
++ diag.cancel();
++ attr_segments[1].ident.to_string() == name
++ },
++ }
+ },
++ )
+ } else {
+ false
+ }
+ })
+}
+
+fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
+ for attr in get_attr(sess, attrs, name) {
+ if let Some(ref value) = attr.value_str() {
+ if let Ok(value) = FromStr::from_str(&value.as_str()) {
+ f(value)
+ } else {
+ sess.span_err(attr.span, "not a number");
+ }
+ } else {
+ sess.span_err(attr.span, "bad clippy attribute");
+ }
+ }
+}
+
+/// Return true if the attributes contain any of `proc_macro`,
+/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
+pub fn is_proc_macro(attrs: &[ast::Attribute]) -> bool {
+ attrs.iter().any(is_proc_macro_attr)
+}
--- /dev/null
+//! Read configurations files.
+
+#![deny(clippy::missing_docs_in_private_items)]
+
+use lazy_static::lazy_static;
+use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem};
+use rustc_span::source_map;
+use source_map::Span;
+use std::path::{Path, PathBuf};
+use std::sync::Mutex;
+use std::{env, fmt, fs, io};
+
+/// Gets the configuration file from arguments.
+pub fn file_from_args(args: &[NestedMetaItem]) -> Result<Option<PathBuf>, (&'static str, Span)> {
+ for arg in args.iter().filter_map(NestedMetaItem::meta_item) {
+ if arg.check_name(sym!(conf_file)) {
+ return match arg.kind {
+ MetaItemKind::Word | MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)),
+ MetaItemKind::NameValue(ref value) => {
+ if let LitKind::Str(ref file, _) = value.kind {
+ Ok(Some(file.to_string().into()))
+ } else {
+ Err(("`conf_file` value must be a string", value.span))
+ }
+ },
+ };
+ }
+ }
+
+ Ok(None)
+}
+
+/// Error from reading a configuration file.
+#[derive(Debug)]
+pub enum Error {
+ /// An I/O error.
+ Io(io::Error),
+ /// Not valid toml or doesn't fit the expected config format
+ Toml(String),
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Io(err) => err.fmt(f),
+ Self::Toml(err) => err.fmt(f),
+ }
+ }
+}
+
+impl From<io::Error> for Error {
+ fn from(e: io::Error) -> Self {
+ Self::Io(e)
+ }
+}
+
+lazy_static! {
+ static ref ERRORS: Mutex<Vec<Error>> = Mutex::new(Vec::new());
+}
+
+macro_rules! define_Conf {
+ ($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => {
+ mod helpers {
+ use serde::Deserialize;
+ /// Type used to store lint configuration.
+ #[derive(Deserialize)]
+ #[serde(rename_all = "kebab-case", deny_unknown_fields)]
+ pub struct Conf {
+ $(
+ #[$doc]
+ #[serde(default = $config_str)]
+ #[serde(with = $config_str)]
+ pub $config: $Ty,
+ )+
+ #[allow(dead_code)]
+ #[serde(default)]
+ third_party: Option<::toml::Value>,
+ }
+
+ $(
+ mod $config {
+ use serde::Deserialize;
+ pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> {
+ use super::super::{ERRORS, Error};
+ Ok(
+ <$Ty>::deserialize(deserializer).unwrap_or_else(|e| {
+ ERRORS
+ .lock()
+ .expect("no threading here")
+ .push(Error::Toml(e.to_string()));
+ super::$config()
+ })
+ )
+ }
+ }
+
+ #[must_use]
+ fn $config() -> $Ty {
+ let x = $default;
+ x
+ }
+ )+
+ }
+ };
+}
+
+pub use self::helpers::Conf;
+define_Conf! {
+ /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
+ (blacklisted_names, "blacklisted_names": Vec<String>, ["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<u64>, None),
+ /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
+ (doc_valid_idents, "doc_valid_idents": Vec<String>, [
+ "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
+ "DirectX",
+ "ECMAScript",
+ "GPLv2", "GPLv3",
+ "GitHub", "GitLab",
+ "IPv4", "IPv6",
+ "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript",
+ "NaN", "NaNs",
+ "OAuth",
+ "OCaml",
+ "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap",
+ "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. 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<u64>, None),
+ /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
+ (too_many_lines_threshold, "too_many_lines_threshold": u64, 100),
+ /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack
+ (array_size_threshold, "array_size_threshold": u64, 512_000),
+ /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
+ (vec_box_size_threshold, "vec_box_size_threshold": u64, 4096),
++ /// Lint: 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),
+}
+
+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<Option<PathBuf>> {
+ /// 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<Error>) -> (Conf, Vec<Error>) {
+ (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<Error>) {
+ 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<u64> = 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)
+ },
+ }
+}
--- /dev/null
- for arg in *arg_list {
- match arg {
- GenericArg::Lifetime(ref l) => self.hash_lifetime(l),
- GenericArg::Type(ref ty) => self.hash_ty(&ty),
- GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
- }
- }
+use crate::consts::{constant_context, constant_simple};
+use crate::utils::differing_macro_contexts;
+use rustc_ast::ast::InlineAsmTemplatePiece;
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
+use rustc_hir::{
+ BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, Field, FnRetTy, GenericArg,
+ GenericArgs, Guard, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path, PathSegment, QPath,
+ Stmt, StmtKind, Ty, TyKind, TypeBinding,
+};
+use rustc_lint::LateContext;
+use rustc_middle::ich::StableHashingContextProvider;
+use rustc_middle::ty::TypeckTables;
+use rustc_span::Symbol;
+use std::hash::Hash;
+
+/// Type used to check whether two ast are the same. This is different from the
+/// operator
+/// `==` on ast types as this operator would compare true equality with ID and
+/// span.
+///
+/// Note that some expressions kinds are not considered but could be added.
+pub struct SpanlessEq<'a, 'tcx> {
+ /// Context used to evaluate constant expressions.
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_tables: Option<&'tcx TypeckTables<'tcx>>,
+ /// If is true, never consider as equal expressions containing function
+ /// calls.
+ ignore_fn: bool,
+}
+
+impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
+ pub fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_tables: cx.maybe_typeck_tables(),
+ ignore_fn: false,
+ }
+ }
+
+ pub fn ignore_fn(self) -> Self {
+ Self {
+ ignore_fn: true,
+ ..self
+ }
+ }
+
+ /// Checks whether two statements are the same.
+ pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&StmtKind::Local(ref l), &StmtKind::Local(ref r)) => {
+ self.eq_pat(&l.pat, &r.pat)
+ && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r))
+ && both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
+ },
+ (&StmtKind::Expr(ref l), &StmtKind::Expr(ref r)) | (&StmtKind::Semi(ref l), &StmtKind::Semi(ref r)) => {
+ self.eq_expr(l, r)
+ },
+ _ => false,
+ }
+ }
+
+ /// Checks whether two blocks are the same.
+ pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
+ over(&left.stmts, &right.stmts, |l, r| self.eq_stmt(l, r))
+ && both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
+ }
+
+ #[allow(clippy::similar_names)]
+ pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ if self.ignore_fn && differing_macro_contexts(left.span, right.span) {
+ return false;
+ }
+
+ if let Some(tables) = self.maybe_typeck_tables {
+ if let (Some(l), Some(r)) = (
+ constant_simple(self.cx, tables, left),
+ constant_simple(self.cx, tables, right),
+ ) {
+ if l == r {
+ return true;
+ }
+ }
+ }
+
+ match (&left.kind, &right.kind) {
+ (&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => {
+ lb == rb && l_mut == r_mut && self.eq_expr(le, re)
+ },
+ (&ExprKind::Continue(li), &ExprKind::Continue(ri)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.as_str() == r.ident.as_str())
+ },
+ (&ExprKind::Assign(ref ll, ref lr, _), &ExprKind::Assign(ref rl, ref rr, _)) => {
+ self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ },
+ (&ExprKind::AssignOp(ref lo, ref ll, ref lr), &ExprKind::AssignOp(ref ro, ref rl, ref rr)) => {
+ lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ },
+ (&ExprKind::Block(ref l, _), &ExprKind::Block(ref r, _)) => self.eq_block(l, r),
+ (&ExprKind::Binary(l_op, ref ll, ref lr), &ExprKind::Binary(r_op, ref rl, ref rr)) => {
+ l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ || swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| {
+ l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ })
+ },
+ (&ExprKind::Break(li, ref le), &ExprKind::Break(ri, ref re)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.as_str() == r.ident.as_str())
+ && both(le, re, |l, r| self.eq_expr(l, r))
+ },
+ (&ExprKind::Box(ref l), &ExprKind::Box(ref r)) => self.eq_expr(l, r),
+ (&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => {
+ !self.ignore_fn && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
+ },
+ (&ExprKind::Cast(ref lx, ref lt), &ExprKind::Cast(ref rx, ref rt))
+ | (&ExprKind::Type(ref lx, ref lt), &ExprKind::Type(ref rx, ref rt)) => {
+ self.eq_expr(lx, rx) && self.eq_ty(lt, rt)
+ },
+ (&ExprKind::Field(ref l_f_exp, ref l_f_ident), &ExprKind::Field(ref r_f_exp, ref r_f_ident)) => {
+ l_f_ident.name == r_f_ident.name && self.eq_expr(l_f_exp, r_f_exp)
+ },
+ (&ExprKind::Index(ref la, ref li), &ExprKind::Index(ref ra, ref ri)) => {
+ self.eq_expr(la, ra) && self.eq_expr(li, ri)
+ },
+ (&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
+ (&ExprKind::Loop(ref lb, ref ll, ref lls), &ExprKind::Loop(ref rb, ref rl, ref rls)) => {
+ lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.as_str() == r.ident.as_str())
+ },
+ (&ExprKind::Match(ref le, ref la, ref ls), &ExprKind::Match(ref re, ref ra, ref rs)) => {
+ ls == rs
+ && self.eq_expr(le, re)
+ && over(la, ra, |l, r| {
+ self.eq_expr(&l.body, &r.body)
+ && both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r))
+ && self.eq_pat(&l.pat, &r.pat)
+ })
+ },
+ (&ExprKind::MethodCall(l_path, _, l_args, _), &ExprKind::MethodCall(r_path, _, r_args, _)) => {
+ !self.ignore_fn && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args)
+ },
+ (&ExprKind::Repeat(ref le, ref ll_id), &ExprKind::Repeat(ref re, ref rl_id)) => {
+ let mut celcx = constant_context(self.cx, self.cx.tcx.body_tables(ll_id.body));
+ let ll = celcx.expr(&self.cx.tcx.hir().body(ll_id.body).value);
+ let mut celcx = constant_context(self.cx, self.cx.tcx.body_tables(rl_id.body));
+ let rl = celcx.expr(&self.cx.tcx.hir().body(rl_id.body).value);
+
+ self.eq_expr(le, re) && ll == rl
+ },
+ (&ExprKind::Ret(ref l), &ExprKind::Ret(ref r)) => both(l, r, |l, r| self.eq_expr(l, r)),
+ (&ExprKind::Path(ref l), &ExprKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&ExprKind::Struct(ref l_path, ref lf, ref lo), &ExprKind::Struct(ref r_path, ref rf, ref ro)) => {
+ self.eq_qpath(l_path, r_path)
+ && both(lo, ro, |l, r| self.eq_expr(l, r))
+ && over(lf, rf, |l, r| self.eq_field(l, r))
+ },
+ (&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
+ (&ExprKind::Unary(l_op, ref le), &ExprKind::Unary(r_op, ref re)) => l_op == r_op && self.eq_expr(le, re),
+ (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
+ (&ExprKind::DropTemps(ref le), &ExprKind::DropTemps(ref re)) => self.eq_expr(le, re),
+ _ => false,
+ }
+ }
+
+ fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool {
+ over(left, right, |l, r| self.eq_expr(l, r))
+ }
+
+ fn eq_field(&mut self, left: &Field<'_>, right: &Field<'_>) -> bool {
+ left.ident.name == right.ident.name && self.eq_expr(&left.expr, &right.expr)
+ }
+
+ fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool {
+ match (left, right) {
+ (Guard::If(l), Guard::If(r)) => self.eq_expr(l, r),
+ }
+ }
+
+ fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool {
+ match (left, right) {
+ (GenericArg::Lifetime(l_lt), GenericArg::Lifetime(r_lt)) => Self::eq_lifetime(l_lt, r_lt),
+ (GenericArg::Type(l_ty), GenericArg::Type(r_ty)) => self.eq_ty(l_ty, r_ty),
+ _ => false,
+ }
+ }
+
+ fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool {
+ left.name == right.name
+ }
+
+ /// Checks whether two patterns are the same.
+ pub fn eq_pat(&mut self, left: &Pat<'_>, right: &Pat<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&PatKind::Box(ref l), &PatKind::Box(ref r)) => self.eq_pat(l, r),
+ (&PatKind::TupleStruct(ref lp, ref la, ls), &PatKind::TupleStruct(ref rp, ref ra, rs)) => {
+ self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat(l, r)) && ls == rs
+ },
+ (&PatKind::Binding(ref lb, .., ref li, ref lp), &PatKind::Binding(ref rb, .., ref ri, ref rp)) => {
+ lb == rb && li.name.as_str() == ri.name.as_str() && both(lp, rp, |l, r| self.eq_pat(l, r))
+ },
+ (&PatKind::Path(ref l), &PatKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&PatKind::Lit(ref l), &PatKind::Lit(ref r)) => self.eq_expr(l, r),
+ (&PatKind::Tuple(ref l, ls), &PatKind::Tuple(ref r, rs)) => {
+ ls == rs && over(l, r, |l, r| self.eq_pat(l, r))
+ },
+ (&PatKind::Range(ref ls, ref le, li), &PatKind::Range(ref rs, ref re, ri)) => {
+ both(ls, rs, |a, b| self.eq_expr(a, b)) && both(le, re, |a, b| self.eq_expr(a, b)) && (li == ri)
+ },
+ (&PatKind::Ref(ref le, ref lm), &PatKind::Ref(ref re, ref rm)) => lm == rm && self.eq_pat(le, re),
+ (&PatKind::Slice(ref ls, ref li, ref le), &PatKind::Slice(ref rs, ref ri, ref re)) => {
+ over(ls, rs, |l, r| self.eq_pat(l, r))
+ && over(le, re, |l, r| self.eq_pat(l, r))
+ && both(li, ri, |l, r| self.eq_pat(l, r))
+ },
+ (&PatKind::Wild, &PatKind::Wild) => true,
+ _ => false,
+ }
+ }
+
+ #[allow(clippy::similar_names)]
+ fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
+ match (left, right) {
+ (&QPath::Resolved(ref lty, ref lpath), &QPath::Resolved(ref rty, ref rpath)) => {
+ both(lty, rty, |l, r| self.eq_ty(l, r)) && self.eq_path(lpath, rpath)
+ },
+ (&QPath::TypeRelative(ref lty, ref lseg), &QPath::TypeRelative(ref rty, ref rseg)) => {
+ self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg)
+ },
+ _ => false,
+ }
+ }
+
+ fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
+ left.is_global() == right.is_global()
+ && over(&left.segments, &right.segments, |l, r| self.eq_path_segment(l, r))
+ }
+
+ fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool {
+ if !(left.parenthesized || right.parenthesized) {
+ over(&left.args, &right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work
+ && over(&left.bindings, &right.bindings, |l, r| self.eq_type_binding(l, r))
+ } else if left.parenthesized && right.parenthesized {
+ over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r))
+ && both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| {
+ self.eq_ty(l, r)
+ })
+ } else {
+ false
+ }
+ }
+
+ pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
+ left.len() == right.len() && left.iter().zip(right).all(|(l, r)| self.eq_path_segment(l, r))
+ }
+
+ pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
+ // The == of idents doesn't work with different contexts,
+ // we have to be explicit about hygiene
+ if left.ident.as_str() != right.ident.as_str() {
+ return false;
+ }
+ match (&left.args, &right.args) {
+ (&None, &None) => true,
+ (&Some(ref l), &Some(ref r)) => self.eq_path_parameters(l, r),
+ _ => false,
+ }
+ }
+
+ pub fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
+ self.eq_ty_kind(&left.kind, &right.kind)
+ }
+
+ #[allow(clippy::similar_names)]
+ pub fn eq_ty_kind(&mut self, left: &TyKind<'_>, right: &TyKind<'_>) -> bool {
+ match (left, right) {
+ (&TyKind::Slice(ref l_vec), &TyKind::Slice(ref r_vec)) => self.eq_ty(l_vec, r_vec),
+ (&TyKind::Array(ref lt, ref ll_id), &TyKind::Array(ref rt, ref rl_id)) => {
+ let old_maybe_typeck_tables = self.maybe_typeck_tables;
+
+ let mut celcx = constant_context(self.cx, self.cx.tcx.body_tables(ll_id.body));
+ self.maybe_typeck_tables = Some(self.cx.tcx.body_tables(ll_id.body));
+ let ll = celcx.expr(&self.cx.tcx.hir().body(ll_id.body).value);
+
+ let mut celcx = constant_context(self.cx, self.cx.tcx.body_tables(rl_id.body));
+ self.maybe_typeck_tables = Some(self.cx.tcx.body_tables(rl_id.body));
+ let rl = celcx.expr(&self.cx.tcx.hir().body(rl_id.body).value);
+
+ let eq_ty = self.eq_ty(lt, rt);
+ self.maybe_typeck_tables = old_maybe_typeck_tables;
+ eq_ty && ll == rl
+ },
+ (&TyKind::Ptr(ref l_mut), &TyKind::Ptr(ref r_mut)) => {
+ l_mut.mutbl == r_mut.mutbl && self.eq_ty(&*l_mut.ty, &*r_mut.ty)
+ },
+ (&TyKind::Rptr(_, ref l_rmut), &TyKind::Rptr(_, ref r_rmut)) => {
+ l_rmut.mutbl == r_rmut.mutbl && self.eq_ty(&*l_rmut.ty, &*r_rmut.ty)
+ },
+ (&TyKind::Path(ref l), &TyKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&TyKind::Tup(ref l), &TyKind::Tup(ref r)) => over(l, r, |l, r| self.eq_ty(l, r)),
+ (&TyKind::Infer, &TyKind::Infer) => true,
+ _ => false,
+ }
+ }
+
+ fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool {
+ left.ident.name == right.ident.name && self.eq_ty(&left.ty(), &right.ty())
+ }
+}
+
+fn swap_binop<'a>(
+ binop: BinOpKind,
+ lhs: &'a Expr<'a>,
+ rhs: &'a Expr<'a>,
+) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> {
+ match binop {
+ BinOpKind::Add | BinOpKind::Eq | BinOpKind::Ne | BinOpKind::BitAnd | BinOpKind::BitXor | BinOpKind::BitOr => {
+ Some((binop, rhs, lhs))
+ },
+ BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)),
+ BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)),
+ BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)),
+ BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)),
+ BinOpKind::Mul // Not always commutative, e.g. with matrices. See issue #5698
+ | BinOpKind::Shl
+ | BinOpKind::Shr
+ | BinOpKind::Rem
+ | BinOpKind::Sub
+ | BinOpKind::Div
+ | BinOpKind::And
+ | BinOpKind::Or => None,
+ }
+}
+
+/// Checks if the two `Option`s are both `None` or some equal values as per
+/// `eq_fn`.
+pub fn both<X>(l: &Option<X>, r: &Option<X>, mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ l.as_ref()
+ .map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
+}
+
+/// Checks if two slices are equal as per `eq_fn`.
+pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
+}
+
+/// Type used to hash an ast element. This is different from the `Hash` trait
+/// on ast types as this
+/// trait would consider IDs and spans.
+///
+/// All expressions kind are hashed, but some might have a weaker hash.
+pub struct SpanlessHash<'a, 'tcx> {
+ /// Context used to evaluate constant expressions.
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_tables: Option<&'tcx TypeckTables<'tcx>>,
+ s: StableHasher,
+}
+
+impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
+ pub fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_tables: cx.maybe_typeck_tables(),
+ s: StableHasher::new(),
+ }
+ }
+
+ pub fn finish(self) -> u64 {
+ self.s.finish()
+ }
+
+ pub fn hash_block(&mut self, b: &Block<'_>) {
+ for s in b.stmts {
+ self.hash_stmt(s);
+ }
+
+ if let Some(ref e) = b.expr {
+ self.hash_expr(e);
+ }
+
+ match b.rules {
+ BlockCheckMode::DefaultBlock => 0,
+ BlockCheckMode::UnsafeBlock(_) => 1,
+ BlockCheckMode::PushUnsafeBlock(_) => 2,
+ BlockCheckMode::PopUnsafeBlock(_) => 3,
+ }
+ .hash(&mut self.s);
+ }
+
+ #[allow(clippy::many_single_char_names, clippy::too_many_lines)]
+ pub fn hash_expr(&mut self, e: &Expr<'_>) {
+ let simple_const = self
+ .maybe_typeck_tables
+ .and_then(|tables| constant_simple(self.cx, tables, e));
+
+ // const hashing may result in the same hash as some unrelated node, so add a sort of
+ // discriminant depending on which path we're choosing next
+ simple_const.is_some().hash(&mut self.s);
+
+ if let Some(e) = simple_const {
+ return e.hash(&mut self.s);
+ }
+
+ std::mem::discriminant(&e.kind).hash(&mut self.s);
+
+ match e.kind {
+ ExprKind::AddrOf(kind, m, ref e) => {
+ match kind {
+ BorrowKind::Ref => 0,
+ BorrowKind::Raw => 1,
+ }
+ .hash(&mut self.s);
+ m.hash(&mut self.s);
+ self.hash_expr(e);
+ },
+ ExprKind::Continue(i) => {
+ if let Some(i) = i.label {
+ self.hash_name(i.ident.name);
+ }
+ },
+ ExprKind::Assign(ref l, ref r, _) => {
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::AssignOp(ref o, ref l, ref r) => {
+ o.node
+ .hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::Block(ref b, _) => {
+ self.hash_block(b);
+ },
+ ExprKind::Binary(op, ref l, ref r) => {
+ op.node
+ .hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::Break(i, ref j) => {
+ if let Some(i) = i.label {
+ self.hash_name(i.ident.name);
+ }
+ if let Some(ref j) = *j {
+ self.hash_expr(&*j);
+ }
+ },
+ ExprKind::Box(ref e) | ExprKind::DropTemps(ref e) | ExprKind::Yield(ref e, _) => {
+ self.hash_expr(e);
+ },
+ ExprKind::Call(ref fun, args) => {
+ self.hash_expr(fun);
+ self.hash_exprs(args);
+ },
+ ExprKind::Cast(ref e, ref ty) | ExprKind::Type(ref e, ref ty) => {
+ self.hash_expr(e);
+ self.hash_ty(ty);
+ },
+ ExprKind::Closure(cap, _, eid, _, _) => {
+ match cap {
+ CaptureBy::Value => 0,
+ CaptureBy::Ref => 1,
+ }
+ .hash(&mut self.s);
+ // closures inherit TypeckTables
+ self.hash_expr(&self.cx.tcx.hir().body(eid).value);
+ },
+ ExprKind::Field(ref e, ref f) => {
+ self.hash_expr(e);
+ self.hash_name(f.name);
+ },
+ ExprKind::Index(ref a, ref i) => {
+ self.hash_expr(a);
+ self.hash_expr(i);
+ },
+ ExprKind::InlineAsm(ref asm) => {
+ for piece in asm.template {
+ match piece {
+ InlineAsmTemplatePiece::String(s) => s.hash(&mut self.s),
+ InlineAsmTemplatePiece::Placeholder {
+ operand_idx,
+ modifier,
+ span: _,
+ } => {
+ operand_idx.hash(&mut self.s);
+ modifier.hash(&mut self.s);
+ },
+ }
+ }
+ asm.options.hash(&mut self.s);
+ for op in asm.operands {
+ match op {
+ InlineAsmOperand::In { reg, expr } => {
+ reg.hash(&mut self.s);
+ self.hash_expr(expr);
+ },
+ InlineAsmOperand::Out { reg, late, expr } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ if let Some(expr) = expr {
+ self.hash_expr(expr);
+ }
+ },
+ InlineAsmOperand::InOut { reg, late, expr } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ self.hash_expr(expr);
+ },
+ InlineAsmOperand::SplitInOut {
+ reg,
+ late,
+ in_expr,
+ out_expr,
+ } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ self.hash_expr(in_expr);
+ if let Some(out_expr) = out_expr {
+ self.hash_expr(out_expr);
+ }
+ },
+ InlineAsmOperand::Const { expr } | InlineAsmOperand::Sym { expr } => self.hash_expr(expr),
+ }
+ }
+ },
+ ExprKind::LlvmInlineAsm(..) | ExprKind::Err => {},
+ ExprKind::Lit(ref l) => {
+ l.node.hash(&mut self.s);
+ },
+ ExprKind::Loop(ref b, ref i, _) => {
+ self.hash_block(b);
+ if let Some(i) = *i {
+ self.hash_name(i.ident.name);
+ }
+ },
+ ExprKind::Match(ref e, arms, ref s) => {
+ self.hash_expr(e);
+
+ for arm in arms {
+ // TODO: arm.pat?
+ if let Some(ref e) = arm.guard {
+ self.hash_guard(e);
+ }
+ self.hash_expr(&arm.body);
+ }
+
+ s.hash(&mut self.s);
+ },
+ ExprKind::MethodCall(ref path, ref _tys, args, ref _fn_span) => {
+ self.hash_name(path.ident.name);
+ self.hash_exprs(args);
+ },
+ ExprKind::Repeat(ref e, ref l_id) => {
+ self.hash_expr(e);
+ self.hash_body(l_id.body);
+ },
+ ExprKind::Ret(ref e) => {
+ if let Some(ref e) = *e {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Path(ref qpath) => {
+ self.hash_qpath(qpath);
+ },
+ ExprKind::Struct(ref path, fields, ref expr) => {
+ self.hash_qpath(path);
+
+ for f in fields {
+ self.hash_name(f.ident.name);
+ self.hash_expr(&f.expr);
+ }
+
+ if let Some(ref e) = *expr {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Tup(tup) => {
+ self.hash_exprs(tup);
+ },
+ ExprKind::Array(v) => {
+ self.hash_exprs(v);
+ },
+ ExprKind::Unary(lop, ref le) => {
+ lop.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ self.hash_expr(le);
+ },
+ }
+ }
+
+ pub fn hash_exprs(&mut self, e: &[Expr<'_>]) {
+ for e in e {
+ self.hash_expr(e);
+ }
+ }
+
+ pub fn hash_name(&mut self, n: Symbol) {
+ n.as_str().hash(&mut self.s);
+ }
+
+ pub fn hash_qpath(&mut self, p: &QPath<'_>) {
+ match *p {
+ QPath::Resolved(_, ref path) => {
+ self.hash_path(path);
+ },
+ QPath::TypeRelative(_, ref path) => {
+ self.hash_name(path.ident.name);
+ },
+ }
+ // self.maybe_typeck_tables.unwrap().qpath_res(p, id).hash(&mut self.s);
+ }
+
+ pub fn hash_path(&mut self, p: &Path<'_>) {
+ p.is_global().hash(&mut self.s);
+ for p in p.segments {
+ self.hash_name(p.ident.name);
+ }
+ }
+
+ pub fn hash_stmt(&mut self, b: &Stmt<'_>) {
+ std::mem::discriminant(&b.kind).hash(&mut self.s);
+
+ match &b.kind {
+ StmtKind::Local(local) => {
+ if let Some(ref init) = local.init {
+ self.hash_expr(init);
+ }
+ },
+ StmtKind::Item(..) => {},
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
+ self.hash_expr(expr);
+ },
+ }
+ }
+
+ pub fn hash_guard(&mut self, g: &Guard<'_>) {
+ match g {
+ Guard::If(ref expr) => {
+ self.hash_expr(expr);
+ },
+ }
+ }
+
+ pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
+ std::mem::discriminant(&lifetime.name).hash(&mut self.s);
+ if let LifetimeName::Param(ref name) = lifetime.name {
+ std::mem::discriminant(name).hash(&mut self.s);
+ match name {
+ ParamName::Plain(ref ident) => {
+ ident.name.hash(&mut self.s);
+ },
+ ParamName::Fresh(ref size) => {
+ size.hash(&mut self.s);
+ },
+ ParamName::Error => {},
+ }
+ }
+ }
+
+ pub fn hash_ty(&mut self, ty: &Ty<'_>) {
+ self.hash_tykind(&ty.kind);
+ }
+
+ pub fn hash_tykind(&mut self, ty: &TyKind<'_>) {
+ std::mem::discriminant(ty).hash(&mut self.s);
+ match ty {
+ TyKind::Slice(ty) => {
+ self.hash_ty(ty);
+ },
+ TyKind::Array(ty, anon_const) => {
+ self.hash_ty(ty);
+ self.hash_body(anon_const.body);
+ },
+ TyKind::Ptr(mut_ty) => {
+ self.hash_ty(&mut_ty.ty);
+ mut_ty.mutbl.hash(&mut self.s);
+ },
+ TyKind::Rptr(lifetime, mut_ty) => {
+ self.hash_lifetime(lifetime);
+ self.hash_ty(&mut_ty.ty);
+ mut_ty.mutbl.hash(&mut self.s);
+ },
+ TyKind::BareFn(bfn) => {
+ bfn.unsafety.hash(&mut self.s);
+ bfn.abi.hash(&mut self.s);
+ for arg in bfn.decl.inputs {
+ self.hash_ty(&arg);
+ }
+ match bfn.decl.output {
+ FnRetTy::DefaultReturn(_) => {
+ ().hash(&mut self.s);
+ },
+ FnRetTy::Return(ref ty) => {
+ self.hash_ty(ty);
+ },
+ }
+ bfn.decl.c_variadic.hash(&mut self.s);
+ },
+ TyKind::Tup(ty_list) => {
+ for ty in *ty_list {
+ self.hash_ty(ty);
+ }
+ },
+ TyKind::Path(qpath) => match qpath {
+ QPath::Resolved(ref maybe_ty, ref path) => {
+ if let Some(ref ty) = maybe_ty {
+ self.hash_ty(ty);
+ }
+ for segment in path.segments {
+ segment.ident.name.hash(&mut self.s);
++ self.hash_generic_args(segment.generic_args().args);
+ }
+ },
+ QPath::TypeRelative(ref ty, ref segment) => {
+ self.hash_ty(ty);
+ segment.ident.name.hash(&mut self.s);
+ },
+ },
+ TyKind::OpaqueDef(_, arg_list) => {
++ self.hash_generic_args(arg_list);
+ },
+ TyKind::TraitObject(_, lifetime) => {
+ self.hash_lifetime(lifetime);
+ },
+ TyKind::Typeof(anon_const) => {
+ self.hash_body(anon_const.body);
+ },
+ TyKind::Err | TyKind::Infer | TyKind::Never => {},
+ }
+ }
+
+ pub fn hash_body(&mut self, body_id: BodyId) {
+ // swap out TypeckTables when hashing a body
+ let old_maybe_typeck_tables = self.maybe_typeck_tables.replace(self.cx.tcx.body_tables(body_id));
+ self.hash_expr(&self.cx.tcx.hir().body(body_id).value);
+ self.maybe_typeck_tables = old_maybe_typeck_tables;
+ }
++
++ fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) {
++ for arg in arg_list {
++ match arg {
++ GenericArg::Lifetime(ref l) => self.hash_lifetime(l),
++ GenericArg::Type(ref ty) => self.hash_ty(&ty),
++ GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
++ }
++ }
++ }
+}
--- /dev/null
- if let ExpnKind::Desugaring(..) = span.ctxt().outer_expn_data().kind {
- false
- } else {
- true
- }
+#[macro_use]
+pub mod sym;
+
+#[allow(clippy::module_name_repetitions)]
+pub mod ast_utils;
+pub mod attrs;
+pub mod author;
+pub mod camel_case;
+pub mod comparisons;
+pub mod conf;
+pub mod constants;
+mod diagnostics;
+pub mod higher;
+mod hir_utils;
+pub mod inspector;
+pub mod internal_lints;
+pub mod numeric_literal;
+pub mod paths;
+pub mod ptr;
+pub mod sugg;
+pub mod usage;
+pub use self::attrs::*;
+pub use self::diagnostics::*;
+pub use self::hir_utils::{both, over, SpanlessEq, SpanlessHash};
+
+use std::borrow::Cow;
+use std::mem;
+
+use if_chain::if_chain;
+use rustc_ast::ast::{self, Attribute, LitKind};
+use rustc_attr as attr;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
+use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
+use rustc_hir::Node;
+use rustc_hir::{
+ def, Arm, Block, Body, Constness, Crate, Expr, ExprKind, FnDecl, HirId, ImplItem, ImplItemKind, Item, ItemKind,
+ MatchSource, Param, Pat, PatKind, Path, PathSegment, QPath, TraitItem, TraitItemKind, TraitRef, TyKind, Unsafety,
+};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, Level, Lint, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty::{self, layout::IntegerExt, subst::GenericArg, Ty, TyCtxt, TypeFoldable};
+use rustc_span::hygiene::{ExpnKind, MacroKind};
+use rustc_span::source_map::original_sp;
+use rustc_span::symbol::{self, kw, Symbol};
+use rustc_span::{BytePos, Pos, Span, DUMMY_SP};
+use rustc_target::abi::Integer;
+use rustc_trait_selection::traits::query::normalize::AtExt;
+use smallvec::SmallVec;
+
+use crate::consts::{constant, Constant};
+use crate::reexport::Name;
+
+/// Returns `true` if the two spans come from differing expansions (i.e., one is
+/// from a macro and one isn't).
+#[must_use]
+pub fn differing_macro_contexts(lhs: Span, rhs: Span) -> bool {
+ rhs.ctxt() != lhs.ctxt()
+}
+
+/// Returns `true` if the given `NodeId` is inside a constant context
+///
+/// # Example
+///
+/// ```rust,ignore
+/// if in_constant(cx, expr.hir_id) {
+/// // Do something
+/// }
+/// ```
+pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool {
+ let parent_id = cx.tcx.hir().get_parent_item(id);
+ match cx.tcx.hir().get(parent_id) {
+ Node::Item(&Item {
+ kind: ItemKind::Const(..) | ItemKind::Static(..),
+ ..
+ })
+ | Node::TraitItem(&TraitItem {
+ kind: TraitItemKind::Const(..),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Const(..),
+ ..
+ })
+ | Node::AnonConst(_) => true,
+ Node::Item(&Item {
+ kind: ItemKind::Fn(ref sig, ..),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Fn(ref sig, _),
+ ..
+ }) => sig.header.constness == Constness::Const,
+ _ => false,
+ }
+}
+
+/// Returns `true` if this `span` was expanded by any macro.
+#[must_use]
+pub fn in_macro(span: Span) -> bool {
+ if span.from_expansion() {
- match pat.kind {
- PatKind::Wild => true,
- _ => false,
- }
++ !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
+ } else {
+ false
+ }
+}
+// If the snippet is empty, it's an attribute that was inserted during macro
+// expansion and we want to ignore those, because they could come from external
+// sources that the user has no control over.
+// For some reason these attributes don't have any expansion info on them, so
+// we have to check it this way until there is a better way.
+pub fn is_present_in_source<T: LintContext>(cx: &T, span: Span) -> bool {
+ if let Some(snippet) = snippet_opt(cx, span) {
+ if snippet.is_empty() {
+ return false;
+ }
+ }
+ true
+}
+
+/// Checks if given pattern is a wildcard (`_`)
+pub fn is_wild<'tcx>(pat: &impl std::ops::Deref<Target = Pat<'tcx>>) -> bool {
- if let Some(trt_id) = trt_id {
- match_def_path(cx, trt_id, path)
- } else {
- false
- }
++ matches!(pat.kind, PatKind::Wild)
+}
+
+/// Checks if type is struct, enum or union type with the given def path.
+pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool {
+ match ty.kind {
+ ty::Adt(adt, _) => match_def_path(cx, adt.did, path),
+ _ => false,
+ }
+}
+
+/// Checks if the type is equal to a diagnostic item
+pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
+ match ty.kind {
+ ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
+ _ => false,
+ }
+}
+
+/// Checks if the method call given in `expr` belongs to the given trait.
+pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
+ let def_id = cx.tables().type_dependent_def_id(expr.hir_id).unwrap();
+ let trt_id = cx.tcx.trait_of_item(def_id);
- if let Some(first_char_pos) = first_char_in_first_line(cx, span) {
- span.with_lo(first_char_pos)
- } else {
- span
- }
++ trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path))
+}
+
+/// Checks if an expression references a variable of the given name.
+pub fn match_var(expr: &Expr<'_>, var: Name) -> bool {
+ if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind {
+ if let [p] = path.segments {
+ return p.ident.name == var;
+ }
+ }
+ false
+}
+
+pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
+ match *path {
+ QPath::Resolved(_, ref path) => path.segments.last().expect("A path must have at least one segment"),
+ QPath::TypeRelative(_, ref seg) => seg,
+ }
+}
+
+pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
+ match *path {
+ QPath::Resolved(_, ref path) => path.segments.get(0),
+ QPath::TypeRelative(_, ref seg) => Some(seg),
+ }
+}
+
+/// Matches a `QPath` against a slice of segment string literals.
+///
+/// There is also `match_path` if you are dealing with a `rustc_hir::Path` instead of a
+/// `rustc_hir::QPath`.
+///
+/// # Examples
+/// ```rust,ignore
+/// match_qpath(path, &["std", "rt", "begin_unwind"])
+/// ```
+pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
+ match *path {
+ QPath::Resolved(_, ref path) => match_path(path, segments),
+ QPath::TypeRelative(ref ty, ref segment) => match ty.kind {
+ TyKind::Path(ref inner_path) => {
+ if let [prefix @ .., end] = segments {
+ if match_qpath(inner_path, prefix) {
+ return segment.ident.name.as_str() == *end;
+ }
+ }
+ false
+ },
+ _ => false,
+ },
+ }
+}
+
+/// Matches a `Path` against a slice of segment string literals.
+///
+/// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a
+/// `rustc_hir::Path`.
+///
+/// # Examples
+///
+/// ```rust,ignore
+/// if match_path(&trait_ref.path, &paths::HASH) {
+/// // This is the `std::hash::Hash` trait.
+/// }
+///
+/// if match_path(ty_path, &["rustc", "lint", "Lint"]) {
+/// // This is a `rustc_middle::lint::Lint`.
+/// }
+/// ```
+pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool {
+ path.segments
+ .iter()
+ .rev()
+ .zip(segments.iter().rev())
+ .all(|(a, b)| a.ident.name.as_str() == *b)
+}
+
+/// Matches a `Path` against a slice of segment string literals, e.g.
+///
+/// # Examples
+/// ```rust,ignore
+/// match_path_ast(path, &["std", "rt", "begin_unwind"])
+/// ```
+pub fn match_path_ast(path: &ast::Path, segments: &[&str]) -> bool {
+ path.segments
+ .iter()
+ .rev()
+ .zip(segments.iter().rev())
+ .all(|(a, b)| a.ident.name.as_str() == *b)
+}
+
+/// Gets the definition associated to a path.
+pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> {
+ let crates = cx.tcx.crates();
+ let krate = crates
+ .iter()
+ .find(|&&krate| cx.tcx.crate_name(krate).as_str() == path[0]);
+ if let Some(krate) = krate {
+ let krate = DefId {
+ krate: *krate,
+ index: CRATE_DEF_INDEX,
+ };
+ let mut items = cx.tcx.item_children(krate);
+ let mut path_it = path.iter().skip(1).peekable();
+
+ loop {
+ let segment = match path_it.next() {
+ Some(segment) => segment,
+ None => return None,
+ };
+
+ let result = SmallVec::<[_; 8]>::new();
+ for item in mem::replace(&mut items, cx.tcx.arena.alloc_slice(&result)).iter() {
+ if item.ident.name.as_str() == *segment {
+ if path_it.peek().is_none() {
+ return Some(item.res);
+ }
+
+ items = cx.tcx.item_children(item.res.def_id());
+ break;
+ }
+ }
+ }
+ } else {
+ None
+ }
+}
+
+pub fn qpath_res(cx: &LateContext<'_>, qpath: &hir::QPath<'_>, id: hir::HirId) -> Res {
+ match qpath {
+ hir::QPath::Resolved(_, path) => path.res,
+ hir::QPath::TypeRelative(..) => {
+ if cx.tcx.has_typeck_tables(id.owner.to_def_id()) {
+ cx.tcx
+ .typeck_tables_of(id.owner.to_def_id().expect_local())
+ .qpath_res(qpath, id)
+ } else {
+ Res::Err
+ }
+ },
+ }
+}
+
+/// Convenience function to get the `DefId` of a trait by path.
+/// It could be a trait or trait alias.
+pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> {
+ let res = match path_to_res(cx, path) {
+ Some(res) => res,
+ None => return None,
+ };
+
+ match res {
+ Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
+ Res::Err => unreachable!("this trait resolution is impossible: {:?}", &path),
+ _ => None,
+ }
+}
+
+/// Checks whether a type implements a trait.
+/// See also `get_trait_def_id`.
+pub fn implements_trait<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ trait_id: DefId,
+ ty_params: &[GenericArg<'tcx>],
+) -> bool {
+ // Do not check on infer_types to avoid panic in evaluate_obligation.
+ if ty.has_infer_types() {
+ return false;
+ }
+ let ty = cx.tcx.erase_regions(&ty);
+ let ty_params = cx.tcx.mk_substs(ty_params.iter());
+ cx.tcx.type_implements_trait((trait_id, ty, ty_params, cx.param_env))
+}
+
+/// Gets the `hir::TraitRef` of the trait the given method is implemented for.
+///
+/// Use this if you want to find the `TraitRef` of the `Add` trait in this example:
+///
+/// ```rust
+/// struct Point(isize, isize);
+///
+/// impl std::ops::Add for Point {
+/// type Output = Self;
+///
+/// fn add(self, other: Self) -> Self {
+/// Point(0, 0)
+/// }
+/// }
+/// ```
+pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx TraitRef<'tcx>> {
+ // Get the implemented trait for the current function
+ let parent_impl = cx.tcx.hir().get_parent_item(hir_id);
+ if_chain! {
+ if parent_impl != hir::CRATE_HIR_ID;
+ if let hir::Node::Item(item) = cx.tcx.hir().get(parent_impl);
+ if let hir::ItemKind::Impl{ of_trait: trait_ref, .. } = &item.kind;
+ then { return trait_ref.as_ref(); }
+ }
+ None
+}
+
+/// Checks whether this type implements `Drop`.
+pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ match ty.ty_adt_def() {
+ Some(def) => def.has_dtor(cx.tcx),
+ None => false,
+ }
+}
+
+/// Returns the method names and argument list of nested method call expressions that make up
+/// `expr`. method/span lists are sorted with the most recent call first.
+pub fn method_calls<'tcx>(
+ expr: &'tcx Expr<'tcx>,
+ max_depth: usize,
+) -> (Vec<Symbol>, Vec<&'tcx [Expr<'tcx>]>, Vec<Span>) {
+ let mut method_names = Vec::with_capacity(max_depth);
+ let mut arg_lists = Vec::with_capacity(max_depth);
+ let mut spans = Vec::with_capacity(max_depth);
+
+ let mut current = expr;
+ for _ in 0..max_depth {
+ if let ExprKind::MethodCall(path, span, args, _) = ¤t.kind {
+ if args.iter().any(|e| e.span.from_expansion()) {
+ break;
+ }
+ method_names.push(path.ident.name);
+ arg_lists.push(&**args);
+ spans.push(*span);
+ current = &args[0];
+ } else {
+ break;
+ }
+ }
+
+ (method_names, arg_lists, spans)
+}
+
+/// Matches an `Expr` against a chain of methods, and return the matched `Expr`s.
+///
+/// For example, if `expr` represents the `.baz()` in `foo.bar().baz()`,
+/// `method_chain_args(expr, &["bar", "baz"])` will return a `Vec`
+/// containing the `Expr`s for
+/// `.bar()` and `.baz()`
+pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<&'a [Expr<'a>]>> {
+ let mut current = expr;
+ let mut matched = Vec::with_capacity(methods.len());
+ for method_name in methods.iter().rev() {
+ // method chains are stored last -> first
+ if let ExprKind::MethodCall(ref path, _, ref args, _) = current.kind {
+ if path.ident.name.as_str() == *method_name {
+ if args.iter().any(|e| e.span.from_expansion()) {
+ return None;
+ }
+ matched.push(&**args); // build up `matched` backwards
+ current = &args[0] // go to parent expression
+ } else {
+ return None;
+ }
+ } else {
+ return None;
+ }
+ }
+ // Reverse `matched` so that it is in the same order as `methods`.
+ matched.reverse();
+ Some(matched)
+}
+
+/// Returns `true` if the provided `def_id` is an entrypoint to a program.
+pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ cx.tcx
+ .entry_fn(LOCAL_CRATE)
+ .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id.to_def_id())
+}
+
+/// Gets the name of the item the expression is in, if available.
+pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Name> {
+ let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id);
+ match cx.tcx.hir().find(parent_id) {
+ Some(
+ Node::Item(Item { ident, .. })
+ | Node::TraitItem(TraitItem { ident, .. })
+ | Node::ImplItem(ImplItem { ident, .. }),
+ ) => Some(ident.name),
+ _ => None,
+ }
+}
+
+/// Gets the name of a `Pat`, if any.
+pub fn get_pat_name(pat: &Pat<'_>) -> Option<Name> {
+ match pat.kind {
+ PatKind::Binding(.., ref spname, _) => Some(spname.name),
+ PatKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name),
+ PatKind::Box(ref p) | PatKind::Ref(ref p, _) => get_pat_name(&*p),
+ _ => None,
+ }
+}
+
+struct ContainsName {
+ name: Name,
+ result: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for ContainsName {
+ type Map = Map<'tcx>;
+
+ fn visit_name(&mut self, _: Span, name: Name) {
+ if self.name == name {
+ self.result = true;
+ }
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Checks if an `Expr` contains a certain name.
+pub fn contains_name(name: Name, expr: &Expr<'_>) -> bool {
+ let mut cn = ContainsName { name, result: false };
+ cn.visit_expr(expr);
+ cn.result
+}
+
+/// Converts a span to a code snippet if available, otherwise use default.
+///
+/// This is useful if you want to provide suggestions for your lint or more generally, if you want
+/// to convert a given `Span` to a `str`.
+///
+/// # Example
+/// ```rust,ignore
+/// snippet(cx, expr.span, "..")
+/// ```
+pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
+ snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from)
+}
+
+/// Same as `snippet`, but it adapts the applicability level by following rules:
+///
+/// - Applicability level `Unspecified` will never be changed.
+/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
+/// - If the default value is used and the applicability level is `MachineApplicable`, change it to
+/// `HasPlaceholders`
+pub fn snippet_with_applicability<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ applicability: &mut Applicability,
+) -> Cow<'a, str> {
+ if *applicability != Applicability::Unspecified && span.from_expansion() {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ snippet_opt(cx, span).map_or_else(
+ || {
+ if *applicability == Applicability::MachineApplicable {
+ *applicability = Applicability::HasPlaceholders;
+ }
+ Cow::Borrowed(default)
+ },
+ From::from,
+ )
+}
+
+/// Same as `snippet`, but should only be used when it's clear that the input span is
+/// not a macro argument.
+pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
+ snippet(cx, span.source_callsite(), default)
+}
+
+/// Converts a span to a code snippet. Returns `None` if not available.
+pub fn snippet_opt<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ cx.sess().source_map().span_to_snippet(span).ok()
+}
+
+/// Converts a span (from a block) to a code snippet if available, otherwise use default.
+///
+/// This trims the code of indentation, except for the first line. Use it for blocks or block-like
+/// things which need to be printed as such.
+///
+/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the
+/// resulting snippet of the given span.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// snippet_block(cx, block.span, "..", None)
+/// // where, `block` is the block of the if expr
+/// if x {
+/// y;
+/// }
+/// // will return the snippet
+/// {
+/// y;
+/// }
+/// ```
+///
+/// ```rust,ignore
+/// snippet_block(cx, block.span, "..", Some(if_expr.span))
+/// // where, `block` is the block of the if expr
+/// if x {
+/// y;
+/// }
+/// // will return the snippet
+/// {
+/// y;
+/// } // aligned with `if`
+/// ```
+/// Note that the first line of the snippet always has 0 indentation.
+pub fn snippet_block<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+) -> Cow<'a, str> {
+ let snip = snippet(cx, span, default);
+ let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
+ trim_multiline(snip, true, indent)
+}
+
+/// Same as `snippet_block`, but adapts the applicability level by the rules of
+/// `snippet_with_applicabiliy`.
+pub fn snippet_block_with_applicability<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+ applicability: &mut Applicability,
+) -> Cow<'a, str> {
+ let snip = snippet_with_applicability(cx, span, default, applicability);
+ let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
+ trim_multiline(snip, true, indent)
+}
+
+/// Returns a new Span that extends the original Span to the first non-whitespace char of the first
+/// line.
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^
+/// // will be converted to
+/// let x = ();
+/// // ^^^^^^^^^^
+/// ```
+pub fn first_line_of_span<T: LintContext>(cx: &T, span: Span) -> Span {
- if let Some(snip) = snippet_opt(cx, line_span) {
++ first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos))
+}
+
+fn first_char_in_first_line<T: LintContext>(cx: &T, span: Span) -> Option<BytePos> {
+ let line_span = line_span(cx, span);
- } else {
- None
- }
++ snippet_opt(cx, line_span).and_then(|snip| {
+ snip.find(|c: char| !c.is_whitespace())
+ .map(|pos| line_span.lo() + BytePos::from_usize(pos))
- if let Some(snip) = snippet_opt(cx, line_span(cx, span)) {
- snip.find(|c: char| !c.is_whitespace())
- } else {
- None
- }
++ })
+}
+
+/// Returns the indentation of the line of a span
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^ -- will return 0
+/// let x = ();
+/// // ^^ -- will return 4
+/// ```
+pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
- if let Some(node) = enclosing_node {
- match node {
- Node::Block(block) => Some(block),
- Node::Item(&Item {
- kind: ItemKind::Fn(_, _, eid),
- ..
- })
- | Node::ImplItem(&ImplItem {
- kind: ImplItemKind::Fn(_, eid),
- ..
- }) => match cx.tcx.hir().body(eid).value.kind {
- ExprKind::Block(ref block, _) => Some(block),
- _ => None,
- },
++ snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
+}
+
+/// Extends the span to the beginning of the spans line, incl. whitespaces.
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^
+/// // will be converted to
+/// let x = ();
+/// // ^^^^^^^^^^^^^^
+/// ```
+fn line_span<T: LintContext>(cx: &T, span: Span) -> Span {
+ let span = original_sp(span, DUMMY_SP);
+ let source_map_and_line = cx.sess().source_map().lookup_line(span.lo()).unwrap();
+ let line_no = source_map_and_line.line;
+ let line_start = source_map_and_line.sf.lines[line_no];
+ Span::new(line_start, span.hi(), span.ctxt())
+}
+
+/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
+/// Also takes an `Option<String>` which can be put inside the braces.
+pub fn expr_block<'a, T: LintContext>(
+ cx: &T,
+ expr: &Expr<'_>,
+ option: Option<String>,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+) -> Cow<'a, str> {
+ let code = snippet_block(cx, expr.span, default, indent_relative_to);
+ let string = option.unwrap_or_default();
+ if expr.span.from_expansion() {
+ Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default)))
+ } else if let ExprKind::Block(_, _) = expr.kind {
+ Cow::Owned(format!("{}{}", code, string))
+ } else if string.is_empty() {
+ Cow::Owned(format!("{{ {} }}", code))
+ } else {
+ Cow::Owned(format!("{{\n{};\n{}\n}}", code, string))
+ }
+}
+
+/// Trim indentation from a multiline string with possibility of ignoring the
+/// first line.
+fn trim_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
+ let s_space = trim_multiline_inner(s, ignore_first, indent, ' ');
+ let s_tab = trim_multiline_inner(s_space, ignore_first, indent, '\t');
+ trim_multiline_inner(s_tab, ignore_first, indent, ' ')
+}
+
+fn trim_multiline_inner(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>, ch: char) -> Cow<'_, str> {
+ let mut x = s
+ .lines()
+ .skip(ignore_first as usize)
+ .filter_map(|l| {
+ if l.is_empty() {
+ None
+ } else {
+ // ignore empty lines
+ Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0)
+ }
+ })
+ .min()
+ .unwrap_or(0);
+ if let Some(indent) = indent {
+ x = x.saturating_sub(indent);
+ }
+ if x > 0 {
+ Cow::Owned(
+ s.lines()
+ .enumerate()
+ .map(|(i, l)| {
+ if (ignore_first && i == 0) || l.is_empty() {
+ l
+ } else {
+ l.split_at(x).1
+ }
+ })
+ .collect::<Vec<_>>()
+ .join("\n"),
+ )
+ } else {
+ s
+ }
+}
+
+/// Gets the parent expression, if any –- this is useful to constrain a lint.
+pub fn get_parent_expr<'c>(cx: &'c LateContext<'_>, e: &Expr<'_>) -> Option<&'c Expr<'c>> {
+ let map = &cx.tcx.hir();
+ let hir_id = e.hir_id;
+ let parent_id = map.get_parent_node(hir_id);
+ if hir_id == parent_id {
+ return None;
+ }
+ map.find(parent_id).and_then(|node| {
+ if let Node::Expr(parent) = node {
+ Some(parent)
+ } else {
+ None
+ }
+ })
+}
+
+pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> {
+ let map = &cx.tcx.hir();
+ let enclosing_node = map
+ .get_enclosing_scope(hir_id)
+ .and_then(|enclosing_id| map.find(enclosing_id));
- }
- } else {
- None
- }
++ enclosing_node.and_then(|node| match node {
++ Node::Block(block) => Some(block),
++ Node::Item(&Item {
++ kind: ItemKind::Fn(_, _, eid),
++ ..
++ })
++ | Node::ImplItem(&ImplItem {
++ kind: ImplItemKind::Fn(_, eid),
++ ..
++ }) => match cx.tcx.hir().body(eid).value.kind {
++ ExprKind::Block(ref block, _) => Some(block),
+ _ => None,
- if let Some(did) = did {
- must_use_attr(&cx.tcx.get_attrs(did)).is_some()
- } else {
- false
- }
++ },
++ _ => None,
++ })
+}
+
+/// Returns the base type for HIR references and pointers.
+pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
+ match ty.kind {
+ TyKind::Ptr(ref mut_ty) | TyKind::Rptr(_, ref mut_ty) => walk_ptrs_hir_ty(&mut_ty.ty),
+ _ => ty,
+ }
+}
+
+/// Returns the base type for references and raw pointers.
+pub fn walk_ptrs_ty(ty: Ty<'_>) -> Ty<'_> {
+ match ty.kind {
+ ty::Ref(_, ty, _) => walk_ptrs_ty(ty),
+ _ => ty,
+ }
+}
+
+/// Returns the base type for references and raw pointers, and count reference
+/// depth.
+pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) {
+ fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) {
+ match ty.kind {
+ ty::Ref(_, ty, _) => inner(ty, depth + 1),
+ _ => (ty, depth),
+ }
+ }
+ inner(ty, 0)
+}
+
+/// Checks whether the given expression is a constant integer of the given value.
+/// unlike `is_integer_literal`, this version does const folding
+pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {
+ if is_integer_literal(e, value) {
+ return true;
+ }
+ let map = cx.tcx.hir();
+ let parent_item = map.get_parent_item(e.hir_id);
+ if let Some((Constant::Int(v), _)) = map
+ .maybe_body_owned_by(parent_item)
+ .and_then(|body_id| constant(cx, cx.tcx.body_tables(body_id), e))
+ {
+ value == v
+ } else {
+ false
+ }
+}
+
+/// Checks whether the given expression is a constant literal of the given value.
+pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool {
+ // FIXME: use constant folding
+ if let ExprKind::Lit(ref spanned) = expr.kind {
+ if let LitKind::Int(v, _) = spanned.node {
+ return v == value;
+ }
+ }
+ false
+}
+
+/// Returns `true` if the given `Expr` has been coerced before.
+///
+/// Examples of coercions can be found in the Nomicon at
+/// <https://doc.rust-lang.org/nomicon/coercions.html>.
+///
+/// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_typeck::check::coercion` for more
+/// information on adjustments and coercions.
+pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ cx.tables().adjustments().get(e.hir_id).is_some()
+}
+
+/// Returns the pre-expansion span if is this comes from an expansion of the
+/// macro `name`.
+/// See also `is_direct_expn_of`.
+#[must_use]
+pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
+ loop {
+ if span.from_expansion() {
+ let data = span.ctxt().outer_expn_data();
+ let new_span = data.call_site;
+
+ if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
+ if mac_name.as_str() == name {
+ return Some(new_span);
+ }
+ }
+
+ span = new_span;
+ } else {
+ return None;
+ }
+ }
+}
+
+/// Returns the pre-expansion span if the span directly comes from an expansion
+/// of the macro `name`.
+/// The difference with `is_expn_of` is that in
+/// ```rust,ignore
+/// foo!(bar!(42));
+/// ```
+/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only
+/// `bar!` by
+/// `is_direct_expn_of`.
+#[must_use]
+pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
+ if span.from_expansion() {
+ let data = span.ctxt().outer_expn_data();
+ let new_span = data.call_site;
+
+ if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
+ if mac_name.as_str() == name {
+ return Some(new_span);
+ }
+ }
+ }
+
+ None
+}
+
+/// Convenience function to get the return type of a function.
+pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId) -> Ty<'tcx> {
+ let fn_def_id = cx.tcx.hir().local_def_id(fn_item);
+ let ret_ty = cx.tcx.fn_sig(fn_def_id).output();
+ cx.tcx.erase_late_bound_regions(&ret_ty)
+}
+
+/// Returns `true` if the given type is an `unsafe` function.
+pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ match ty.kind {
+ ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).unsafety() == Unsafety::Unsafe,
+ _ => false,
+ }
+}
+
+pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ ty.is_copy_modulo_regions(cx.tcx.at(DUMMY_SP), cx.param_env)
+}
+
+/// Checks if an expression is constructing a tuple-like enum variant or struct
+pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let ExprKind::Call(ref fun, _) = expr.kind {
+ if let ExprKind::Path(ref qp) = fun.kind {
+ let res = cx.qpath_res(qp, fun.hir_id);
+ return match res {
+ def::Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true,
+ def::Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id),
+ _ => false,
+ };
+ }
+ }
+ false
+}
+
+/// Returns `true` if a pattern is refutable.
+// TODO: should be implemented using rustc/mir_build/hair machinery
+pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
+ fn is_enum_variant(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool {
+ matches!(
+ cx.qpath_res(qpath, id),
+ def::Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _)
+ )
+ }
+
+ fn are_refutable<'a, I: Iterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, mut i: I) -> bool {
+ i.any(|pat| is_refutable(cx, pat))
+ }
+
+ match pat.kind {
+ PatKind::Wild => false,
+ PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)),
+ PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => is_refutable(cx, pat),
+ PatKind::Lit(..) | PatKind::Range(..) => true,
+ PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id),
+ PatKind::Or(ref pats) => {
+ // TODO: should be the honest check, that pats is exhaustive set
+ are_refutable(cx, pats.iter().map(|pat| &**pat))
+ },
+ PatKind::Tuple(ref pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)),
+ PatKind::Struct(ref qpath, ref fields, _) => {
+ is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| &*field.pat))
+ },
+ PatKind::TupleStruct(ref qpath, ref pats, _) => {
+ is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats.iter().map(|pat| &**pat))
+ },
+ PatKind::Slice(ref head, ref middle, ref tail) => {
+ match &cx.tables().node_type(pat.hir_id).kind {
+ ty::Slice(..) => {
+ // [..] is the only irrefutable slice pattern.
+ !head.is_empty() || middle.is_none() || !tail.is_empty()
+ },
+ ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter()).map(|pat| &**pat)),
+ _ => {
+ // unreachable!()
+ true
+ },
+ }
+ },
+ }
+}
+
+/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d
+/// implementations have.
+pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool {
+ attr::contains_name(attrs, sym!(automatically_derived))
+}
+
+/// Remove blocks around an expression.
+///
+/// Ie. `x`, `{ x }` and `{{{{ x }}}}` all give `x`. `{ x; y }` and `{}` return
+/// themselves.
+pub fn remove_blocks<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
+ while let ExprKind::Block(ref block, ..) = expr.kind {
+ match (block.stmts.is_empty(), block.expr.as_ref()) {
+ (true, Some(e)) => expr = e,
+ _ => break,
+ }
+ }
+ expr
+}
+
+pub fn is_self(slf: &Param<'_>) -> bool {
+ if let PatKind::Binding(.., name, _) = slf.pat.kind {
+ name.name == kw::SelfLower
+ } else {
+ false
+ }
+}
+
+pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
+ if_chain! {
+ if let TyKind::Path(ref qp) = slf.kind;
+ if let QPath::Resolved(None, ref path) = *qp;
+ if let Res::SelfTy(..) = path.res;
+ then {
+ return true
+ }
+ }
+ false
+}
+
+pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> {
+ (0..decl.inputs.len()).map(move |i| &body.params[i])
+}
+
+/// Checks if a given expression is a match expression expanded from the `?`
+/// operator or the `try` macro.
+pub fn is_try<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ fn is_ok(arm: &Arm<'_>) -> bool {
+ if_chain! {
+ if let PatKind::TupleStruct(ref path, ref pat, None) = arm.pat.kind;
+ if match_qpath(path, &paths::RESULT_OK[1..]);
+ if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind;
+ if let ExprKind::Path(QPath::Resolved(None, ref path)) = arm.body.kind;
+ if let Res::Local(lid) = path.res;
+ if lid == hir_id;
+ then {
+ return true;
+ }
+ }
+ false
+ }
+
+ fn is_err(arm: &Arm<'_>) -> bool {
+ if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
+ match_qpath(path, &paths::RESULT_ERR[1..])
+ } else {
+ false
+ }
+ }
+
+ if let ExprKind::Match(_, ref arms, ref source) = expr.kind {
+ // desugared from a `?` operator
+ if let MatchSource::TryDesugar = *source {
+ return Some(expr);
+ }
+
+ if_chain! {
+ if arms.len() == 2;
+ if arms[0].guard.is_none();
+ if arms[1].guard.is_none();
+ if (is_ok(&arms[0]) && is_err(&arms[1])) ||
+ (is_ok(&arms[1]) && is_err(&arms[0]));
+ then {
+ return Some(expr);
+ }
+ }
+ }
+
+ None
+}
+
+/// Returns `true` if the lint is allowed in the current context
+///
+/// Useful for skipping long running code when it's unnecessary
+pub fn is_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
+ cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow
+}
+
+pub fn get_arg_name(pat: &Pat<'_>) -> Option<Name> {
+ match pat.kind {
+ PatKind::Binding(.., ident, None) => Some(ident.name),
+ PatKind::Ref(ref subpat, _) => get_arg_name(subpat),
+ _ => None,
+ }
+}
+
+pub fn int_bits(tcx: TyCtxt<'_>, ity: ast::IntTy) -> u64 {
+ Integer::from_attr(&tcx, attr::IntType::SignedInt(ity)).size().bits()
+}
+
+#[allow(clippy::cast_possible_wrap)]
+/// Turn a constant int byte representation into an i128
+pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: ast::IntTy) -> i128 {
+ let amt = 128 - int_bits(tcx, ity);
+ ((u as i128) << amt) >> amt
+}
+
+#[allow(clippy::cast_sign_loss)]
+/// clip unused bytes
+pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: ast::IntTy) -> u128 {
+ let amt = 128 - int_bits(tcx, ity);
+ ((u as u128) << amt) >> amt
+}
+
+/// clip unused bytes
+pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: ast::UintTy) -> u128 {
+ let bits = Integer::from_attr(&tcx, attr::IntType::UnsignedInt(ity)).size().bits();
+ let amt = 128 - bits;
+ (u << amt) >> amt
+}
+
+/// Removes block comments from the given `Vec` of lines.
+///
+/// # Examples
+///
+/// ```rust,ignore
+/// without_block_comments(vec!["/*", "foo", "*/"]);
+/// // => vec![]
+///
+/// without_block_comments(vec!["bar", "/*", "foo", "*/"]);
+/// // => vec!["bar"]
+/// ```
+pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> {
+ let mut without = vec![];
+
+ let mut nest_level = 0;
+
+ for line in lines {
+ if line.contains("/*") {
+ nest_level += 1;
+ continue;
+ } else if line.contains("*/") {
+ nest_level -= 1;
+ continue;
+ }
+
+ if nest_level == 0 {
+ without.push(line);
+ }
+ }
+
+ without
+}
+
+pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
+ let map = &tcx.hir();
+ let mut prev_enclosing_node = None;
+ let mut enclosing_node = node;
+ while Some(enclosing_node) != prev_enclosing_node {
+ if is_automatically_derived(map.attrs(enclosing_node)) {
+ return true;
+ }
+ prev_enclosing_node = Some(enclosing_node);
+ enclosing_node = map.get_parent_item(enclosing_node);
+ }
+ false
+}
+
+/// Returns true if ty has `iter` or `iter_mut` methods
+pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<&'static str> {
+ // FIXME: instead of this hard-coded list, we should check if `<adt>::iter`
+ // exists and has the desired signature. Unfortunately FnCtxt is not exported
+ // so we can't use its `lookup_method` method.
+ let into_iter_collections: [&[&str]; 13] = [
+ &paths::VEC,
+ &paths::OPTION,
+ &paths::RESULT,
+ &paths::BTREESET,
+ &paths::BTREEMAP,
+ &paths::VEC_DEQUE,
+ &paths::LINKED_LIST,
+ &paths::BINARY_HEAP,
+ &paths::HASHSET,
+ &paths::HASHMAP,
+ &paths::PATH_BUF,
+ &paths::PATH,
+ &paths::RECEIVER,
+ ];
+
+ let ty_to_check = match probably_ref_ty.kind {
+ ty::Ref(_, ty_to_check, _) => ty_to_check,
+ _ => probably_ref_ty,
+ };
+
+ let def_id = match ty_to_check.kind {
+ ty::Array(..) => return Some("array"),
+ ty::Slice(..) => return Some("slice"),
+ ty::Adt(adt, _) => adt.did,
+ _ => return None,
+ };
+
+ for path in &into_iter_collections {
+ if match_def_path(cx, def_id, path) {
+ return Some(*path.last().unwrap());
+ }
+ }
+ None
+}
+
+/// Matches a function call with the given path and returns the arguments.
+///
+/// Usage:
+///
+/// ```rust,ignore
+/// if let Some(args) = match_function_call(cx, begin_panic_call, &paths::BEGIN_PANIC);
+/// ```
+pub fn match_function_call<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ path: &[&str],
+) -> Option<&'tcx [Expr<'tcx>]> {
+ if_chain! {
+ if let ExprKind::Call(ref fun, ref args) = expr.kind;
+ if let ExprKind::Path(ref qpath) = fun.kind;
+ if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
+ if match_def_path(cx, fun_def_id, path);
+ then {
+ return Some(&args)
+ }
+ };
+ None
+}
+
+/// Checks if `Ty` is normalizable. This function is useful
+/// to avoid crashes on `layout_of`.
+pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool {
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ let cause = rustc_middle::traits::ObligationCause::dummy();
+ infcx.at(&cause, param_env).normalize(&ty).is_ok()
+ })
+}
+
+pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
+ // We have to convert `syms` to `&[Symbol]` here because rustc's `match_def_path`
+ // accepts only that. We should probably move to Symbols in Clippy as well.
+ let syms = syms.iter().map(|p| Symbol::intern(p)).collect::<Vec<Symbol>>();
+ cx.match_def_path(did, &syms)
+}
+
+/// Returns the list of condition expressions and the list of blocks in a
+/// sequence of `if/else`.
+/// E.g., this returns `([a, b], [c, d, e])` for the expression
+/// `if a { c } else if b { d } else { e }`.
+pub fn if_sequence<'tcx>(
+ mut expr: &'tcx Expr<'tcx>,
+) -> (SmallVec<[&'tcx Expr<'tcx>; 1]>, SmallVec<[&'tcx Block<'tcx>; 1]>) {
+ let mut conds = SmallVec::new();
+ let mut blocks: SmallVec<[&Block<'_>; 1]> = SmallVec::new();
+
+ while let Some((ref cond, ref then_expr, ref else_expr)) = higher::if_block(&expr) {
+ conds.push(&**cond);
+ if let ExprKind::Block(ref block, _) = then_expr.kind {
+ blocks.push(block);
+ } else {
+ panic!("ExprKind::If node is not an ExprKind::Block");
+ }
+
+ if let Some(ref else_expr) = *else_expr {
+ expr = else_expr;
+ } else {
+ break;
+ }
+ }
+
+ // final `else {..}`
+ if !blocks.is_empty() {
+ if let ExprKind::Block(ref block, _) = expr.kind {
+ blocks.push(&**block);
+ }
+ }
+
+ (conds, blocks)
+}
+
+pub fn parent_node_is_if_expr(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool {
+ let map = cx.tcx.hir();
+ let parent_id = map.get_parent_node(expr.hir_id);
+ let parent_node = map.get(parent_id);
+
+ match parent_node {
+ Node::Expr(e) => higher::if_block(&e).is_some(),
+ Node::Arm(e) => higher::if_block(&e.body).is_some(),
+ _ => false,
+ }
+}
+
+// Finds the attribute with the given name, if any
+pub fn attr_by_name<'a>(attrs: &'a [Attribute], name: &'_ str) -> Option<&'a Attribute> {
+ attrs
+ .iter()
+ .find(|attr| attr.ident().map_or(false, |ident| ident.as_str() == name))
+}
+
+// Finds the `#[must_use]` attribute, if any
+pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> {
+ attr_by_name(attrs, "must_use")
+}
+
+// Returns whether the type has #[must_use] attribute
+pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ match ty.kind {
+ ty::Adt(ref adt, _) => must_use_attr(&cx.tcx.get_attrs(adt.did)).is_some(),
+ ty::Foreign(ref did) => must_use_attr(&cx.tcx.get_attrs(*did)).is_some(),
+ ty::Slice(ref ty)
+ | ty::Array(ref ty, _)
+ | ty::RawPtr(ty::TypeAndMut { ref ty, .. })
+ | ty::Ref(_, ref ty, _) => {
+ // for the Array case we don't need to care for the len == 0 case
+ // because we don't want to lint functions returning empty arrays
+ is_must_use_ty(cx, *ty)
+ },
+ ty::Tuple(ref substs) => substs.types().any(|ty| is_must_use_ty(cx, ty)),
+ ty::Opaque(ref def_id, _) => {
+ for (predicate, _) in cx.tcx.predicates_of(*def_id).predicates {
+ if let ty::PredicateKind::Trait(ref poly_trait_predicate, _) = predicate.kind() {
+ if must_use_attr(&cx.tcx.get_attrs(poly_trait_predicate.skip_binder().trait_ref.def_id)).is_some() {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ ty::Dynamic(binder, _) => {
+ for predicate in binder.skip_binder().iter() {
+ if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate {
+ if must_use_attr(&cx.tcx.get_attrs(trait_ref.def_id)).is_some() {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ _ => false,
+ }
+}
+
+// check if expr is calling method or function with #[must_use] attribyte
+pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let did = match expr.kind {
+ ExprKind::Call(ref path, _) => if_chain! {
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id);
+ then {
+ Some(did)
+ } else {
+ None
+ }
+ },
+ ExprKind::MethodCall(_, _, _, _) => cx.tables().type_dependent_def_id(expr.hir_id),
+ _ => None,
+ };
+
++ did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
+}
+
+pub fn is_no_std_crate(krate: &Crate<'_>) -> bool {
+ krate.item.attrs.iter().any(|attr| {
+ if let ast::AttrKind::Normal(ref attr) = attr.kind {
+ attr.path == symbol::sym::no_std
+ } else {
+ false
+ }
+ })
+}
+
+/// Check if parent of a hir node is a trait implementation block.
+/// For example, `f` in
+/// ```rust,ignore
+/// impl Trait for S {
+/// fn f() {}
+/// }
+/// ```
+pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool {
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), .. })
+ } else {
+ false
+ }
+}
+
+/// Check if it's even possible to satisfy the `where` clause for the item.
+///
+/// `trivial_bounds` feature allows functions with unsatisfiable bounds, for example:
+///
+/// ```ignore
+/// fn foo() where i32: Iterator {
+/// for _ in 2i32 {}
+/// }
+/// ```
+pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
+ use rustc_trait_selection::traits;
+ let predicates =
+ cx.tcx
+ .predicates_of(did)
+ .predicates
+ .iter()
+ .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
+ !traits::normalize_and_test_predicates(
+ cx.tcx,
+ traits::elaborate_predicates(cx.tcx, predicates)
+ .map(|o| o.predicate)
+ .collect::<Vec<_>>(),
+ )
+}
+
++/// Returns the `DefId` of the callee if the given expression is a function or method call.
++pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
++ match &expr.kind {
++ ExprKind::MethodCall(..) => cx.tables().type_dependent_def_id(expr.hir_id),
++ ExprKind::Call(
++ Expr {
++ kind: ExprKind::Path(qpath),
++ ..
++ },
++ ..,
++ ) => cx.tables().qpath_res(qpath, expr.hir_id).opt_def_id(),
++ _ => None,
++ }
++}
++
+pub fn run_lints(cx: &LateContext<'_>, lints: &[&'static Lint], id: HirId) -> bool {
+ lints.iter().any(|lint| {
+ matches!(
+ cx.tcx.lint_level_at_node(lint, id),
+ (Level::Forbid | Level::Deny | Level::Warn, _)
+ )
+ })
+}
+
+#[macro_export]
+macro_rules! unwrap_cargo_metadata {
+ ($cx: ident, $lint: ident, $deps: expr) => {{
+ let mut command = cargo_metadata::MetadataCommand::new();
+ if !$deps {
+ command.no_deps();
+ }
+
+ match command.exec() {
+ Ok(metadata) => metadata,
+ Err(err) => {
+ span_lint($cx, $lint, DUMMY_SP, &format!("could not read cargo metadata: {}", err));
+ return;
+ },
+ }
+ }};
+}
+
+#[cfg(test)]
+mod test {
+ use super::{trim_multiline, without_block_comments};
+
+ #[test]
+ fn test_trim_multiline_single_line() {
+ assert_eq!("", trim_multiline("".into(), false, None));
+ assert_eq!("...", trim_multiline("...".into(), false, None));
+ assert_eq!("...", trim_multiline(" ...".into(), false, None));
+ assert_eq!("...", trim_multiline("\t...".into(), false, None));
+ assert_eq!("...", trim_multiline("\t\t...".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_trim_multiline_block() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", trim_multiline(" if x {
+ y
+ } else {
+ z
+ }".into(), false, None));
+ assert_eq!("\
+ if x {
+ \ty
+ } else {
+ \tz
+ }", trim_multiline(" if x {
+ \ty
+ } else {
+ \tz
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_trim_multiline_empty_line() {
+ assert_eq!("\
+ if x {
+ y
+
+ } else {
+ z
+ }", trim_multiline(" if x {
+ y
+
+ } else {
+ z
+ }".into(), false, None));
+ }
+
+ #[test]
+ fn test_without_block_comments_lines_without_block_comments() {
+ let result = without_block_comments(vec!["/*", "", "*/"]);
+ println!("result: {:?}", result);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]);
+ assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]);
+
+ let result = without_block_comments(vec!["/* rust", "", "*/"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* one-line comment */"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["foo", "bar", "baz"]);
+ assert_eq!(result, vec!["foo", "bar", "baz"]);
+ }
+}
--- /dev/null
- let float = if let LitKind::Float(..) = lit_kind { true } else { false };
+use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind};
+
+#[derive(Debug, PartialEq)]
+pub enum Radix {
+ Binary,
+ Octal,
+ Decimal,
+ Hexadecimal,
+}
+
+impl Radix {
+ /// Returns a reasonable digit group size for this radix.
+ #[must_use]
+ fn suggest_grouping(&self) -> usize {
+ match *self {
+ Self::Binary | Self::Hexadecimal => 4,
+ Self::Octal | Self::Decimal => 3,
+ }
+ }
+}
+
+/// A helper method to format numeric literals with digit grouping.
+/// `lit` must be a valid numeric literal without suffix.
+pub fn format(lit: &str, type_suffix: Option<&str>, float: bool) -> String {
+ NumericLiteral::new(lit, type_suffix, float).format()
+}
+
+#[derive(Debug)]
+pub struct NumericLiteral<'a> {
+ /// Which radix the literal was represented in.
+ pub radix: Radix,
+ /// The radix prefix, if present.
+ pub prefix: Option<&'a str>,
+
+ /// The integer part of the number.
+ pub integer: &'a str,
+ /// The fraction part of the number.
+ pub fraction: Option<&'a str>,
+ /// The character used as exponent seperator (b'e' or b'E') and the exponent part.
+ pub exponent: Option<(char, &'a str)>,
+
+ /// The type suffix, including preceding underscore if present.
+ pub suffix: Option<&'a str>,
+}
+
+impl<'a> NumericLiteral<'a> {
+ pub fn from_lit(src: &'a str, lit: &Lit) -> Option<NumericLiteral<'a>> {
+ NumericLiteral::from_lit_kind(src, &lit.kind)
+ }
+
+ pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option<NumericLiteral<'a>> {
+ if lit_kind.is_numeric() && src.chars().next().map_or(false, |c| c.is_digit(10)) {
+ let (unsuffixed, suffix) = split_suffix(&src, lit_kind);
- if let Some(suffix_length) = lit_suffix_length(lit_kind) {
++ let float = matches!(lit_kind, LitKind::Float(..));
+ Some(NumericLiteral::new(unsuffixed, suffix, float))
+ } else {
+ None
+ }
+ }
+
+ #[must_use]
+ pub fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self {
+ // Determine delimiter for radix prefix, if present, and radix.
+ let radix = if lit.starts_with("0x") {
+ Radix::Hexadecimal
+ } else if lit.starts_with("0b") {
+ Radix::Binary
+ } else if lit.starts_with("0o") {
+ Radix::Octal
+ } else {
+ Radix::Decimal
+ };
+
+ // Grab part of the literal after prefix, if present.
+ let (prefix, mut sans_prefix) = if let Radix::Decimal = radix {
+ (None, lit)
+ } else {
+ let (p, s) = lit.split_at(2);
+ (Some(p), s)
+ };
+
+ if suffix.is_some() && sans_prefix.ends_with('_') {
+ // The '_' before the suffix isn't part of the digits
+ sans_prefix = &sans_prefix[..sans_prefix.len() - 1];
+ }
+
+ let (integer, fraction, exponent) = Self::split_digit_parts(sans_prefix, float);
+
+ Self {
+ radix,
+ prefix,
+ integer,
+ fraction,
+ exponent,
+ suffix,
+ }
+ }
+
+ pub fn is_decimal(&self) -> bool {
+ self.radix == Radix::Decimal
+ }
+
+ pub fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(char, &str)>) {
+ let mut integer = digits;
+ let mut fraction = None;
+ let mut exponent = None;
+
+ if float {
+ for (i, c) in digits.char_indices() {
+ match c {
+ '.' => {
+ integer = &digits[..i];
+ fraction = Some(&digits[i + 1..]);
+ },
+ 'e' | 'E' => {
+ if integer.len() > i {
+ integer = &digits[..i];
+ } else {
+ fraction = Some(&digits[integer.len() + 1..i]);
+ };
+ exponent = Some((c, &digits[i + 1..]));
+ break;
+ },
+ _ => {},
+ }
+ }
+ }
+
+ (integer, fraction, exponent)
+ }
+
+ /// Returns literal formatted in a sensible way.
+ pub fn format(&self) -> String {
+ let mut output = String::new();
+
+ if let Some(prefix) = self.prefix {
+ output.push_str(prefix);
+ }
+
+ let group_size = self.radix.suggest_grouping();
+
+ Self::group_digits(
+ &mut output,
+ self.integer,
+ group_size,
+ true,
+ self.radix == Radix::Hexadecimal,
+ );
+
+ if let Some(fraction) = self.fraction {
+ output.push('.');
+ Self::group_digits(&mut output, fraction, group_size, false, false);
+ }
+
+ if let Some((separator, exponent)) = self.exponent {
+ output.push(separator);
+ Self::group_digits(&mut output, exponent, group_size, true, false);
+ }
+
+ if let Some(suffix) = self.suffix {
+ output.push('_');
+ output.push_str(suffix);
+ }
+
+ output
+ }
+
+ pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) {
+ debug_assert!(group_size > 0);
+
+ let mut digits = input.chars().filter(|&c| c != '_');
+
+ let first_group_size;
+
+ if partial_group_first {
+ first_group_size = (digits.clone().count() - 1) % group_size + 1;
+ if pad {
+ for _ in 0..group_size - first_group_size {
+ output.push('0');
+ }
+ }
+ } else {
+ first_group_size = group_size;
+ }
+
+ for _ in 0..first_group_size {
+ if let Some(digit) = digits.next() {
+ output.push(digit);
+ }
+ }
+
+ for (c, i) in digits.zip((0..group_size).cycle()) {
+ if i == 0 {
+ output.push('_');
+ }
+ output.push(c);
+ }
+ }
+}
+
+fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
+ debug_assert!(lit_kind.is_numeric());
- } else {
- (src, None)
- }
++ lit_suffix_length(lit_kind).map_or((src, None), |suffix_length| {
+ let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length);
+ (unsuffixed, Some(suffix))
++ })
+}
+
+fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {
+ debug_assert!(lit_kind.is_numeric());
+ let suffix = match lit_kind {
+ LitKind::Int(_, int_lit_kind) => match int_lit_kind {
+ LitIntType::Signed(int_ty) => Some(int_ty.name_str()),
+ LitIntType::Unsigned(uint_ty) => Some(uint_ty.name_str()),
+ LitIntType::Unsuffixed => None,
+ },
+ LitKind::Float(_, float_lit_kind) => match float_lit_kind {
+ LitFloatType::Suffixed(float_ty) => Some(float_ty.name_str()),
+ LitFloatType::Unsuffixed => None,
+ },
+ _ => None,
+ };
+
+ suffix.map(str::len)
+}
--- /dev/null
- pub const REGEX: [&str; 3] = ["regex", "re_unicode", "Regex"];
+//! This module contains paths to types and functions Clippy needs to know
+//! about.
+//!
+//! Whenever possible, please consider diagnostic items over hardcoded paths.
+//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
+
+pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"];
+pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
+pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
+pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
+pub const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
+pub const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_fmt"];
+pub const BINARY_HEAP: [&str; 4] = ["alloc", "collections", "binary_heap", "BinaryHeap"];
+pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
+pub const BOX: [&str; 3] = ["alloc", "boxed", "Box"];
+pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeMap"];
+pub const BTREEMAP_ENTRY: [&str; 5] = ["alloc", "collections", "btree", "map", "Entry"];
+pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"];
+pub const CLONE_TRAIT: [&str; 3] = ["core", "clone", "Clone"];
+pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
+pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
+pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
+pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
+pub const CSTRING: [&str; 4] = ["std", "ffi", "c_str", "CString"];
+pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
+pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
+pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
+pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
+pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
+pub const DISPLAY_FMT_METHOD: [&str; 4] = ["core", "fmt", "Display", "fmt"];
+pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
+pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"];
+pub const DROP: [&str; 3] = ["core", "mem", "drop"];
+pub const DROP_TRAIT: [&str; 4] = ["core", "ops", "drop", "Drop"];
+pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
+pub const EARLY_CONTEXT: [&str; 4] = ["rustc", "lint", "context", "EarlyContext"];
+pub const EXIT: [&str; 3] = ["std", "process", "exit"];
+pub const FILE: [&str; 3] = ["std", "fs", "File"];
+pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
+pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
+pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
+pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
+pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
+pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"];
+pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
+pub const HASH: [&str; 2] = ["hash", "Hash"];
+pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
+pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
+pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
+pub const INDEX: [&str; 3] = ["core", "ops", "Index"];
+pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
+pub const INTO: [&str; 3] = ["core", "convert", "Into"];
+pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"];
+pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
+pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
+pub const ITERATOR: [&str; 5] = ["core", "iter", "traits", "iterator", "Iterator"];
+pub const LATE_CONTEXT: [&str; 4] = ["rustc", "lint", "context", "LateContext"];
+pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"];
+pub const LINT: [&str; 3] = ["rustc_session", "lint", "Lint"];
+pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"];
+pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"];
+pub const MEM_MAYBEUNINIT: [&str; 4] = ["core", "mem", "maybe_uninit", "MaybeUninit"];
+pub const MEM_MAYBEUNINIT_UNINIT: [&str; 5] = ["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"];
+pub const MEM_REPLACE: [&str; 3] = ["core", "mem", "replace"];
+pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
+pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
+pub const OPS_MODULE: [&str; 2] = ["core", "ops"];
+pub const OPTION: [&str; 3] = ["core", "option", "Option"];
+pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
+pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
+pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
+pub const OS_STRING: [&str; 4] = ["std", "ffi", "os_str", "OsString"];
+pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
+pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
+pub const PARKING_LOT_MUTEX_GUARD: [&str; 2] = ["parking_lot", "MutexGuard"];
+pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 2] = ["parking_lot", "RwLockReadGuard"];
+pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 2] = ["parking_lot", "RwLockWriteGuard"];
+pub const PATH: [&str; 3] = ["std", "path", "Path"];
+pub const PATH_BUF: [&str; 3] = ["std", "path", "PathBuf"];
+pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
+pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
+pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
+pub const PTR_NULL: [&str; 2] = ["ptr", "null"];
+pub const PTR_NULL_MUT: [&str; 2] = ["ptr", "null_mut"];
+pub const RANGE: [&str; 3] = ["core", "ops", "Range"];
+pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
+pub const RANGE_FROM: [&str; 3] = ["core", "ops", "RangeFrom"];
+pub const RANGE_FROM_STD: [&str; 3] = ["std", "ops", "RangeFrom"];
+pub const RANGE_FULL: [&str; 4] = ["core", "ops", "range", "RangeFull"];
+pub const RANGE_FULL_STD: [&str; 3] = ["std", "ops", "RangeFull"];
+pub const RANGE_INCLUSIVE_NEW: [&str; 4] = ["core", "ops", "RangeInclusive", "new"];
+pub const RANGE_INCLUSIVE_STD_NEW: [&str; 4] = ["std", "ops", "RangeInclusive", "new"];
+pub const RANGE_STD: [&str; 3] = ["std", "ops", "Range"];
+pub const RANGE_TO: [&str; 3] = ["core", "ops", "RangeTo"];
+pub const RANGE_TO_INCLUSIVE: [&str; 3] = ["core", "ops", "RangeToInclusive"];
+pub const RANGE_TO_INCLUSIVE_STD: [&str; 3] = ["std", "ops", "RangeToInclusive"];
+pub const RANGE_TO_STD: [&str; 3] = ["std", "ops", "RangeTo"];
+pub const RC: [&str; 3] = ["alloc", "rc", "Rc"];
+pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
+pub const RECEIVER: [&str; 4] = ["std", "sync", "mpsc", "Receiver"];
+pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
+pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
+pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
+pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
+pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
+pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
+pub const REPEAT: [&str; 3] = ["core", "iter", "repeat"];
+pub const RESULT: [&str; 3] = ["core", "result", "Result"];
+pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
+pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
+pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
+pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
+pub const SERDE_DESERIALIZE: [&str; 2] = ["_serde", "Deserialize"];
+pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
+pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
+pub const SLICE_ITER: [&str; 3] = ["core", "slice", "Iter"];
+pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
+pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
+pub const STD_CONVERT_IDENTITY: [&str; 3] = ["std", "convert", "identity"];
+pub const STD_MEM_TRANSMUTE: [&str; 3] = ["std", "mem", "transmute"];
+pub const STD_PTR_NULL: [&str; 3] = ["std", "ptr", "null"];
+pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
+pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
+pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
+pub const TO_OWNED: [&str; 3] = ["alloc", "borrow", "ToOwned"];
+pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
+pub const TO_STRING: [&str; 3] = ["alloc", "string", "ToString"];
+pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
+pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
+pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"];
+pub const TRY_FROM_ERROR: [&str; 4] = ["std", "ops", "Try", "from_error"];
+pub const TRY_INTO_RESULT: [&str; 4] = ["std", "ops", "Try", "into_result"];
+pub const TRY_INTO_TRAIT: [&str; 3] = ["core", "convert", "TryInto"];
+pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"];
+pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
+pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
+pub const VEC_DEQUE: [&str; 4] = ["alloc", "collections", "vec_deque", "VecDeque"];
+pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
+pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
+pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
+pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
+pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
--- /dev/null
- fn is_shift(op: &AssocOp) -> bool {
- matches!(*op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
+//! Contains utility functions to generate suggestions.
+#![deny(clippy::missing_docs_in_private_items)]
+
+use crate::utils::{higher, snippet, snippet_opt, snippet_with_macro_callsite};
+use rustc_ast::util::parser::AssocOp;
+use rustc_ast::{ast, token};
+use rustc_ast_pretty::pprust::token_kind_to_string;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{EarlyContext, LateContext, LintContext};
+use rustc_span::source_map::{CharPos, Span};
+use rustc_span::{BytePos, Pos};
+use std::borrow::Cow;
+use std::convert::TryInto;
+use std::fmt::Display;
+
+/// A helper type to build suggestion correctly handling parenthesis.
+pub enum Sugg<'a> {
+ /// An expression that never needs parenthesis such as `1337` or `[0; 42]`.
+ NonParen(Cow<'a, str>),
+ /// An expression that does not fit in other variants.
+ MaybeParen(Cow<'a, str>),
+ /// A binary operator expression, including `as`-casts and explicit type
+ /// coercion.
+ BinOp(AssocOp, Cow<'a, str>),
+}
+
+/// Literal constant `1`, for convenience.
+pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
+
+impl Display for Sugg<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ match *self {
+ Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => s.fmt(f),
+ }
+ }
+}
+
+#[allow(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method
+impl<'a> Sugg<'a> {
+ /// Prepare a suggestion from an expression.
+ pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
+ snippet_opt(cx, expr.span).map(|snippet| {
+ let snippet = Cow::Owned(snippet);
+ Self::hir_from_snippet(cx, expr, snippet)
+ })
+ }
+
+ /// Convenience function around `hir_opt` for suggestions with a default
+ /// text.
+ pub fn hir(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
+ Self::hir_opt(cx, expr).unwrap_or_else(|| Sugg::NonParen(Cow::Borrowed(default)))
+ }
+
+ /// Same as `hir`, but it adapts the applicability level by following rules:
+ ///
+ /// - Applicability level `Unspecified` will never be changed.
+ /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
+ /// - If the default value is used and the applicability level is `MachineApplicable`, change it
+ /// to
+ /// `HasPlaceholders`
+ pub fn hir_with_applicability(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ default: &'a str,
+ applicability: &mut Applicability,
+ ) -> Self {
+ if *applicability != Applicability::Unspecified && expr.span.from_expansion() {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ Self::hir_opt(cx, expr).unwrap_or_else(|| {
+ if *applicability == Applicability::MachineApplicable {
+ *applicability = Applicability::HasPlaceholders;
+ }
+ Sugg::NonParen(Cow::Borrowed(default))
+ })
+ }
+
+ /// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro.
+ pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
+ let snippet = snippet_with_macro_callsite(cx, expr.span, default);
+
+ Self::hir_from_snippet(cx, expr, snippet)
+ }
+
+ /// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
+ /// function variants of `Sugg`, since these use different snippet functions.
+ fn hir_from_snippet(cx: &LateContext<'_>, expr: &hir::Expr<'_>, snippet: Cow<'a, str>) -> Self {
+ if let Some(range) = higher::range(cx, expr) {
+ let op = match range.limits {
+ ast::RangeLimits::HalfOpen => AssocOp::DotDot,
+ ast::RangeLimits::Closed => AssocOp::DotDotEq,
+ };
+ return Sugg::BinOp(op, snippet);
+ }
+
+ match expr.kind {
+ hir::ExprKind::AddrOf(..)
+ | hir::ExprKind::Box(..)
+ | hir::ExprKind::Closure(..)
+ | hir::ExprKind::Unary(..)
+ | hir::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
+ hir::ExprKind::Continue(..)
+ | hir::ExprKind::Yield(..)
+ | hir::ExprKind::Array(..)
+ | hir::ExprKind::Block(..)
+ | hir::ExprKind::Break(..)
+ | hir::ExprKind::Call(..)
+ | hir::ExprKind::Field(..)
+ | hir::ExprKind::Index(..)
+ | hir::ExprKind::InlineAsm(..)
+ | hir::ExprKind::LlvmInlineAsm(..)
+ | hir::ExprKind::Lit(..)
+ | hir::ExprKind::Loop(..)
+ | hir::ExprKind::MethodCall(..)
+ | hir::ExprKind::Path(..)
+ | hir::ExprKind::Repeat(..)
+ | hir::ExprKind::Ret(..)
+ | hir::ExprKind::Struct(..)
+ | hir::ExprKind::Tup(..)
+ | hir::ExprKind::DropTemps(_)
+ | hir::ExprKind::Err => Sugg::NonParen(snippet),
+ hir::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
+ hir::ExprKind::AssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet),
+ hir::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(higher::binop(op.node)), snippet),
+ hir::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
+ hir::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
+ }
+ }
+
+ /// Prepare a suggestion from an expression.
+ pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
+ use rustc_ast::ast::RangeLimits;
+
+ let snippet = snippet(cx, expr.span, default);
+
+ match expr.kind {
+ ast::ExprKind::AddrOf(..)
+ | ast::ExprKind::Box(..)
+ | ast::ExprKind::Closure(..)
+ | ast::ExprKind::If(..)
+ | ast::ExprKind::Let(..)
+ | ast::ExprKind::Unary(..)
+ | ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
+ ast::ExprKind::Async(..)
+ | ast::ExprKind::Block(..)
+ | ast::ExprKind::Break(..)
+ | ast::ExprKind::Call(..)
+ | ast::ExprKind::Continue(..)
+ | ast::ExprKind::Yield(..)
+ | ast::ExprKind::Field(..)
+ | ast::ExprKind::ForLoop(..)
+ | ast::ExprKind::Index(..)
+ | ast::ExprKind::InlineAsm(..)
+ | ast::ExprKind::LlvmInlineAsm(..)
+ | ast::ExprKind::Lit(..)
+ | ast::ExprKind::Loop(..)
+ | ast::ExprKind::MacCall(..)
+ | ast::ExprKind::MethodCall(..)
+ | ast::ExprKind::Paren(..)
+ | ast::ExprKind::Path(..)
+ | ast::ExprKind::Repeat(..)
+ | ast::ExprKind::Ret(..)
+ | ast::ExprKind::Struct(..)
+ | ast::ExprKind::Try(..)
+ | ast::ExprKind::TryBlock(..)
+ | ast::ExprKind::Tup(..)
+ | ast::ExprKind::Array(..)
+ | ast::ExprKind::While(..)
+ | ast::ExprKind::Await(..)
+ | ast::ExprKind::Err => Sugg::NonParen(snippet),
+ ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet),
+ ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotEq, snippet),
+ ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
+ ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet),
+ ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet),
+ ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
+ ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
+ }
+ }
+
+ /// Convenience method to create the `<lhs> && <rhs>` suggestion.
+ pub fn and(self, rhs: &Self) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::And, &self, rhs)
+ }
+
+ /// Convenience method to create the `<lhs> & <rhs>` suggestion.
+ pub fn bit_and(self, rhs: &Self) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::BitAnd, &self, rhs)
+ }
+
+ /// Convenience method to create the `<lhs> as <rhs>` suggestion.
+ pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
+ make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
+ }
+
+ /// Convenience method to create the `&<expr>` suggestion.
+ pub fn addr(self) -> Sugg<'static> {
+ make_unop("&", self)
+ }
+
+ /// Convenience method to create the `&mut <expr>` suggestion.
+ pub fn mut_addr(self) -> Sugg<'static> {
+ make_unop("&mut ", self)
+ }
+
+ /// Convenience method to create the `*<expr>` suggestion.
+ pub fn deref(self) -> Sugg<'static> {
+ make_unop("*", self)
+ }
+
+ /// Convenience method to create the `&*<expr>` suggestion. Currently this
+ /// is needed because `sugg.deref().addr()` produces an unnecessary set of
+ /// parentheses around the deref.
+ pub fn addr_deref(self) -> Sugg<'static> {
+ make_unop("&*", self)
+ }
+
+ /// Convenience method to create the `&mut *<expr>` suggestion. Currently
+ /// this is needed because `sugg.deref().mut_addr()` produces an unnecessary
+ /// set of parentheses around the deref.
+ pub fn mut_addr_deref(self) -> Sugg<'static> {
+ make_unop("&mut *", self)
+ }
+
+ /// Convenience method to transform suggestion into a return call
+ pub fn make_return(self) -> Sugg<'static> {
+ Sugg::NonParen(Cow::Owned(format!("return {}", self)))
+ }
+
+ /// Convenience method to transform suggestion into a block
+ /// where the suggestion is a trailing expression
+ pub fn blockify(self) -> Sugg<'static> {
+ Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self)))
+ }
+
+ /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
+ /// suggestion.
+ #[allow(dead_code)]
+ pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
+ match limit {
+ ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
+ ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
+ }
+ }
+
+ /// Adds parenthesis to any expression that might need them. Suitable to the
+ /// `self` argument of a method call
+ /// (e.g., to build `bar.foo()` or `(1 + 2).foo()`).
+ pub fn maybe_par(self) -> Self {
+ match self {
+ Sugg::NonParen(..) => self,
+ // `(x)` and `(x).y()` both don't need additional parens.
+ Sugg::MaybeParen(sugg) => {
+ if sugg.starts_with('(') && sugg.ends_with(')') {
+ Sugg::MaybeParen(sugg)
+ } else {
+ Sugg::NonParen(format!("({})", sugg).into())
+ }
+ },
+ Sugg::BinOp(_, sugg) => Sugg::NonParen(format!("({})", sugg).into()),
+ }
+ }
+}
+
+impl<'a, 'b> std::ops::Add<Sugg<'b>> for Sugg<'a> {
+ type Output = Sugg<'static>;
+ fn add(self, rhs: Sugg<'b>) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::Add, &self, &rhs)
+ }
+}
+
+impl<'a, 'b> std::ops::Sub<Sugg<'b>> for Sugg<'a> {
+ type Output = Sugg<'static>;
+ fn sub(self, rhs: Sugg<'b>) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::Sub, &self, &rhs)
+ }
+}
+
+impl<'a> std::ops::Not for Sugg<'a> {
+ type Output = Sugg<'static>;
+ fn not(self) -> Sugg<'static> {
+ make_unop("!", self)
+ }
+}
+
+/// Helper type to display either `foo` or `(foo)`.
+struct ParenHelper<T> {
+ /// `true` if parentheses are needed.
+ paren: bool,
+ /// The main thing to display.
+ wrapped: T,
+}
+
+impl<T> ParenHelper<T> {
+ /// Builds a `ParenHelper`.
+ fn new(paren: bool, wrapped: T) -> Self {
+ Self { paren, wrapped }
+ }
+}
+
+impl<T: Display> Display for ParenHelper<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ if self.paren {
+ write!(f, "({})", self.wrapped)
+ } else {
+ self.wrapped.fmt(f)
+ }
+ }
+}
+
+/// Builds the string for `<op><expr>` adding parenthesis when necessary.
+///
+/// For convenience, the operator is taken as a string because all unary
+/// operators have the same
+/// precedence.
+pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
+ Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into())
+}
+
+/// Builds the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
+///
+/// Precedence of shift operator relative to other arithmetic operation is
+/// often confusing so
+/// parenthesis will always be added for a mix of these.
+pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
+ /// Returns `true` if the operator is a shift operator `<<` or `>>`.
- fn is_arith(op: &AssocOp) -> bool {
++ fn is_shift(op: AssocOp) -> bool {
++ matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
+ }
+
+ /// Returns `true` if the operator is a arithmetic operator
+ /// (i.e., `+`, `-`, `*`, `/`, `%`).
- *op,
++ fn is_arith(op: AssocOp) -> bool {
+ matches!(
- fn needs_paren(op: &AssocOp, other: &AssocOp, dir: Associativity) -> bool {
++ op,
+ AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
+ )
+ }
+
+ /// Returns `true` if the operator `op` needs parenthesis with the operator
+ /// `other` in the direction `dir`.
- let lhs_paren = if let Sugg::BinOp(ref lop, _) = *lhs {
- needs_paren(&op, lop, Associativity::Left)
++ fn needs_paren(op: AssocOp, other: AssocOp, dir: Associativity) -> bool {
+ other.precedence() < op.precedence()
+ || (other.precedence() == op.precedence()
+ && ((op != other && associativity(op) != dir)
+ || (op == other && associativity(op) != Associativity::Both)))
+ || is_shift(op) && is_arith(other)
+ || is_shift(other) && is_arith(op)
+ }
+
- let rhs_paren = if let Sugg::BinOp(ref rop, _) = *rhs {
- needs_paren(&op, rop, Associativity::Right)
++ let lhs_paren = if let Sugg::BinOp(lop, _) = *lhs {
++ needs_paren(op, lop, Associativity::Left)
+ } else {
+ false
+ };
+
- fn associativity(op: &AssocOp) -> Associativity {
++ let rhs_paren = if let Sugg::BinOp(rop, _) = *rhs {
++ needs_paren(op, rop, Associativity::Right)
+ } else {
+ false
+ };
+
+ let lhs = ParenHelper::new(lhs_paren, lhs);
+ let rhs = ParenHelper::new(rhs_paren, rhs);
+ let sugg = match op {
+ AssocOp::Add
+ | AssocOp::BitAnd
+ | AssocOp::BitOr
+ | AssocOp::BitXor
+ | AssocOp::Divide
+ | AssocOp::Equal
+ | AssocOp::Greater
+ | AssocOp::GreaterEqual
+ | AssocOp::LAnd
+ | AssocOp::LOr
+ | AssocOp::Less
+ | AssocOp::LessEqual
+ | AssocOp::Modulus
+ | AssocOp::Multiply
+ | AssocOp::NotEqual
+ | AssocOp::ShiftLeft
+ | AssocOp::ShiftRight
+ | AssocOp::Subtract => format!(
+ "{} {} {}",
+ lhs,
+ op.to_ast_binop().expect("Those are AST ops").to_string(),
+ rhs
+ ),
+ AssocOp::Assign => format!("{} = {}", lhs, rhs),
+ AssocOp::AssignOp(op) => format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs),
+ AssocOp::As => format!("{} as {}", lhs, rhs),
+ AssocOp::DotDot => format!("{}..{}", lhs, rhs),
+ AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
+ AssocOp::Colon => format!("{}: {}", lhs, rhs),
+ };
+
+ Sugg::BinOp(op, sugg.into())
+}
+
+/// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
+pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
+}
+
+#[derive(PartialEq, Eq, Clone, Copy)]
+/// Operator associativity.
+enum Associativity {
+ /// The operator is both left-associative and right-associative.
+ Both,
+ /// The operator is left-associative.
+ Left,
+ /// The operator is not associative.
+ None,
+ /// The operator is right-associative.
+ Right,
+}
+
+/// Returns the associativity/fixity of an operator. The difference with
+/// `AssocOp::fixity` is that an operator can be both left and right associative
+/// (such as `+`: `a + b + c == (a + b) + c == a + (b + c)`.
+///
+/// Chained `as` and explicit `:` type coercion never need inner parenthesis so
+/// they are considered
+/// associative.
+#[must_use]
- match *op {
++fn associativity(op: AssocOp) -> Associativity {
+ use rustc_ast::util::parser::AssocOp::{
+ Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Colon, Divide, DotDot, DotDotEq, Equal, Greater,
+ GreaterEqual, LAnd, LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
+ };
+
- if let Some(line) = lo.file.get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */) {
- if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
- // We can mix char and byte positions here because we only consider `[ \t]`.
- if lo.col == CharPos(pos) {
- Some(line[..pos].into())
++ match op {
+ Assign | AssignOp(_) => Associativity::Right,
+ Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both,
+ Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
+ | Subtract => Associativity::Left,
+ DotDot | DotDotEq => Associativity::None,
+ }
+}
+
+/// Converts a `hir::BinOp` to the corresponding assigning binary operator.
+fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
+ use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
+
+ AssocOp::AssignOp(match op.node {
+ hir::BinOpKind::Add => Plus,
+ hir::BinOpKind::BitAnd => And,
+ hir::BinOpKind::BitOr => Or,
+ hir::BinOpKind::BitXor => Caret,
+ hir::BinOpKind::Div => Slash,
+ hir::BinOpKind::Mul => Star,
+ hir::BinOpKind::Rem => Percent,
+ hir::BinOpKind::Shl => Shl,
+ hir::BinOpKind::Shr => Shr,
+ hir::BinOpKind::Sub => Minus,
+
+ hir::BinOpKind::And
+ | hir::BinOpKind::Eq
+ | hir::BinOpKind::Ge
+ | hir::BinOpKind::Gt
+ | hir::BinOpKind::Le
+ | hir::BinOpKind::Lt
+ | hir::BinOpKind::Ne
+ | hir::BinOpKind::Or => panic!("This operator does not exist"),
+ })
+}
+
+/// Converts an `ast::BinOp` to the corresponding assigning binary operator.
+fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
+ use rustc_ast::ast::BinOpKind::{
+ Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
+ };
+ use rustc_ast::token::BinOpToken;
+
+ AssocOp::AssignOp(match op.node {
+ Add => BinOpToken::Plus,
+ BitAnd => BinOpToken::And,
+ BitOr => BinOpToken::Or,
+ BitXor => BinOpToken::Caret,
+ Div => BinOpToken::Slash,
+ Mul => BinOpToken::Star,
+ Rem => BinOpToken::Percent,
+ Shl => BinOpToken::Shl,
+ Shr => BinOpToken::Shr,
+ Sub => BinOpToken::Minus,
+ And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
+ })
+}
+
+/// Returns the indentation before `span` if there are nothing but `[ \t]`
+/// before it on its line.
+fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ let lo = cx.sess().source_map().lookup_char_pos(span.lo());
- } else {
- None
- }
- } else {
- None
- }
++ lo.file
++ .get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */)
++ .and_then(|line| {
++ if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
++ // We can mix char and byte positions here because we only consider `[ \t]`.
++ if lo.col == CharPos(pos) {
++ Some(line[..pos].into())
++ } else {
++ None
++ }
+ } else {
+ None
+ }
++ })
+}
+
+/// Convenience extension trait for `DiagnosticBuilder`.
+pub trait DiagnosticBuilderExt<T: LintContext> {
+ /// Suggests to add an attribute to an item.
+ ///
+ /// Correctly handles indentation of the attribute and item.
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_item_with_attr(cx, item, "#[derive(Default)]");
+ /// ```
+ fn suggest_item_with_attr<D: Display + ?Sized>(
+ &mut self,
+ cx: &T,
+ item: Span,
+ msg: &str,
+ attr: &D,
+ applicability: Applicability,
+ );
+
+ /// Suggest to add an item before another.
+ ///
+ /// The item should not be indented (except for inner indentation).
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_prepend_item(cx, item,
+ /// "fn foo() {
+ /// bar();
+ /// }");
+ /// ```
+ fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability);
+
+ /// Suggest to completely remove an item.
+ ///
+ /// This will remove an item and all following whitespace until the next non-whitespace
+ /// character. This should work correctly if item is on the same indentation level as the
+ /// following item.
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_remove_item(cx, item, "remove this")
+ /// ```
+ fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
+}
+
+impl<T: LintContext> DiagnosticBuilderExt<T> for rustc_errors::DiagnosticBuilder<'_> {
+ fn suggest_item_with_attr<D: Display + ?Sized>(
+ &mut self,
+ cx: &T,
+ item: Span,
+ msg: &str,
+ attr: &D,
+ applicability: Applicability,
+ ) {
+ if let Some(indent) = indentation(cx, item) {
+ let span = item.with_hi(item.lo());
+
+ self.span_suggestion(span, msg, format!("{}\n{}", attr, indent), applicability);
+ }
+ }
+
+ fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) {
+ if let Some(indent) = indentation(cx, item) {
+ let span = item.with_hi(item.lo());
+
+ let mut first = true;
+ let new_item = new_item
+ .lines()
+ .map(|l| {
+ if first {
+ first = false;
+ format!("{}\n", l)
+ } else {
+ format!("{}{}\n", indent, l)
+ }
+ })
+ .collect::<String>();
+
+ self.span_suggestion(span, msg, format!("{}\n{}", new_item, indent), applicability);
+ }
+ }
+
+ fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) {
+ let mut remove_span = item;
+ let hi = cx.sess().source_map().next_point(remove_span).hi();
+ let fmpos = cx.sess().source_map().lookup_byte_offset(hi);
+
+ if let Some(ref src) = fmpos.sf.src {
+ let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n');
+
+ if let Some(non_whitespace_offset) = non_whitespace_offset {
+ remove_span = remove_span
+ .with_hi(remove_span.hi() + BytePos(non_whitespace_offset.try_into().expect("offset too large")))
+ }
+ }
+
+ self.span_suggestion(remove_span, msg, String::new(), applicability);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::Sugg;
+ use std::borrow::Cow;
+
+ const SUGGESTION: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("function_call()"));
+
+ #[test]
+ fn make_return_transform_sugg_into_a_return_call() {
+ assert_eq!("return function_call()", SUGGESTION.make_return().to_string());
+ }
+
+ #[test]
+ fn blockify_transforms_sugg_into_a_block() {
+ assert_eq!("{ function_call() }", SUGGESTION.blockify().to_string());
+ }
+}
--- /dev/null
- /// string that
- /// ends in a newline.
+use std::borrow::Cow;
+use std::ops::Range;
+
+use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then};
+use rustc_ast::ast::{Expr, ExprKind, Item, ItemKind, MacCall, StrLit, StrStyle};
+use rustc_ast::token;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_errors::Applicability;
+use rustc_lexer::unescape::{self, EscapeError};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_parse::parser;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::Symbol;
+use rustc_span::{BytePos, Span};
+
+declare_clippy_lint! {
+ /// **What it does:** This lint warns when you use `println!("")` to
+ /// print a newline.
+ ///
+ /// **Why is this bad?** You should use `println!()`, which is simpler.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
++ /// // 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
- let suggestion = match expr {
- Some(expr) => snippet_with_applicability(cx, expr.span, "v", &mut applicability),
- None => {
++ /// string that ends in a newline.
+ ///
+ /// **Why is this bad?** You should use `println!()` instead, which appends the
+ /// newline.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let name = "World";
+ /// print!("Hello {}!\n", name);
+ /// ```
+ /// use println!() instead
+ /// ```rust
+ /// # let name = "World";
+ /// println!("Hello {}!", name);
+ /// ```
+ pub PRINT_WITH_NEWLINE,
+ style,
+ "using `print!()` with a format string that ends in a single newline"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for printing on *stdout*. The purpose of this lint
+ /// is to catch debugging remnants.
+ ///
+ /// **Why is this bad?** People often print on *stdout* while debugging an
+ /// application and might forget to remove those prints afterward.
+ ///
+ /// **Known problems:** Only catches `print!` and `println!` calls.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// println!("Hello world!");
+ /// ```
+ pub PRINT_STDOUT,
+ restriction,
+ "printing on stdout"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for use of `Debug` formatting. The purpose of this
+ /// lint is to catch debugging remnants.
+ ///
+ /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate
+ /// debugging Rust code. It should not be used in user-facing output.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let foo = "bar";
+ /// println!("{:?}", foo);
+ /// ```
+ pub USE_DEBUG,
+ restriction,
+ "use of `Debug`-based formatting"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** This lint warns about the use of literals as `print!`/`println!` args.
+ ///
+ /// **Why is this bad?** Using literals as `println!` args is inefficient
+ /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
+ /// (i.e., just put the literal in the format string)
+ ///
+ /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
+ /// -- e.g., `println!("{}", env!("FOO"))`.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// println!("{}", "foo");
+ /// ```
+ /// use the literal without formatting:
+ /// ```rust
+ /// println!("foo");
+ /// ```
+ pub PRINT_LITERAL,
+ style,
+ "printing a literal with a format string"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** This lint warns when you use `writeln!(buf, "")` to
+ /// print a newline.
+ ///
+ /// **Why is this bad?** You should use `writeln!(buf)`, which is simpler.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
++ ///
++ /// // 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,
+ USE_DEBUG,
+ PRINT_LITERAL,
+ WRITE_WITH_NEWLINE,
+ WRITELN_EMPTY_STRING,
+ WRITE_LITERAL
+]);
+
+impl EarlyLintPass for Write {
+ fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) {
+ if let ItemKind::Impl {
+ of_trait: Some(trait_ref),
+ ..
+ } = &item.kind
+ {
+ let trait_name = trait_ref
+ .path
+ .segments
+ .iter()
+ .last()
+ .expect("path has at least one segment")
+ .ident
+ .name;
+ if trait_name == sym!(Debug) {
+ self.in_debug_impl = true;
+ }
+ }
+ }
+
+ fn check_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) {
+ self.in_debug_impl = false;
+ }
+
+ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) {
+ if mac.path == sym!(println) {
+ span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`");
+ if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), false) {
+ if fmt_str.symbol == Symbol::intern("") {
+ span_lint_and_sugg(
+ cx,
+ PRINTLN_EMPTY_STRING,
+ mac.span(),
+ "using `println!(\"\")`",
+ "replace it with",
+ "println!()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ } else if mac.path == sym!(print) {
+ span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`");
+ if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), false) {
+ if check_newlines(&fmt_str) {
+ span_lint_and_then(
+ cx,
+ PRINT_WITH_NEWLINE,
+ mac.span(),
+ "using `print!()` with a format string that ends in a single newline",
+ |err| {
+ err.multipart_suggestion(
+ "use `println!` instead",
+ vec![
+ (mac.path.span, String::from("println")),
+ (newline_span(&fmt_str), String::new()),
+ ],
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+ } else if mac.path == sym!(write) {
+ if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
+ if check_newlines(&fmt_str) {
+ span_lint_and_then(
+ cx,
+ WRITE_WITH_NEWLINE,
+ mac.span(),
+ "using `write!()` with a format string that ends in a single newline",
+ |err| {
+ err.multipart_suggestion(
+ "use `writeln!()` instead",
+ vec![
+ (mac.path.span, String::from("writeln")),
+ (newline_span(&fmt_str), String::new()),
+ ],
+ Applicability::MachineApplicable,
+ );
+ },
+ )
+ }
+ }
+ } else if mac.path == sym!(writeln) {
+ if let (Some(fmt_str), expr) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
+ if fmt_str.symbol == Symbol::intern("") {
+ let mut applicability = Applicability::MachineApplicable;
- };
++ let suggestion = expr.map_or_else(
++ || {
+ applicability = Applicability::HasPlaceholders;
+ Cow::Borrowed("v")
+ },
++ |e| snippet_with_applicability(cx, e.span, "v", &mut Applicability::MachineApplicable),
++ );
+
+ span_lint_and_sugg(
+ cx,
+ WRITELN_EMPTY_STRING,
+ mac.span(),
+ format!("using `writeln!({}, \"\")`", suggestion).as_str(),
+ "replace it with",
+ format!("writeln!({})", suggestion),
+ applicability,
+ );
+ }
+ }
+ }
+ }
+}
+
+/// Given a format string that ends in a newline and its span, calculates the span of the
+/// newline.
+fn newline_span(fmtstr: &StrLit) -> Span {
+ let sp = fmtstr.span;
+ let contents = &fmtstr.symbol.as_str();
+
+ let newline_sp_hi = sp.hi()
+ - match fmtstr.style {
+ StrStyle::Cooked => BytePos(1),
+ StrStyle::Raw(hashes) => BytePos((1 + hashes).into()),
+ };
+
+ let newline_sp_len = if contents.ends_with('\n') {
+ BytePos(1)
+ } else if contents.ends_with(r"\n") {
+ BytePos(2)
+ } else {
+ panic!("expected format string to contain a newline");
+ };
+
+ sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi)
+}
+
+impl Write {
+ /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
+ /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
+ /// the contents of the string, whether it's a raw string, and the span of the literal in the
+ /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
+ /// `format_str` should be written to.
+ ///
+ /// Example:
+ ///
+ /// Calling this function on
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// # let something = "something";
+ /// writeln!(buf, "string to write: {}", something);
+ /// ```
+ /// will return
+ /// ```rust,ignore
+ /// (Some("string to write: {}"), Some(buf))
+ /// ```
+ #[allow(clippy::too_many_lines)]
+ fn check_tts<'a>(
+ &self,
+ cx: &EarlyContext<'a>,
+ tts: &TokenStream,
+ is_write: bool,
+ ) -> (Option<StrLit>, Option<Expr>) {
+ use rustc_parse_format::{
+ AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, ParseMode, Parser,
+ Piece,
+ };
+ let tts = tts.clone();
+
+ let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None);
+ let mut expr: Option<Expr> = 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(_) => {
+ let mut all_simple = true;
+ let mut seen = false;
+ for arg in &args {
+ match arg.position {
+ ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
+ if n == idx {
+ all_simple &= arg.format == SIMPLE;
+ seen = true;
+ }
+ },
+ ArgumentNamed(_) => {},
+ }
+ }
+ if all_simple && seen {
+ span_lint(cx, lint, token_expr.span, "literal with an empty format string");
+ }
+ idx += 1;
+ },
+ ExprKind::Assign(lhs, rhs, _) => {
+ if let ExprKind::Lit(_) = rhs.kind {
+ if let ExprKind::Path(_, p) = &lhs.kind {
+ let mut all_simple = true;
+ let mut seen = false;
+ for arg in &args {
+ match arg.position {
+ ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
+ ArgumentNamed(name) => {
+ if *p == name {
+ seen = true;
+ all_simple &= arg.format == SIMPLE;
+ }
+ },
+ }
+ }
+ if all_simple && seen {
+ span_lint(cx, lint, rhs.span, "literal with an empty format string");
+ }
+ }
+ }
+ },
+ _ => idx += 1,
+ }
+ }
+ }
+}
+
+/// Checks if the format string contains a single newline that terminates it.
+///
+/// Literal and escaped newlines are both checked (only literal for raw strings).
+fn check_newlines(fmtstr: &StrLit) -> bool {
+ let mut has_internal_newline = false;
+ let mut last_was_cr = false;
+ let mut should_lint = false;
+
+ let contents = &fmtstr.symbol.as_str();
+
+ let mut cb = |r: Range<usize>, c: Result<char, EscapeError>| {
+ 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
+}
--- /dev/null
- args.windows(2).any(|args| {
- args[1] == "help"
- && match args[0].as_str() {
- "-W" | "-A" | "-D" | "-F" => true,
- _ => false,
- }
- })
+#![feature(rustc_private)]
+#![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_data_structures;
+extern crate rustc_driver;
+extern crate rustc_errors;
+extern crate rustc_interface;
+extern crate rustc_middle;
+
+use rustc_interface::interface;
+use rustc_middle::ty::TyCtxt;
+use rustc_tools_util::VersionInfo;
+
+use lazy_static::lazy_static;
+use std::borrow::Cow;
+use std::env;
+use std::ops::Deref;
+use std::panic;
+use std::path::{Path, PathBuf};
+use std::process::{exit, Command};
+
+mod lintlist;
+
+/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
+/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
+fn arg_value<'a, T: Deref<Target = str>>(
+ args: &'a [T],
+ find_arg: &str,
+ pred: impl Fn(&str) -> bool,
+) -> Option<&'a str> {
+ let mut args = args.iter().map(Deref::deref);
+ while let Some(arg) = args.next() {
+ let mut arg = arg.splitn(2, '=');
+ if arg.next() != Some(find_arg) {
+ continue;
+ }
+
+ match arg.next().or_else(|| args.next()) {
+ Some(v) if pred(v) => return Some(v),
+ _ => {},
+ }
+ }
+ None
+}
+
+#[test]
+fn test_arg_value() {
+ let args = &["--bar=bar", "--foobar", "123", "--foo"];
+
+ assert_eq!(arg_value(&[] as &[&str], "--foobar", |_| true), None);
+ assert_eq!(arg_value(args, "--bar", |_| false), None);
+ assert_eq!(arg_value(args, "--bar", |_| true), Some("bar"));
+ assert_eq!(arg_value(args, "--bar", |p| p == "bar"), Some("bar"));
+ assert_eq!(arg_value(args, "--bar", |p| p == "foo"), None);
+ assert_eq!(arg_value(args, "--foobar", |p| p == "foo"), None);
+ assert_eq!(arg_value(args, "--foobar", |p| p == "123"), Some("123"));
+ assert_eq!(arg_value(args, "--foo", |_| true), None);
+}
+
+struct DefaultCallbacks;
+impl rustc_driver::Callbacks for DefaultCallbacks {}
+
+struct ClippyCallbacks;
+impl rustc_driver::Callbacks for ClippyCallbacks {
+ fn config(&mut self, config: &mut interface::Config) {
+ let previous = config.register_lints.take();
+ config.register_lints = Some(Box::new(move |sess, mut lint_store| {
+ // technically we're ~guaranteed that this is none but might as well call anything that
+ // is there already. Certainly it can't hurt.
+ if let Some(previous) = &previous {
+ (previous)(sess, lint_store);
+ }
+
+ let conf = clippy_lints::read_conf(&[], &sess);
+ clippy_lints::register_plugins(&mut lint_store, &sess, &conf);
+ clippy_lints::register_pre_expansion_lints(&mut lint_store);
+ clippy_lints::register_renamed(&mut lint_store);
+ }));
+
+ // FIXME: #4825; This is required, because Clippy lints that are based on MIR have to be
+ // run on the unoptimized MIR. On the other hand this results in some false negatives. If
+ // MIR passes can be enabled / disabled separately, we should figure out, what passes to
+ // use for Clippy.
+ config.opts.debugging_opts.mir_opt_level = 0;
+ }
+}
+
+#[allow(clippy::find_map, clippy::filter_map)]
+fn describe_lints() {
+ use lintlist::{Level, Lint, ALL_LINTS, LINT_LEVELS};
+ use rustc_data_structures::fx::FxHashSet;
+
+ println!(
+ "
+Available lint options:
+ -W <foo> Warn about <foo>
+ -A <foo> Allow <foo>
+ -D <foo> Deny <foo>
+ -F <foo> Forbid <foo> (deny <foo> and all attempts to override)
+
+"
+ );
+
+ let lint_level = |lint: &Lint| {
+ LINT_LEVELS
+ .iter()
+ .find(|level_mapping| level_mapping.0 == lint.group)
+ .map(|(_, level)| match level {
+ Level::Allow => "allow",
+ Level::Warn => "warn",
+ Level::Deny => "deny",
+ })
+ .unwrap()
+ };
+
+ let mut lints: Vec<_> = ALL_LINTS.iter().collect();
+ // The sort doesn't case-fold but it's doubtful we care.
+ lints.sort_by_cached_key(|x: &&Lint| (lint_level(x), x.name));
+
+ let max_lint_name_len = lints
+ .iter()
+ .map(|lint| lint.name.len())
+ .map(|len| len + "clippy::".len())
+ .max()
+ .unwrap_or(0);
+
+ let padded = |x: &str| {
+ let mut s = " ".repeat(max_lint_name_len - x.chars().count());
+ s.push_str(x);
+ s
+ };
+
+ let scoped = |x: &str| format!("clippy::{}", x);
+
+ let lint_groups: FxHashSet<_> = lints.iter().map(|lint| lint.group).collect();
+
+ println!("Lint checks provided by clippy:\n");
+ println!(" {} {:7.7} meaning", padded("name"), "default");
+ println!(" {} {:7.7} -------", padded("----"), "-------");
+
+ let print_lints = |lints: &[&Lint]| {
+ for lint in lints {
+ let name = lint.name.replace("_", "-");
+ println!(
+ " {} {:7.7} {}",
+ padded(&scoped(&name)),
+ lint_level(lint),
+ lint.desc
+ );
+ }
+ println!("\n");
+ };
+
+ print_lints(&lints);
+
+ let max_group_name_len = std::cmp::max(
+ "clippy::all".len(),
+ lint_groups
+ .iter()
+ .map(|group| group.len())
+ .map(|len| len + "clippy::".len())
+ .max()
+ .unwrap_or(0),
+ );
+
+ let padded_group = |x: &str| {
+ let mut s = " ".repeat(max_group_name_len - x.chars().count());
+ s.push_str(x);
+ s
+ };
+
+ println!("Lint groups provided by clippy:\n");
+ println!(" {} sub-lints", padded_group("name"));
+ println!(" {} ---------", padded_group("----"));
+ println!(" {} the set of all clippy lints", padded_group("clippy::all"));
+
+ let print_lint_groups = || {
+ for group in lint_groups {
+ let name = group.to_lowercase().replace("_", "-");
+ let desc = lints
+ .iter()
+ .filter(|&lint| lint.group == group)
+ .map(|lint| lint.name)
+ .map(|name| name.replace("_", "-"))
+ .collect::<Vec<String>>()
+ .join(", ");
+ println!(" {} {}", padded_group(&scoped(&name)), desc);
+ }
+ println!("\n");
+ };
+
+ print_lint_groups();
+}
+
+fn display_help() {
+ println!(
+ "\
+Checks a package to catch common mistakes and improve your Rust code.
+
+Usage:
+ cargo clippy [options] [--] [<opts>...]
+
+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";
+
+lazy_static! {
+ static ref ICE_HOOK: Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static> = {
+ let hook = panic::take_hook();
+ panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL)));
+ hook
+ };
+}
+
+fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
+ // Invoke our ICE handler, which prints the actual panic message and optionally a backtrace
+ (*ICE_HOOK)(info);
+
+ // Separate the output with an empty line
+ eprintln!();
+
+ let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
+ rustc_errors::ColorConfig::Auto,
+ None,
+ false,
+ false,
+ None,
+ false,
+ ));
+ let handler = rustc_errors::Handler::with_emitter(true, None, emitter);
+
+ // a .span_bug or .bug call has already printed what
+ // it wants to print.
+ if !info.payload().is::<rustc_errors::ExplicitBug>() {
+ 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<Cow<'static, str>> = vec![
+ "the compiler unexpectedly panicked. this is a bug.".into(),
+ format!("we would appreciate a bug report: {}", bug_report_url).into(),
+ format!("Clippy version: {}", version_info).into(),
+ ];
+
+ for note in &xs {
+ handler.note_without_error(¬e);
+ }
+
+ // If backtraces are enabled, also print the query stack
+ let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0");
+
+ if backtrace {
+ TyCtxt::try_print_query_stack(&handler);
+ }
+}
+
+fn toolchain_path(home: Option<String>, toolchain: Option<String>) -> Option<PathBuf> {
+ home.and_then(|home| {
+ toolchain.map(|toolchain| {
+ let mut path = PathBuf::from(home);
+ path.push("toolchains");
+ path.push(toolchain);
+ path
+ })
+ })
+}
+
+pub fn main() {
+ rustc_driver::init_rustc_env_logger();
+ lazy_static::initialize(&ICE_HOOK);
+ exit(rustc_driver::catch_with_exit_code(move || {
+ let mut orig_args: Vec<String> = 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<String> = orig_args.clone();
+ if !have_sys_root_arg {
+ args.extend(vec!["--sysroot".into(), sys_root]);
+ };
+
+ return rustc_driver::run_compiler(&args, &mut DefaultCallbacks, None, None);
+ }
+
+ 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);
+ }
+
+ let should_describe_lints = || {
+ let args: Vec<_> = env::args().collect();
++ args.windows(2)
++ .any(|args| args[1] == "help" && matches!(args[0].as_str(), "-W" | "-A" | "-D" | "-F"))
+ };
+
+ if !wrapper_mode && should_describe_lints() {
+ describe_lints();
+ exit(0);
+ }
+
+ // this conditional check for the --sysroot flag is there so users can call
+ // `clippy_driver` directly
+ // without having to pass --sysroot or anything
+ let mut args: Vec<String> = orig_args.clone();
+ if !have_sys_root_arg {
+ args.extend(vec!["--sysroot".into(), sys_root]);
+ };
+
+ // this check ensures that dependencies are built but not linted and the final
+ // crate is linted but not built
+ let clippy_enabled = env::var("CLIPPY_TESTS").map_or(false, |val| val == "true")
+ || arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_none();
+
+ if clippy_enabled {
+ args.extend(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()]);
+ if let Ok(extra_args) = env::var("CLIPPY_ARGS") {
+ args.extend(extra_args.split("__CLIPPY_HACKERY__").filter_map(|s| {
+ if s.is_empty() {
+ None
+ } else {
+ Some(s.to_string())
+ }
+ }));
+ }
+ }
+ let mut clippy = ClippyCallbacks;
+ let mut default = DefaultCallbacks;
+ let callbacks: &mut (dyn rustc_driver::Callbacks + Send) =
+ if clippy_enabled { &mut clippy } else { &mut default };
+ rustc_driver::run_compiler(&args, callbacks, None, None)
+ }))
+}
--- /dev/null
- group: "complexity",
+//! This file is managed by `cargo dev update_lints`. Do not edit.
+
+use lazy_static::lazy_static;
+
+pub mod lint;
+pub use lint::Level;
+pub use lint::Lint;
+pub use lint::LINT_LEVELS;
+
+lazy_static! {
+// begin lint list, do not remove this comment, it’s used in `update_lints`
+pub static ref ALL_LINTS: Vec<Lint> = vec![
+ Lint {
+ name: "absurd_extreme_comparisons",
+ group: "correctness",
+ desc: "a comparison with a maximum or minimum value that is always true or false",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "almost_swapped",
+ group: "correctness",
+ desc: "`foo = bar; bar = foo` sequence",
+ deprecation: None,
+ module: "swap",
+ },
+ Lint {
+ name: "approx_constant",
+ group: "correctness",
+ desc: "the approximate of a known float constant (in `std::fXX::consts`)",
+ deprecation: None,
+ module: "approx_const",
+ },
+ Lint {
+ name: "as_conversions",
+ group: "restriction",
+ desc: "using a potentially dangerous silent `as` conversion",
+ deprecation: None,
+ module: "as_conversions",
+ },
+ Lint {
+ name: "assertions_on_constants",
+ group: "style",
+ desc: "`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`",
+ deprecation: None,
+ module: "assertions_on_constants",
+ },
+ Lint {
+ name: "assign_op_pattern",
+ group: "style",
+ desc: "assigning the result of an operation on a variable to that same variable",
+ deprecation: None,
+ module: "assign_ops",
+ },
+ Lint {
+ name: "await_holding_lock",
+ group: "pedantic",
+ desc: "Inside an async function, holding a MutexGuard while calling await",
+ deprecation: None,
+ module: "await_holding_lock",
+ },
+ Lint {
+ name: "bad_bit_mask",
+ group: "correctness",
+ desc: "expressions of the form `_ & mask == select` that will only ever return `true` or `false`",
+ deprecation: None,
+ module: "bit_mask",
+ },
+ Lint {
+ name: "bind_instead_of_map",
+ group: "complexity",
+ desc: "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "blacklisted_name",
+ group: "style",
+ desc: "usage of a blacklisted/placeholder name",
+ deprecation: None,
+ module: "blacklisted_name",
+ },
++ Lint {
++ name: "blanket_clippy_restriction_lints",
++ group: "style",
++ desc: "enabling the complete restriction group",
++ deprecation: None,
++ module: "attrs",
++ },
+ Lint {
+ name: "blocks_in_if_conditions",
+ group: "style",
+ desc: "useless or complex blocks that can be eliminated in conditions",
+ deprecation: None,
+ module: "blocks_in_if_conditions",
+ },
+ Lint {
+ name: "bool_comparison",
+ group: "complexity",
+ desc: "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`",
+ deprecation: None,
+ module: "needless_bool",
+ },
+ Lint {
+ name: "borrow_interior_mutable_const",
+ group: "correctness",
+ desc: "referencing `const` with interior mutability",
+ deprecation: None,
+ module: "non_copy_const",
+ },
+ Lint {
+ name: "borrowed_box",
+ group: "complexity",
+ desc: "a borrow of a boxed type",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "box_vec",
+ group: "perf",
+ desc: "usage of `Box<Vec<T>>`, vector elements are already on the heap",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "boxed_local",
+ group: "perf",
+ desc: "using `Box<T>` where unnecessary",
+ deprecation: None,
+ module: "escape",
+ },
+ Lint {
+ name: "builtin_type_shadow",
+ group: "style",
+ desc: "shadowing a builtin type",
+ deprecation: None,
+ module: "misc_early",
+ },
+ Lint {
+ name: "cargo_common_metadata",
+ group: "cargo",
+ desc: "common metadata is defined in `Cargo.toml`",
+ deprecation: None,
+ module: "cargo_common_metadata",
+ },
+ Lint {
+ name: "cast_lossless",
+ group: "pedantic",
+ desc: "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "cast_possible_truncation",
+ group: "pedantic",
+ desc: "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "cast_possible_wrap",
+ group: "pedantic",
+ desc: "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "cast_precision_loss",
+ group: "pedantic",
+ desc: "casts that cause loss of precision, e.g., `x as f32` where `x: u64`",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "cast_ptr_alignment",
+ group: "pedantic",
+ desc: "cast from a pointer to a more-strictly-aligned pointer",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "cast_ref_to_mut",
+ group: "correctness",
+ desc: "a cast of reference to a mutable pointer",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "cast_sign_loss",
+ group: "pedantic",
+ desc: "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "char_lit_as_u8",
+ group: "complexity",
+ desc: "casting a character literal to `u8` truncates",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "chars_last_cmp",
+ group: "style",
+ desc: "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "chars_next_cmp",
+ group: "style",
+ desc: "using `.chars().next()` to check if a string starts with a char",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "checked_conversions",
+ group: "pedantic",
+ desc: "`try_from` could replace manual bounds checking when casting",
+ deprecation: None,
+ module: "checked_conversions",
+ },
+ Lint {
+ name: "clone_double_ref",
+ group: "correctness",
+ desc: "using `clone` on `&&T`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "clone_on_copy",
+ group: "complexity",
+ desc: "using `clone` on a `Copy` type",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "clone_on_ref_ptr",
+ group: "restriction",
+ desc: "using \'clone\' on a ref-counted pointer",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "cmp_nan",
+ group: "correctness",
+ desc: "comparisons to `NAN`, which will always return false, probably not intended",
+ deprecation: None,
+ module: "misc",
+ },
+ Lint {
+ name: "cmp_null",
+ group: "style",
+ desc: "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead.",
+ deprecation: None,
+ module: "ptr",
+ },
+ Lint {
+ name: "cmp_owned",
+ group: "perf",
+ desc: "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`",
+ deprecation: None,
+ module: "misc",
+ },
+ Lint {
+ name: "cognitive_complexity",
+ group: "nursery",
+ desc: "functions that should be split up into multiple functions",
+ deprecation: None,
+ module: "cognitive_complexity",
+ },
+ Lint {
+ name: "collapsible_if",
+ group: "style",
+ desc: "`if`s that can be collapsed (e.g., `if x { if y { ... } }` and `else { if x { ... } }`)",
+ deprecation: None,
+ module: "collapsible_if",
+ },
+ Lint {
+ name: "comparison_chain",
+ group: "style",
+ desc: "`if`s that can be rewritten with `match` and `cmp`",
+ deprecation: None,
+ module: "comparison_chain",
+ },
+ Lint {
+ name: "copy_iterator",
+ group: "pedantic",
+ desc: "implementing `Iterator` on a `Copy` type",
+ deprecation: None,
+ module: "copy_iterator",
+ },
+ Lint {
+ name: "crosspointer_transmute",
+ group: "complexity",
+ desc: "transmutes that have to or from types that are a pointer to the other",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "dbg_macro",
+ group: "restriction",
+ desc: "`dbg!` macro is intended as a debugging tool",
+ deprecation: None,
+ module: "dbg_macro",
+ },
+ Lint {
+ name: "debug_assert_with_mut_call",
+ group: "nursery",
+ desc: "mutable arguments in `debug_assert{,_ne,_eq}!`",
+ deprecation: None,
+ module: "mutable_debug_assertion",
+ },
+ Lint {
+ name: "decimal_literal_representation",
+ group: "restriction",
+ desc: "using decimal representation when hexadecimal would be better",
+ deprecation: None,
+ module: "literal_representation",
+ },
+ Lint {
+ name: "declare_interior_mutable_const",
+ group: "correctness",
+ desc: "declaring `const` with interior mutability",
+ deprecation: None,
+ module: "non_copy_const",
+ },
+ Lint {
+ name: "default_trait_access",
+ group: "pedantic",
+ desc: "checks for literal calls to `Default::default()`",
+ deprecation: None,
+ module: "default_trait_access",
+ },
+ Lint {
+ name: "deprecated_cfg_attr",
+ group: "complexity",
+ desc: "usage of `cfg_attr(rustfmt)` instead of tool attributes",
+ deprecation: None,
+ module: "attrs",
+ },
+ Lint {
+ name: "deprecated_semver",
+ group: "correctness",
+ desc: "use of `#[deprecated(since = \"x\")]` where x is not semver",
+ deprecation: None,
+ module: "attrs",
+ },
+ Lint {
+ name: "deref_addrof",
+ group: "complexity",
+ desc: "use of `*&` or `*&mut` in an expression",
+ deprecation: None,
+ module: "reference",
+ },
+ Lint {
+ name: "derive_hash_xor_eq",
+ group: "correctness",
+ desc: "deriving `Hash` but implementing `PartialEq` explicitly",
+ deprecation: None,
+ module: "derive",
+ },
+ Lint {
+ name: "diverging_sub_expression",
+ group: "complexity",
+ desc: "whether an expression contains a diverging sub expression",
+ deprecation: None,
+ module: "eval_order_dependence",
+ },
+ Lint {
+ name: "doc_markdown",
+ group: "pedantic",
+ desc: "presence of `_`, `::` or camel-case outside backticks in documentation",
+ deprecation: None,
+ module: "doc",
+ },
+ Lint {
+ name: "double_comparisons",
+ group: "complexity",
+ desc: "unnecessary double comparisons that can be simplified",
+ deprecation: None,
+ module: "double_comparison",
+ },
+ Lint {
+ name: "double_must_use",
+ group: "style",
+ desc: "`#[must_use]` attribute on a `#[must_use]`-returning function / method",
+ deprecation: None,
+ module: "functions",
+ },
+ Lint {
+ name: "double_neg",
+ group: "style",
+ desc: "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++",
+ deprecation: None,
+ module: "misc_early",
+ },
+ Lint {
+ name: "double_parens",
+ group: "complexity",
+ desc: "Warn on unnecessary double parentheses",
+ deprecation: None,
+ module: "double_parens",
+ },
+ Lint {
+ name: "drop_bounds",
+ group: "correctness",
+ desc: "Bounds of the form `T: Drop` are useless",
+ deprecation: None,
+ module: "drop_bounds",
+ },
+ Lint {
+ name: "drop_copy",
+ group: "correctness",
+ desc: "calls to `std::mem::drop` with a value that implements Copy",
+ deprecation: None,
+ module: "drop_forget_ref",
+ },
+ Lint {
+ name: "drop_ref",
+ group: "correctness",
+ desc: "calls to `std::mem::drop` with a reference instead of an owned value",
+ deprecation: None,
+ module: "drop_forget_ref",
+ },
+ Lint {
+ name: "duplicate_underscore_argument",
+ group: "style",
+ desc: "function arguments having names which only differ by an underscore",
+ deprecation: None,
+ module: "misc_early",
+ },
+ Lint {
+ name: "duration_subsec",
+ group: "complexity",
+ desc: "checks for calculation of subsecond microseconds or milliseconds",
+ deprecation: None,
+ module: "duration_subsec",
+ },
+ Lint {
+ name: "else_if_without_else",
+ group: "restriction",
+ desc: "`if` expression with an `else if`, but without a final `else` branch",
+ deprecation: None,
+ module: "else_if_without_else",
+ },
+ Lint {
+ name: "empty_enum",
+ group: "pedantic",
+ desc: "enum with no variants",
+ deprecation: None,
+ module: "empty_enum",
+ },
+ Lint {
+ name: "empty_line_after_outer_attr",
+ group: "nursery",
+ desc: "empty line after outer attribute",
+ deprecation: None,
+ module: "attrs",
+ },
+ Lint {
+ name: "empty_loop",
+ group: "style",
+ desc: "empty `loop {}`, which should block or sleep",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "enum_clike_unportable_variant",
+ group: "correctness",
+ desc: "C-like enums that are `repr(isize/usize)` and have values that don\'t fit into an `i32`",
+ deprecation: None,
+ module: "enum_clike",
+ },
+ Lint {
+ name: "enum_glob_use",
+ group: "pedantic",
+ desc: "use items that import all variants of an enum",
+ deprecation: None,
+ module: "wildcard_imports",
+ },
+ Lint {
+ name: "enum_variant_names",
+ group: "style",
+ desc: "enums where all variants share a prefix/postfix",
+ deprecation: None,
+ module: "enum_variants",
+ },
+ Lint {
+ name: "eq_op",
+ group: "correctness",
+ desc: "equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)",
+ deprecation: None,
+ module: "eq_op",
+ },
+ Lint {
+ name: "erasing_op",
+ group: "correctness",
+ desc: "using erasing operations, e.g., `x * 0` or `y & 0`",
+ deprecation: None,
+ module: "erasing_op",
+ },
+ Lint {
+ name: "eval_order_dependence",
+ group: "complexity",
+ desc: "whether a variable read occurs before a write depends on sub-expression evaluation order",
+ deprecation: None,
+ module: "eval_order_dependence",
+ },
+ Lint {
+ name: "excessive_precision",
+ group: "style",
+ desc: "excessive precision for float literal",
+ deprecation: None,
+ module: "float_literal",
+ },
+ Lint {
+ name: "exit",
+ group: "restriction",
+ desc: "`std::process::exit` is called, terminating the program",
+ deprecation: None,
+ module: "exit",
+ },
+ Lint {
+ name: "expect_fun_call",
+ group: "perf",
+ desc: "using any `expect` method with a function call",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "expect_used",
+ group: "restriction",
+ desc: "using `.expect()` on `Result` or `Option`, which might be better handled",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "expl_impl_clone_on_copy",
+ group: "pedantic",
+ desc: "implementing `Clone` explicitly on `Copy` types",
+ deprecation: None,
+ module: "derive",
+ },
+ Lint {
+ name: "explicit_counter_loop",
+ group: "complexity",
+ desc: "for-looping with an explicit counter when `_.enumerate()` would do",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "explicit_deref_methods",
+ group: "pedantic",
+ desc: "Explicit use of deref or deref_mut method while not in a method chain.",
+ deprecation: None,
+ module: "dereference",
+ },
+ Lint {
+ name: "explicit_into_iter_loop",
+ group: "pedantic",
+ desc: "for-looping over `_.into_iter()` when `_` would do",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "explicit_iter_loop",
+ group: "pedantic",
+ desc: "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "explicit_write",
+ group: "complexity",
+ desc: "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work",
+ deprecation: None,
+ module: "explicit_write",
+ },
+ Lint {
+ name: "extra_unused_lifetimes",
+ group: "complexity",
+ desc: "unused lifetimes in function definitions",
+ deprecation: None,
+ module: "lifetimes",
+ },
+ Lint {
+ name: "fallible_impl_from",
+ group: "nursery",
+ desc: "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`",
+ deprecation: None,
+ module: "fallible_impl_from",
+ },
+ Lint {
+ name: "filetype_is_file",
+ group: "restriction",
+ desc: "`FileType::is_file` is not recommended to test for readable file type",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "filter_map",
+ group: "pedantic",
+ desc: "using combinations of `filter`, `map`, `filter_map` and `flat_map` which can usually be written as a single method call",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "filter_map_next",
+ group: "pedantic",
+ desc: "using combination of `filter_map` and `next` which can usually be written as a single method call",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "filter_next",
+ group: "complexity",
+ desc: "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "find_map",
+ group: "pedantic",
+ desc: "using a combination of `find` and `map` can usually be written as a single method call",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "flat_map_identity",
+ group: "complexity",
+ desc: "call to `flat_map` where `flatten` is sufficient",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "float_arithmetic",
+ group: "restriction",
+ desc: "any floating-point arithmetic statement",
+ deprecation: None,
+ module: "arithmetic",
+ },
+ Lint {
+ name: "float_cmp",
+ group: "correctness",
+ desc: "using `==` or `!=` on float values instead of comparing difference with an epsilon",
+ deprecation: None,
+ module: "misc",
+ },
+ Lint {
+ name: "float_cmp_const",
+ group: "restriction",
+ desc: "using `==` or `!=` on float constants instead of comparing difference with an epsilon",
+ deprecation: None,
+ module: "misc",
+ },
+ Lint {
+ name: "fn_address_comparisons",
+ group: "correctness",
+ desc: "comparison with an address of a function item",
+ deprecation: None,
+ module: "unnamed_address",
+ },
+ Lint {
+ name: "fn_params_excessive_bools",
+ group: "pedantic",
+ desc: "using too many bools in function parameters",
+ deprecation: None,
+ module: "excessive_bools",
+ },
+ Lint {
+ name: "fn_to_numeric_cast",
+ group: "style",
+ desc: "casting a function pointer to a numeric type other than usize",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "fn_to_numeric_cast_with_truncation",
+ group: "style",
+ desc: "casting a function pointer to a numeric type not wide enough to store the address",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "for_kv_map",
+ group: "style",
+ desc: "looping on a map using `iter` when `keys` or `values` would do",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "for_loops_over_fallibles",
+ group: "correctness",
+ desc: "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "forget_copy",
+ group: "correctness",
+ desc: "calls to `std::mem::forget` with a value that implements Copy",
+ deprecation: None,
+ module: "drop_forget_ref",
+ },
+ Lint {
+ name: "forget_ref",
+ group: "correctness",
+ desc: "calls to `std::mem::forget` with a reference instead of an owned value",
+ deprecation: None,
+ module: "drop_forget_ref",
+ },
+ Lint {
+ name: "future_not_send",
+ group: "nursery",
+ desc: "public Futures must be Send",
+ deprecation: None,
+ module: "future_not_send",
+ },
+ Lint {
+ name: "get_last_with_len",
+ group: "complexity",
+ desc: "Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler",
+ deprecation: None,
+ module: "get_last_with_len",
+ },
+ Lint {
+ name: "get_unwrap",
+ group: "restriction",
+ desc: "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "identity_op",
+ group: "complexity",
+ desc: "using identity operations, e.g., `x + 0` or `y / 1`",
+ deprecation: None,
+ module: "identity_op",
+ },
+ Lint {
+ name: "if_let_mutex",
+ group: "correctness",
+ desc: "locking a `Mutex` in an `if let` block can cause deadlocks",
+ deprecation: None,
+ module: "if_let_mutex",
+ },
+ Lint {
+ name: "if_let_some_result",
+ group: "style",
+ desc: "usage of `ok()` in `if let Some(pat)` statements is unnecessary, match on `Ok(pat)` instead",
+ deprecation: None,
+ module: "if_let_some_result",
+ },
+ Lint {
+ name: "if_not_else",
+ group: "pedantic",
+ desc: "`if` branches that could be swapped so no negation operation is necessary on the condition",
+ deprecation: None,
+ module: "if_not_else",
+ },
+ Lint {
+ name: "if_same_then_else",
+ group: "correctness",
+ desc: "`if` with the same `then` and `else` blocks",
+ deprecation: None,
+ module: "copies",
+ },
+ Lint {
+ name: "ifs_same_cond",
+ group: "correctness",
+ desc: "consecutive `if`s with the same condition",
+ deprecation: None,
+ module: "copies",
+ },
+ Lint {
+ name: "implicit_hasher",
+ group: "pedantic",
+ desc: "missing generalization over different hashers",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "implicit_return",
+ group: "restriction",
+ desc: "use a return statement like `return expr` instead of an expression",
+ deprecation: None,
+ module: "implicit_return",
+ },
+ Lint {
+ name: "implicit_saturating_sub",
+ group: "pedantic",
+ desc: "Perform saturating subtraction instead of implicitly checking lower bound of data type",
+ deprecation: None,
+ module: "implicit_saturating_sub",
+ },
+ Lint {
+ name: "imprecise_flops",
+ group: "nursery",
+ desc: "usage of imprecise floating point operations",
+ deprecation: None,
+ module: "floating_point_arithmetic",
+ },
+ Lint {
+ name: "inconsistent_digit_grouping",
+ group: "style",
+ desc: "integer literals with digits grouped inconsistently",
+ deprecation: None,
+ module: "literal_representation",
+ },
+ Lint {
+ name: "indexing_slicing",
+ group: "restriction",
+ desc: "indexing/slicing usage",
+ deprecation: None,
+ module: "indexing_slicing",
+ },
+ Lint {
+ name: "ineffective_bit_mask",
+ group: "correctness",
+ desc: "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`",
+ deprecation: None,
+ module: "bit_mask",
+ },
+ Lint {
+ name: "inefficient_to_string",
+ group: "pedantic",
+ desc: "using `to_string` on `&&T` where `T: ToString`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "infallible_destructuring_match",
+ group: "style",
+ desc: "a `match` statement with a single infallible arm instead of a `let`",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "infinite_iter",
+ group: "correctness",
+ desc: "infinite iteration",
+ deprecation: None,
+ module: "infinite_iter",
+ },
+ Lint {
+ name: "inherent_to_string",
+ group: "style",
+ desc: "type implements inherent method `to_string()`, but should instead implement the `Display` trait",
+ deprecation: None,
+ module: "inherent_to_string",
+ },
+ Lint {
+ name: "inherent_to_string_shadow_display",
+ group: "correctness",
+ desc: "type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait",
+ deprecation: None,
+ module: "inherent_to_string",
+ },
+ Lint {
+ name: "inline_always",
+ group: "pedantic",
+ desc: "use of `#[inline(always)]`",
+ deprecation: None,
+ module: "attrs",
+ },
+ Lint {
+ name: "inline_fn_without_body",
+ group: "correctness",
+ desc: "use of `#[inline]` on trait methods without bodies",
+ deprecation: None,
+ module: "inline_fn_without_body",
+ },
+ Lint {
+ name: "int_plus_one",
+ group: "complexity",
+ desc: "instead of using `x >= y + 1`, use `x > y`",
+ deprecation: None,
+ module: "int_plus_one",
+ },
+ Lint {
+ name: "integer_arithmetic",
+ group: "restriction",
+ desc: "any integer arithmetic expression which could overflow or panic",
+ deprecation: None,
+ module: "arithmetic",
+ },
+ Lint {
+ name: "integer_division",
+ group: "restriction",
+ desc: "integer division may cause loss of precision",
+ deprecation: None,
+ module: "integer_division",
+ },
+ Lint {
+ name: "into_iter_on_ref",
+ group: "style",
+ desc: "using `.into_iter()` on a reference",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "invalid_atomic_ordering",
+ group: "correctness",
+ desc: "usage of invalid atomic ordering in atomic loads/stores and memory fences",
+ deprecation: None,
+ module: "atomic_ordering",
+ },
+ Lint {
+ name: "invalid_regex",
+ group: "correctness",
+ desc: "invalid regular expressions",
+ deprecation: None,
+ module: "regex",
+ },
+ Lint {
+ name: "invalid_upcast_comparisons",
+ group: "pedantic",
+ desc: "a comparison involving an upcast which is always true or false",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "items_after_statements",
+ group: "pedantic",
+ desc: "blocks where an item comes after a statement",
+ deprecation: None,
+ module: "items_after_statements",
+ },
+ Lint {
+ name: "iter_cloned_collect",
+ group: "style",
+ desc: "using `.cloned().collect()` on slice to create a `Vec`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "iter_next_loop",
+ group: "correctness",
+ desc: "for-looping over `_.next()` which is probably not intended",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "iter_next_slice",
+ group: "style",
+ desc: "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "iter_nth",
+ group: "perf",
+ desc: "using `.iter().nth()` on a standard library type with O(1) element access",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "iter_nth_zero",
+ group: "style",
+ desc: "replace `iter.nth(0)` with `iter.next()`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "iter_skip_next",
+ group: "style",
+ desc: "using `.skip(x).next()` on an iterator",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "iterator_step_by_zero",
+ group: "correctness",
+ desc: "using `Iterator::step_by(0)`, which will panic at runtime",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "just_underscores_and_digits",
+ group: "style",
+ desc: "unclear name",
+ deprecation: None,
+ module: "non_expressive_names",
+ },
+ Lint {
+ name: "large_const_arrays",
+ group: "perf",
+ desc: "large non-scalar const array may cause performance overhead",
+ deprecation: None,
+ module: "large_const_arrays",
+ },
+ Lint {
+ name: "large_digit_groups",
+ group: "pedantic",
+ desc: "grouping digits into groups that are too large",
+ deprecation: None,
+ module: "literal_representation",
+ },
+ Lint {
+ name: "large_enum_variant",
+ group: "perf",
+ desc: "large size difference between variants on an enum",
+ deprecation: None,
+ module: "large_enum_variant",
+ },
+ Lint {
+ name: "large_stack_arrays",
+ group: "pedantic",
+ desc: "allocating large arrays on stack may cause stack overflow",
+ deprecation: None,
+ module: "large_stack_arrays",
+ },
+ Lint {
+ name: "len_without_is_empty",
+ group: "style",
+ desc: "traits or impls with a public `len` method but no corresponding `is_empty` method",
+ deprecation: None,
+ module: "len_zero",
+ },
+ Lint {
+ name: "len_zero",
+ group: "style",
+ desc: "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead",
+ deprecation: None,
+ module: "len_zero",
+ },
+ Lint {
+ name: "let_and_return",
+ group: "style",
+ desc: "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block",
+ deprecation: None,
+ module: "let_and_return",
+ },
+ Lint {
+ name: "let_underscore_lock",
+ group: "correctness",
+ desc: "non-binding let on a synchronization lock",
+ deprecation: None,
+ module: "let_underscore",
+ },
+ Lint {
+ name: "let_underscore_must_use",
+ group: "restriction",
+ desc: "non-binding let on a `#[must_use]` expression",
+ deprecation: None,
+ module: "let_underscore",
+ },
+ Lint {
+ name: "let_unit_value",
+ group: "pedantic",
+ desc: "creating a `let` binding to a value of unit type, which usually can\'t be used afterwards",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "linkedlist",
+ group: "pedantic",
+ desc: "usage of LinkedList, usually a vector is faster, or a more specialized data structure like a `VecDeque`",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "logic_bug",
+ group: "correctness",
+ desc: "boolean expressions that contain terminals which can be eliminated",
+ deprecation: None,
+ module: "booleans",
+ },
+ Lint {
+ name: "lossy_float_literal",
+ group: "restriction",
+ desc: "lossy whole number float literals",
+ deprecation: None,
+ module: "float_literal",
+ },
+ Lint {
+ name: "macro_use_imports",
+ group: "pedantic",
+ desc: "#[macro_use] is no longer needed",
+ deprecation: None,
+ module: "macro_use",
+ },
+ Lint {
+ name: "main_recursion",
+ group: "style",
+ desc: "recursion using the entrypoint",
+ deprecation: None,
+ module: "main_recursion",
+ },
+ Lint {
+ name: "manual_async_fn",
+ group: "style",
+ desc: "manual implementations of `async` functions can be simplified using the dedicated syntax",
+ deprecation: None,
+ module: "manual_async_fn",
+ },
+ Lint {
+ name: "manual_memcpy",
+ group: "perf",
+ desc: "manually copying items between slices",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "manual_non_exhaustive",
+ group: "style",
+ desc: "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]",
+ deprecation: None,
+ module: "manual_non_exhaustive",
+ },
+ Lint {
+ name: "manual_saturating_arithmetic",
+ group: "style",
+ desc: "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "manual_swap",
+ group: "complexity",
+ desc: "manual swap of two variables",
+ deprecation: None,
+ module: "swap",
+ },
+ Lint {
+ name: "many_single_char_names",
+ group: "style",
+ desc: "too many single character bindings",
+ deprecation: None,
+ module: "non_expressive_names",
+ },
+ Lint {
+ name: "map_clone",
+ group: "style",
+ desc: "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types",
+ deprecation: None,
+ module: "map_clone",
+ },
+ Lint {
+ name: "map_entry",
+ group: "perf",
+ desc: "use of `contains_key` followed by `insert` on a `HashMap` or `BTreeMap`",
+ deprecation: None,
+ module: "entry",
+ },
+ Lint {
+ name: "map_flatten",
+ group: "pedantic",
+ desc: "using combinations of `flatten` and `map` which can usually be written as a single method call",
+ deprecation: None,
+ module: "methods",
+ },
++ Lint {
++ name: "map_identity",
++ group: "complexity",
++ desc: "using iterator.map(|x| x)",
++ deprecation: None,
++ module: "map_identity",
++ },
+ Lint {
+ name: "map_unwrap_or",
+ group: "pedantic",
+ desc: "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)`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "match_as_ref",
+ group: "complexity",
+ desc: "a `match` on an Option value instead of using `as_ref()` or `as_mut`",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "match_bool",
+ group: "pedantic",
+ desc: "a `match` on a boolean expression instead of an `if..else` block",
+ deprecation: None,
+ module: "matches",
+ },
++ Lint {
++ name: "match_like_matches_macro",
++ group: "style",
++ desc: "a match that could be written with the matches! macro",
++ deprecation: None,
++ module: "matches",
++ },
+ Lint {
+ name: "match_on_vec_items",
+ group: "pedantic",
+ desc: "matching on vector elements can panic",
+ deprecation: None,
+ module: "match_on_vec_items",
+ },
+ Lint {
+ name: "match_overlapping_arm",
+ group: "style",
+ desc: "a `match` with overlapping arms",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "match_ref_pats",
+ group: "style",
+ desc: "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "match_same_arms",
+ group: "pedantic",
+ desc: "`match` with identical arm bodies",
+ deprecation: None,
+ module: "copies",
+ },
+ Lint {
+ name: "match_single_binding",
+ group: "complexity",
+ desc: "a match with a single binding instead of using `let` statement",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "match_wild_err_arm",
+ group: "pedantic",
+ desc: "a `match` with `Err(_)` arm and take drastic actions",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "match_wildcard_for_single_variants",
+ group: "pedantic",
+ desc: "a wildcard enum match for a single variant",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "maybe_infinite_iter",
+ group: "pedantic",
+ desc: "possible infinite iteration",
+ deprecation: None,
+ module: "infinite_iter",
+ },
+ Lint {
+ name: "mem_discriminant_non_enum",
+ group: "correctness",
+ desc: "calling `mem::descriminant` on non-enum type",
+ deprecation: None,
+ module: "mem_discriminant",
+ },
+ Lint {
+ name: "mem_forget",
+ group: "restriction",
+ desc: "`mem::forget` usage on `Drop` types, likely to cause memory leaks",
+ deprecation: None,
+ module: "mem_forget",
+ },
+ Lint {
+ name: "mem_replace_option_with_none",
+ group: "style",
+ desc: "replacing an `Option` with `None` instead of `take()`",
+ deprecation: None,
+ module: "mem_replace",
+ },
+ Lint {
+ name: "mem_replace_with_default",
+ group: "style",
+ desc: "replacing a value of type `T` with `T::default()` instead of using `std::mem::take`",
+ deprecation: None,
+ module: "mem_replace",
+ },
+ Lint {
+ name: "mem_replace_with_uninit",
+ group: "correctness",
+ desc: "`mem::replace(&mut _, mem::uninitialized())` or `mem::replace(&mut _, mem::zeroed())`",
+ deprecation: None,
+ module: "mem_replace",
+ },
+ Lint {
+ name: "min_max",
+ group: "correctness",
+ desc: "`min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant",
+ deprecation: None,
+ module: "minmax",
+ },
+ Lint {
+ name: "mismatched_target_os",
+ group: "correctness",
+ desc: "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`",
+ deprecation: None,
+ module: "attrs",
+ },
+ Lint {
+ name: "misrefactored_assign_op",
+ group: "complexity",
+ desc: "having a variable on both sides of an assign op",
+ deprecation: None,
+ module: "assign_ops",
+ },
+ Lint {
+ name: "missing_const_for_fn",
+ group: "nursery",
+ desc: "Lint functions definitions that could be made `const fn`",
+ deprecation: None,
+ module: "missing_const_for_fn",
+ },
+ Lint {
+ name: "missing_docs_in_private_items",
+ group: "restriction",
+ desc: "detects missing documentation for public and private members",
+ deprecation: None,
+ module: "missing_doc",
+ },
+ Lint {
+ name: "missing_errors_doc",
+ group: "pedantic",
+ desc: "`pub fn` returns `Result` without `# Errors` in doc comment",
+ deprecation: None,
+ module: "doc",
+ },
+ Lint {
+ name: "missing_inline_in_public_items",
+ group: "restriction",
+ desc: "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)",
+ deprecation: None,
+ module: "missing_inline",
+ },
+ Lint {
+ name: "missing_safety_doc",
+ group: "style",
+ desc: "`pub unsafe fn` without `# Safety` docs",
+ deprecation: None,
+ module: "doc",
+ },
+ Lint {
+ name: "mistyped_literal_suffixes",
+ group: "correctness",
+ desc: "mistyped literal suffix",
+ deprecation: None,
+ module: "literal_representation",
+ },
+ Lint {
+ name: "mixed_case_hex_literals",
+ group: "style",
+ desc: "hex literals whose letter digits are not consistently upper- or lowercased",
+ deprecation: None,
+ module: "misc_early",
+ },
+ Lint {
+ name: "module_inception",
+ group: "style",
+ desc: "modules that have the same name as their parent module",
+ deprecation: None,
+ module: "enum_variants",
+ },
+ Lint {
+ name: "module_name_repetitions",
+ group: "pedantic",
+ desc: "type names prefixed/postfixed with their containing module\'s name",
+ deprecation: None,
+ module: "enum_variants",
+ },
+ Lint {
+ name: "modulo_arithmetic",
+ group: "restriction",
+ desc: "any modulo arithmetic statement",
+ deprecation: None,
+ module: "modulo_arithmetic",
+ },
+ Lint {
+ name: "modulo_one",
+ group: "correctness",
+ desc: "taking a number modulo 1, which always returns 0",
+ deprecation: None,
+ module: "misc",
+ },
+ Lint {
+ name: "multiple_crate_versions",
+ group: "cargo",
+ desc: "multiple versions of the same crate being used",
+ deprecation: None,
+ module: "multiple_crate_versions",
+ },
+ Lint {
+ name: "multiple_inherent_impl",
+ group: "restriction",
+ desc: "Multiple inherent impl that could be grouped",
+ deprecation: None,
+ module: "inherent_impl",
+ },
+ Lint {
+ name: "must_use_candidate",
+ group: "pedantic",
+ desc: "function or method that could take a `#[must_use]` attribute",
+ deprecation: None,
+ module: "functions",
+ },
+ Lint {
+ name: "must_use_unit",
+ group: "style",
+ desc: "`#[must_use]` attribute on a unit-returning function / method",
+ deprecation: None,
+ module: "functions",
+ },
+ Lint {
+ name: "mut_from_ref",
+ group: "correctness",
+ desc: "fns that create mutable refs from immutable ref args",
+ deprecation: None,
+ module: "ptr",
+ },
+ Lint {
+ name: "mut_mut",
+ group: "pedantic",
+ desc: "usage of double-mut refs, e.g., `&mut &mut ...`",
+ deprecation: None,
+ module: "mut_mut",
+ },
+ Lint {
+ name: "mut_range_bound",
+ group: "complexity",
+ desc: "for loop over a range where one of the bounds is a mutable variable",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "mutable_key_type",
+ group: "correctness",
+ desc: "Check for mutable `Map`/`Set` key type",
+ deprecation: None,
+ module: "mut_key",
+ },
+ Lint {
+ name: "mutex_atomic",
+ group: "perf",
+ desc: "using a mutex where an atomic value could be used instead",
+ deprecation: None,
+ module: "mutex_atomic",
+ },
+ Lint {
+ name: "mutex_integer",
+ group: "nursery",
+ desc: "using a mutex for an integer type",
+ deprecation: None,
+ module: "mutex_atomic",
+ },
+ Lint {
+ name: "naive_bytecount",
+ group: "perf",
+ desc: "use of naive `<slice>.filter(|&x| x == y).count()` to count byte values",
+ deprecation: None,
+ module: "bytecount",
+ },
+ Lint {
+ name: "needless_bool",
+ group: "complexity",
+ desc: "if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`",
+ deprecation: None,
+ module: "needless_bool",
+ },
+ Lint {
+ name: "needless_borrow",
+ group: "nursery",
+ desc: "taking a reference that is going to be automatically dereferenced",
+ deprecation: None,
+ module: "needless_borrow",
+ },
+ Lint {
+ name: "needless_borrowed_reference",
+ group: "complexity",
+ desc: "taking a needless borrowed reference",
+ deprecation: None,
+ module: "needless_borrowed_ref",
+ },
+ Lint {
+ name: "needless_collect",
+ group: "perf",
+ desc: "collecting an iterator when collect is not needed",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "needless_continue",
+ group: "pedantic",
+ desc: "`continue` statements that can be replaced by a rearrangement of code",
+ deprecation: None,
+ module: "needless_continue",
+ },
+ Lint {
+ name: "needless_doctest_main",
+ group: "style",
+ desc: "presence of `fn main() {` in code examples",
+ deprecation: None,
+ module: "doc",
+ },
+ Lint {
+ name: "needless_lifetimes",
+ group: "complexity",
+ desc: "using explicit lifetimes for references in function arguments when elision rules would allow omitting them",
+ deprecation: None,
+ module: "lifetimes",
+ },
+ Lint {
+ name: "needless_pass_by_value",
+ group: "pedantic",
+ desc: "functions taking arguments by value, but not consuming them in its body",
+ deprecation: None,
+ module: "needless_pass_by_value",
+ },
+ Lint {
+ name: "needless_range_loop",
+ group: "style",
+ desc: "for-looping over a range of indices where an iterator over items would do",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "needless_return",
+ group: "style",
+ desc: "using a return statement like `return expr;` where an expression would suffice",
+ deprecation: None,
+ module: "returns",
+ },
+ Lint {
+ name: "needless_update",
+ group: "complexity",
+ desc: "using `Foo { ..base }` when there are no missing fields",
+ deprecation: None,
+ module: "needless_update",
+ },
+ Lint {
+ name: "neg_cmp_op_on_partial_ord",
+ group: "complexity",
+ desc: "The use of negated comparison operators on partially ordered types may produce confusing code.",
+ deprecation: None,
+ module: "neg_cmp_op_on_partial_ord",
+ },
+ Lint {
+ name: "neg_multiply",
+ group: "style",
+ desc: "multiplying integers with `-1`",
+ deprecation: None,
+ module: "neg_multiply",
+ },
+ Lint {
+ name: "never_loop",
+ group: "correctness",
+ desc: "any loop that will always `break` or `return`",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "new_ret_no_self",
+ group: "style",
+ desc: "not returning type containing `Self` in a `new` method",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "new_without_default",
+ group: "style",
+ desc: "`fn new() -> Self` method without `Default` implementation",
+ deprecation: None,
+ module: "new_without_default",
+ },
+ Lint {
+ name: "no_effect",
+ group: "complexity",
+ desc: "statements with no effect",
+ deprecation: None,
+ module: "no_effect",
+ },
+ Lint {
+ name: "non_ascii_literal",
+ group: "pedantic",
+ desc: "using any literal non-ASCII chars in a string literal instead of using the `\\\\u` escape",
+ deprecation: None,
+ module: "unicode",
+ },
+ Lint {
+ name: "nonminimal_bool",
+ group: "complexity",
+ desc: "boolean expressions that can be written more concisely",
+ deprecation: None,
+ module: "booleans",
+ },
+ Lint {
+ name: "nonsensical_open_options",
+ group: "correctness",
+ desc: "nonsensical combination of options for opening a file",
+ deprecation: None,
+ module: "open_options",
+ },
+ Lint {
+ name: "not_unsafe_ptr_arg_deref",
+ group: "correctness",
+ desc: "public functions dereferencing raw pointer arguments but not marked `unsafe`",
+ deprecation: None,
+ module: "functions",
+ },
+ Lint {
+ name: "ok_expect",
+ group: "style",
+ desc: "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "op_ref",
+ group: "style",
+ desc: "taking a reference to satisfy the type constraints on `==`",
+ deprecation: None,
+ module: "eq_op",
+ },
+ Lint {
+ name: "option_as_ref_deref",
+ group: "complexity",
+ desc: "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "option_env_unwrap",
+ group: "correctness",
+ desc: "using `option_env!(...).unwrap()` to get environment variable",
+ deprecation: None,
+ module: "option_env_unwrap",
+ },
++ Lint {
++ name: "option_if_let_else",
++ group: "pedantic",
++ desc: "reimplementation of Option::map_or",
++ deprecation: None,
++ module: "option_if_let_else",
++ },
+ Lint {
+ name: "option_map_or_none",
+ group: "style",
+ desc: "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "option_map_unit_fn",
+ group: "complexity",
+ desc: "using `option.map(f)`, where `f` is a function or closure that returns `()`",
+ deprecation: None,
+ module: "map_unit_fn",
+ },
+ Lint {
+ name: "option_option",
+ group: "pedantic",
+ desc: "usage of `Option<Option<T>>`",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "or_fun_call",
+ group: "perf",
+ desc: "using any `*or` method with a function call, which suggests `*or_else`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "out_of_bounds_indexing",
+ group: "correctness",
+ desc: "out of bounds constant indexing",
+ deprecation: None,
+ module: "indexing_slicing",
+ },
+ Lint {
+ name: "overflow_check_conditional",
+ group: "complexity",
+ desc: "overflow checks inspired by C which are likely to panic",
+ deprecation: None,
+ module: "overflow_check_conditional",
+ },
+ Lint {
+ name: "panic",
+ group: "restriction",
+ desc: "usage of the `panic!` macro",
+ deprecation: None,
+ module: "panic_unimplemented",
+ },
+ Lint {
+ name: "panic_params",
+ group: "style",
+ desc: "missing parameters in `panic!` calls",
+ deprecation: None,
+ module: "panic_unimplemented",
+ },
+ Lint {
+ name: "panicking_unwrap",
+ group: "correctness",
+ desc: "checks for calls of `unwrap[_err]()` that will always fail",
+ deprecation: None,
+ module: "unwrap",
+ },
+ Lint {
+ name: "partialeq_ne_impl",
+ group: "complexity",
+ desc: "re-implementing `PartialEq::ne`",
+ deprecation: None,
+ module: "partialeq_ne_impl",
+ },
+ Lint {
+ name: "path_buf_push_overwrite",
+ group: "nursery",
+ desc: "calling `push` with file system root on `PathBuf` can overwrite it",
+ deprecation: None,
+ module: "path_buf_push_overwrite",
+ },
++ Lint {
++ name: "pattern_type_mismatch",
++ group: "restriction",
++ desc: "type of pattern does not match the expression type",
++ deprecation: None,
++ module: "pattern_type_mismatch",
++ },
+ Lint {
+ name: "possible_missing_comma",
+ group: "correctness",
+ desc: "possible missing comma in array",
+ deprecation: None,
+ module: "formatting",
+ },
+ Lint {
+ name: "precedence",
+ group: "complexity",
+ desc: "operations where precedence may be unclear",
+ deprecation: None,
+ module: "precedence",
+ },
+ Lint {
+ name: "print_literal",
+ group: "style",
+ desc: "printing a literal with a format string",
+ deprecation: None,
+ module: "write",
+ },
+ Lint {
+ name: "print_stdout",
+ group: "restriction",
+ desc: "printing on stdout",
+ deprecation: None,
+ module: "write",
+ },
+ Lint {
+ name: "print_with_newline",
+ group: "style",
+ desc: "using `print!()` with a format string that ends in a single newline",
+ deprecation: None,
+ module: "write",
+ },
+ Lint {
+ name: "println_empty_string",
+ group: "style",
+ desc: "using `println!(\"\")` with an empty string",
+ deprecation: None,
+ module: "write",
+ },
+ Lint {
+ name: "ptr_arg",
+ group: "style",
+ desc: "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively",
+ deprecation: None,
+ module: "ptr",
+ },
+ Lint {
+ name: "ptr_offset_with_cast",
+ group: "complexity",
+ desc: "unneeded pointer offset cast",
+ deprecation: None,
+ module: "ptr_offset_with_cast",
+ },
+ Lint {
+ name: "pub_enum_variant_names",
+ group: "pedantic",
+ desc: "public enums where all variants share a prefix/postfix",
+ deprecation: None,
+ module: "enum_variants",
+ },
+ Lint {
+ name: "question_mark",
+ group: "style",
+ desc: "checks for expressions that could be replaced by the question mark operator",
+ deprecation: None,
+ module: "question_mark",
+ },
+ Lint {
+ name: "range_minus_one",
- module: "redundant_pattern_matching",
++ group: "pedantic",
+ desc: "`x..=(y-1)` reads better as `x..y`",
+ deprecation: None,
+ module: "ranges",
+ },
+ Lint {
+ name: "range_plus_one",
+ group: "pedantic",
+ desc: "`x..(y+1)` reads better as `x..=y`",
+ deprecation: None,
+ module: "ranges",
+ },
+ Lint {
+ name: "range_zip_with_len",
+ group: "complexity",
+ desc: "zipping iterator with a range when `enumerate()` would do",
+ deprecation: None,
+ module: "ranges",
+ },
+ Lint {
+ name: "redundant_allocation",
+ group: "perf",
+ desc: "redundant allocation",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "redundant_clone",
+ group: "perf",
+ desc: "`clone()` of an owned value that is going to be dropped immediately",
+ deprecation: None,
+ module: "redundant_clone",
+ },
+ Lint {
+ name: "redundant_closure",
+ group: "style",
+ desc: "redundant closures, i.e., `|a| foo(a)` (which can be written as just `foo`)",
+ deprecation: None,
+ module: "eta_reduction",
+ },
+ Lint {
+ name: "redundant_closure_call",
+ group: "complexity",
+ desc: "throwaway closures called in the expression they are defined",
+ deprecation: None,
+ module: "misc_early",
+ },
+ Lint {
+ name: "redundant_closure_for_method_calls",
+ group: "pedantic",
+ desc: "redundant closures for method calls",
+ deprecation: None,
+ module: "eta_reduction",
+ },
+ Lint {
+ name: "redundant_field_names",
+ group: "style",
+ desc: "checks for fields in struct literals where shorthands could be used",
+ deprecation: None,
+ module: "redundant_field_names",
+ },
+ Lint {
+ name: "redundant_pattern",
+ group: "style",
+ desc: "using `name @ _` in a pattern",
+ deprecation: None,
+ module: "misc_early",
+ },
+ Lint {
+ name: "redundant_pattern_matching",
+ group: "style",
+ desc: "use the proper utility function avoiding an `if let`",
+ deprecation: None,
- name: "regex_macro",
- group: "style",
- desc: "use of `regex!(_)` instead of `Regex::new(_)`",
++ module: "matches",
+ },
+ Lint {
+ name: "redundant_pub_crate",
+ group: "nursery",
+ desc: "Using `pub(crate)` visibility on items that are not crate visible due to the visibility of the module that contains them.",
+ deprecation: None,
+ module: "redundant_pub_crate",
+ },
+ Lint {
+ name: "redundant_static_lifetimes",
+ group: "style",
+ desc: "Using explicit `\'static` lifetime for constants or statics when elision rules would allow omitting them.",
+ deprecation: None,
+ module: "redundant_static_lifetimes",
+ },
+ Lint {
+ name: "ref_in_deref",
+ group: "complexity",
+ desc: "Use of reference in auto dereference expression.",
+ deprecation: None,
+ module: "reference",
+ },
+ Lint {
- module: "regex",
++ name: "repeat_once",
++ group: "complexity",
++ desc: "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` ",
+ deprecation: None,
++ module: "repeat_once",
+ },
+ Lint {
+ name: "rest_pat_in_fully_bound_structs",
+ group: "restriction",
+ desc: "a match on a struct that binds all fields but still uses the wildcard pattern",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "result_map_or_into_option",
+ group: "style",
+ desc: "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "result_map_unit_fn",
+ group: "complexity",
+ desc: "using `result.map(f)`, where `f` is a function or closure that returns `()`",
+ deprecation: None,
+ module: "map_unit_fn",
+ },
+ Lint {
+ name: "reversed_empty_ranges",
+ group: "correctness",
+ desc: "reversing the limits of range expressions, resulting in empty ranges",
+ deprecation: None,
+ module: "ranges",
+ },
+ Lint {
+ name: "same_functions_in_if_condition",
+ group: "pedantic",
+ desc: "consecutive `if`s with the same function call",
+ deprecation: None,
+ module: "copies",
+ },
+ Lint {
+ name: "search_is_some",
+ group: "complexity",
+ desc: "using an iterator search followed by `is_some()`, which is more succinctly expressed as a call to `any()`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "serde_api_misuse",
+ group: "correctness",
+ desc: "various things that will negatively affect your serde experience",
+ deprecation: None,
+ module: "serde_api",
+ },
+ Lint {
+ name: "shadow_reuse",
+ group: "restriction",
+ desc: "rebinding a name to an expression that re-uses the original value, e.g., `let x = x + 1`",
+ deprecation: None,
+ module: "shadow",
+ },
+ Lint {
+ name: "shadow_same",
+ group: "restriction",
+ desc: "rebinding a name to itself, e.g., `let mut x = &mut x`",
+ deprecation: None,
+ module: "shadow",
+ },
+ Lint {
+ name: "shadow_unrelated",
+ group: "pedantic",
+ desc: "rebinding a name without even using the original value",
+ deprecation: None,
+ module: "shadow",
+ },
+ Lint {
+ name: "short_circuit_statement",
+ group: "complexity",
+ desc: "using a short circuit boolean condition as a statement",
+ deprecation: None,
+ module: "misc",
+ },
+ Lint {
+ name: "should_implement_trait",
+ group: "style",
+ desc: "defining a method that should be implementing a std trait",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "similar_names",
+ group: "pedantic",
+ desc: "similarly named items and bindings",
+ deprecation: None,
+ module: "non_expressive_names",
+ },
+ Lint {
+ name: "single_char_pattern",
+ group: "perf",
+ desc: "using a single-character str where a char could be used, e.g., `_.split(\"x\")`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "single_component_path_imports",
+ group: "style",
+ desc: "imports with single component path are redundant",
+ deprecation: None,
+ module: "single_component_path_imports",
+ },
+ Lint {
+ name: "single_match",
+ group: "style",
+ desc: "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "single_match_else",
+ group: "pedantic",
+ desc: "a `match` statement with two arms where the second arm\'s pattern is a placeholder instead of a specific match pattern",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "skip_while_next",
+ group: "complexity",
+ desc: "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "slow_vector_initialization",
+ group: "perf",
+ desc: "slow vector initialization",
+ deprecation: None,
+ module: "slow_vector_initialization",
+ },
+ Lint {
+ name: "string_add",
+ group: "restriction",
+ desc: "using `x + ..` where x is a `String` instead of `push_str()`",
+ deprecation: None,
+ module: "strings",
+ },
+ Lint {
+ name: "string_add_assign",
+ group: "pedantic",
+ desc: "using `x = x + ..` where x is a `String` instead of `push_str()`",
+ deprecation: None,
+ module: "strings",
+ },
+ Lint {
+ name: "string_extend_chars",
+ group: "style",
+ desc: "using `x.extend(s.chars())` where s is a `&str` or `String`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "string_lit_as_bytes",
+ group: "style",
+ desc: "calling `as_bytes` on a string literal instead of using a byte string literal",
+ deprecation: None,
+ module: "strings",
+ },
+ Lint {
+ name: "struct_excessive_bools",
+ group: "pedantic",
+ desc: "using too many bools in a struct",
+ deprecation: None,
+ module: "excessive_bools",
+ },
+ Lint {
+ name: "suboptimal_flops",
+ group: "nursery",
+ desc: "usage of sub-optimal floating point operations",
+ deprecation: None,
+ module: "floating_point_arithmetic",
+ },
+ Lint {
+ name: "suspicious_arithmetic_impl",
+ group: "correctness",
+ desc: "suspicious use of operators in impl of arithmetic trait",
+ deprecation: None,
+ module: "suspicious_trait_impl",
+ },
+ Lint {
+ name: "suspicious_assignment_formatting",
+ group: "style",
+ desc: "suspicious formatting of `*=`, `-=` or `!=`",
+ deprecation: None,
+ module: "formatting",
+ },
+ Lint {
+ name: "suspicious_else_formatting",
+ group: "style",
+ desc: "suspicious formatting of `else`",
+ deprecation: None,
+ module: "formatting",
+ },
+ Lint {
+ name: "suspicious_map",
+ group: "complexity",
+ desc: "suspicious usage of map",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "suspicious_op_assign_impl",
+ group: "correctness",
+ desc: "suspicious use of operators in impl of OpAssign trait",
+ deprecation: None,
+ module: "suspicious_trait_impl",
+ },
+ Lint {
+ name: "suspicious_unary_op_formatting",
+ group: "style",
+ desc: "suspicious formatting of unary `-` or `!` on the RHS of a BinOp",
+ deprecation: None,
+ module: "formatting",
+ },
+ Lint {
+ name: "tabs_in_doc_comments",
+ group: "style",
+ desc: "using tabs in doc comments is not recommended",
+ deprecation: None,
+ module: "tabs_in_doc_comments",
+ },
+ Lint {
+ name: "temporary_assignment",
+ group: "complexity",
+ desc: "assignments to temporaries",
+ deprecation: None,
+ module: "temporary_assignment",
+ },
+ Lint {
+ name: "temporary_cstring_as_ptr",
+ group: "correctness",
+ desc: "getting the inner pointer of a temporary `CString`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "to_digit_is_some",
+ group: "style",
+ desc: "`char.is_digit()` is clearer",
+ deprecation: None,
+ module: "to_digit_is_some",
+ },
+ Lint {
+ name: "todo",
+ group: "restriction",
+ desc: "`todo!` should not be present in production code",
+ deprecation: None,
+ module: "panic_unimplemented",
+ },
+ Lint {
+ name: "too_many_arguments",
+ group: "complexity",
+ desc: "functions with too many arguments",
+ deprecation: None,
+ module: "functions",
+ },
+ Lint {
+ name: "too_many_lines",
+ group: "pedantic",
+ desc: "functions with too many lines",
+ deprecation: None,
+ module: "functions",
+ },
+ Lint {
+ name: "toplevel_ref_arg",
+ group: "style",
+ desc: "an entire binding declared as `ref`, in a function argument or a `let` statement",
+ deprecation: None,
+ module: "misc",
+ },
+ Lint {
+ name: "transmute_bytes_to_str",
+ group: "complexity",
+ desc: "transmutes from a `&[u8]` to a `&str`",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "transmute_float_to_int",
+ group: "complexity",
+ desc: "transmutes from a float to an integer",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "transmute_int_to_bool",
+ group: "complexity",
+ desc: "transmutes from an integer to a `bool`",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "transmute_int_to_char",
+ group: "complexity",
+ desc: "transmutes from an integer to a `char`",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "transmute_int_to_float",
+ group: "complexity",
+ desc: "transmutes from an integer to a float",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "transmute_ptr_to_ptr",
+ group: "complexity",
+ desc: "transmutes from a pointer to a pointer / a reference to a reference",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "transmute_ptr_to_ref",
+ group: "complexity",
+ desc: "transmutes from a pointer to a reference type",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "transmuting_null",
+ group: "correctness",
+ desc: "transmutes from a null pointer to a reference, which is undefined behavior",
+ deprecation: None,
+ module: "transmuting_null",
+ },
+ Lint {
+ name: "trivial_regex",
+ group: "style",
+ desc: "trivial regular expressions",
+ deprecation: None,
+ module: "regex",
+ },
+ Lint {
+ name: "trivially_copy_pass_by_ref",
+ group: "pedantic",
+ desc: "functions taking small copyable arguments by reference",
+ deprecation: None,
+ module: "trivially_copy_pass_by_ref",
+ },
+ Lint {
+ name: "try_err",
+ group: "style",
+ desc: "return errors explicitly rather than hiding them behind a `?`",
+ deprecation: None,
+ module: "try_err",
+ },
+ Lint {
+ name: "type_complexity",
+ group: "complexity",
+ desc: "usage of very complex types that might be better factored into `type` definitions",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "type_repetition_in_bounds",
+ group: "pedantic",
+ desc: "Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`",
+ deprecation: None,
+ module: "trait_bounds",
+ },
+ Lint {
+ name: "unicode_not_nfc",
+ group: "pedantic",
+ desc: "using a Unicode literal not in NFC normal form (see [Unicode tr15](http://www.unicode.org/reports/tr15/) for further information)",
+ deprecation: None,
+ module: "unicode",
+ },
+ Lint {
+ name: "unimplemented",
+ group: "restriction",
+ desc: "`unimplemented!` should not be present in production code",
+ deprecation: None,
+ module: "panic_unimplemented",
+ },
+ Lint {
+ name: "uninit_assumed_init",
+ group: "correctness",
+ desc: "`MaybeUninit::uninit().assume_init()`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "unit_arg",
+ group: "complexity",
+ desc: "passing unit to a function",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "unit_cmp",
+ group: "correctness",
+ desc: "comparing unit values",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "unknown_clippy_lints",
+ group: "style",
+ desc: "unknown_lints for scoped Clippy lints",
+ deprecation: None,
+ module: "attrs",
+ },
+ Lint {
+ name: "unnecessary_cast",
+ group: "complexity",
+ desc: "cast to the same type, e.g., `x as i32` where `x: i32`",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "unnecessary_filter_map",
+ group: "complexity",
+ desc: "using `filter_map` when a more succinct alternative exists",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "unnecessary_fold",
+ group: "style",
+ desc: "using `fold` when a more succinct alternative exists",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "unnecessary_mut_passed",
+ group: "style",
+ desc: "an argument passed as a mutable reference although the callee only demands an immutable reference",
+ deprecation: None,
+ module: "mut_reference",
+ },
+ Lint {
+ name: "unnecessary_operation",
+ group: "complexity",
+ desc: "outer expressions with no effect",
+ deprecation: None,
+ module: "no_effect",
+ },
+ Lint {
+ name: "unnecessary_sort_by",
+ group: "complexity",
+ desc: "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer",
+ deprecation: None,
+ module: "unnecessary_sort_by",
+ },
+ Lint {
+ name: "unnecessary_unwrap",
+ group: "complexity",
+ desc: "checks for calls of `unwrap[_err]()` that cannot fail",
+ deprecation: None,
+ module: "unwrap",
+ },
+ Lint {
+ name: "unneeded_field_pattern",
+ group: "restriction",
+ desc: "struct fields bound to a wildcard instead of using `..`",
+ deprecation: None,
+ module: "misc_early",
+ },
+ Lint {
+ name: "unneeded_wildcard_pattern",
+ group: "complexity",
+ desc: "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)",
+ deprecation: None,
+ module: "misc_early",
+ },
+ Lint {
+ name: "unnested_or_patterns",
+ group: "pedantic",
+ desc: "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`",
+ deprecation: None,
+ module: "unnested_or_patterns",
+ },
+ Lint {
+ name: "unreachable",
+ group: "restriction",
+ desc: "`unreachable!` should not be present in production code",
+ deprecation: None,
+ module: "panic_unimplemented",
+ },
+ Lint {
+ name: "unreadable_literal",
+ group: "pedantic",
+ desc: "long integer literal without underscores",
+ deprecation: None,
+ module: "literal_representation",
+ },
+ Lint {
+ name: "unsafe_derive_deserialize",
+ group: "pedantic",
+ desc: "deriving `serde::Deserialize` on a type that has methods using `unsafe`",
+ deprecation: None,
+ module: "derive",
+ },
+ Lint {
+ name: "unsafe_removed_from_name",
+ group: "style",
+ desc: "`unsafe` removed from API names on import",
+ deprecation: None,
+ module: "unsafe_removed_from_name",
+ },
+ Lint {
+ name: "unseparated_literal_suffix",
+ group: "pedantic",
+ desc: "literals whose suffix is not separated by an underscore",
+ deprecation: None,
+ module: "misc_early",
+ },
+ Lint {
+ name: "unsound_collection_transmute",
+ group: "correctness",
+ desc: "transmute between collections of layout-incompatible types",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "unused_io_amount",
+ group: "correctness",
+ desc: "unused written/read amount",
+ deprecation: None,
+ module: "unused_io_amount",
+ },
+ Lint {
+ name: "unused_self",
+ group: "pedantic",
+ desc: "methods that contain a `self` argument but don\'t use it",
+ deprecation: None,
+ module: "unused_self",
+ },
+ Lint {
+ name: "unused_unit",
+ group: "style",
+ desc: "needless unit expression",
+ deprecation: None,
+ module: "returns",
+ },
+ Lint {
+ name: "unwrap_used",
+ group: "restriction",
+ desc: "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "use_debug",
+ group: "restriction",
+ desc: "use of `Debug`-based formatting",
+ deprecation: None,
+ module: "write",
+ },
+ Lint {
+ name: "use_self",
+ group: "nursery",
+ desc: "Unnecessary structure name repetition whereas `Self` is applicable",
+ deprecation: None,
+ module: "use_self",
+ },
+ Lint {
+ name: "used_underscore_binding",
+ group: "pedantic",
+ desc: "using a binding which is prefixed with an underscore",
+ deprecation: None,
+ module: "misc",
+ },
+ Lint {
+ name: "useless_asref",
+ group: "complexity",
+ desc: "using `as_ref` where the types before and after the call are the same",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "useless_attribute",
+ group: "correctness",
+ desc: "use of lint attributes on `extern crate` items",
+ deprecation: None,
+ module: "attrs",
+ },
+ Lint {
+ name: "useless_conversion",
+ group: "complexity",
+ desc: "calls to `Into`, `TryInto`, `From`, `TryFrom`, `IntoIter` that performs useless conversions to the same type",
+ deprecation: None,
+ module: "useless_conversion",
+ },
+ Lint {
+ name: "useless_format",
+ group: "complexity",
+ desc: "useless use of `format!`",
+ deprecation: None,
+ module: "format",
+ },
+ Lint {
+ name: "useless_let_if_seq",
+ group: "nursery",
+ desc: "unidiomatic `let mut` declaration followed by initialization in `if`",
+ deprecation: None,
+ module: "let_if_seq",
+ },
+ Lint {
+ name: "useless_transmute",
+ group: "nursery",
+ desc: "transmutes that have the same to and from types or could be a cast/coercion",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "useless_vec",
+ group: "perf",
+ desc: "useless `vec!`",
+ deprecation: None,
+ module: "vec",
+ },
+ Lint {
+ name: "vec_box",
+ group: "complexity",
+ desc: "usage of `Vec<Box<T>>` where T: Sized, vector elements are already on the heap",
+ deprecation: None,
+ module: "types",
+ },
+ Lint {
+ name: "vec_resize_to_zero",
+ group: "correctness",
+ desc: "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake",
+ deprecation: None,
+ module: "vec_resize_to_zero",
+ },
+ Lint {
+ name: "verbose_bit_mask",
+ group: "style",
+ desc: "expressions where a bit mask is less readable than the corresponding method call",
+ deprecation: None,
+ module: "bit_mask",
+ },
+ Lint {
+ name: "verbose_file_reads",
+ group: "restriction",
+ desc: "use of `File::read_to_end` or `File::read_to_string`",
+ deprecation: None,
+ module: "verbose_file_reads",
+ },
+ Lint {
+ name: "vtable_address_comparisons",
+ group: "correctness",
+ desc: "comparison with an address of a trait vtable",
+ deprecation: None,
+ module: "unnamed_address",
+ },
+ Lint {
+ name: "while_immutable_condition",
+ group: "correctness",
+ desc: "variables used within while expression are not mutated in the body",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "while_let_loop",
+ group: "complexity",
+ desc: "`loop { if let { ... } else break }`, which can be written as a `while let` loop",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "while_let_on_iterator",
+ group: "style",
+ desc: "using a while-let loop instead of a for loop on an iterator",
+ deprecation: None,
+ module: "loops",
+ },
+ Lint {
+ name: "wildcard_dependencies",
+ group: "cargo",
+ desc: "wildcard dependencies being used",
+ deprecation: None,
+ module: "wildcard_dependencies",
+ },
+ Lint {
+ name: "wildcard_enum_match_arm",
+ group: "restriction",
+ desc: "a wildcard enum match arm using `_`",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "wildcard_imports",
+ group: "pedantic",
+ desc: "lint `use _::*` statements",
+ deprecation: None,
+ module: "wildcard_imports",
+ },
+ Lint {
+ name: "wildcard_in_or_patterns",
+ group: "complexity",
+ desc: "a wildcard pattern used with others patterns in same match arm",
+ deprecation: None,
+ module: "matches",
+ },
+ Lint {
+ name: "write_literal",
+ group: "style",
+ desc: "writing a literal with a format string",
+ deprecation: None,
+ module: "write",
+ },
+ Lint {
+ name: "write_with_newline",
+ group: "style",
+ desc: "using `write!()` with a format string that ends in a single newline",
+ deprecation: None,
+ module: "write",
+ },
+ Lint {
+ name: "writeln_empty_string",
+ group: "style",
+ desc: "using `writeln!(buf, \"\")` with an empty string",
+ deprecation: None,
+ module: "write",
+ },
+ Lint {
+ name: "wrong_pub_self_convention",
+ group: "restriction",
+ desc: "defining a public method named with an established prefix (like \"into_\") that takes `self` with the wrong convention",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "wrong_self_convention",
+ group: "style",
+ desc: "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention",
+ deprecation: None,
+ module: "methods",
+ },
+ Lint {
+ name: "wrong_transmute",
+ group: "correctness",
+ desc: "transmutes that are confusing at best, undefined behaviour at worst and always useless",
+ deprecation: None,
+ module: "transmute",
+ },
+ Lint {
+ name: "zero_divided_by_zero",
+ group: "complexity",
+ desc: "usage of `0.0 / 0.0` to obtain NaN instead of `f32::NAN` or `f64::NAN`",
+ deprecation: None,
+ module: "zero_div_zero",
+ },
+ Lint {
+ name: "zero_prefixed_literal",
+ group: "complexity",
+ desc: "integer literals starting with `0`",
+ deprecation: None,
+ module: "misc_early",
+ },
+ Lint {
+ name: "zero_ptr",
+ group: "style",
+ desc: "using `0 as *{const, mut} T`",
+ deprecation: None,
+ module: "misc",
+ },
+ Lint {
+ name: "zero_width_space",
+ group: "correctness",
+ desc: "using a zero-width space in a string literal, which is confusing",
+ deprecation: None,
+ module: "unicode",
+ },
+ Lint {
+ name: "zst_offset",
+ group: "correctness",
+ desc: "Check for offset calculations on raw pointers to zero-sized types",
+ deprecation: None,
+ module: "methods",
+ },
+];
+// end lint list, do not remove this comment, it’s used in `update_lints`
+}
--- /dev/null
- if let Some(path) = option_env!("HOST_LIBS") {
- PathBuf::from(path)
- } else {
- cargo::CARGO_TARGET_DIR.join(env!("PROFILE"))
- }
+#![feature(test)] // compiletest_rs requires this attribute
+
+use compiletest_rs as compiletest;
+use compiletest_rs::common::Mode as TestMode;
+
+use std::env::{self, set_var};
+use std::ffi::OsStr;
+use std::fs;
+use std::io;
+use std::path::{Path, PathBuf};
+
+mod cargo;
+
+fn host_lib() -> PathBuf {
- if let Some(path) = option_env!("CLIPPY_DRIVER_PATH") {
- PathBuf::from(path)
- } else {
- cargo::TARGET_LIB.join("clippy-driver")
- }
++ 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.ends_with(".rlib") {
+ 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(name) = env::var("TESTNAME") {
+ config.filter = Some(name);
+ }
+
+ if let Some(path) = option_env!("RUSTC_LIB_PATH") {
+ let path = PathBuf::from(path);
+ config.run_lib_path = path.clone();
+ config.compile_lib_path = path;
+ }
+
+ config.target_rustcflags = Some(format!(
+ "-L {0} -L {1} -Dwarnings -Zui-testing {2}",
+ host_lib().join("deps").display(),
+ cargo::TARGET_LIB.join("deps").display(),
+ third_party_crates(),
+ ));
+
+ config.build_base = if cargo::is_rustc_test_suite() {
+ // This make the stderr files go to clippy OUT_DIR on rustc repo build dir
+ let mut path = PathBuf::from(env!("OUT_DIR"));
+ path.push("test_build_base");
+ path
+ } else {
+ host_lib().join("test_build_base")
+ };
+ config.rustc_path = clippy_driver_path();
+ config
+}
+
+fn run_mode(cfg: &mut compiletest::Config) {
+ cfg.mode = TestMode::Ui;
+ cfg.src_base = Path::new("tests").join("ui");
+ compiletest::run_tests(&cfg);
+}
+
+fn run_ui_toml(config: &mut compiletest::Config) {
+ fn run_tests(config: &compiletest::Config, mut tests: Vec<tester::TestDescAndFn>) -> Result<bool, io::Error> {
+ 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 res = run_tests(&config, tests);
+ 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) {
+ if cargo::is_rustc_test_suite() {
+ return;
+ }
+ fn run_tests(
+ config: &compiletest::Config,
+ filter: &Option<String>,
+ mut tests: Vec<tester::TestDescAndFn>,
+ ) -> Result<bool, io::Error> {
+ 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();
+ match &filter {
+ Some(name) if !dir_path.ends_with(name) => 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,
+ }
+
+ 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)
+ }
+
+ 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 filter = env::var("TESTNAME").ok();
+ let res = run_tests(&config, &filter, tests);
+ env::set_current_dir(current_dir).unwrap();
+
+ 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_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);
+}
--- /dev/null
--- /dev/null
++# 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"
--- /dev/null
- error: multiple versions for dependency `winapi`: 0.2.8, 0.3.8
++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
+
--- /dev/null
- error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `third-party` at line 5 column 1
++error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `third-party` at line 5 column 1
+
+error: aborting due to previous error
+
--- /dev/null
+#![warn(clippy::inline_always, clippy::deprecated_semver)]
+#![allow(clippy::assertions_on_constants)]
++// Test that the whole restriction group is not enabled
++#![warn(clippy::restriction)]
++#![deny(clippy::restriction)]
++#![forbid(clippy::restriction)]
++#![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()
+ }
+}
--- /dev/null
- --> $DIR/attrs.rs:3:1
+error: you have declared `#[inline(always)]` on `test_attr_lint`. This is usually a bad idea
- --> $DIR/attrs.rs:23:14
++ --> $DIR/attrs.rs:9: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:26:14
++ --> $DIR/attrs.rs:29:14
+ |
+LL | #[deprecated(since = "forever")]
+ | ^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::deprecated-semver` implied by `-D warnings`
+
+error: the since field must contain a semver-compliant version
- error: aborting due to 3 previous errors
++ --> $DIR/attrs.rs:32:14
+ |
+LL | #[deprecated(since = "1")]
+ | ^^^^^^^^^^^
+
++error: restriction lints are not meant to be all enabled
++ --> $DIR/attrs.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/attrs.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/attrs.rs:6:11
++ |
++LL | #![forbid(clippy::restriction)]
++ | ^^^^^^^^^^^^^^^^^^^
++ |
++ = help: try enabling only the lints you really need
++
++error: aborting due to 6 previous errors
+
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![allow(
++ unused,
++ clippy::redundant_clone,
++ clippy::deref_addrof,
++ clippy::no_effect,
++ clippy::unnecessary_operation
++)]
++
++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);
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![allow(
++ unused,
++ clippy::redundant_clone,
++ clippy::deref_addrof,
++ clippy::no_effect,
++ clippy::unnecessary_operation
++)]
++
++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());
++}
--- /dev/null
--- /dev/null
++error: using `clone` on a `Copy` type
++ --> $DIR/clone_on_copy.rs:22:5
++ |
++LL | 42.clone();
++ | ^^^^^^^^^^ help: try removing the `clone` call: `42`
++ |
++ = note: `-D clippy::clone-on-copy` implied by `-D warnings`
++
++error: using `clone` on a `Copy` type
++ --> $DIR/clone_on_copy.rs:26:5
++ |
++LL | (&42).clone();
++ | ^^^^^^^^^^^^^ help: try dereferencing it: `*(&42)`
++
++error: using `clone` on a `Copy` type
++ --> $DIR/clone_on_copy.rs:29:5
++ |
++LL | rc.borrow().clone();
++ | ^^^^^^^^^^^^^^^^^^^ help: try dereferencing it: `*rc.borrow()`
++
++error: using `clone` on a `Copy` type
++ --> $DIR/clone_on_copy.rs:35:14
++ |
++LL | is_ascii('z'.clone());
++ | ^^^^^^^^^^^ help: try removing the `clone` call: `'z'`
++
++error: using `clone` on a `Copy` type
++ --> $DIR/clone_on_copy.rs:39:14
++ |
++LL | vec.push(42.clone());
++ | ^^^^^^^^^^ help: try removing the `clone` call: `42`
++
++error: aborting due to 5 previous errors
++
--- /dev/null
--- /dev/null
++// 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<Borrowed> for Owned {
++ fn borrow(&self) -> &Borrowed {
++ static VALUE: Borrowed = Borrowed {};
++ &VALUE
++ }
++ }
++ };
++}
++
++// Only Borrowed == Owned is implemented
++mod borrowed_eq_owned {
++ impl_types!();
++
++ impl PartialEq<Owned> 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<Borrowed> 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<Borrowed> for T can't be implemented due to the orphan rules
++ impl<T> PartialEq<T> for Borrowed
++ where
++ T: AsRef<str> + ?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() {}
--- /dev/null
--- /dev/null
++// 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<Borrowed> for Owned {
++ fn borrow(&self) -> &Borrowed {
++ static VALUE: Borrowed = Borrowed {};
++ &VALUE
++ }
++ }
++ };
++}
++
++// Only Borrowed == Owned is implemented
++mod borrowed_eq_owned {
++ impl_types!();
++
++ impl PartialEq<Owned> 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<Borrowed> 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<Borrowed> for T can't be implemented due to the orphan rules
++ impl<T> PartialEq<T> for Borrowed
++ where
++ T: AsRef<str> + ?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() {}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
- help: try
+error: this `else { if .. }` block can be collapsed
+ --> $DIR/collapsible_else_if.rs:12:12
+ |
+LL | } else {
+ | ____________^
+LL | | if y == "world" {
+LL | | println!("world!")
+LL | | }
+LL | | }
+ | |_____^
+ |
+ = note: `-D clippy::collapsible-if` implied by `-D warnings`
- help: try
++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:20:12
+ |
+LL | } else {
+ | ____________^
+LL | | if let Some(42) = Some(42) {
+LL | | println!("world!")
+LL | | }
+LL | | }
+ | |_____^
+ |
- help: try
++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:28:12
+ |
+LL | } else {
+ | ____________^
+LL | | if y == "world" {
+LL | | println!("world")
+LL | | }
+... |
+LL | | }
+LL | | }
+ | |_____^
+ |
- help: try
++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:39:12
+ |
+LL | } else {
+ | ____________^
+LL | | if let Some(42) = Some(42) {
+LL | | println!("world")
+LL | | }
+... |
+LL | | }
+LL | | }
+ | |_____^
+ |
- help: try
++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:50:12
+ |
+LL | } else {
+ | ____________^
+LL | | if let Some(42) = Some(42) {
+LL | | println!("world")
+LL | | }
+... |
+LL | | }
+LL | | }
+ | |_____^
+ |
- help: try
++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:61:12
+ |
+LL | } else {
+ | ____________^
+LL | | if x == "hello" {
+LL | | println!("world")
+LL | | }
+... |
+LL | | }
+LL | | }
+ | |_____^
+ |
- help: try
++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:72: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
+
--- /dev/null
- help: try
+error: this `if` statement can be collapsed
+ --> $DIR/collapsible_if.rs:9:5
+ |
+LL | / if x == "hello" {
+LL | | if y == "world" {
+LL | | println!("Hello world!");
+LL | | }
+LL | | }
+ | |_____^
+ |
+ = note: `-D clippy::collapsible-if` implied by `-D warnings`
- help: try
++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: try
++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: try
++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: try
++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: try
++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: try
++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: aborting due to 7 previous errors
+
--- /dev/null
+#[warn(clippy::str_to_string)]
+#[warn(clippy::string_to_string)]
+#[warn(clippy::unstable_as_slice)]
+#[warn(clippy::unstable_as_mut_slice)]
+#[warn(clippy::misaligned_transmute)]
+#[warn(clippy::unused_collect)]
+#[warn(clippy::invalid_ref)]
+#[warn(clippy::into_iter_on_array)]
+#[warn(clippy::unused_label)]
++#[warn(clippy::regex_macro)]
+
+fn main() {}
--- /dev/null
- error: aborting due to 10 previous errors
+error: lint `clippy::str_to_string` has been removed: `using `str::to_string` is common even today and specialization will likely happen soon`
+ --> $DIR/deprecated.rs:1:8
+ |
+LL | #[warn(clippy::str_to_string)]
+ | ^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D renamed-and-removed-lints` implied by `-D warnings`
+
+error: lint `clippy::string_to_string` has been removed: `using `string::to_string` is common even today and specialization will likely happen soon`
+ --> $DIR/deprecated.rs:2:8
+ |
+LL | #[warn(clippy::string_to_string)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: lint `clippy::unstable_as_slice` has been removed: ``Vec::as_slice` has been stabilized in 1.7`
+ --> $DIR/deprecated.rs:3:8
+ |
+LL | #[warn(clippy::unstable_as_slice)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: lint `clippy::unstable_as_mut_slice` has been removed: ``Vec::as_mut_slice` has been stabilized in 1.7`
+ --> $DIR/deprecated.rs:4:8
+ |
+LL | #[warn(clippy::unstable_as_mut_slice)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: lint `clippy::misaligned_transmute` has been removed: `this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr`
+ --> $DIR/deprecated.rs:5:8
+ |
+LL | #[warn(clippy::misaligned_transmute)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: lint `clippy::unused_collect` has been removed: ``collect` has been marked as #[must_use] in rustc and that covers all cases of this lint`
+ --> $DIR/deprecated.rs:6:8
+ |
+LL | #[warn(clippy::unused_collect)]
+ | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: lint `clippy::invalid_ref` has been removed: `superseded by rustc lint `invalid_value``
+ --> $DIR/deprecated.rs:7:8
+ |
+LL | #[warn(clippy::invalid_ref)]
+ | ^^^^^^^^^^^^^^^^^^^
+
+error: lint `clippy::into_iter_on_array` has been removed: `this lint has been uplifted to rustc and is now called `array_into_iter``
+ --> $DIR/deprecated.rs:8:8
+ |
+LL | #[warn(clippy::into_iter_on_array)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: lint `clippy::unused_label` has been removed: `this lint has been uplifted to rustc and is now called `unused_labels``
+ --> $DIR/deprecated.rs:9:8
+ |
+LL | #[warn(clippy::unused_label)]
+ | ^^^^^^^^^^^^^^^^^^^^
+
++error: lint `clippy::regex_macro` has been removed: `the regex! macro has been removed from the regex crate in 2018`
++ --> $DIR/deprecated.rs:10:8
++ |
++LL | #[warn(clippy::regex_macro)]
++ | ^^^^^^^^^^^^^^^^^^^
++
+error: lint `clippy::str_to_string` has been removed: `using `str::to_string` is common even today and specialization will likely happen soon`
+ --> $DIR/deprecated.rs:1:8
+ |
+LL | #[warn(clippy::str_to_string)]
+ | ^^^^^^^^^^^^^^^^^^^^^
+
++error: aborting due to 11 previous errors
+
--- /dev/null
+#![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<i32> = a.iter().find(|s| s.parse::<i32>().is_ok()).map(|s| s.parse().unwrap());
+
++ #[allow(clippy::match_like_matches_macro)]
+ let _: Option<Flavor> = desserts_of_the_week
+ .iter()
+ .find(|dessert| match *dessert {
+ Dessert::Cake(_) => true,
+ _ => false,
+ })
+ .map(|dessert| match *dessert {
+ Dessert::Cake(ref flavor) => *flavor,
+ _ => unreachable!(),
+ });
+}
--- /dev/null
- --> $DIR/find_map.rs:22:29
+error: called `find(p).map(q)` on an `Iterator`
+ --> $DIR/find_map.rs:20:26
+ |
+LL | let _: Option<i32> = a.iter().find(|s| s.parse::<i32>().is_ok()).map(|s| s.parse().unwrap());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::find-map` implied by `-D warnings`
+ = help: this is more succinctly expressed by calling `.find_map(..)` instead
+
+error: called `find(p).map(q)` on an `Iterator`
++ --> $DIR/find_map.rs:23:29
+ |
+LL | let _: Option<Flavor> = desserts_of_the_week
+ | _____________________________^
+LL | | .iter()
+LL | | .find(|dessert| match *dessert {
+LL | | Dessert::Cake(_) => true,
+... |
+LL | | _ => unreachable!(),
+LL | | });
+ | |__________^
+ |
+ = help: this is more succinctly expressed by calling `.find_map(..)` instead
+
+error: aborting due to 2 previous errors
+
--- /dev/null
--- /dev/null
++// 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();
++}
--- /dev/null
--- /dev/null
++// 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();
++}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
- let _ = x.powi(2).ln_1p();
- let _ = (x.powi(2) / 2.0).ln_1p();
+// run-rustfix
+#![allow(dead_code, clippy::double_parens)]
+#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)]
+
+const TWO: f32 = 2.0;
+const E: f32 = std::f32::consts::E;
+
+fn check_log_base() {
+ let x = 1f32;
+ let _ = x.log2();
+ let _ = x.log10();
+ let _ = x.ln();
+ let _ = x.log2();
+ let _ = x.ln();
+
+ let x = 1f64;
+ let _ = x.log2();
+ let _ = x.log10();
+ let _ = x.ln();
+}
+
+fn check_ln1p() {
+ let x = 1f32;
+ let _ = 2.0f32.ln_1p();
+ let _ = 2.0f32.ln_1p();
+ let _ = x.ln_1p();
+ let _ = (x / 2.0).ln_1p();
- let _ = x.powi(2).ln_1p();
++ let _ = x.powi(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(2).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(2).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() {}
--- /dev/null
- let _ = (1.0 + x.powi(2)).ln();
- let _ = (1.0 + x.powi(2) / 2.0).ln();
+// 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 _ = (x.powi(2) + 1.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 _ = (1.0 + x.powi(2)).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 _ = (x.powi(2) + 1.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() {}
--- /dev/null
- LL | let _ = (1.0 + x.powi(2)).ln();
- | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2).ln_1p()`
+error: logarithm for bases 2, 10 and e can be computed more accurately
+ --> $DIR/floating_point_log.rs:10:13
+ |
+LL | let _ = x.log(2f32);
+ | ^^^^^^^^^^^ help: consider using: `x.log2()`
+ |
+ = note: `-D clippy::suboptimal-flops` implied by `-D warnings`
+
+error: logarithm for bases 2, 10 and e can be computed more accurately
+ --> $DIR/floating_point_log.rs:11:13
+ |
+LL | let _ = x.log(10f32);
+ | ^^^^^^^^^^^^ help: consider using: `x.log10()`
+
+error: logarithm for bases 2, 10 and e can be computed more accurately
+ --> $DIR/floating_point_log.rs:12:13
+ |
+LL | let _ = x.log(std::f32::consts::E);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()`
+
+error: logarithm for bases 2, 10 and e can be computed more accurately
+ --> $DIR/floating_point_log.rs:13:13
+ |
+LL | let _ = x.log(TWO);
+ | ^^^^^^^^^^ help: consider using: `x.log2()`
+
+error: logarithm for bases 2, 10 and e can be computed more accurately
+ --> $DIR/floating_point_log.rs:14:13
+ |
+LL | let _ = x.log(E);
+ | ^^^^^^^^ help: consider using: `x.ln()`
+
+error: logarithm for bases 2, 10 and e can be computed more accurately
+ --> $DIR/floating_point_log.rs:17:13
+ |
+LL | let _ = x.log(2f64);
+ | ^^^^^^^^^^^ help: consider using: `x.log2()`
+
+error: logarithm for bases 2, 10 and e can be computed more accurately
+ --> $DIR/floating_point_log.rs:18:13
+ |
+LL | let _ = x.log(10f64);
+ | ^^^^^^^^^^^^ help: consider using: `x.log10()`
+
+error: logarithm for bases 2, 10 and e can be computed more accurately
+ --> $DIR/floating_point_log.rs:19:13
+ |
+LL | let _ = x.log(std::f64::consts::E);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()`
+
+error: ln(1 + x) can be computed more accurately
+ --> $DIR/floating_point_log.rs:24:13
+ |
+LL | let _ = (1f32 + 2.).ln();
+ | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()`
+ |
+ = note: `-D clippy::imprecise-flops` implied by `-D warnings`
+
+error: ln(1 + x) can be computed more accurately
+ --> $DIR/floating_point_log.rs:25:13
+ |
+LL | let _ = (1f32 + 2.0).ln();
+ | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()`
+
+error: ln(1 + x) can be computed more accurately
+ --> $DIR/floating_point_log.rs:26:13
+ |
+LL | let _ = (1.0 + x).ln();
+ | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()`
+
+error: ln(1 + x) can be computed more accurately
+ --> $DIR/floating_point_log.rs:27:13
+ |
+LL | let _ = (1.0 + x / 2.0).ln();
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()`
+
+error: ln(1 + x) can be computed more accurately
+ --> $DIR/floating_point_log.rs:28:13
+ |
- LL | let _ = (1.0 + x.powi(2) / 2.0).ln();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x.powi(2) / 2.0).ln_1p()`
++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 _ = (x.powi(2) + 1.0).ln();
- | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2).ln_1p()`
++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 _ = (1.0 + x.powi(2)).ln();
- | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2).ln_1p()`
++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 _ = (x.powi(2) + 1.0).ln();
- | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2).ln_1p()`
++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
+
--- /dev/null
--- /dev/null
++// 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);
++}
--- /dev/null
--- /dev/null
++// 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);
++}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
+// 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();
+}
--- /dev/null
+// 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();
+}
--- /dev/null
- error: aborting due to 9 previous errors
+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
+
--- /dev/null
- let _ = x.powi(2);
+// run-rustfix
+#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)]
+
+fn main() {
+ let x = 3f32;
+ let _ = x.exp2();
+ let _ = 3.1f32.exp2();
+ let _ = (-3.1f32).exp2();
+ let _ = x.exp();
+ let _ = 3.1f32.exp();
+ let _ = (-3.1f32).exp();
+ let _ = x.sqrt();
+ let _ = x.cbrt();
- let _ = x.powi(2);
++ let _ = x.powi(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);
+}
--- /dev/null
- let _ = x.powf(2.0);
+// run-rustfix
+#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)]
+
+fn main() {
+ let x = 3f32;
+ let _ = 2f32.powf(x);
+ let _ = 2f32.powf(3.1);
+ let _ = 2f32.powf(-3.1);
+ let _ = std::f32::consts::E.powf(x);
+ let _ = std::f32::consts::E.powf(3.1);
+ let _ = std::f32::consts::E.powf(-3.1);
+ let _ = x.powf(1.0 / 2.0);
+ let _ = x.powf(1.0 / 3.0);
- let _ = x.powf(2.0);
++ let _ = x.powf(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);
+}
--- /dev/null
- LL | let _ = x.powf(2.0);
- | ^^^^^^^^^^^ help: consider using: `x.powi(2)`
+error: exponent for bases 2 and e can be computed more accurately
+ --> $DIR/floating_point_powf.rs:6:13
+ |
+LL | let _ = 2f32.powf(x);
+ | ^^^^^^^^^^^^ help: consider using: `x.exp2()`
+ |
+ = note: `-D clippy::suboptimal-flops` implied by `-D warnings`
+
+error: exponent for bases 2 and e can be computed more accurately
+ --> $DIR/floating_point_powf.rs:7:13
+ |
+LL | let _ = 2f32.powf(3.1);
+ | ^^^^^^^^^^^^^^ help: consider using: `3.1f32.exp2()`
+
+error: exponent for bases 2 and e can be computed more accurately
+ --> $DIR/floating_point_powf.rs:8:13
+ |
+LL | let _ = 2f32.powf(-3.1);
+ | ^^^^^^^^^^^^^^^ help: consider using: `(-3.1f32).exp2()`
+
+error: exponent for bases 2 and e can be computed more accurately
+ --> $DIR/floating_point_powf.rs:9:13
+ |
+LL | let _ = std::f32::consts::E.powf(x);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.exp()`
+
+error: exponent for bases 2 and e can be computed more accurately
+ --> $DIR/floating_point_powf.rs:10:13
+ |
+LL | let _ = std::f32::consts::E.powf(3.1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `3.1f32.exp()`
+
+error: exponent for bases 2 and e can be computed more accurately
+ --> $DIR/floating_point_powf.rs:11:13
+ |
+LL | let _ = std::f32::consts::E.powf(-3.1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-3.1f32).exp()`
+
+error: square-root of a number can be computed more efficiently and accurately
+ --> $DIR/floating_point_powf.rs:12:13
+ |
+LL | let _ = x.powf(1.0 / 2.0);
+ | ^^^^^^^^^^^^^^^^^ help: consider using: `x.sqrt()`
+
+error: cube-root of a number can be computed more accurately
+ --> $DIR/floating_point_powf.rs:13:13
+ |
+LL | let _ = x.powf(1.0 / 3.0);
+ | ^^^^^^^^^^^^^^^^^ help: consider using: `x.cbrt()`
+ |
+ = note: `-D clippy::imprecise-flops` implied by `-D warnings`
+
+error: exponentiation with integer powers can be computed more efficiently
+ --> $DIR/floating_point_powf.rs:14:13
+ |
- LL | let _ = x.powf(2.0);
- | ^^^^^^^^^^^ help: consider using: `x.powi(2)`
++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
+
--- /dev/null
--- /dev/null
++// 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();
++}
--- /dev/null
--- /dev/null
++// 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();
++}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
--- /dev/null
++// 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;
++}
--- /dev/null
--- /dev/null
++// 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;
++}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(clippy::missing_docs_in_private_items)]
++#![allow(clippy::map_identity)]
+
+fn main() {
+ let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect();
+ let _: Option<_> = (Some(Some(1))).and_then(|x| x);
+}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(clippy::missing_docs_in_private_items)]
++#![allow(clippy::map_identity)]
+
+fn main() {
+ let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
+ let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
+}
--- /dev/null
- --> $DIR/map_flatten.rs:7:21
+error: called `map(..).flatten()` on an `Iterator`. This is more succinctly expressed by calling `.flat_map(..)`
- --> $DIR/map_flatten.rs:8:24
++ --> $DIR/map_flatten.rs:8:21
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `vec![5_i8; 6].into_iter().flat_map(|x| 0..x)`
+ |
+ = note: `-D clippy::map-flatten` implied by `-D warnings`
+
+error: called `map(..).flatten()` on an `Option`. This is more succinctly expressed by calling `.and_then(..)`
++ --> $DIR/map_flatten.rs:9:24
+ |
+LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `(Some(Some(1))).and_then(|x| x)`
+
+error: aborting due to 2 previous errors
+
--- /dev/null
--- /dev/null
++// 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<u8> = Some(3);
++ let _: Result<i8, f32> = 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<u8> = None.map(|x: u8| x - 1);
++ let _: Result<i8, f32> = Err(2.3).map(|x: i8| {
++ return x + 3;
++ });
++}
++
++fn not_identity(x: &u16) -> u16 {
++ *x
++}
--- /dev/null
--- /dev/null
++// 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<u8> = Some(3).map(|x| x);
++ let _: Result<i8, f32> = 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<u8> = None.map(|x: u8| x - 1);
++ let _: Result<i8, f32> = Err(2.3).map(|x: i8| {
++ return x + 3;
++ });
++}
++
++fn not_identity(x: &u16) -> u16 {
++ *x
++}
--- /dev/null
--- /dev/null
++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<u8> = 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<i8, f32> = Ok(-3).map(|x| {
++ | ____________________________________^
++LL | | return x;
++LL | | });
++ | |______^ help: remove the call to `map`
++
++error: aborting due to 5 previous errors
++
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![warn(clippy::match_like_matches_macro)]
++#![allow(unreachable_patterns)]
++
++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,
++ };
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![warn(clippy::match_like_matches_macro)]
++#![allow(unreachable_patterns)]
++
++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,
++ };
++}
--- /dev/null
--- /dev/null
++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: aborting due to 5 previous errors
++
--- /dev/null
- #[allow(clippy::unnested_or_patterns)]
+//! 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);
+}
--- /dev/null
--- /dev/null
++// run-rustfix
++#![warn(clippy::option_if_let_else)]
++
++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<i32>) {
++ 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>) -> u32 {
++ arg.map_or(13, |x| {
++ let y = x * x;
++ y * y
++ })
++}
++
++fn test_map_or_else(arg: Option<u32>) {
++ 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>) -> 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);
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++#![warn(clippy::option_if_let_else)]
++
++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<i32>) {
++ 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>) -> u32 {
++ if let Some(x) = arg {
++ let y = x * x;
++ y * y
++ } else {
++ 13
++ }
++}
++
++fn test_map_or_else(arg: Option<u32>) {
++ 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>) -> 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);
++}
--- /dev/null
--- /dev/null
++error: use Option::map_or instead of an if let/else
++ --> $DIR/option_if_let_else.rs:5: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:15: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:23: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:24: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:25: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:31: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:32: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:38: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:47: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:56: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:85: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 11 previous errors
++
--- /dev/null
--- /dev/null
++#![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(_) => (),
++ _ => (),
++ }
++}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
--- /dev/null
++#![allow(clippy::all)]
++#![warn(clippy::pattern_type_mismatch)]
++
++fn main() {}
++
++fn alternatives() {
++ enum Value<'a> {
++ Unused,
++ A(&'a Option<i32>),
++ 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 {}
++}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
--- /dev/null
++#![allow(clippy::all)]
++#![warn(clippy::pattern_type_mismatch)]
++
++fn main() {}
++
++fn struct_types() {
++ struct Struct<'a> {
++ ref_inner: &'a Option<i32>,
++ }
++ 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<i32> },
++ }
++ 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 {}
++}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
--- /dev/null
++#![allow(clippy::all)]
++#![warn(clippy::pattern_type_mismatch)]
++
++fn main() {}
++
++fn tuple_types() {
++ struct TupleStruct<'a>(&'a Option<i32>);
++ 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<i32>),
++ }
++ 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 {}
++}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
--- /dev/null
++#![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: 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);
++}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
+// 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(..);
+}
--- /dev/null
+// 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(..);
+}
--- /dev/null
- --> $DIR/range_plus_minus_one.rs:14:14
+error: an inclusive range would be more readable
- --> $DIR/range_plus_minus_one.rs:17:14
++ --> $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:20:14
++ --> $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:26:14
++ --> $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:30:13
++ --> $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
++ --> $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
++ --> $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
++ --> $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:37:14
++ --> $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
+
--- /dev/null
- #![allow(clippy::unit_arg, unused_must_use, clippy::needless_bool, deprecated)]
+// run-rustfix
+
+#![warn(clippy::all)]
+#![warn(clippy::redundant_pattern_matching)]
++#![allow(
++ clippy::unit_arg,
++ unused_must_use,
++ clippy::needless_bool,
++ clippy::match_like_matches_macro,
++ deprecated
++)]
+
+fn main() {
+ if Ok::<i32, i32>(42).is_ok() {}
+
+ if Err::<i32, i32>(42).is_err() {}
+
+ if None::<()>.is_none() {}
+
+ if Some(42).is_some() {}
+
+ if Some(42).is_some() {
+ foo();
+ } else {
+ bar();
+ }
+
+ while Some(42).is_some() {}
+
+ while Some(42).is_none() {}
+
+ while None::<()>.is_none() {}
+
+ while Ok::<i32, i32>(10).is_ok() {}
+
+ while Ok::<i32, i32>(10).is_err() {}
+
+ let mut v = vec![1, 2, 3];
+ while v.pop().is_some() {
+ foo();
+ }
+
+ if Ok::<i32, i32>(42).is_ok() {}
+
+ if Err::<i32, i32>(42).is_err() {}
+
+ if None::<i32>.is_none() {}
+
+ if Some(42).is_some() {}
+
+ if let Ok(x) = Ok::<i32, i32>(42) {
+ println!("{}", x);
+ }
+
+ Ok::<i32, i32>(42).is_ok();
+
+ Ok::<i32, i32>(42).is_err();
+
+ Err::<i32, i32>(42).is_err();
+
+ Err::<i32, i32>(42).is_ok();
+
+ Some(42).is_some();
+
+ None::<()>.is_none();
+
+ let _ = None::<()>.is_none();
+
+ let _ = if Ok::<usize, ()>(4).is_ok() { true } else { false };
+
+ let opt = Some(false);
+ let x = if opt.is_some() { true } else { false };
+ takes_bool(x);
+
+ issue5504();
+ issue5697();
+
+ let _ = if gen_opt().is_some() {
+ 1
+ } else if gen_opt().is_none() {
+ 2
+ } else if gen_res().is_ok() {
+ 3
+ } else if gen_res().is_err() {
+ 4
+ } else {
+ 5
+ };
+}
+
+fn gen_opt() -> Option<()> {
+ None
+}
+
+fn gen_res() -> Result<(), ()> {
+ Ok(())
+}
+
+fn takes_bool(_: bool) {}
+
+fn foo() {}
+
+fn bar() {}
+
+macro_rules! m {
+ () => {
+ Some(42u32)
+ };
+}
+
+fn issue5504() {
+ fn result_opt() -> Result<Option<i32>, i32> {
+ Err(42)
+ }
+
+ fn try_result_opt() -> Result<i32, i32> {
+ 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() {}
+}
+
+// None of these should be linted because none of the suggested methods
+// are `const fn` without toggling a feature.
+const fn issue5697() {
+ if let Ok(_) = Ok::<i32, i32>(42) {}
+
+ if let Err(_) = Err::<i32, i32>(42) {}
+
+ if let Some(_) = Some(42) {}
+
+ if let None = None::<()> {}
+
+ while let Ok(_) = Ok::<i32, i32>(10) {}
+
+ while let Err(_) = Ok::<i32, i32>(10) {}
+
+ while let Some(_) = Some(42) {}
+
+ while let None = None::<()> {}
+
+ match Ok::<i32, i32>(42) {
+ Ok(_) => true,
+ Err(_) => false,
+ };
+
+ match Err::<i32, i32>(42) {
+ Ok(_) => false,
+ Err(_) => true,
+ };
+ match Some(42) {
+ Some(_) => true,
+ None => false,
+ };
+
+ match None::<()> {
+ Some(_) => false,
+ None => true,
+ };
+}
--- /dev/null
- #![allow(clippy::unit_arg, unused_must_use, clippy::needless_bool, deprecated)]
+// run-rustfix
+
+#![warn(clippy::all)]
+#![warn(clippy::redundant_pattern_matching)]
++#![allow(
++ clippy::unit_arg,
++ unused_must_use,
++ clippy::needless_bool,
++ clippy::match_like_matches_macro,
++ deprecated
++)]
+
+fn main() {
+ if let Ok(_) = Ok::<i32, i32>(42) {}
+
+ if let Err(_) = Err::<i32, i32>(42) {}
+
+ if let None = None::<()> {}
+
+ if let Some(_) = Some(42) {}
+
+ if let Some(_) = Some(42) {
+ foo();
+ } else {
+ bar();
+ }
+
+ while let Some(_) = Some(42) {}
+
+ while let None = Some(42) {}
+
+ while let None = None::<()> {}
+
+ while let Ok(_) = Ok::<i32, i32>(10) {}
+
+ while let Err(_) = Ok::<i32, i32>(10) {}
+
+ let mut v = vec![1, 2, 3];
+ while let Some(_) = v.pop() {
+ foo();
+ }
+
+ if Ok::<i32, i32>(42).is_ok() {}
+
+ if Err::<i32, i32>(42).is_err() {}
+
+ if None::<i32>.is_none() {}
+
+ if Some(42).is_some() {}
+
+ if let Ok(x) = Ok::<i32, i32>(42) {
+ println!("{}", x);
+ }
+
+ match Ok::<i32, i32>(42) {
+ Ok(_) => true,
+ Err(_) => false,
+ };
+
+ match Ok::<i32, i32>(42) {
+ Ok(_) => false,
+ Err(_) => true,
+ };
+
+ match Err::<i32, i32>(42) {
+ Ok(_) => false,
+ Err(_) => true,
+ };
+
+ match Err::<i32, i32>(42) {
+ Ok(_) => true,
+ Err(_) => false,
+ };
+
+ match Some(42) {
+ Some(_) => true,
+ None => false,
+ };
+
+ match None::<()> {
+ Some(_) => false,
+ None => true,
+ };
+
+ let _ = match None::<()> {
+ Some(_) => false,
+ None => true,
+ };
+
+ let _ = if let Ok(_) = Ok::<usize, ()>(4) { true } else { false };
+
+ let opt = Some(false);
+ let x = if let Some(_) = opt { true } else { false };
+ takes_bool(x);
+
+ issue5504();
+ issue5697();
+
+ let _ = if let Some(_) = gen_opt() {
+ 1
+ } else if let None = gen_opt() {
+ 2
+ } else if let Ok(_) = gen_res() {
+ 3
+ } else if let Err(_) = gen_res() {
+ 4
+ } else {
+ 5
+ };
+}
+
+fn gen_opt() -> Option<()> {
+ None
+}
+
+fn gen_res() -> Result<(), ()> {
+ Ok(())
+}
+
+fn takes_bool(_: bool) {}
+
+fn foo() {}
+
+fn bar() {}
+
+macro_rules! m {
+ () => {
+ Some(42u32)
+ };
+}
+
+fn issue5504() {
+ fn result_opt() -> Result<Option<i32>, i32> {
+ Err(42)
+ }
+
+ fn try_result_opt() -> Result<i32, i32> {
+ 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!() {}
+}
+
+// None of these should be linted because none of the suggested methods
+// are `const fn` without toggling a feature.
+const fn issue5697() {
+ if let Ok(_) = Ok::<i32, i32>(42) {}
+
+ if let Err(_) = Err::<i32, i32>(42) {}
+
+ if let Some(_) = Some(42) {}
+
+ if let None = None::<()> {}
+
+ while let Ok(_) = Ok::<i32, i32>(10) {}
+
+ while let Err(_) = Ok::<i32, i32>(10) {}
+
+ while let Some(_) = Some(42) {}
+
+ while let None = None::<()> {}
+
+ match Ok::<i32, i32>(42) {
+ Ok(_) => true,
+ Err(_) => false,
+ };
+
+ match Err::<i32, i32>(42) {
+ Ok(_) => false,
+ Err(_) => true,
+ };
+ match Some(42) {
+ Some(_) => true,
+ None => false,
+ };
+
+ match None::<()> {
+ Some(_) => false,
+ None => true,
+ };
+}
--- /dev/null
- --> $DIR/redundant_pattern_matching.rs:8:12
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching.rs:10:12
++ --> $DIR/redundant_pattern_matching.rs:14:12
+ |
+LL | if let Ok(_) = Ok::<i32, i32>(42) {}
+ | -------^^^^^--------------------- help: try this: `if Ok::<i32, i32>(42).is_ok()`
+ |
+ = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching.rs:12:12
++ --> $DIR/redundant_pattern_matching.rs:16:12
+ |
+LL | if let Err(_) = Err::<i32, i32>(42) {}
+ | -------^^^^^^---------------------- help: try this: `if Err::<i32, i32>(42).is_err()`
+
+error: redundant pattern matching, consider using `is_none()`
- --> $DIR/redundant_pattern_matching.rs:14:12
++ --> $DIR/redundant_pattern_matching.rs:18:12
+ |
+LL | if let None = None::<()> {}
+ | -------^^^^------------- help: try this: `if None::<()>.is_none()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching.rs:16:12
++ --> $DIR/redundant_pattern_matching.rs:20:12
+ |
+LL | if let Some(_) = Some(42) {}
+ | -------^^^^^^^----------- help: try this: `if Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching.rs:22:15
++ --> $DIR/redundant_pattern_matching.rs:22:12
+ |
+LL | if let Some(_) = Some(42) {
+ | -------^^^^^^^----------- help: try this: `if Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching.rs:24:15
++ --> $DIR/redundant_pattern_matching.rs:28:15
+ |
+LL | while let Some(_) = Some(42) {}
+ | ----------^^^^^^^----------- help: try this: `while Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_none()`
- --> $DIR/redundant_pattern_matching.rs:26:15
++ --> $DIR/redundant_pattern_matching.rs:30:15
+ |
+LL | while let None = Some(42) {}
+ | ----------^^^^----------- help: try this: `while Some(42).is_none()`
+
+error: redundant pattern matching, consider using `is_none()`
- --> $DIR/redundant_pattern_matching.rs:28:15
++ --> $DIR/redundant_pattern_matching.rs:32:15
+ |
+LL | while let None = None::<()> {}
+ | ----------^^^^------------- help: try this: `while None::<()>.is_none()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching.rs:30:15
++ --> $DIR/redundant_pattern_matching.rs:34:15
+ |
+LL | while let Ok(_) = Ok::<i32, i32>(10) {}
+ | ----------^^^^^--------------------- help: try this: `while Ok::<i32, i32>(10).is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching.rs:33:15
++ --> $DIR/redundant_pattern_matching.rs:36:15
+ |
+LL | while let Err(_) = Ok::<i32, i32>(10) {}
+ | ----------^^^^^^--------------------- help: try this: `while Ok::<i32, i32>(10).is_err()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching.rs:49:5
++ --> $DIR/redundant_pattern_matching.rs:39:15
+ |
+LL | while let Some(_) = v.pop() {
+ | ----------^^^^^^^---------- help: try this: `while v.pop().is_some()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching.rs:54:5
++ --> $DIR/redundant_pattern_matching.rs:55:5
+ |
+LL | / match Ok::<i32, i32>(42) {
+LL | | Ok(_) => true,
+LL | | Err(_) => false,
+LL | | };
+ | |_____^ help: try this: `Ok::<i32, i32>(42).is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching.rs:59:5
++ --> $DIR/redundant_pattern_matching.rs:60:5
+ |
+LL | / match Ok::<i32, i32>(42) {
+LL | | Ok(_) => false,
+LL | | Err(_) => true,
+LL | | };
+ | |_____^ help: try this: `Ok::<i32, i32>(42).is_err()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching.rs:64:5
++ --> $DIR/redundant_pattern_matching.rs:65:5
+ |
+LL | / match Err::<i32, i32>(42) {
+LL | | Ok(_) => false,
+LL | | Err(_) => true,
+LL | | };
+ | |_____^ help: try this: `Err::<i32, i32>(42).is_err()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching.rs:69:5
++ --> $DIR/redundant_pattern_matching.rs:70:5
+ |
+LL | / match Err::<i32, i32>(42) {
+LL | | Ok(_) => true,
+LL | | Err(_) => false,
+LL | | };
+ | |_____^ help: try this: `Err::<i32, i32>(42).is_ok()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching.rs:74:5
++ --> $DIR/redundant_pattern_matching.rs:75:5
+ |
+LL | / match Some(42) {
+LL | | Some(_) => true,
+LL | | None => false,
+LL | | };
+ | |_____^ help: try this: `Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_none()`
- --> $DIR/redundant_pattern_matching.rs:79:13
++ --> $DIR/redundant_pattern_matching.rs:80:5
+ |
+LL | / match None::<()> {
+LL | | Some(_) => false,
+LL | | None => true,
+LL | | };
+ | |_____^ help: try this: `None::<()>.is_none()`
+
+error: redundant pattern matching, consider using `is_none()`
- --> $DIR/redundant_pattern_matching.rs:84:20
++ --> $DIR/redundant_pattern_matching.rs:85:13
+ |
+LL | let _ = match None::<()> {
+ | _____________^
+LL | | Some(_) => false,
+LL | | None => true,
+LL | | };
+ | |_____^ help: try this: `None::<()>.is_none()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching.rs:87:20
++ --> $DIR/redundant_pattern_matching.rs:90:20
+ |
+LL | let _ = if let Ok(_) = Ok::<usize, ()>(4) { true } else { false };
+ | -------^^^^^--------------------- help: try this: `if Ok::<usize, ()>(4).is_ok()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching.rs:93:20
++ --> $DIR/redundant_pattern_matching.rs:93:20
+ |
+LL | let x = if let Some(_) = opt { true } else { false };
+ | -------^^^^^^^------ help: try this: `if opt.is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching.rs:95:19
++ --> $DIR/redundant_pattern_matching.rs:99:20
+ |
+LL | let _ = if let Some(_) = gen_opt() {
+ | -------^^^^^^^------------ help: try this: `if gen_opt().is_some()`
+
+error: redundant pattern matching, consider using `is_none()`
- --> $DIR/redundant_pattern_matching.rs:97:19
++ --> $DIR/redundant_pattern_matching.rs:101:19
+ |
+LL | } else if let None = gen_opt() {
+ | -------^^^^------------ help: try this: `if gen_opt().is_none()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching.rs:99:19
++ --> $DIR/redundant_pattern_matching.rs:103:19
+ |
+LL | } else if let Ok(_) = gen_res() {
+ | -------^^^^^------------ help: try this: `if gen_res().is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching.rs:132:19
++ --> $DIR/redundant_pattern_matching.rs:105:19
+ |
+LL | } else if let Err(_) = gen_res() {
+ | -------^^^^^^------------ help: try this: `if gen_res().is_err()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching.rs:133:16
++ --> $DIR/redundant_pattern_matching.rs:138:19
+ |
+LL | while let Some(_) = r#try!(result_opt()) {}
+ | ----------^^^^^^^----------------------- help: try this: `while r#try!(result_opt()).is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching.rs:139:12
++ --> $DIR/redundant_pattern_matching.rs:139:16
+ |
+LL | if let Some(_) = r#try!(result_opt()) {}
+ | -------^^^^^^^----------------------- help: try this: `if r#try!(result_opt()).is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching.rs:140:15
++ --> $DIR/redundant_pattern_matching.rs:145:12
+ |
+LL | if let Some(_) = m!() {}
+ | -------^^^^^^^------- help: try this: `if m!().is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
++ --> $DIR/redundant_pattern_matching.rs:146:15
+ |
+LL | while let Some(_) = m!() {}
+ | ----------^^^^^^^------- help: try this: `while m!().is_some()`
+
+error: aborting due to 28 previous errors
+
--- /dev/null
- #![allow(unused)]
+// run-rustfix
+
+#![feature(const_result)]
+#![warn(clippy::redundant_pattern_matching)]
++#![allow(clippy::match_like_matches_macro, unused)]
+
+// Test that results are linted with the feature enabled.
+
+const fn issue_5697() {
+ if Ok::<i32, i32>(42).is_ok() {}
+
+ if Err::<i32, i32>(42).is_err() {}
+
+ while Ok::<i32, i32>(10).is_ok() {}
+
+ while Ok::<i32, i32>(10).is_err() {}
+
+ Ok::<i32, i32>(42).is_ok();
+
+ Err::<i32, i32>(42).is_err();
+
+ // These should not be linted until `const_option` is implemented.
+ // See https://github.com/rust-lang/rust/issues/67441
+
+ 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,
+ };
+}
+
+fn main() {}
--- /dev/null
- #![allow(unused)]
+// run-rustfix
+
+#![feature(const_result)]
+#![warn(clippy::redundant_pattern_matching)]
++#![allow(clippy::match_like_matches_macro, unused)]
+
+// Test that results are linted with the feature enabled.
+
+const fn issue_5697() {
+ if let Ok(_) = Ok::<i32, i32>(42) {}
+
+ if let Err(_) = Err::<i32, i32>(42) {}
+
+ while let Ok(_) = Ok::<i32, i32>(10) {}
+
+ while let Err(_) = Ok::<i32, i32>(10) {}
+
+ match Ok::<i32, i32>(42) {
+ Ok(_) => true,
+ Err(_) => false,
+ };
+
+ match Err::<i32, i32>(42) {
+ Ok(_) => false,
+ Err(_) => true,
+ };
+
+ // These should not be linted until `const_option` is implemented.
+ // See https://github.com/rust-lang/rust/issues/67441
+
+ 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,
+ };
+}
+
+fn main() {}
--- /dev/null
- #![warn(clippy::invalid_regex, clippy::trivial_regex, clippy::regex_macro)]
+#![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");
+}
+
+fn main() {
+ syntax_error();
+ trivial_regex();
+}
--- /dev/null
--- /dev/null
++// 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();
++}
--- /dev/null
--- /dev/null
++// 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);
++}
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
+#![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;
++ },
++ }
+}
--- /dev/null
- --> $DIR/single_match_else.rs:12:5
+error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
- error: aborting due to previous error
++ --> $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
+
--- /dev/null
- #[deny(clippy::type_repetition_in_bounds)]
++#![deny(clippy::type_repetition_in_bounds)]
++
++use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
+
+pub fn foo<T>(_t: T)
+where
+ T: Copy,
+ T: Clone,
+{
+ unimplemented!();
+}
+
+pub fn bar<T, U>(_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<Output = Self> + AddAssign + Sub<Output = Self> + SubAssign,
++ Self: Mul<Output = Self> + MulAssign + Div<Output = Self> + DivAssign,
++{
++}
++
++trait LotsOfBounds
++where
++ Self: Clone + Copy + Default + Ord,
++ Self: Add<Output = Self> + AddAssign + Sub<Output = Self> + SubAssign,
++ Self: Mul<Output = Self> + MulAssign + Div<Output = Self> + DivAssign,
++{
++}
++
++// Generic distinction (see #4323)
++mod issue4323 {
++ pub struct Foo<A>(A);
++ pub struct Bar<A, B> {
++ a: Foo<A>,
++ b: Foo<B>,
++ }
++
++ impl<A, B> Unpin for Bar<A, B>
++ where
++ Foo<A>: Unpin,
++ Foo<B>: 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<S>
++ where
++ S: Foo,
++ {
++ foo: S,
++ }
++}
++
+fn main() {}
--- /dev/null
- --> $DIR/type_repetition_in_bounds.rs:6:5
+error: this type has already been used as a bound predicate
- --> $DIR/type_repetition_in_bounds.rs:1:8
++ --> $DIR/type_repetition_in_bounds.rs:8:5
+ |
+LL | T: Clone,
+ | ^^^^^^^^
+ |
+note: the lint level is defined here
- LL | #[deny(clippy::type_repetition_in_bounds)]
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/type_repetition_in_bounds.rs:1:9
+ |
- error: aborting due to previous error
++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
+
--- /dev/null
- 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());
- }
-
+// does not test any rustfixable lints
+
+#![warn(clippy::clone_on_ref_ptr)]
+#![allow(unused, clippy::redundant_clone)]
+
+use std::cell::RefCell;
+use std::rc::{self, Rc};
+use std::sync::{self, Arc};
+
+trait SomeTrait {}
+struct SomeImpl;
+impl SomeTrait for SomeImpl {}
+
+fn main() {}
+
+fn 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<dyn SomeTrait> = x.clone();
+}
+
+fn clone_on_copy_generic<T: Copy>(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();
+ }
+}
--- /dev/null
- error: using `clone` on a `Copy` type
- --> $DIR/unnecessary_clone.rs:21:5
- |
- LL | 42.clone();
- | ^^^^^^^^^^ help: try removing the `clone` call: `42`
- |
- = note: `-D clippy::clone-on-copy` implied by `-D warnings`
-
- error: using `clone` on a `Copy` type
- --> $DIR/unnecessary_clone.rs:25:5
- |
- LL | (&42).clone();
- | ^^^^^^^^^^^^^ help: try dereferencing it: `*(&42)`
-
- error: using `clone` on a `Copy` type
- --> $DIR/unnecessary_clone.rs:28:5
- |
- LL | rc.borrow().clone();
- | ^^^^^^^^^^^^^^^^^^^ help: try dereferencing it: `*rc.borrow()`
-
- error: using `clone` on a `Copy` type
- --> $DIR/unnecessary_clone.rs:34:14
- |
- LL | is_ascii('z'.clone());
- | ^^^^^^^^^^^ help: try removing the `clone` call: `'z'`
-
- error: using `clone` on a `Copy` type
- --> $DIR/unnecessary_clone.rs:38:14
- |
- LL | vec.push(42.clone());
- | ^^^^^^^^^^ help: try removing the `clone` call: `42`
-
+error: using `.clone()` on a ref-counted pointer
- --> $DIR/unnecessary_clone.rs:48:5
++ --> $DIR/unnecessary_clone.rs:23:5
+ |
+LL | rc.clone();
+ | ^^^^^^^^^^ help: try this: `Rc::<bool>::clone(&rc)`
+ |
+ = note: `-D clippy::clone-on-ref-ptr` implied by `-D warnings`
+
+error: using `.clone()` on a ref-counted pointer
- --> $DIR/unnecessary_clone.rs:51:5
++ --> $DIR/unnecessary_clone.rs:26:5
+ |
+LL | arc.clone();
+ | ^^^^^^^^^^^ help: try this: `Arc::<bool>::clone(&arc)`
+
+error: using `.clone()` on a ref-counted pointer
- --> $DIR/unnecessary_clone.rs:54:5
++ --> $DIR/unnecessary_clone.rs:29:5
+ |
+LL | rcweak.clone();
+ | ^^^^^^^^^^^^^^ help: try this: `Weak::<bool>::clone(&rcweak)`
+
+error: using `.clone()` on a ref-counted pointer
- --> $DIR/unnecessary_clone.rs:57:5
++ --> $DIR/unnecessary_clone.rs:32:5
+ |
+LL | arc_weak.clone();
+ | ^^^^^^^^^^^^^^^^ help: try this: `Weak::<bool>::clone(&arc_weak)`
+
+error: using `.clone()` on a ref-counted pointer
- --> $DIR/unnecessary_clone.rs:61:33
++ --> $DIR/unnecessary_clone.rs:36:33
+ |
+LL | let _: Arc<dyn SomeTrait> = x.clone();
+ | ^^^^^^^^^ help: try this: `Arc::<SomeImpl>::clone(&x)`
+
+error: using `clone` on a `Copy` type
- --> $DIR/unnecessary_clone.rs:65:5
++ --> $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 a `Copy` type
- --> $DIR/unnecessary_clone.rs:67:5
++ --> $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 instead of cloning the inner type
- --> $DIR/unnecessary_clone.rs:73:22
++ --> $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<i32>>::clone(y);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: using `clone` on a `Copy` type
- --> $DIR/unnecessary_clone.rs:109:20
++ --> $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 instead of cloning the inner type
- --> $DIR/unnecessary_clone.rs:114:22
++ --> $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 instead of cloning the inner type
- --> $DIR/unnecessary_clone.rs:115:18
++ --> $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: aborting due to 16 previous errors
++error: aborting due to 11 previous errors
+
--- /dev/null
- fn id(x: isize) -> isize {
- x
- }
+// run-rustfix
+
+use std::cmp::Reverse;
+
- fn main() {
++fn unnecessary_sort_by() {
++ fn id(x: isize) -> isize {
++ x
++ }
+
+ let mut vec: Vec<isize> = 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_key(|&b| Reverse(b));
+ 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));
+}
++
++// Should not be linted to avoid hitting https://github.com/rust-lang/rust/issues/34162
++mod issue_5754 {
++ struct Test(String);
++
++ #[derive(PartialOrd, Ord, PartialEq, Eq)]
++ struct Wrapper<'a>(&'a str);
++
++ impl Test {
++ fn name(&self) -> &str {
++ &self.0
++ }
++
++ fn wrapped(&self) -> Wrapper<'_> {
++ Wrapper(&self.0)
++ }
++ }
++
++ pub fn test() {
++ let mut args: Vec<Test> = 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()));
++ }
++}
++
++fn main() {
++ unnecessary_sort_by();
++ issue_5754::test();
++}
--- /dev/null
- fn id(x: isize) -> isize {
- x
- }
+// run-rustfix
+
+use std::cmp::Reverse;
+
- fn main() {
++fn unnecessary_sort_by() {
++ fn id(x: isize) -> isize {
++ x
++ }
+
+ let mut vec: Vec<isize> = 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));
+ 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));
+}
++
++// Should not be linted to avoid hitting https://github.com/rust-lang/rust/issues/34162
++mod issue_5754 {
++ struct Test(String);
++
++ #[derive(PartialOrd, Ord, PartialEq, Eq)]
++ struct Wrapper<'a>(&'a str);
++
++ impl Test {
++ fn name(&self) -> &str {
++ &self.0
++ }
++
++ fn wrapped(&self) -> Wrapper<'_> {
++ Wrapper(&self.0)
++ }
++ }
++
++ pub fn test() {
++ let mut args: Vec<Test> = 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()));
++ }
++}
++
++fn main() {
++ unnecessary_sort_by();
++ issue_5754::test();
++}
--- /dev/null
--- /dev/null
++#![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) {}
++}